Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
13 changes: 11 additions & 2 deletions packages/orm/src/client/contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,12 @@ import type { ClientOptions, QueryOptions } from './options';
import type { ExtClientMembersBase, ExtQueryArgsBase, RuntimePlugin } from './plugin';
import type { ZenStackPromise } from './promise';
import type { ToKysely } from './query-builder';
import type { GetSlicedModels, GetSlicedOperations, GetSlicedProcedures } from './type-utils';
import type {
GetSlicedModels,
GetSlicedOperations,
GetSlicedProcedures,
ModelHasRequiredUnsupportedField,
} from './type-utils';
import type { ZodSchemaFactory } from './zod/factory';

type TransactionUnsupportedMethods = (typeof TRANSACTION_UNSUPPORTED_METHODS)[number];
Expand Down Expand Up @@ -285,7 +290,9 @@ type SliceOperations<
[Key in keyof T as Key extends GetSlicedOperations<Schema, Model, Options> ? Key : never]: T[Key];
},
// exclude operations not applicable to delegate models
IsDelegateModel<Schema, Model> extends true ? OperationsIneligibleForDelegateModels : never
| (IsDelegateModel<Schema, Model> extends true ? OperationsIneligibleForDelegateModels : never)
// exclude create operations for models with required Unsupported fields
| (ModelHasRequiredUnsupportedField<Schema, Model> extends true ? OperationsIneligibleForUnsupportedModels : never)
>;

export type AllModelOperations<
Expand Down Expand Up @@ -882,6 +889,8 @@ type CommonModelOperations<

export type OperationsIneligibleForDelegateModels = 'create' | 'createMany' | 'createManyAndReturn' | 'upsert';

export type OperationsIneligibleForUnsupportedModels = 'create' | 'createMany' | 'createManyAndReturn' | 'upsert';

