Skip to content

Commit b234fb2

Browse files
authored
refactor: clean up transaction semantics for entity mutation hooks (#206)
* refactor: clean up transaction semantics for entity mutation hooks * addressing review comments * update
1 parent a9ef4b0 commit b234fb2

File tree

11 files changed

+1359
-731
lines changed

11 files changed

+1359
-731
lines changed

.coderabbit.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ early_access: false
44
reviews:
55
auto_review:
66
enabled: true
7+
base_branches: ['dev', 'main']
78
sequence_diagrams: false
89
chat:
910
auto_reply: true

TODO.md

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
## V3 Alpha Todo
22

3-
- [ ] Infra
4-
- [ ] Dependency injection
53
- [ ] CLI
64
- [x] generate
75
- [x] migrate
@@ -11,7 +9,7 @@
119
- [ ] format
1210
- [x] plugin mechanism
1311
- [x] built-in plugins
14-
- [x] ts
12+
- [x] typescript
1513
- [x] prisma
1614
- [ ] ZModel
1715
- [x] Import
@@ -34,10 +32,12 @@
3432
- [x] Pagination
3533
- [x] Skip and limit
3634
- [x] Cursor
37-
- [x] Filtering
35+
- [ ] Filtering
3836
- [x] Unique fields
3937
- [x] Scalar fields
4038
- [x] Relation fields
39+
- [ ] JSON filtering
40+
- [ ] Full-text search
4141
- [x] Sort
4242
- [x] Scalar fields
4343
- [x] Relation fields
@@ -46,7 +46,6 @@
4646
- [x] Sorting
4747
- [x] Pagination
4848
- [x] Distinct
49-
- [ ] JSON filtering
5049
- [x] Update
5150
- [x] Input validation
5251
- [x] Top-level
@@ -66,10 +65,10 @@
6665
- [x] Transactions
6766
- [x] Interactive transaction
6867
- [x] Sequential transaction
69-
- [ ] Extensions
68+
- [ ] Extensibility
7069
- [x] Query builder API
7170
- [x] Computed fields
72-
- [x] Prisma client extension
71+
- [x] Plugin
7372
- [ ] Custom procedures
7473
- [ ] Misc
7574
- [x] JSDoc for CRUD methods

packages/runtime/src/client/client-impl.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
Kysely,
88
Log,
99
sql,
10+
Transaction,
1011
type KyselyProps,
1112
} from 'kysely';
1213
import type { GetModels, ProcedureDef, SchemaDef } from '../schema';
@@ -155,6 +156,12 @@ export class ClientImpl<Schema extends SchemaDef> {
155156
}
156157
}
157158

159+
forceTransaction() {
160+
if (!this.kysely.isTransaction) {
161+
this.kysely = new Transaction(this.kyselyProps);
162+
}
163+
}
164+
158165
private async interactiveTransaction(
159166
callback: (tx: ClientContract<Schema>) => Promise<any>,
160167
options?: { isolationLevel?: TransactionIsolationLevel },

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import { clone } from '../../../utils/clone';
2222
import { enumerate } from '../../../utils/enumerate';
2323
import { extractFields, fieldsToSelectObject } from '../../../utils/object-utils';
2424
import { NUMERIC_FIELD_TYPES } from '../../constants';
25-
import type { CRUD } from '../../contract';
25+
import { TransactionIsolationLevel, type CRUD } from '../../contract';
2626
import type { FindArgs, SelectIncludeOmit, WhereInput } from '../../crud-types';
2727
import { InternalError, NotFoundError, QueryError } from '../../errors';
2828
import type { ToKysely } from '../../query-builder';
@@ -2089,7 +2089,7 @@ export abstract class BaseOperationHandler<Schema extends SchemaDef> {
20892089
} else {
20902090
// otherwise, create a new transaction and execute the callback
20912091
let txBuilder = this.kysely.transaction();
2092-
txBuilder = txBuilder.setIsolationLevel(isolationLevel ?? 'repeatable read');
2092+
txBuilder = txBuilder.setIsolationLevel(isolationLevel ?? TransactionIsolationLevel.RepeatableRead);
20932093
return txBuilder.execute(callback);
20942094
}
20952095
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { invariant } from '@zenstackhq/common-helpers';
2+
import { type OperationNode, AliasNode, IdentifierNode } from 'kysely';
3+
4+
/**
5+
* Strips alias from the node if it exists.
6+
*/
7+
export function stripAlias(node: OperationNode) {
8+
if (AliasNode.is(node)) {
9+
invariant(IdentifierNode.is(node.alias), 'Expected identifier as alias');
10+
return { alias: node.alias.name, node: node.node };
11+
} else {
12+
return { alias: undefined, node };
13+
}
14+
}

packages/runtime/src/client/executor/name-mapper.ts

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
} from 'kysely';
2020
import type { FieldDef, ModelDef, SchemaDef } from '../../schema';
2121
import { getModel, requireModel } from '../query-utils';
22+
import { stripAlias } from './kysely-utils';
2223

