Refacto: écritures SQL pilotées par schéma (SqlSchema.void) dans les projecteurs #25

Closed
opened 2026-07-04 12:11:07 +00:00 by momsse · 0 comments
Owner

Migré depuis viziertronic/octant#60 — ouvert le 2026-06-16 par @momsse.

Contexte

Retour de review sur la PR #59 (comment inline) : les projecteurs écrivent du SQL en interpolant les champs d'event « à la main », sans profiter du couplage schéma offert par @effect/sql.

Aujourd'hui c'est asymétrique :

  • Lectures : les query adapters décodent via SqlSchema.findOne/findAll + un Result schema.
  • Écritures : les projecteurs encodent à la main, p. ex. dans active-session.projector.ts / account-token-expiry.projector.ts :
    const expiresAtDate = new Date(DateTime.toEpochMillis(event.expiresAt))
    return this.execute('upsertSession', this.sql`INSERT ... VALUES (..., ${expiresAtDate})`)
    

@effect/sql expose le symétrique de la lecture : SqlSchema.void({ Request, execute }), qui encode l'input via un schéma avant d'exécuter le SQL.

Note : @effect/sql est volontairement « thin » (pas un ORM). Le SQL en tagged-template reste idiomatique ; l'objectif n'est pas de masquer le SQL mais de rendre les écritures schéma-symétriques des lectures et de supprimer l'encodage manuel.

Proposition

  1. @octant/postgres-projection : ajouter un helper executeWrite(operation, statement: Effect<_, SqlError | SchemaError>) (variante de execute, qui n'accepte aujourd'hui que SqlError), gérant au même endroit le span et le mapping SqlError/SchemaErrorProjectionError. Un seul endroit, réutilisé par tous les projecteurs.
  2. Migrer les écritures porteuses de timestamps vers SqlSchema.void + un Request schema réutilisant les mêmes codecs que la lecture (Schema.DateTimeUtcFromDate, ids brandés) :
    • postgres-authentication (PR #59) : active-session.projector.ts, account-token-expiry.projector.ts
    • postgres-authorization (PR 11, #11) : projecteurs équivalents — appliquer symétriquement.
  3. Laisser en SQL brut (déjà idiomatique, gain nul) : les DELETE … WHERE, UPDATE … WHERE, et les inserts/upserts purement string (user-lookup). sql.insert/sql.update ne couvrent pas ON CONFLICT, donc inutiles pour nos upserts.

Pourquoi pas dans la PR #59

PR #59 est merge-ready et la review a convergé ; la refacto est transverse (package partagé + 2 packages infra). On la traite à part pour ne pas rouvrir le cycle de review ni ajouter du drift au snapshot.

Critères d'acceptation

  • executeWrite schéma-aware dans @octant/postgres-projection (+ tests)
  • Plus aucun new Date(DateTime.toEpochMillis(...)) dans les projecteurs ; encodage via Schema.DateTimeUtcFromDate
  • Écritures timestamp de postgres-authentication ET postgres-authorization migrées vers SqlSchema.void
  • Request schemas réutilisent les codecs/brands des read-models
  • pnpm typecheck / lint / test au vert ; comportement inchangé (tests d'atomicité inline toujours verts)

🤖 Generated with Claude Code

> _Migré depuis [viziertronic/octant#60](https://github.com/viziertronic/octant/issues/60) — ouvert le 2026-06-16 par @momsse._ ## Contexte Retour de review sur la PR #59 ([comment inline](https://github.com/viziertronic/octant/pull/59#discussion_r3424013662)) : les projecteurs écrivent du SQL en interpolant les champs d'event « à la main », sans profiter du couplage schéma offert par `@effect/sql`. Aujourd'hui c'est **asymétrique** : - **Lectures** : les query adapters décodent via `SqlSchema.findOne/findAll` + un `Result` schema. ✅ - **Écritures** : les projecteurs encodent à la main, p. ex. dans `active-session.projector.ts` / `account-token-expiry.projector.ts` : ```ts const expiresAtDate = new Date(DateTime.toEpochMillis(event.expiresAt)) return this.execute('upsertSession', this.sql`INSERT ... VALUES (..., ${expiresAtDate})`) ``` `@effect/sql` expose le symétrique de la lecture : **`SqlSchema.void({ Request, execute })`**, qui **encode** l'input via un schéma avant d'exécuter le SQL. > Note : `@effect/sql` est volontairement « thin » (pas un ORM). Le SQL en tagged-template reste idiomatique ; l'objectif n'est pas de masquer le SQL mais de **rendre les écritures schéma-symétriques des lectures** et de supprimer l'encodage manuel. ## Proposition 1. **`@octant/postgres-projection`** : ajouter un helper `executeWrite(operation, statement: Effect<_, SqlError | SchemaError>)` (variante de `execute`, qui n'accepte aujourd'hui que `SqlError`), gérant au même endroit le span et le mapping `SqlError`/`SchemaError` → `ProjectionError`. Un seul endroit, réutilisé par tous les projecteurs. 2. **Migrer les écritures porteuses de timestamps** vers `SqlSchema.void` + un `Request` schema réutilisant les mêmes codecs que la lecture (`Schema.DateTimeUtcFromDate`, ids brandés) : - `postgres-authentication` (PR #59) : `active-session.projector.ts`, `account-token-expiry.projector.ts` - `postgres-authorization` (PR 11, #11) : projecteurs équivalents — **appliquer symétriquement**. 3. **Laisser en SQL brut** (déjà idiomatique, gain nul) : les `DELETE … WHERE`, `UPDATE … WHERE`, et les inserts/upserts purement `string` (`user-lookup`). `sql.insert`/`sql.update` ne couvrent pas `ON CONFLICT`, donc inutiles pour nos upserts. ## Pourquoi pas dans la PR #59 PR #59 est merge-ready et la review a convergé ; la refacto est **transverse** (package partagé + 2 packages infra). On la traite à part pour ne pas rouvrir le cycle de review ni ajouter du drift au snapshot. ## Critères d'acceptation - [ ] `executeWrite` schéma-aware dans `@octant/postgres-projection` (+ tests) - [ ] Plus aucun `new Date(DateTime.toEpochMillis(...))` dans les projecteurs ; encodage via `Schema.DateTimeUtcFromDate` - [ ] Écritures timestamp de `postgres-authentication` ET `postgres-authorization` migrées vers `SqlSchema.void` - [ ] `Request` schemas réutilisent les codecs/brands des read-models - [ ] `pnpm typecheck` / `lint` / `test` au vert ; comportement inchangé (tests d'atomicité inline toujours verts) 🤖 Generated with [Claude Code](https://claude.com/claude-code)
momsse 2026-07-04 12:11:07 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
momsse/octant#25
No description provided.