Skip to content

Commit bac3683

Browse files
authored
feat: polymorphism (#990)
1 parent c8a19e9 commit bac3683

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+3850
-778
lines changed

.eslintrc.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"plugin:jest/recommended"
1414
],
1515
"rules": {
16-
"jest/expect-expect": "off",
17-
"@typescript-eslint/no-unused-vars": ["error", { "varsIgnorePattern": "^_", "argsIgnorePattern": "^_" }]
16+
"@typescript-eslint/no-unused-vars": ["error", { "varsIgnorePattern": "^_", "argsIgnorePattern": "^_" }],
17+
"jest/expect-expect": "off"
1818
}
1919
}

packages/language/src/ast.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,17 @@ declare module './generated/ast' {
5252
interface DataModelAttribute {
5353
$inheritedFrom?: DataModel;
5454
}
55+
56+
export interface DataModel {
57+
/**
58+
* Indicates whether the model is already merged with the base types
59+
*/
60+
$baseMerged?: boolean;
61+
}
62+
}
63+
64+
export interface InheritableNode extends AstNode {
65+
$inheritedFrom?: DataModel;
5566
}
5667

5768
export interface InheritableNode extends AstNode {

packages/plugins/swr/tests/test-model-meta.ts

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11,39 +11,46 @@ const fieldDefaults = {
1111
};
1212

1313
export const modelMeta: ModelMeta = {
14-
fields: {
14+
models: {
1515
user: {
16-
id: {
17-
...fieldDefaults,
18-
type: 'String',
19-
isId: true,
20-
name: 'id',
21-
isOptional: false,
22-
},
23-
name: { ...fieldDefaults, type: 'String', name: 'name' },
24-
email: { ...fieldDefaults, type: 'String', name: 'name', isOptional: false },
25-
posts: {
26-
...fieldDefaults,
27-
type: 'Post',
28-
isDataModel: true,
29-
isArray: true,
30-
name: 'posts',
16+
name: 'user',
17+
fields: {
18+
id: {
19+
...fieldDefaults,
20+
type: 'String',
21+
isId: true,
22+
name: 'id',
23+
isOptional: false,
24+
},
25+
name: { ...fieldDefaults, type: 'String', name: 'name' },
26+
email: { ...fieldDefaults, type: 'String', name: 'name', isOptional: false },
27+
posts: {
28+
...fieldDefaults,
29+
type: 'Post',
30+
isDataModel: true,
31+
isArray: true,
32+
name: 'posts',
33+
},
3134
},
35+
uniqueConstraints: {},
3236
},
3337
post: {
34-
id: {
35-
...fieldDefaults,
36-
type: 'String',
37-
isId: true,
38-
name: 'id',
39-
isOptional: false,
38+
name: 'post',
39+
fields: {
40+
id: {
41+
...fieldDefaults,
42+
type: 'String',
43+
isId: true,
44+
name: 'id',
45+
isOptional: false,
46+
},
47+
title: { ...fieldDefaults, type: 'String', name: 'title' },
48+
owner: { ...fieldDefaults, type: 'User', name: 'owner', isDataModel: true, isRelationOwner: true },
49+
ownerId: { ...fieldDefaults, type: 'User', name: 'owner', isForeignKey: true },
4050
},
41-
title: { ...fieldDefaults, type: 'String', name: 'title' },
42-
owner: { ...fieldDefaults, type: 'User', name: 'owner', isDataModel: true, isRelationOwner: true },
43-
ownerId: { ...fieldDefaults, type: 'User', name: 'owner', isForeignKey: true },
51+
uniqueConstraints: {},
4452
},
4553
},
46-
uniqueConstraints: {},
4754
deleteCascade: {
4855
user: ['Post'],
4956
},

packages/plugins/tanstack-query/tests/test-model-meta.ts

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11,39 +11,46 @@ const fieldDefaults = {
1111
};
1212

1313
export const modelMeta: ModelMeta = {
14-
fields: {
14+
models: {
1515
user: {
16-
id: {
17-
...fieldDefaults,
18-
type: 'String',
19-
isId: true,
20-
name: 'id',
21-
isOptional: false,
22-
},
23-
name: { ...fieldDefaults, type: 'String', name: 'name' },
24-
email: { ...fieldDefaults, type: 'String', name: 'name', isOptional: false },
25-
posts: {
26-
...fieldDefaults,
27-
type: 'Post',
28-
isDataModel: true,
29-
isArray: true,
30-
name: 'posts',
16+
name: 'user',
17+
fields: {
18+
id: {
19+
...fieldDefaults,
20+
type: 'String',
21+
isId: true,
22+
name: 'id',
23+
isOptional: false,
24+
},
25+
name: { ...fieldDefaults, type: 'String', name: 'name' },
26+
email: { ...fieldDefaults, type: 'String', name: 'name', isOptional: false },
27+
posts: {
28+
...fieldDefaults,
29+
type: 'Post',
30+
isDataModel: true,
31+
isArray: true,
32+
name: 'posts',
33+
},
3134
},
35+
uniqueConstraints: {},
3236
},
3337
post: {
34-
id: {
35-
...fieldDefaults,
36-
type: 'String',
37-
isId: true,
38-
name: 'id',
39-
isOptional: false,
38+
name: 'post',
39+
fields: {
40+
id: {
41+
...fieldDefaults,
42+
type: 'String',
43+
isId: true,
44+
name: 'id',
45+
isOptional: false,
46+
},
47+
title: { ...fieldDefaults, type: 'String', name: 'title' },
48+
owner: { ...fieldDefaults, type: 'User', name: 'owner', isDataModel: true, isRelationOwner: true },
49+
ownerId: { ...fieldDefaults, type: 'User', name: 'owner', isForeignKey: true },
4050
},
41-
title: { ...fieldDefaults, type: 'String', name: 'title' },
42-
owner: { ...fieldDefaults, type: 'User', name: 'owner', isDataModel: true, isRelationOwner: true },
43-
ownerId: { ...fieldDefaults, type: 'User', name: 'owner', isForeignKey: true },
51+
uniqueConstraints: {},
4452
},
4553
},
46-
uniqueConstraints: {},
4754
deleteCascade: {
4855
user: ['Post'],
4956
},

packages/runtime/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,10 @@
5656
"bcryptjs": "^2.4.3",
5757
"buffer": "^6.0.3",
5858
"change-case": "^4.1.2",
59+
"colors": "1.4.0",
5960
"decimal.js": "^10.4.2",
6061
"deepcopy": "^2.1.0",
62+
"deepmerge": "^4.3.1",
6163
"lower-case-first": "^2.0.2",
6264
"pluralize": "^8.0.0",
6365
"semver": "^7.5.2",

packages/runtime/src/constants.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,3 +97,8 @@ export const FIELD_LEVEL_OVERRIDE_UPDATE_GUARD_PREFIX = 'updateFieldGuardOverrid
9797
* Flag that indicates if the model has field-level access control
9898
*/
9999
export const HAS_FIELD_LEVEL_POLICY_FLAG = 'hasFieldLevelPolicy';
100+
101+
/**
102+
* Prefix for auxiliary relation field generated for delegated models
103+
*/
104+
export const DELEGATE_AUX_RELATION_PREFIX = 'delegate_aux';

packages/runtime/src/cross/model-meta.ts

Lines changed: 60 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,14 @@ import { lowerCaseFirst } from 'lower-case-first';
44
* Runtime information of a data model or field attribute
55
*/
66
export type RuntimeAttribute = {
7+
/**
8+
* Attribute name
9+
*/
710
name: string;
11+
12+
/**
13+
* Attribute arguments
14+
*/
815
args: Array<{ name?: string; value: unknown }>;
916
};
1017

@@ -72,6 +79,11 @@ export type FieldInfo = {
7279
*/
7380
foreignKeyMapping?: Record<string, string>;
7481

82+
/**
83+
* Model from which the field is inherited
84+
*/
85+
inheritedFrom?: string;
86+
7587
/**
7688
* A function that provides a default value for the field
7789
*/
@@ -90,23 +102,53 @@ export type FieldInfo = {
90102
export type UniqueConstraint = { name: string; fields: string[] };
91103

92104
/**
93-
* ZModel data model metadata
105+
* Metadata for a data model
94106
*/
95-
export type ModelMeta = {
107+
export type ModelInfo = {
108+
/**
109+
* Model name
110+
*/
111+
name: string;
112+
113+
/**
114+
* Base types
115+
*/
116+
baseTypes?: string[];
117+
118+
/**
119+
* Fields
120+
*/
121+
fields: Record<string, FieldInfo>;
122+
123+
/**
124+
* Unique constraints
125+
*/
126+
uniqueConstraints?: Record<string, UniqueConstraint>;
127+
128+
/**
129+
* Attributes on the model
130+
*/
131+
attributes?: RuntimeAttribute[];
132+
96133
/**
97-
* Model fields
134+
* Discriminator field name
98135
*/
99-
fields: Record<string, Record<string, FieldInfo>>;
136+
discriminator?: string;
137+
};
100138

139+
/**
140+
* ZModel data model metadata
141+
*/
142+
export type ModelMeta = {
101143
/**
102-
* Model unique constraints
144+
* Data models
103145
*/
104-
uniqueConstraints: Record<string, Record<string, UniqueConstraint>>;
146+
models: Record<string, ModelInfo>;
105147

106148
/**
107-
* Information for cascading delete
149+
* Mapping from model name to models that will be deleted because of it due to cascade delete
108150
*/
109-
deleteCascade: Record<string, string[]>;
151+
deleteCascade?: Record<string, string[]>;
110152

111153
/**
112154
* Name of model that backs the `auth()` function
@@ -117,8 +159,8 @@ export type ModelMeta = {
117159
/**
118160
* Resolves a model field to its metadata. Returns undefined if not found.
119161
*/
120-
export function resolveField(modelMeta: ModelMeta, model: string, field: string) {
121-
return modelMeta.fields[lowerCaseFirst(model)]?.[field];
162+
export function resolveField(modelMeta: ModelMeta, model: string, field: string): FieldInfo | undefined {
163+
return modelMeta.models[lowerCaseFirst(model)]?.fields?.[field];
122164
}
123165

124166
/**
@@ -136,5 +178,12 @@ export function requireField(modelMeta: ModelMeta, model: string, field: string)
136178
* Gets all fields of a model.
137179
*/
138180
export function getFields(modelMeta: ModelMeta, model: string) {
139-
return modelMeta.fields[lowerCaseFirst(model)];
181+
return modelMeta.models[lowerCaseFirst(model)]?.fields;
182+
}
183+
184+
/**
185+
* Gets unique constraints of a model.
186+
*/
187+
export function getUniqueConstraints(modelMeta: ModelMeta, model: string) {
188+
return modelMeta.models[lowerCaseFirst(model)]?.uniqueConstraints;
140189
}

packages/runtime/src/cross/nested-write-visitor.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -219,8 +219,10 @@ export class NestedWriteVisitor {
219219

220220
case 'set':
221221
if (this.callback.set) {
222-
const newContext = pushNewContext(field, model, {});
223-
await this.callback.set(model, data, newContext);
222+
for (const item of enumerate(data)) {
223+
const newContext = pushNewContext(field, model, item, true);
224+
await this.callback.set(model, item, newContext);
225+
}
224226
}
225227
break;
226228

packages/runtime/src/cross/query-analyzer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ function collectDeleteCascades(model: string, modelMeta: ModelMeta, result: Set<
8181
}
8282
visited.add(model);
8383

84-
const cascades = modelMeta.deleteCascade[lowerCaseFirst(model)];
84+
const cascades = modelMeta.deleteCascade?.[lowerCaseFirst(model)];
8585

8686
if (!cascades) {
8787
return;

packages/runtime/src/cross/utils.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { lowerCaseFirst } from 'lower-case-first';
2-
import { ModelMeta } from '.';
2+
import { ModelInfo, ModelMeta } from '.';
33

44
/**
55
* Gets field names in a data model entity, filtering out internal fields.
@@ -47,7 +47,7 @@ export function zip<T1, T2>(x: Enumerable<T1>, y: Enumerable<T2>): Array<[T1, T2
4747
}
4848

4949
export function getIdFields(modelMeta: ModelMeta, model: string, throwIfNotFound = false) {
50-
let fields = modelMeta.fields[lowerCaseFirst(model)];
50+
let fields = modelMeta.models[lowerCaseFirst(model)]?.fields;
5151
if (!fields) {
5252
if (throwIfNotFound) {
5353
throw new Error(`Unable to load fields for ${model}`);
@@ -61,3 +61,19 @@ export function getIdFields(modelMeta: ModelMeta, model: string, throwIfNotFound
6161
}
6262
return result;
6363
}
64+
65+
export function getModelInfo<Throw extends boolean = false>(
66+
modelMeta: ModelMeta,
67+
model: string,
68+
throwIfNotFound: Throw = false as Throw
69+
): Throw extends true ? ModelInfo : ModelInfo | undefined {
70+
const info = modelMeta.models[lowerCaseFirst(model)];
71+
if (!info && throwIfNotFound) {
72+
throw new Error(`Unable to load info for ${model}`);
73+
}
74+
return info;
75+
}
76+
77+
export function isDelegateModel(modelMeta: ModelMeta, model: string) {
78+
return !!getModelInfo(modelMeta, model)?.attributes?.some((attr) => attr.name === '@@delegate');
79+
}

0 commit comments

Comments
 (0)