2324
type Scope = {
2425
model: string;
@@ -94,7 +95,7 @@ export class QueryNameMapper extends OperationNodeTransformer {
9495
}
9596

9697
protected override transformJoin(node: JoinNode) {
97-
const { alias, node: innerNode } = this.stripAlias(node.table);
98+
const { alias, node: innerNode } = stripAlias(node.table);
9899
if (TableNode.is(innerNode!)) {
99100
const modelName = innerNode.table.identifier.name;
100101
if (this.hasMappedColumns(modelName)) {
@@ -150,7 +151,11 @@ export class QueryNameMapper extends OperationNodeTransformer {
150151
}
151152

152153
protected override transformUpdateQuery(node: UpdateQueryNode) {
153-
const { alias, node: innerTable } = this.stripAlias(node.table);
154+
if (!node.table) {
155+
return super.transformUpdateQuery(node);
156+
}
157+
158+
const { alias, node: innerTable } = stripAlias(node.table);
154159
if (!innerTable || !TableNode.is(innerTable)) {
155160
return super.transformUpdateQuery(node);
156161
}
@@ -170,7 +175,7 @@ export class QueryNameMapper extends OperationNodeTransformer {
170175

171176
// process name mapping in each "from"
172177
const froms = node.from.froms.map((from) => {
173-
const { alias, node: innerNode } = this.stripAlias(from);
178+
const { alias, node: innerNode } = stripAlias(from);
174179
if (TableNode.is(innerNode!)) {
175180
// map table name
176181
return this.wrapAlias(this.processTableRef(innerNode), alias);
@@ -289,17 +294,6 @@ export class QueryNameMapper extends OperationNodeTransformer {
289294
}
290295
}
291296

292-
private stripAlias(node: OperationNode | undefined) {
293-
if (!node) {
294-
return { alias: undefined, node };
295-
}
296-
if (AliasNode.is(node)) {
297-
invariant(IdentifierNode.is(node.alias), 'Expected identifier as alias');
298-
return { alias: node.alias.name, node: node.node };
299-
}
300-
return { alias: undefined, node };
301-
}
302-
303297
private hasMappedColumns(modelName: string) {
304298
return [...this.fieldToColumnMap.keys()].some((key) => key.startsWith(modelName + '.'));
305299
}
@@ -310,7 +304,7 @@ export class QueryNameMapper extends OperationNodeTransformer {
310304
}
311305
return node.froms
312306
.map((from) => {
313-
const { alias, node: innerNode } = this.stripAlias(from);
307+
const { alias, node: innerNode } = stripAlias(from);
314308
if (innerNode && TableNode.is(innerNode)) {
315309
return { model: innerNode.table.identifier.name, alias, namesMapped };
316310
} else {
@@ -325,7 +319,7 @@ export class QueryNameMapper extends OperationNodeTransformer {
325319
return {
326320
...super.transformFrom(node),
327321
froms: node.froms.map((from) => {
328-
const { alias, node: innerNode } = this.stripAlias(from);
322+
const { alias, node: innerNode } = stripAlias(from);
329323
if (!innerNode) {
330324
return super.transformNode(from);
331325
}

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,12 @@ export class ZenStackDriver implements Driver {
7979
this.#txConnections.delete(connection);
8080
if (callbacks) {
8181
for (const callback of callbacks) {
82-
await callback();
82+
try {
83+
await callback();
84+
} catch (err) {
85+
// errors in commit callbacks are logged but do not fail the commit
86+
console.error(`Error executing transaction commit callback: ${err}`);
87+
}
8388
}
8489
}
8590
return result;

0 commit comments

Comments
 (0)