Skip to content

Commit 8ac174c

Browse files
authored
fix orm onUpdate timestamp normalization (#251)
1 parent a898a56 commit 8ac174c

4 files changed

Lines changed: 68 additions & 5 deletions

File tree

.changeset/stale-onupdate-dates.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"kitcn": patch
3+
---
4+
5+
## Patches
6+
7+
- Fix ORM updates so timestamp `$onUpdateFn` hooks can return `Date` values.

convex/orm/constraints.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
isNull,
1717
or,
1818
text,
19+
timestamp,
1920
unique,
2021
unsetToken,
2122
} from 'kitcn/orm';
@@ -48,6 +49,14 @@ const hookUsers = convexTable(
4849
(t) => [index('by_name').on(t.name)]
4950
);
5051

52+
const timestampHookUsers = convexTable('timestamp_hook_users', {
53+
name: text().notNull(),
54+
updatedAt: timestamp()
55+
.notNull()
56+
.defaultNow()
57+
.$onUpdateFn(() => new Date('2026-04-27T09:02:18.240Z')),
58+
});
59+
5160
const checkUsers = convexTable(
5261
'check_users',
5362
{
@@ -126,6 +135,7 @@ const polymorphicComments = convexTable(
126135
const rawSchema = {
127136
default_users: defaultUsers,
128137
hook_users: hookUsers,
138+
timestamp_hook_users: timestampHookUsers,
129139
check_users: checkUsers,
130140
unique_column_users: uniqueColumnUsers,
131141
unique_table_users: uniqueTableUsers,
@@ -214,6 +224,23 @@ describe('column hooks', () => {
214224
}
215225
}));
216226

227+
it('normalizes timestamp $onUpdateFn Date values before patching', async () =>
228+
withCtx(async ({ orm }) => {
229+
const [user] = await orm
230+
.insert(timestampHookUsers)
231+
.values({ name: 'Ada' })
232+
.returning();
233+
234+
const [updated] = await orm
235+
.update(timestampHookUsers)
236+
.set({ name: 'Updated' })
237+
.where(eq(timestampHookUsers.id, user.id))
238+
.returning();
239+
240+
expect(updated.name).toBe('Updated');
241+
expect(updated.updatedAt).toEqual(new Date('2026-04-27T09:02:18.240Z'));
242+
}));
243+
217244
it('$onUpdateFn does not override explicit set', async () =>
218245
withCtx(async ({ orm }) => {
219246
hookUpdatedAtCalls = 0;
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Issue 249 onUpdate date normalization
2+
3+
## Source
4+
5+
- GitHub issue: https://github.com/udecode/kitcn/issues/249
6+
- Type: bug
7+
- Expected outcome: ORM update hooks returning `Date` for timestamp columns are
8+
normalized before Convex `db.patch`.
9+
10+
## Scope
11+
12+
- Package code: `packages/kitcn/src/orm/update.ts`
13+
- Regression: `convex/orm/constraints.test.ts`
14+
- Release artifact: create or update an unreleased changeset.
15+
- Browser: not applicable.
16+
17+
## Verification
18+
19+
- Red regression for `timestamp().$onUpdateFn(() => new Date())`: confirmed
20+
with `bunx vitest run convex/orm/constraints.test.ts -t "normalizes timestamp"`.
21+
- Targeted ORM test: `bunx vitest run convex/orm/constraints.test.ts`.
22+
- Package build: `bun --cwd packages/kitcn build`.
23+
- Lint fix: `bun lint:fix`.
24+
- Typecheck: `bun typecheck`.
25+
- PR gate: `bun check`.

packages/kitcn/src/orm/update.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,10 @@ export class ConvexUpdateBuilder<
464464
...onUpdateSet,
465465
...(normalizedSetValues as any),
466466
} as UpdateSet<TTable>;
467+
const writeSet = normalizeDateFieldsForWrite(
468+
this.table,
469+
effectiveSet as any
470+
) as UpdateSet<TTable>;
467471

468472
const tableName = getTableName(this.table);
469473

@@ -636,7 +640,7 @@ export class ConvexUpdateBuilder<
636640

637641
const updates = await Promise.all(
638642
rows.map(async (row) => {
639-
const updatedRow = { ...(row as any), ...(effectiveSet as any) };
643+
const updatedRow = { ...(row as any), ...(writeSet as any) };
640644
const decision = await evaluateUpdateDecision({
641645
table: this.table,
642646
existingRow: row as Record<string, unknown>,
@@ -669,11 +673,11 @@ export class ConvexUpdateBuilder<
669673
continue;
670674
}
671675
enforcePolymorphicWrite(this.table, updatedRow, {
672-
changedFields: new Set(Object.keys(effectiveSet as any)),
676+
changedFields: new Set(Object.keys(writeSet as any)),
673677
});
674678
enforceCheckConstraints(this.table, updatedRow);
675679
await enforceForeignKeys(this.db, this.table, updatedRow, {
676-
changedFields: new Set(Object.keys(effectiveSet as any)),
680+
changedFields: new Set(Object.keys(writeSet as any)),
677681
});
678682

679683
await applyIncomingForeignKeyActionsOnUpdate(
@@ -698,9 +702,9 @@ export class ConvexUpdateBuilder<
698702
);
699703
await enforceUniqueIndexes(this.db, this.table, updatedRow, {
700704
currentId: (row as any)._id,
701-
changedFields: new Set(Object.keys(effectiveSet as any)),
705+
changedFields: new Set(Object.keys(writeSet as any)),
702706
});
703-
await this.db.patch(tableName, (row as any)._id, effectiveSet as any);
707+
await this.db.patch(tableName, (row as any)._id, writeSet as any);
704708
numAffected++;
705709

706710
if (!this.returningFields) {

0 commit comments

Comments
 (0)