Skip to content

Commit b44df85

Browse files
committed
feat: fields argument for @updatedAt
1 parent 5def947 commit b44df85

5 files changed

Lines changed: 45 additions & 15 deletions

File tree

packages/language/res/stdlib.zmodel

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -405,9 +405,13 @@ attribute @omit()
405405
*
406406
* @param ignore: A list of field names that are not considered when the ORM client is determining whether any
407407
* updates have been made to a record. An update that only contains ignored fields does not change the
408-
* timestamp.
408+
* timestamp. Mutually exclusive with the `fields` parameter.
409+
410+
* @param fields: A list of field names that are considered when the ORM client is determining whether any
411+
* updates have been made to a record. The timestamp will only change when any of the specified fields
412+
* are updated. Mutually exclusive with the `ignore` parameter.
409413
*/
410-
attribute @updatedAt(ignore: FieldReference[]?) @@@targetField([DateTimeField]) @@@prisma
414+
attribute @updatedAt(ignore: FieldReference[]?, fields: FieldReference[]?) @@@targetField([DateTimeField]) @@@prisma
411415

412416
/**
413417
* Add full text index (MySQL only).

packages/language/src/validators/attribute-application-validator.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,15 @@ export default class AttributeApplicationValidator implements AstValidator<Attri
280280
}
281281
}
282282

283+
@check('@updatedAt')
284+
private _checkUpdatedAt(attr: AttributeApplication, accept: ValidationAcceptor) {
285+
const ignoreArg = attr.args.find(arg => arg.$resolvedParam.name === 'ignore');
286+
const fieldsArg = attr.args.find(arg => arg.$resolvedParam.name === 'fields');
287+
if (ignoreArg && fieldsArg) {
288+
accept('error', `\`ignore\` and \`fields\` are mutually exclusive`, { node: attr.$container });
289+
}
290+
}
291+
283292
@check('@@validate')
284293
private _checkValidate(attr: AttributeApplication, accept: ValidationAcceptor) {
285294
const condition = attr.args[0]?.value;

packages/orm/src/client/crud/operations/base.ts

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1149,14 +1149,29 @@ export abstract class BaseOperationHandler<Schema extends SchemaDef> {
11491149
const autoUpdatedFields: string[] = [];
11501150
for (const [fieldName, fieldDef] of Object.entries(modelDef.fields)) {
11511151
if (fieldDef.updatedAt && finalData[fieldName] === undefined) {
1152-
const ignoredFields = new Set(typeof fieldDef.updatedAt === 'boolean' ? [] : fieldDef.updatedAt.ignore);
1153-
const hasNonIgnoredFields = Object.keys(data).some(
1154-
(field) =>
1155-
(isScalarField(this.schema, modelDef.name, field) ||
1156-
isForeignKeyField(this.schema, modelDef.name, field)) &&
1157-
!ignoredFields.has(field),
1158-
);
1159-
if (hasNonIgnoredFields) {
1152+
let hasUpdated = true;
1153+
if (typeof fieldDef.updatedAt === 'object') {
1154+
if (fieldDef.updatedAt.ignore) {
1155+
const ignoredFields = new Set(fieldDef.updatedAt.ignore);
1156+
const hasNonIgnoredFields = Object.keys(data).some(
1157+
(field) =>
1158+
(isScalarField(this.schema, modelDef.name, field) ||
1159+
isForeignKeyField(this.schema, modelDef.name, field)) &&
1160+
!ignoredFields.has(field),
1161+
);
1162+
hasUpdated = hasNonIgnoredFields;
1163+
} else if (fieldDef.updatedAt.fields) {
1164+
const targetFields = new Set(fieldDef.updatedAt.fields);
1165+
const hasAnyTargetFields = Object.keys(data).some(
1166+
(field) =>
1167+
(isScalarField(this.schema, modelDef.name, field) ||
1168+
isForeignKeyField(this.schema, modelDef.name, field)) &&
1169+
targetFields.has(field),
1170+
);
1171+
hasUpdated = hasAnyTargetFields;
1172+
}
1173+
}
1174+
if (hasUpdated) {
11601175
if (finalData === data) {
11611176
finalData = clone(data);
11621177
}

packages/schema/src/schema.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ export type RelationInfo = {
6161

6262
export type UpdatedAtInfo = {
6363
ignore?: readonly string[];
64+
fields?: readonly string[];
6465
};
6566

6667
export type FieldDef = {

packages/sdk/src/ts-schema-generator.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -522,10 +522,10 @@ export class TsSchemaGenerator {
522522
);
523523
}
524524

525-
private createUpdatedAtObject(ignoreArg: AttributeArg) {
525+
private createUpdatedAtObject(arg: AttributeArg) {
526526
return ts.factory.createObjectLiteralExpression([
527-
ts.factory.createPropertyAssignment('ignore', ts.factory.createArrayLiteralExpression(
528-
(ignoreArg.value as ArrayExpr).items.map((item) => ts.factory.createStringLiteral((item as ReferenceExpr).target.$refText))
527+
ts.factory.createPropertyAssignment(arg.$resolvedParam.name, ts.factory.createArrayLiteralExpression(
528+
(arg.value as ArrayExpr).items.map((item) => ts.factory.createStringLiteral((item as ReferenceExpr).target.$refText))
529529
))
530530
]);
531531
}
@@ -575,9 +575,10 @@ export class TsSchemaGenerator {
575575
const updatedAtAttrib = getAttribute(field, '@updatedAt') as DataFieldAttribute | undefined;
576576
if (updatedAtAttrib) {
577577
const ignoreArg = updatedAtAttrib.args.find(arg => arg.$resolvedParam?.name === 'ignore');
578+
const fieldsArg = updatedAtAttrib.args.find(arg => arg.$resolvedParam?.name === 'fields');
578579
objectFields.push(ts.factory.createPropertyAssignment('updatedAt',
579-
ignoreArg
580-
? this.createUpdatedAtObject(ignoreArg)
580+
(ignoreArg || fieldsArg)
581+
? this.createUpdatedAtObject(ignoreArg ?? fieldsArg!)
581582
: ts.factory.createTrue()
582583
));
583584
}

0 commit comments

Comments
 (0)