Skip to content

Commit feaecb9

Browse files
committed
validate computed field configuration on startup
1 parent e10d1b3 commit feaecb9

File tree

2 files changed

+78
-0
lines changed

2 files changed

+78
-0
lines changed

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,11 @@ export class ClientImpl {
7575
...this.$options.functions,
7676
};
7777

78+
if (!baseClient) {
79+
// validate computed fields configuration once for the root client
80+
this.validateComputedFieldsConfig();
81+
}
82+
7883
// here we use kysely's props constructor so we can pass a custom query executor
7984
if (baseClient) {
8085
this.kyselyProps = {
@@ -139,6 +144,29 @@ export class ClientImpl {
139144
return new ClientImpl(this.schema, this.$options, this, executor);
140145
}
141146

147+
/**
148+
* Validates that all computed fields in the schema have corresponding configurations.
149+
*/
150+
private validateComputedFieldsConfig() {
151+
const computedFieldsConfig = ('computedFields' in this.$options)
152+
? (this.$options.computedFields as Record<string, any> | undefined)
153+
: undefined;
154+
155+
for (const [modelName, modelDef] of Object.entries(this.$schema.models)) {
156+
if (modelDef.computedFields) {
157+
for (const fieldName of Object.keys(modelDef.computedFields)) {
158+
// Check if the computed field has a configuration
159+
if (!computedFieldsConfig || !computedFieldsConfig[modelName] || !computedFieldsConfig[modelName][fieldName]) {
160+
throw createConfigError(
161+
`Computed field "${fieldName}" in model "${modelName}" does not have a configuration. ` +
162+
`Please provide an implementation in the computedFields option.`
163+
);
164+
}
165+
}
166+
}
167+
}
168+
}
169+
142170
// overload for interactive transaction
143171
$transaction<T>(
144172
callback: (tx: ClientContract<SchemaDef>) => Promise<T>,

tests/e2e/orm/client-api/computed-fields.test.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,46 @@ import { sql } from 'kysely';
33
import { describe, expect, it } from 'vitest';
44

55
describe('Computed fields tests', () => {
6+
it('throws error when computed field configuration is missing', async () => {
7+
await expect(
8+
createTestClient(
9+
`
10+
model User {
11+
id Int @id @default(autoincrement())
12+
name String
13+
upperName String @computed
14+
}
15+
`,
16+
{
17+
// missing computedFields configuration
18+
} as any,
19+
),
20+
).rejects.toThrow('Computed field "upperName" in model "User" does not have a configuration');
21+
});
22+
23+
it('throws error when computed field is missing from configuration', async () => {
24+
await expect(
25+
createTestClient(
26+
`
27+
model User {
28+
id Int @id @default(autoincrement())
29+
name String
30+
upperName String @computed
31+
lowerName String @computed
32+
}
33+
`,
34+
{
35+
computedFields: {
36+
User: {
37+
// only providing one of two computed fields
38+
upperName: (eb: any) => eb.fn('upper', ['name']),
39+
},
40+
},
41+
} as any,
42+
),
43+
).rejects.toThrow('Computed field "lowerName" in model "User" does not have a configuration');
44+
});
45+
646
it('works with non-optional fields', async () => {
747
const db = await createTestClient(
848
`
@@ -102,6 +142,11 @@ model User {
102142
}
103143
`,
104144
{
145+
computedFields: {
146+
User: {
147+
upperName: (eb: any) => eb.fn('upper', ['name']),
148+
},
149+
},
105150
extraSourceFiles: {
106151
main: `
107152
import { ZenStackClient } from '@zenstackhq/orm';
@@ -169,6 +214,11 @@ model User {
169214
}
170215
`,
171216
{
217+
computedFields: {
218+
User: {
219+
upperName: (eb: any) => eb.lit(null),
220+
},
221+
},
172222
extraSourceFiles: {
173223
main: `
174224
import { ZenStackClient } from '@zenstackhq/orm';

0 commit comments

Comments
 (0)