Skip to content
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
2 changes: 1 addition & 1 deletion packages/language/res/stdlib.zmodel
Original file line number Diff line number Diff line change
Expand Up @@ -701,7 +701,7 @@ function raw(value: String): Any {
/**
* Marks a field to be strong-typed JSON.
*/
attribute @json() @@@targetField([TypeDefField]) @@@deprecated('The "@json" attribute is not needed anymore. ZenStack will automatically use JSON to store typed fields.')
attribute @json() @@@targetField([TypeDefField])

/**
* Marks a field to be computed.
Expand Down
62 changes: 34 additions & 28 deletions packages/runtime/src/client/crud-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import type {
FieldDef,
FieldHasDefault,
FieldIsArray,
FieldIsOptional,
FieldIsRelation,
FieldIsRelationArray,
FieldType,
Expand All @@ -19,12 +18,14 @@ import type {
GetTypeDefField,
GetTypeDefFields,
GetTypeDefs,
ModelFieldIsOptional,
NonRelationFields,
RelationFields,
RelationFieldType,
RelationInfo,
ScalarFields,
SchemaDef,
TypeDefFieldIsOptional,
} from '../schema';
import type {
AtLeast,
Expand Down Expand Up @@ -86,21 +87,21 @@ type ModelSelectResult<Schema extends SchemaDef, Model extends GetModels<Schema>
Schema,
RelationFieldType<Schema, Model, Key>,
Pick<Select[Key], 'select'>,
FieldIsOptional<Schema, Model, Key>,
ModelFieldIsOptional<Schema, Model, Key>,
FieldIsArray<Schema, Model, Key>
>
: ModelResult<
Schema,
RelationFieldType<Schema, Model, Key>,
Pick<Select[Key], 'include' | 'omit'>,
FieldIsOptional<Schema, Model, Key>,
ModelFieldIsOptional<Schema, Model, Key>,
FieldIsArray<Schema, Model, Key>
>
: DefaultModelResult<
Schema,
RelationFieldType<Schema, Model, Key>,
Omit,
FieldIsOptional<Schema, Model, Key>,
ModelFieldIsOptional<Schema, Model, Key>,
FieldIsArray<Schema, Model, Key>
>
: never;
Expand Down Expand Up @@ -143,14 +144,14 @@ export type ModelResult<
Schema,
RelationFieldType<Schema, Model, Key>,
I[Key],
FieldIsOptional<Schema, Model, Key>,
ModelFieldIsOptional<Schema, Model, Key>,
FieldIsArray<Schema, Model, Key>
>
: DefaultModelResult<
Schema,
RelationFieldType<Schema, Model, Key>,
undefined,
FieldIsOptional<Schema, Model, Key>,
ModelFieldIsOptional<Schema, Model, Key>,
FieldIsArray<Schema, Model, Key>
>;
}
Expand All @@ -169,9 +170,17 @@ export type SimplifiedModelResult<
Array = false,
> = Simplify<ModelResult<Schema, Model, Args, Optional, Array>>;

export type TypeDefResult<Schema extends SchemaDef, TypeDef extends GetTypeDefs<Schema>> = {
[Key in GetTypeDefFields<Schema, TypeDef>]: MapTypeDefFieldType<Schema, TypeDef, Key>;
};
export type TypeDefResult<Schema extends SchemaDef, TypeDef extends GetTypeDefs<Schema>> = Optional<
{
[Key in GetTypeDefFields<Schema, TypeDef>]: MapTypeDefFieldType<Schema, TypeDef, Key>;
},
// optionality
keyof {
[Key in GetTypeDefFields<Schema, TypeDef> as TypeDefFieldIsOptional<Schema, TypeDef, Key> extends true
? Key
: never]: Key;
}
>;

export type BatchResult = { count: number };

Expand All @@ -193,11 +202,11 @@ export type WhereInput<
RelationFilter<Schema, Model, Key>
: // enum
GetModelFieldType<Schema, Model, Key> extends GetEnums<Schema>
? EnumFilter<Schema, GetModelFieldType<Schema, Model, Key>, FieldIsOptional<Schema, Model, Key>>
? EnumFilter<Schema, GetModelFieldType<Schema, Model, Key>, ModelFieldIsOptional<Schema, Model, Key>>
: FieldIsArray<Schema, Model, Key> extends true
? ArrayFilter<GetModelFieldType<Schema, Model, Key>>
: // primitive
PrimitiveFilter<GetModelFieldType<Schema, Model, Key>, FieldIsOptional<Schema, Model, Key>>;
PrimitiveFilter<GetModelFieldType<Schema, Model, Key>, ModelFieldIsOptional<Schema, Model, Key>>;
} & {
$expr?: (eb: ExpressionBuilder<ToKyselySchema<Schema>, Model>) => OperandExpression<SqlBool>;
} & {
Expand Down Expand Up @@ -290,7 +299,7 @@ export type OrderBy<
WithRelation extends boolean,
WithAggregation extends boolean,
> = {
[Key in NonRelationFields<Schema, Model>]?: FieldIsOptional<Schema, Model, Key> extends true
[Key in NonRelationFields<Schema, Model>]?: ModelFieldIsOptional<Schema, Model, Key> extends true
?
| SortOrder
| {
Expand Down Expand Up @@ -391,7 +400,7 @@ export type IncludeInput<Schema extends SchemaDef, Model extends GetModels<Schem
// where clause is allowed only if the relation is array or optional
FieldIsArray<Schema, Model, Key> extends true
? true
: FieldIsOptional<Schema, Model, Key> extends true
: ModelFieldIsOptional<Schema, Model, Key> extends true
? true
: false
>;
Expand Down Expand Up @@ -427,14 +436,14 @@ type ToOneRelationFilter<
WhereInput<Schema, RelationFieldType<Schema, Model, Field>> & {
is?: NullableIf<
WhereInput<Schema, RelationFieldType<Schema, Model, Field>>,
FieldIsOptional<Schema, Model, Field>
ModelFieldIsOptional<Schema, Model, Field>
>;
isNot?: NullableIf<
WhereInput<Schema, RelationFieldType<Schema, Model, Field>>,
FieldIsOptional<Schema, Model, Field>
ModelFieldIsOptional<Schema, Model, Field>
>;
},
FieldIsOptional<Schema, Model, Field>
ModelFieldIsOptional<Schema, Model, Field>
>;

type RelationFilter<
Expand All @@ -460,23 +469,20 @@ type MapTypeDefFieldType<
Schema extends SchemaDef,
TypeDef extends GetTypeDefs<Schema>,
Field extends GetTypeDefFields<Schema, TypeDef>,
> =
GetTypeDefField<Schema, TypeDef, Field>['type'] extends GetTypeDefs<Schema>
? WrapType<
TypeDefResult<Schema, GetTypeDefField<Schema, TypeDef, Field>['type']>,
GetTypeDefField<Schema, TypeDef, Field>['optional'],
GetTypeDefField<Schema, TypeDef, Field>['array']
>
: MapFieldDefType<Schema, GetTypeDefField<Schema, TypeDef, Field>>;
> = MapFieldDefType<Schema, GetTypeDefField<Schema, TypeDef, Field>>;

type MapFieldDefType<Schema extends SchemaDef, T extends Pick<FieldDef, 'type' | 'optional' | 'array'>> = WrapType<
T['type'] extends GetEnums<Schema> ? keyof GetEnum<Schema, T['type']> : MapBaseType<T['type']>,
T['type'] extends GetEnums<Schema>
? keyof GetEnum<Schema, T['type']>
: T['type'] extends GetTypeDefs<Schema>
? TypeDefResult<Schema, T['type']> & Record<string, unknown>
: MapBaseType<T['type']>,
T['optional'],
T['array']
>;

type OptionalFieldsForCreate<Schema extends SchemaDef, Model extends GetModels<Schema>> = keyof {
[Key in GetModelFields<Schema, Model> as FieldIsOptional<Schema, Model, Key> extends true
[Key in GetModelFields<Schema, Model> as ModelFieldIsOptional<Schema, Model, Key> extends true
? Key
: FieldHasDefault<Schema, Model, Key> extends true
? Key
Expand Down Expand Up @@ -752,7 +758,7 @@ type ScalarUpdatePayload<
| MapModelFieldType<Schema, Model, Field>
| (Field extends NumericFields<Schema, Model>
? {
set?: NullableIf<number, FieldIsOptional<Schema, Model, Field>>;
set?: NullableIf<number, ModelFieldIsOptional<Schema, Model, Field>>;
increment?: number;
decrement?: number;
multiply?: number;
Expand Down Expand Up @@ -820,7 +826,7 @@ type ToOneRelationUpdateInput<
connectOrCreate?: ConnectOrCreateInput<Schema, Model, Field>;
update?: NestedUpdateInput<Schema, Model, Field>;
upsert?: NestedUpsertInput<Schema, Model, Field>;
} & (FieldIsOptional<Schema, Model, Field> extends true
} & (ModelFieldIsOptional<Schema, Model, Field> extends true
? {
disconnect?: DisconnectInput<Schema, Model, Field>;
delete?: NestedDeleteInput<Schema, Model, Field>;
Expand Down
19 changes: 12 additions & 7 deletions packages/runtime/src/client/crud/dialects/sqlite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,18 @@ export class SqliteCrudDialect<Schema extends SchemaDef> extends BaseCrudDialect
if (Array.isArray(value)) {
return value.map((v) => this.transformPrimitive(v, type, false));
} else {
return match(type)
.with('Boolean', () => (value ? 1 : 0))
.with('DateTime', () => (value instanceof Date ? value.toISOString() : value))
.with('Decimal', () => (value as Decimal).toString())
.with('Bytes', () => Buffer.from(value as Uint8Array))
.with('Json', () => JSON.stringify(value))
.otherwise(() => value);
if (this.schema.typeDefs && type in this.schema.typeDefs) {
// typed JSON field
return JSON.stringify(value);
} else {
return match(type)
.with('Boolean', () => (value ? 1 : 0))
.with('DateTime', () => (value instanceof Date ? value.toISOString() : value))
.with('Decimal', () => (value as Decimal).toString())
.with('Bytes', () => Buffer.from(value as Uint8Array))
.with('Json', () => JSON.stringify(value))
.otherwise(() => value);
}
}
}

Expand Down
3 changes: 2 additions & 1 deletion packages/runtime/src/client/crud/operations/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,8 @@ export abstract class BaseOperationHandler<Schema extends SchemaDef> {
const idFields = getIdFields(this.schema, model);
const query = kysely
.insertInto(model)
.values(updatedData)
.$if(Object.keys(updatedData).length === 0, (qb) => qb.defaultValues())
.$if(Object.keys(updatedData).length > 0, (qb) => qb.values(updatedData))
.returning(idFields as any)
.modifyEnd(
this.makeContextComment({
Expand Down
59 changes: 49 additions & 10 deletions packages/runtime/src/client/crud/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,16 +218,46 @@ export class InputValidator<Schema extends SchemaDef> {
}

private makePrimitiveSchema(type: string) {
return match(type)
.with('String', () => z.string())
.with('Int', () => z.number())
.with('Float', () => z.number())
.with('Boolean', () => z.boolean())
.with('BigInt', () => z.union([z.number(), z.bigint()]))
.with('Decimal', () => z.union([z.number(), z.instanceof(Decimal), z.string()]))
.with('DateTime', () => z.union([z.date(), z.string().datetime()]))
.with('Bytes', () => z.instanceof(Uint8Array))
.otherwise(() => z.unknown());
if (this.schema.typeDefs && type in this.schema.typeDefs) {
return this.makeTypeDefSchema(type);
} else {
return match(type)
.with('String', () => z.string())
.with('Int', () => z.number())
.with('Float', () => z.number())
.with('Boolean', () => z.boolean())
.with('BigInt', () => z.union([z.number(), z.bigint()]))
.with('Decimal', () => z.union([z.number(), z.instanceof(Decimal), z.string()]))
.with('DateTime', () => z.union([z.date(), z.string().datetime()]))
.with('Bytes', () => z.instanceof(Uint8Array))
.otherwise(() => z.unknown());
}
}

private makeTypeDefSchema(type: string): z.ZodType {
const key = `$typedef-${type}`;
let schema = this.schemaCache.get(key);
if (schema) {
return schema;
}
const typeDef = this.schema.typeDefs?.[type];
invariant(typeDef, `Type definition "${type}" not found in schema`);
schema = z.looseObject(
Object.fromEntries(
Object.entries(typeDef.fields).map(([field, def]) => {
let fieldSchema = this.makePrimitiveSchema(def.type);
if (def.array) {
fieldSchema = fieldSchema.array();
}
if (def.optional) {
fieldSchema = fieldSchema.optional();
}
return [field, fieldSchema];
}),
),
);
this.schemaCache.set(key, schema);
return schema;
}

private makeWhereSchema(model: string, unique: boolean, withoutRelationFields = false): ZodType {
Expand Down Expand Up @@ -396,6 +426,10 @@ export class InputValidator<Schema extends SchemaDef> {
}

private makePrimitiveFilterSchema(type: BuiltinType, optional: boolean) {
if (this.schema.typeDefs && type in this.schema.typeDefs) {
// typed JSON field
return this.makeTypeDefFilterSchema(type, optional);
}
return (
match(type)
.with('String', () => this.makeStringFilterSchema(optional))
Expand All @@ -412,6 +446,11 @@ export class InputValidator<Schema extends SchemaDef> {
);
}

private makeTypeDefFilterSchema(_type: string, _optional: boolean) {
// TODO: strong typed JSON filtering
return z.never();
}

private makeDateTimeFilterSchema(optional: boolean): ZodType {
return this.makeCommonPrimitiveFilterSchema(z.union([z.string().datetime(), z.date()]), optional, () =>
z.lazy(() => this.makeDateTimeFilterSchema(optional)),
Expand Down
4 changes: 2 additions & 2 deletions packages/runtime/src/client/query-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import type Decimal from 'decimal.js';
import type { Generated, Kysely } from 'kysely';
import type {
FieldHasDefault,
FieldIsOptional,
ForeignKeyFields,
GetModelFields,
GetModelFieldType,
GetModels,
ModelFieldIsOptional,
ScalarFields,
SchemaDef,
} from '../schema';
Expand Down Expand Up @@ -45,7 +45,7 @@ type MapType<
Schema extends SchemaDef,
Model extends GetModels<Schema>,
Field extends GetModelFields<Schema, Model>,
> = WrapNull<MapBaseType<GetModelFieldType<Schema, Model, Field>>, FieldIsOptional<Schema, Model, Field>>;
> = WrapNull<MapBaseType<GetModelFieldType<Schema, Model, Field>>, ModelFieldIsOptional<Schema, Model, Field>>;

type toKyselyFieldType<
Schema extends SchemaDef,
Expand Down
21 changes: 13 additions & 8 deletions packages/runtime/src/client/result-processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,14 +84,19 @@ export class ResultProcessor<Schema extends SchemaDef> {
}

private transformScalar(value: unknown, type: BuiltinType) {
return match(type)
.with('Boolean', () => this.transformBoolean(value))
.with('DateTime', () => this.transformDate(value))
.with('Bytes', () => this.transformBytes(value))
.with('Decimal', () => this.transformDecimal(value))
.with('BigInt', () => this.transformBigInt(value))
.with('Json', () => this.transformJson(value))
.otherwise(() => value);
if (this.schema.typeDefs && type in this.schema.typeDefs) {
// typed JSON field
return this.transformJson(value);
} else {
return match(type)
.with('Boolean', () => this.transformBoolean(value))
.with('DateTime', () => this.transformDate(value))
.with('Bytes', () => this.transformBytes(value))
.with('Decimal', () => this.transformDecimal(value))
.with('BigInt', () => this.transformBigInt(value))
.with('Json', () => this.transformJson(value))
.otherwise(() => value);
}
}

private transformDecimal(value: unknown) {
Expand Down
Loading