Skip to content
Open
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
123 changes: 79 additions & 44 deletions packages/orm/src/client/crud-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,14 @@ export type ModelResult<
FieldIsArray<Schema, Model, Key>,
ExtResult
>;
} & {
// an included parameterized computed field surfaces as its scalar return type
// (it is not auto-returned, so it only appears when explicitly included)
[Key in keyof I & NonRelationFields<Schema, Model> as I[Key] extends false | undefined
? never
: FieldHasComputedArgs<Schema, Model, Key> extends true
? Key
: never]: MapModelFieldType<Schema, Model, Key>;
} & ('_count' extends keyof I
? I['_count'] extends false | undefined
? {}
Expand Down Expand Up @@ -345,15 +353,15 @@ export type WhereInput<
ScalarOnly extends boolean = false,
WithAggregations extends boolean = false,
> = {
// parameterized computed fields are excluded here — filtering them would require
// query-time args; they are currently only usable in `orderBy`
[Key in GetModelFields<Schema, Model> as FieldHasComputedArgs<Schema, Model, Key> extends true
? never
: ScalarOnly extends true
? Key extends RelationFields<Schema, Model>
? never
: Key
: Key]?: FieldFilter<Schema, Model, Key, Options, WithAggregations>;
[Key in GetModelFields<Schema, Model> as ScalarOnly extends true
? Key extends RelationFields<Schema, Model>
? never
: Key
: Key]?: FieldHasComputedArgs<Schema, Model, Key> extends true
? // a parameterized computed field carries its query-time `args` alongside the filter
// operators; the intersection drops the bare-value shorthand (args must be supplied)
{ args: ComputedFieldArgs<Schema, Model, Key> } & FieldFilter<Schema, Model, Key, Options, WithAggregations>
: FieldFilter<Schema, Model, Key, Options, WithAggregations>;
} & {
$expr?: (eb: ExpressionBuilder<ToKyselySchema<Schema>, Model>) => OperandExpression<SqlBool>;
} & {
Expand Down Expand Up @@ -1355,26 +1363,34 @@ export type IncludeInput<
AllowCount extends boolean = true,
ExtResult extends ExtResultBase<Schema> = {},
> = {
[Key in RelationFields<Schema, Model> as RelationFieldType<Schema, Model, Key> extends GetSlicedModels<
Schema,
Options
>
? Key
: never]?:
| boolean
| FindArgs<
Schema,
RelationFieldType<Schema, Model, Key>,
Options,
FieldIsArray<Schema, Model, Key>,
// where clause is allowed only if the relation is array or optional
FieldIsArray<Schema, Model, Key> extends true
? true
: ModelFieldIsOptional<Schema, Model, Key> extends true
? true
: false,
ExtResult
>;
// A single mapped type over relations + parameterized computed fields. Keeping this as one
// object member (rather than intersecting a separate mapped type) preserves excess-property
// checking on `include`/`select` literals. A parameterized computed field is selectable by
// supplying its query-time `args` (mirrors the relation per-key-object shape); because
// `SelectInput` intersects `IncludeInput`, this also enables `select: { field: { args } }`.
[Key in GetModelFields<Schema, Model> as Key extends RelationFields<Schema, Model>
? RelationFieldType<Schema, Model, Key> extends GetSlicedModels<Schema, Options>
? Key
: never
: FieldHasComputedArgs<Schema, Model, Key> extends true
? Key
: never]?: Key extends RelationFields<Schema, Model>
?
| boolean
| FindArgs<
Schema,
RelationFieldType<Schema, Model, Key>,
Options,
FieldIsArray<Schema, Model, Key>,
// where clause is allowed only if the relation is array or optional
FieldIsArray<Schema, Model, Key> extends true
? true
: ModelFieldIsOptional<Schema, Model, Key> extends true
? true
: false,
ExtResult
>
: { args: ComputedFieldArgs<Schema, Model, Key> };
} & (AllowCount extends true
? // _count is only allowed if the model has to-many relations
HasToManyRelations<Schema, Model> extends true
Expand Down Expand Up @@ -2289,10 +2305,10 @@ export type CountArgs<
} & ExtractExtQueryArgs<ExtQueryArgs, 'count'>;

type CountAggregateInput<Schema extends SchemaDef, Model extends GetModels<Schema>> = {
// a parameterized computed field has no `args` slot in `_count`, so it is excluded
[Key in NonRelationFields<Schema, Model> as FieldHasComputedArgs<Schema, Model, Key> extends true
? never
: Key]?: true;
// a parameterized computed field is counted by supplying its query-time `args`
[Key in NonRelationFields<Schema, Model>]?: FieldHasComputedArgs<Schema, Model, Key> extends true
? { args: ComputedFieldArgs<Schema, Model, Key> }
: true;
} & { _all?: true };

export type CountResult<Schema extends SchemaDef, _Model extends GetModels<Schema>, Args> = Args extends {
Expand Down Expand Up @@ -2371,26 +2387,28 @@ type NumericFields<Schema extends SchemaDef, Model extends GetModels<Schema>> =
| 'Decimal'
? FieldIsArray<Schema, Model, Key> extends true
? never
: // a parameterized computed field has no `args` slot in `_sum`/`_avg`, so it is excluded
FieldHasComputedArgs<Schema, Model, Key> extends true
? never
: Key
: Key
: never]: GetModelField<Schema, Model, Key>;
};

type SumAvgInput<Schema extends SchemaDef, Model extends GetModels<Schema>, ValueType> = {
[Key in NumericFields<Schema, Model>]?: ValueType;
// a parameterized computed field is aggregated by supplying its query-time `args`
[Key in NumericFields<Schema, Model>]?: Key extends GetModelFields<Schema, Model>
? FieldHasComputedArgs<Schema, Model, Key> extends true
? { args: ComputedFieldArgs<Schema, Model, Key> }
: ValueType
: ValueType;
};

type MinMaxInput<Schema extends SchemaDef, Model extends GetModels<Schema>, ValueType> = {
[Key in GetModelFields<Schema, Model> as FieldIsArray<Schema, Model, Key> extends true
? never
: FieldIsRelation<Schema, Model, Key> extends true
? never
: // a parameterized computed field has no `args` slot in `_min`/`_max`, so it is excluded
FieldHasComputedArgs<Schema, Model, Key> extends true
? never
: Key]?: ValueType;
: Key]?: // a parameterized computed field is aggregated by supplying its query-time `args`
FieldHasComputedArgs<Schema, Model, Key> extends true
? { args: ComputedFieldArgs<Schema, Model, Key> }
: ValueType;
};

