Skip to content

feat: polymorphism #990

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Feb 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"plugin:jest/recommended"
],
"rules": {
"jest/expect-expect": "off",
"@typescript-eslint/no-unused-vars": ["error", { "varsIgnorePattern": "^_", "argsIgnorePattern": "^_" }]
"@typescript-eslint/no-unused-vars": ["error", { "varsIgnorePattern": "^_", "argsIgnorePattern": "^_" }],
"jest/expect-expect": "off"
}
}
11 changes: 11 additions & 0 deletions packages/language/src/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,17 @@ declare module './generated/ast' {
interface DataModelAttribute {
$inheritedFrom?: DataModel;
}

export interface DataModel {
/**
* Indicates whether the model is already merged with the base types
*/
$baseMerged?: boolean;
}
}

export interface InheritableNode extends AstNode {
$inheritedFrom?: DataModel;
}

export interface InheritableNode extends AstNode {
Expand Down
59 changes: 33 additions & 26 deletions packages/plugins/swr/tests/test-model-meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,39 +11,46 @@ const fieldDefaults = {
};

export const modelMeta: ModelMeta = {
fields: {
models: {
user: {
id: {
...fieldDefaults,
type: 'String',
isId: true,
name: 'id',
isOptional: false,
},
name: { ...fieldDefaults, type: 'String', name: 'name' },
email: { ...fieldDefaults, type: 'String', name: 'name', isOptional: false },
posts: {
...fieldDefaults,
type: 'Post',
isDataModel: true,
isArray: true,
name: 'posts',
name: 'user',
fields: {
id: {
...fieldDefaults,
type: 'String',
isId: true,
name: 'id',
isOptional: false,
},
name: { ...fieldDefaults, type: 'String', name: 'name' },
email: { ...fieldDefaults, type: 'String', name: 'name', isOptional: false },
posts: {
...fieldDefaults,
type: 'Post',
isDataModel: true,
isArray: true,
name: 'posts',
},
},
uniqueConstraints: {},
},
post: {
id: {
...fieldDefaults,
type: 'String',
isId: true,
name: 'id',
isOptional: false,
name: 'post',
fields: {
id: {
...fieldDefaults,
type: 'String',
isId: true,
name: 'id',
isOptional: false,
},
title: { ...fieldDefaults, type: 'String', name: 'title' },
owner: { ...fieldDefaults, type: 'User', name: 'owner', isDataModel: true, isRelationOwner: true },
ownerId: { ...fieldDefaults, type: 'User', name: 'owner', isForeignKey: true },
},
title: { ...fieldDefaults, type: 'String', name: 'title' },
owner: { ...fieldDefaults, type: 'User', name: 'owner', isDataModel: true, isRelationOwner: true },
ownerId: { ...fieldDefaults, type: 'User', name: 'owner', isForeignKey: true },
uniqueConstraints: {},
},
},
uniqueConstraints: {},
deleteCascade: {
user: ['Post'],
},
Expand Down
59 changes: 33 additions & 26 deletions packages/plugins/tanstack-query/tests/test-model-meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,39 +11,46 @@ const fieldDefaults = {
};

export const modelMeta: ModelMeta = {
fields: {
models: {
user: {
id: {
...fieldDefaults,
type: 'String',
isId: true,
name: 'id',
isOptional: false,
},
name: { ...fieldDefaults, type: 'String', name: 'name' },
email: { ...fieldDefaults, type: 'String', name: 'name', isOptional: false },
posts: {
...fieldDefaults,
type: 'Post',
isDataModel: true,
isArray: true,
name: 'posts',
name: 'user',
fields: {
id: {
...fieldDefaults,
type: 'String',
isId: true,
name: 'id',
isOptional: false,
},
name: { ...fieldDefaults, type: 'String', name: 'name' },
email: { ...fieldDefaults, type: 'String', name: 'name', isOptional: false },
posts: {
...fieldDefaults,
type: 'Post',
isDataModel: true,
isArray: true,
name: 'posts',
},
},
uniqueConstraints: {},
},
post: {
id: {
...fieldDefaults,
type: 'String',
isId: true,
name: 'id',
isOptional: false,
name: 'post',
fields: {
id: {
...fieldDefaults,
type: 'String',
isId: true,
name: 'id',
isOptional: false,
},
title: { ...fieldDefaults, type: 'String', name: 'title' },
owner: { ...fieldDefaults, type: 'User', name: 'owner', isDataModel: true, isRelationOwner: true },
ownerId: { ...fieldDefaults, type: 'User', name: 'owner', isForeignKey: true },
},
title: { ...fieldDefaults, type: 'String', name: 'title' },
owner: { ...fieldDefaults, type: 'User', name: 'owner', isDataModel: true, isRelationOwner: true },
ownerId: { ...fieldDefaults, type: 'User', name: 'owner', isForeignKey: true },
uniqueConstraints: {},
},
},
uniqueConstraints: {},
deleteCascade: {
user: ['Post'],
},
Expand Down
2 changes: 2 additions & 0 deletions packages/runtime/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,10 @@
"bcryptjs": "^2.4.3",
"buffer": "^6.0.3",
"change-case": "^4.1.2",
"colors": "1.4.0",
"decimal.js": "^10.4.2",
"deepcopy": "^2.1.0",
"deepmerge": "^4.3.1",
"lower-case-first": "^2.0.2",
"pluralize": "^8.0.0",
"semver": "^7.5.2",
Expand Down
5 changes: 5 additions & 0 deletions packages/runtime/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,8 @@ export const FIELD_LEVEL_OVERRIDE_UPDATE_GUARD_PREFIX = 'updateFieldGuardOverrid
* Flag that indicates if the model has field-level access control
*/
export const HAS_FIELD_LEVEL_POLICY_FLAG = 'hasFieldLevelPolicy';

/**
* Prefix for auxiliary relation field generated for delegated models
*/
export const DELEGATE_AUX_RELATION_PREFIX = 'delegate_aux';
71 changes: 60 additions & 11 deletions packages/runtime/src/cross/model-meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@ import { lowerCaseFirst } from 'lower-case-first';
* Runtime information of a data model or field attribute
*/
export type RuntimeAttribute = {
/**
* Attribute name
*/
name: string;

/**
* Attribute arguments
*/
args: Array<{ name?: string; value: unknown }>;
};

Expand Down Expand Up @@ -72,6 +79,11 @@ export type FieldInfo = {
*/
foreignKeyMapping?: Record<string, string>;

/**
* Model from which the field is inherited
*/
inheritedFrom?: string;

/**
* A function that provides a default value for the field
*/
Expand All @@ -90,23 +102,53 @@ export type FieldInfo = {
export type UniqueConstraint = { name: string; fields: string[] };

/**
* ZModel data model metadata
* Metadata for a data model
*/
export type ModelMeta = {
export type ModelInfo = {
/**
* Model name
*/
name: string;

/**
* Base types
*/
baseTypes?: string[];

/**
* Fields
*/
fields: Record<string, FieldInfo>;

/**
* Unique constraints
*/
uniqueConstraints?: Record<string, UniqueConstraint>;

/**
* Attributes on the model
*/
attributes?: RuntimeAttribute[];

/**
* Model fields
* Discriminator field name
*/
fields: Record<string, Record<string, FieldInfo>>;
discriminator?: string;
};

/**
* ZModel data model metadata
*/
export type ModelMeta = {
/**
* Model unique constraints
* Data models
*/
uniqueConstraints: Record<string, Record<string, UniqueConstraint>>;
models: Record<string, ModelInfo>;

/**
* Information for cascading delete
* Mapping from model name to models that will be deleted because of it due to cascade delete
*/
deleteCascade: Record<string, string[]>;
deleteCascade?: Record<string, string[]>;

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

/**
Expand All @@ -136,5 +178,12 @@ export function requireField(modelMeta: ModelMeta, model: string, field: string)
* Gets all fields of a model.
*/
export function getFields(modelMeta: ModelMeta, model: string) {
return modelMeta.fields[lowerCaseFirst(model)];
return modelMeta.models[lowerCaseFirst(model)]?.fields;
}

/**
* Gets unique constraints of a model.
*/
export function getUniqueConstraints(modelMeta: ModelMeta, model: string) {
return modelMeta.models[lowerCaseFirst(model)]?.uniqueConstraints;
}
6 changes: 4 additions & 2 deletions packages/runtime/src/cross/nested-write-visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,8 +219,10 @@ export class NestedWriteVisitor {

case 'set':
if (this.callback.set) {
const newContext = pushNewContext(field, model, {});
await this.callback.set(model, data, newContext);
for (const item of enumerate(data)) {
const newContext = pushNewContext(field, model, item, true);
await this.callback.set(model, item, newContext);
}
}
break;

Expand Down
2 changes: 1 addition & 1 deletion packages/runtime/src/cross/query-analyzer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ function collectDeleteCascades(model: string, modelMeta: ModelMeta, result: Set<
}
visited.add(model);

const cascades = modelMeta.deleteCascade[lowerCaseFirst(model)];
const cascades = modelMeta.deleteCascade?.[lowerCaseFirst(model)];

if (!cascades) {
return;
Expand Down
20 changes: 18 additions & 2 deletions packages/runtime/src/cross/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { lowerCaseFirst } from 'lower-case-first';
import { ModelMeta } from '.';
import { ModelInfo, ModelMeta } from '.';

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

export function getIdFields(modelMeta: ModelMeta, model: string, throwIfNotFound = false) {
let fields = modelMeta.fields[lowerCaseFirst(model)];
let fields = modelMeta.models[lowerCaseFirst(model)]?.fields;
if (!fields) {
if (throwIfNotFound) {
throw new Error(`Unable to load fields for ${model}`);
Expand All @@ -61,3 +61,19 @@ export function getIdFields(modelMeta: ModelMeta, model: string, throwIfNotFound
}
return result;
}

export function getModelInfo<Throw extends boolean = false>(
modelMeta: ModelMeta,
model: string,
throwIfNotFound: Throw = false as Throw
): Throw extends true ? ModelInfo : ModelInfo | undefined {
const info = modelMeta.models[lowerCaseFirst(model)];
if (!info && throwIfNotFound) {
throw new Error(`Unable to load info for ${model}`);
}
return info;
}

export function isDelegateModel(modelMeta: ModelMeta, model: string) {
return !!getModelInfo(modelMeta, model)?.attributes?.some((attr) => attr.name === '@@delegate');
}
Loading