You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
field.timestamp accepts an @autoSet: onCreate | onUpdate marker — the canonical way to declare "the CRUD layer owns this timestamp, the caller does not." But no port's code generator emits the stamping logic for it. The generated insert/update leave the column unset (or force the caller to supply a value), so every adopter re-implements now() stamping by hand in a hand-written repository — which defeats the purpose of generated CRUD and reintroduces exactly the drift codegen exists to remove.
In one adopter, this single un-honored declaration was the root cause behind ~32 of 62 hand-written repositories (the large majority of the hand-written repository code by line count). @autoSet is declared once on a base entity's createdAt/updatedAt and inherited by nearly every entity, so honoring it in codegen collapses that entire class of hand-written boilerplate.
Why this belongs in core codegen
created_at / updated_at stamping is pattern-derivable from the metadata (@autoSet: onCreate|onUpdate on a field.timestamp) — exactly like FK finders and CRUD, which codegen already owns. It's the same "if it's derivable from metadata, generate it, don't hand-write it" principle. Leaving it to each adopter guarantees three recurring failures:
Duplicated now() stamping in every writable repository.
A latent lost-update-style bug: a full-row update(model) rewrites created_at (a write-once column) from whatever stale value the model carries, unless the hand-written code remembers to special-case it.
Inconsistent behavior across ports (a lagging port silently stamps differently, or not at all).
patch(id) { … } stamps onUpdatebefore running the caller's partial-update block, so a partial update still bumps updated_at.
insertPreserving(model) — an escape hatch that writes the @autoSet columns verbatim from the model, for import / restore / replication paths that must keep the original timestamps. Only emitted for entities that actually declare @autoSet fields.
The generated column mapper carries stampAutoSet: Boolean = true / includeOnCreate: Boolean = true flags, so the base CRUD and the escape hatch share one definition of the column list rather than duplicating it.
The now() expression is keyed off the column's temporal type, not the parameter's, so this generalizes to any temporal type, not just Instant.
Proposal
Make @autoSet stamping core codegen vocabulary: the generated insert / update / patch honor onCreate / onUpdate on field.timestamp (ideally any temporal type), with an insertPreserving-style verbatim escape hatch, emitted consistently across all ports so a lagging port can't silently diverge on stamping behavior.
Related
Same "pattern-derivable ⇒ generate it" theme as #198 (no generated partial/patch update). Distinct from #197 (identity PKs) which was retired as invalid — this one is a genuine cross-port codegen gap that every adopter currently pays for by hand.
Summary
field.timestampaccepts an@autoSet: onCreate | onUpdatemarker — the canonical way to declare "the CRUD layer owns this timestamp, the caller does not." But no port's code generator emits the stamping logic for it. The generatedinsert/updateleave the column unset (or force the caller to supply a value), so every adopter re-implementsnow()stamping by hand in a hand-written repository — which defeats the purpose of generated CRUD and reintroduces exactly the drift codegen exists to remove.In one adopter, this single un-honored declaration was the root cause behind ~32 of 62 hand-written repositories (the large majority of the hand-written repository code by line count).
@autoSetis declared once on a base entity'screatedAt/updatedAtand inherited by nearly every entity, so honoring it in codegen collapses that entire class of hand-written boilerplate.Why this belongs in core codegen
created_at/updated_atstamping is pattern-derivable from the metadata (@autoSet: onCreate|onUpdateon afield.timestamp) — exactly like FK finders and CRUD, which codegen already owns. It's the same "if it's derivable from metadata, generate it, don't hand-write it" principle. Leaving it to each adopter guarantees three recurring failures:now()stamping in every writable repository.update(model)rewritescreated_at(a write-once column) from whatever stale value the model carries, unless the hand-written code remembers to special-case it.Reference implementation (one adopter's Kotlin/Exposed generator)
Sharing the contract an adopter settled on, in case it's a useful starting point. For a
field.timestamp @autoSet: onCreate|onUpdate:insertstamps everyonCreateandonUpdatecolumn withnow()— the model's value is ignored (a fresh row'supdated_atequals itscreated_at).update(model)stampsonUpdatecolumns withnow()and skipsonCreatecolumns entirely — it never rewritescreated_at. (Omitting this special-case is the latent bug in FR-019: shared + @provided enums (all five ports) + typecheck-gate repair #2 above.)patch(id) { … }stampsonUpdatebefore running the caller's partial-update block, so a partial update still bumpsupdated_at.insertPreserving(model)— an escape hatch that writes the@autoSetcolumns verbatim from the model, for import / restore / replication paths that must keep the original timestamps. Only emitted for entities that actually declare@autoSetfields.stampAutoSet: Boolean = true/includeOnCreate: Boolean = trueflags, so the base CRUD and the escape hatch share one definition of the column list rather than duplicating it.The
now()expression is keyed off the column's temporal type, not the parameter's, so this generalizes to any temporal type, not justInstant.Proposal
Make
@autoSetstamping core codegen vocabulary: the generatedinsert/update/patchhonoronCreate/onUpdateonfield.timestamp(ideally any temporal type), with aninsertPreserving-style verbatim escape hatch, emitted consistently across all ports so a lagging port can't silently diverge on stamping behavior.Related
Same "pattern-derivable ⇒ generate it" theme as #198 (no generated partial/patch update). Distinct from #197 (identity PKs) which was retired as invalid — this one is a genuine cross-port codegen gap that every adopter currently pays for by hand.