export type AggregateResult<Schema extends SchemaDef, _Model extends GetModels<Schema>, Args> = (Args extends {
Expand Down Expand Up @@ -2454,6 +2472,20 @@ type GroupByHaving<
out Options extends QueryOptions<Schema> = QueryOptions<Schema>,
> = Omit<WhereInput<Schema, Model, Options, true, true>, '$expr'>;

// A `groupBy` `by` entry for a parameterized computed field: the field name plus its query-time
// `args` (a plain field name can't carry args). The map is keyed by the parameterized computed
// fields; indexing it yields the union of `{ field, args }` entries (or `never` when there are none).
type ParamComputedByEntryMap<Schema extends SchemaDef, Model extends GetModels<Schema>> = {
[Key in NonRelationFields<Schema, Model> as FieldHasComputedArgs<Schema, Model, Key> extends true
? Key
: never]: { field: Key; args: ComputedFieldArgs<Schema, Model, Key> };
};
type GroupByComputedByEntry<Schema extends SchemaDef, Model extends GetModels<Schema>> =
ParamComputedByEntryMap<Schema, Model>[keyof ParamComputedByEntryMap<Schema, Model>];

// Extracts the grouped field name from a `by` entry (a plain name or a `{ field, args }` object).
type ByFieldName<By> = By extends { field: infer F } ? F : By;

export type GroupByArgs<
Schema extends SchemaDef,
Model extends GetModels<Schema>,
Expand All @@ -2475,7 +2507,10 @@ export type GroupByArgs<
*/
by:
| NonParamComputedNonRelationFields<Schema, Model>
| NonEmptyArray<NonParamComputedNonRelationFields<Schema, Model>>;
| GroupByComputedByEntry<Schema, Model>
| NonEmptyArray<
NonParamComputedNonRelationFields<Schema, Model> | GroupByComputedByEntry<Schema, Model>
>;

/**
* Filter conditions for the grouped records
Expand Down Expand Up @@ -2527,7 +2562,7 @@ export type GroupByResult<
Args extends { by: unknown },
> = Array<
{
[Key in NonRelationFields<Schema, Model> as Key extends ValueOfPotentialTuple<Args['by']>
[Key in NonRelationFields<Schema, Model> as Key extends ByFieldName<ValueOfPotentialTuple<Args['by']>>
? Key
: never]: MapModelFieldType<Schema, Model, Key>;
} & (Args extends { _count: infer Count }
Expand Down
25 changes: 21 additions & 4 deletions packages/orm/src/client/crud/dialects/base-dialect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,12 +236,27 @@ export abstract class BaseCrudDialect<Schema extends SchemaDef> {
if (fieldDef.relation) {
result = this.and(result, this.buildRelationFilter(model, modelAlias, key, fieldDef, payload));
} else {
// a parameterized computed field carries its query-time `args` alongside the filter
// operators; pull them out so they reach the implementation, and filter on the rest
let computedArgs: unknown;
let filterPayload = payload;
if (fieldDef.computed && fieldDef.params && payload && typeof payload === 'object' && 'args' in payload) {
computedArgs = (payload as any).args;
const { args: _args, ...rest } = payload as any;
filterPayload = rest;
}
// if the field is from a base model, build a reference from that model
const fieldRef = this.fieldRef(fieldDef.originModel ?? model, key, fieldDef.originModel ?? modelAlias);
const fieldRef = this.fieldRef(
fieldDef.originModel ?? model,
key,
fieldDef.originModel ?? modelAlias,
true,
computedArgs,
);
if (fieldDef.array) {
result = this.and(result, this.buildArrayFilter(fieldRef, fieldDef, payload));
result = this.and(result, this.buildArrayFilter(fieldRef, fieldDef, filterPayload));
} else {
result = this.and(result, this.buildPrimitiveFilter(fieldRef, fieldDef, payload));
result = this.and(result, this.buildPrimitiveFilter(fieldRef, fieldDef, filterPayload));
}
}
}
Expand Down Expand Up @@ -1460,6 +1475,7 @@ export abstract class BaseCrudDialect<Schema extends SchemaDef> {
model: string,
modelAlias: string,
field: string,
computedArgs?: unknown,
): SelectQueryBuilder<any, any, any> {
const fieldDef = requireField(this.schema, model, field);

Expand All @@ -1468,7 +1484,8 @@ export abstract class BaseCrudDialect<Schema extends SchemaDef> {
const fieldModel = fieldDef.originModel ?? model;
const alias = fieldDef.originModel ?? modelAlias;

return query.select(() => this.fieldRef(fieldModel, field, alias).as(field));
// `computedArgs` is set for a parameterized computed field selected via `select`/`include`
return query.select(() => this.fieldRef(fieldModel, field, alias, true, computedArgs).as(field));
}

buildDelegateJoin(
Expand Down
17 changes: 15 additions & 2 deletions packages/orm/src/client/crud/dialects/lateral-join-dialect-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,8 @@ export abstract class LateralJoinDialectBase<Schema extends SchemaDef> extends B
objArgs,
...Object.entries(relationModelDef.fields)
.filter(([, value]) => !value.relation)
// a parameterized computed field is never auto-returned (it needs args)
.filter(([, value]) => !(value.computed && value.params))
.filter(([name]) => !this.shouldOmitField(omit, relationModel, name))
.map(([field]) => ({
[field]: this.fieldRef(relationModel, field, relationModelAlias, false),
Expand All @@ -286,8 +288,12 @@ export abstract class LateralJoinDialectBase<Schema extends SchemaDef> extends B
const fieldValue = fieldDef.relation
? // reference the synthesized JSON field
eb.ref(`${parentResultName}$${field}.$data`)
: // reference a plain field
this.fieldRef(relationModel, field, relationModelAlias, false);
: fieldDef.computed && fieldDef.params
? // parameterized computed field: inline the computer with its
// query-time args (correlates to the relation's real columns)
this.fieldRef(relationModel, field, relationModelAlias, true, (value as any).args)
: // reference a plain field
this.fieldRef(relationModel, field, relationModelAlias, false);
return { [field]: fieldValue };
}
}),
Expand All @@ -312,6 +318,13 @@ export abstract class LateralJoinDialectBase<Schema extends SchemaDef> extends B
),
};
}
const fieldDef = requireField(this.schema, relationModel, field);
if (fieldDef.computed && fieldDef.params) {
// parameterized computed field included on the relation: inline with args
return {
[field]: this.fieldRef(relationModel, field, relationModelAlias, true, (value as any).args),
};
}
return { [field]: eb.ref(`${parentResultName}$${field}.$data`) };
}),
);
Expand Down
31 changes: 31 additions & 0 deletions packages/orm/src/client/crud/dialects/sqlite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,8 @@ export class SqliteCrudDialect<Schema extends SchemaDef> extends BaseCrudDialect
objArgs.push(
...Object.entries(relationModelDef.fields)
.filter(([, value]) => !value.relation)
// a parameterized computed field is never auto-returned (it needs args)
.filter(([, value]) => !(value.computed && value.params))
.filter(([name]) => !this.shouldOmitField(omit, relationModel, name))
.map(([field]) => [sql.lit(field), this.fieldRef(relationModel, field, subQueryName, false)])
.flatMap((v) => v),
Expand Down Expand Up @@ -295,6 +297,20 @@ export class SqliteCrudDialect<Schema extends SchemaDef> extends BaseCrudDialect
value,
);
return [sql.lit(field), subJson];
} else if (fieldDef.computed && fieldDef.params) {
// parameterized computed field on the relation: inline the
// computer with its query-time args (correlates to the
// relation's real columns, which are materialized)
return [
sql.lit(field),
this.fieldRef(
relationModel,
field,
subQueryName,
true,
(value as any).args,
) as ArgsType,
];
} else {
return [
sql.lit(field),
Expand Down Expand Up @@ -326,6 +342,21 @@ export class SqliteCrudDialect<Schema extends SchemaDef> extends BaseCrudDialect
);
return [sql.lit(field), subJson];
}
const fieldDef = requireField(this.schema, relationModel, field);
if (fieldDef.computed && fieldDef.params) {
// parameterized computed field included on the relation: inline
// the computer with its query-time args
return [
sql.lit(field),
this.fieldRef(
relationModel,
field,
subQueryName,
true,
(value as any).args,
) as ArgsType,
];
}
const subJson = this.buildRelationJSON(
relationModel,
eb,
Expand Down
Loading
Loading