export type ModelOperations<
Schema extends SchemaDef,
Model extends GetModels<Schema>,
Expand Down
11 changes: 4 additions & 7 deletions packages/orm/src/client/crud/dialects/base-dialect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
isInheritedField,
isRelationField,
isTypeDef,
getModelFields,
makeDefaultOrderBy,
requireField,
requireIdFields,
Expand Down Expand Up @@ -1117,17 +1118,13 @@ export abstract class BaseCrudDialect<Schema extends SchemaDef> {
omit: Record<string, boolean | undefined> | undefined | null,
modelAlias: string,
) {
const modelDef = requireModel(this.schema, model);
let result = query;

for (const field of Object.keys(modelDef.fields)) {
if (isRelationField(this.schema, model, field)) {
continue;
}
if (this.shouldOmitField(omit, model, field)) {
for (const fieldDef of getModelFields(this.schema, model, { inherited: true, computed: true })) {
if (this.shouldOmitField(omit, model, fieldDef.name)) {
continue;
}
result = this.buildSelectField(result, model, modelAlias, field);
result = this.buildSelectField(result, model, modelAlias, fieldDef.name);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

// select all fields from delegate descendants and pack into a JSON field `$delegate$Model`
Expand Down
17 changes: 15 additions & 2 deletions packages/orm/src/client/crud/operations/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import type { ToKysely } from '../../query-builder';
import {
ensureArray,
extractIdFields,
fieldHasDefaultValue,
flattenCompoundUniqueFilters,
getDiscriminatorField,
getField,
Expand All @@ -47,6 +48,7 @@ import {
isForeignKeyField,
isRelationField,
isScalarField,
isUnsupportedField,
requireField,
requireIdFields,
requireModel,
Expand Down Expand Up @@ -1148,7 +1150,7 @@ export abstract class BaseOperationHandler<Schema extends SchemaDef> {

const parentWhere = await this.buildUpdateParentRelationFilter(kysely, fromRelation);

let combinedWhere: WhereInput<Schema, GetModels<Schema>, any, false> = where ?? {};
let combinedWhere: Record<string, any> = where ?? {};
if (Object.keys(parentWhere).length > 0) {
combinedWhere = Object.keys(combinedWhere).length > 0 ? { AND: [parentWhere, combinedWhere] } : parentWhere;
}
Expand Down Expand Up @@ -1210,7 +1212,7 @@ export abstract class BaseOperationHandler<Schema extends SchemaDef> {

if (needIdRead) {
const readResult = await this.readUnique(kysely, model, {
where: combinedWhere,
where: combinedWhere as WhereInput<Schema, GetModels<Schema>>,
select: this.makeIdSelect(model),
});
if (!readResult && throwIfNotFound) {
Expand Down Expand Up @@ -2507,6 +2509,17 @@ export abstract class BaseOperationHandler<Schema extends SchemaDef> {
return newArgs;
}

protected checkNoRequiredUnsupportedFields() {
const modelDef = requireModel(this.schema, this.model);
for (const [fieldName, fieldDef] of Object.entries(modelDef.fields)) {
if (isUnsupportedField(fieldDef) && !fieldDef.optional && !fieldHasDefaultValue(fieldDef)) {
throw createNotSupportedError(
`Model "${this.model}" has a required Unsupported field "${fieldName}" and cannot be created/upserted through the ORM client`,
);
}
}
}
Comment thread
ymc9 marked this conversation as resolved.
Outdated

private doNormalizeArgs(args: unknown) {
if (args && typeof args === 'object') {
for (const [key, value] of Object.entries(args)) {
Expand Down
2 changes: 2 additions & 0 deletions packages/orm/src/client/crud/operations/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { BaseOperationHandler } from './base';

export class CreateOperationHandler<Schema extends SchemaDef> extends BaseOperationHandler<Schema> {
async handle(operation: 'create' | 'createMany' | 'createManyAndReturn', args: unknown | undefined) {
this.checkNoRequiredUnsupportedFields();

// normalize args to strip `undefined` fields
const normalizedArgs = this.normalizeArgs(args);

Expand Down
2 changes: 2 additions & 0 deletions packages/orm/src/client/crud/operations/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ export class UpdateOperationHandler<Schema extends SchemaDef> extends BaseOperat
}

private async runUpsert(args: any) {
this.checkNoRequiredUnsupportedFields();

// analyze if we need to read back the updated record, or just return the update result
const { needReadBack, selectedFields } = this.needReadBack(args);

Expand Down
19 changes: 7 additions & 12 deletions packages/orm/src/client/executor/name-mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ import {
getEnum,
getField,
getModel,
getModelFields,
isEnum,
requireModel,
stripAlias,
} from '../query-utils';

Expand Down Expand Up @@ -66,7 +66,7 @@ export class QueryNameMapper extends OperationNodeTransformer {
this.modelToTableMap.set(modelName, mappedName);
}

for (const fieldDef of this.getModelFields(modelDef)) {
for (const fieldDef of getModelFields(this.schema, modelName)) {
const mappedName = this.getMappedName(fieldDef);
if (mappedName) {
this.fieldToColumnMap.set(`${modelName}.${fieldDef.name}`, mappedName);
Expand Down Expand Up @@ -431,7 +431,7 @@ export class QueryNameMapper extends OperationNodeTransformer {
if (!modelDef) {
continue;
}
if (this.getModelFields(modelDef).some((f) => f.name === name)) {
if (getModelFields(this.schema, scope.model).some((f) => f.name === name)) {
return scope;
}
}
Expand Down Expand Up @@ -560,8 +560,7 @@ export class QueryNameMapper extends OperationNodeTransformer {
}

private createSelectAllFields(model: string, alias: OperationNode | undefined) {
const modelDef = requireModel(this.schema, model);
return this.getModelFields(modelDef).map((fieldDef) => {
return getModelFields(this.schema, model).map((fieldDef) => {
const columnName = this.mapFieldName(model, fieldDef.name);
const columnRef = ReferenceNode.create(
ColumnNode.create(columnName),
Expand All @@ -576,9 +575,6 @@ export class QueryNameMapper extends OperationNodeTransformer {
});
}

private getModelFields(modelDef: ModelDef) {
return Object.values(modelDef.fields).filter((f) => !f.relation && !f.computed && !f.originModel);
}

private processSelections(selections: readonly SelectionNode[]) {
const result: SelectionNode[] = [];
Expand Down Expand Up @@ -627,9 +623,8 @@ export class QueryNameMapper extends OperationNodeTransformer {
}

// expand select all to a list of selections with name mapping
const modelDef = requireModel(this.schema, scope.model);
return this.getModelFields(modelDef).map((fieldDef) => {
const columnName = this.mapFieldName(modelDef.name, fieldDef.name);
return getModelFields(this.schema, scope.model).map((fieldDef) => {
const columnName = this.mapFieldName(scope.model!, fieldDef.name);
const columnRef = ReferenceNode.create(ColumnNode.create(columnName));

// process enum value mapping
Expand Down Expand Up @@ -660,7 +655,7 @@ export class QueryNameMapper extends OperationNodeTransformer {
if (!modelDef) {
return false;
}
return this.getModelFields(modelDef).some((fieldDef) => {
return getModelFields(this.schema, model).some((fieldDef) => {
const enumDef = getEnum(this.schema, fieldDef.type);
if (!enumDef) {
return false;
Expand Down
7 changes: 6 additions & 1 deletion packages/orm/src/client/helpers/schema-db-pusher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
type SchemaDef,
} from '../../schema';
import type { ToKysely } from '../query-builder';
import { requireModel } from '../query-utils';
import { isUnsupportedField, requireModel } from '../query-utils';

/**
* This class is for testing purposes only. It should never be used in production.
Expand Down Expand Up @@ -117,6 +117,11 @@ export class SchemaDbPusher<Schema extends SchemaDef> {
continue;
}

if (isUnsupportedField(fieldDef)) {
// Unsupported fields cannot be represented in the ORM's schema pusher
continue;
}

if (fieldDef.relation) {
table = this.addForeignKeyConstraint(table, modelDef.name, fieldName, fieldDef);
} else if (!this.isComputedField(fieldDef)) {
Expand Down
14 changes: 12 additions & 2 deletions packages/orm/src/client/query-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,12 @@ export function requireField(schema: SchemaDef, modelOrType: string, field: stri
}

/**
* Gets all model fields, by default non-relation, non-computed, non-inherited fields only.
* Gets all model fields, by default non-relation, non-computed, non-inherited, non-unsupported fields only.
*/
export function getModelFields(
schema: SchemaDef,
model: string,
options?: { relations?: boolean; computed?: boolean; inherited?: boolean },
options?: { relations?: boolean; computed?: boolean; inherited?: boolean; unsupported?: boolean },
) {
const modelDef = requireModel(schema, model);
return Object.values(modelDef.fields).filter((f) => {
Expand All @@ -88,10 +88,20 @@ export function getModelFields(
if (f.originModel && !options?.inherited) {
return false;
}
if (f.type === 'Unsupported' && !options?.unsupported) {
return false;
}
return true;
});
}

/**
* Checks if a field is of `Unsupported` type.
*/
export function isUnsupportedField(fieldDef: FieldDef) {
return fieldDef.type === 'Unsupported';
}

export function getIdFields<Schema extends SchemaDef>(schema: SchemaDef, model: GetModels<Schema>) {
const modelDef = getModel(schema, model);
return modelDef?.idFields;
Expand Down
23 changes: 22 additions & 1 deletion packages/orm/src/client/type-utils.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,29 @@
import type { GetModels, SchemaDef } from '@zenstackhq/schema';
import type { FieldDef, GetModel, GetModels, SchemaDef } from '@zenstackhq/schema';
import type { GetProcedureNames } from './crud-types';
import type { AllCrudOperations } from './crud/operations/base';
import type { FilterKind, QueryOptions, SlicingOptions } from './options';

/**
* Checks if a model has any required Unsupported fields (non-optional, no default).
* Uses raw field access since `GetModelFields` excludes Unsupported fields.
*/
export type ModelHasRequiredUnsupportedField<Schema extends SchemaDef, Model extends GetModels<Schema>> = true extends {
[Key in Extract<keyof GetModel<Schema, Model>['fields'], string>]: GetModel<
Schema,
Model
>['fields'][Key] extends infer F extends FieldDef
? F['type'] extends 'Unsupported'
? F['optional'] extends true
? false
: 'default' extends keyof F
? false
: true
: false
: false;
}[Extract<keyof GetModel<Schema, Model>['fields'], string>]
? true
: false;
Comment thread
ymc9 marked this conversation as resolved.

type IsNever<T> = [T] extends [never] ? true : false;

// #region Model slicing
Expand Down
Loading
Loading