Back to flin
flin

Historique des versions et requêtes de voyage dans le temps

Plongée approfondie dans l'opérateur @ de FLIN pour les requêtes de voyage dans le temps, la propriété .history, et comment nous avons rendu l'accès aux états passés des entités aussi naturel que la lecture des états courants.

Thales & Claude | March 30, 2026 7 min flin
EN/ FR/ ES
flintime-travelhistoryat-timequeries

Le modèle temporel décrit dans l'article précédent stocke chaque version de chaque entité. Mais du stockage sans accès n'est qu'un usage coûteux du disque. La véritable puissance du système temporel de FLIN réside dans la façon dont les développeurs l'interrogent : l'opérateur @ pour l'accès ponctuel, la propriété .history pour les chronologies complètes, et un ensemble de mots-clés temporels qui rendent les requêtes courantes aussi lisibles que de l'anglais.

Cet article couvre trois fonctionnalités interconnectées construites au cours des sessions 012, 075 et 081 -- l'opérateur @, la propriété .history, et l'infrastructure qui rend les deux naturels.

L'opérateur @ : le voyage dans le temps en deux caractères

L'opérateur @ est la syntaxe de FLIN pour accéder aux états passés d'une entité. Il prend une entité à gauche et une référence temporelle à droite, et retourne l'entité telle qu'elle existait à ce moment.

Trois formes de référence temporelle sont supportées.

Accès par version relative

La forme la plus courante. Un entier négatif compte en arrière depuis la version courante :

flinuser @ -1      // Previous version
user @ -2      // Two versions ago
user @ -10     // Ten versions ago
user @ 0       // First version ever
user @ 1       // Second version

Accès par temps absolu

Une chaîne de date interroge l'état de l'entité à un moment précis :

flinuser @ "2024-01-15"                    // At specific date
user @ "2024-01-15T14:30:00Z"          // At specific datetime

Accès par mot-clé

Sept mots-clés intégrés couvrent les requêtes temporelles les plus courantes :

flinuser @ yesterday
user @ last_week
user @ last_month
Mot-cléDescription
nowMoment actuel
todayDébut d'aujourd'hui (00:00:00 UTC)
yesterdayDébut d'hier
tomorrowDébut de demain
last_weekIl y a sept jours
last_monthIl y a trente jours
last_yearIl y a trois cent soixante-cinq jours

Les mots-clés ne sont pas du sucre syntaxique pour des chaînes de date. Ce sont des jetons de première classe dans le lexeur, analysés comme des noeuds AST dédiés, et compilés en un code temporel d'un seul octet dans le bytecode. user @ yesterday se compile en deux instructions : charger l'utilisateur, puis AtTime(0x03). Pas d'analyse de chaîne à l'exécution. Pas d'arithmétique de dates dans le code applicatif.

Construction de l'opérateur @ : session 012

L'opérateur @ a été implémenté en session 012, une session de cinquante minutes qui a établi l'infrastructure de requêtes temporelles. La structure de données centrale était un struct EntityVersion stocké dans une carte d'historique de versions à l'intérieur de la VM :

rust/// Historical version of an entity for time-travel queries
#[derive(Debug, Clone)]
pub struct EntityVersion {
    pub version: u64,
    pub timestamp: i64,
    pub fields: HashMap<String, Value>,
}

// VM struct additions
/// Key: (entity_type, entity_id), Value: historical versions (oldest first)
version_history: HashMap<(String, u64), Vec<EntityVersion>>,

L'opcode save a été modifié pour prendre un instantané de l'état courant avant d'écrire la nouvelle version. Ce pattern « copier avant écrire » signifie que le tableau d'historique contient toujours les versions passées uniquement. La version courante vit dans le stockage d'entités, et la propriété .history l'ajoute lors de la construction de la chronologie complète.

La session a aussi ajouté des méthodes d'élagage de l'historique de versions, anticipant le coût de stockage d'un historique illimité :

rust/// Prune version history older than timestamp
pub fn prune_versions_before(&mut self, timestamp: i64) -> usize

/// Keep only N most recent versions per entity
pub fn prune_versions_keep_last(&mut self, count: usize) -> usize

/// Get total count of stored versions
pub fn version_history_count(&self) -> usize

Dix nouveaux tests. Trois cent un tests passant au total.

Correction d'AtTime : quand les mots-clés ne fonctionnaient pas

Il y avait un écart embarrassant entre « le modèle temporel existe » et « le modèle temporel fonctionne ». La session 012 a implémenté l'opcode AtTime comme un stub qui retournait simplement l'entité inchangée. Si vous écriviez user @ yesterday, vous obteniez l'utilisateur actuel.

La correction nécessitait de convertir les mots-clés temporels en horodatages réels :

rustOpCode::AtTime => {
    let time_code = self.read_u8(code);
    let entity_val = self.pop()?;

    let target_timestamp = if let Some(tc) = TimeCode::from_byte(time_code) {
        let now = current_timestamp_ms();
        match tc {
            TimeCode::Now => now,
            TimeCode::Today => {
                let secs_today = (now / 1000) - ((now / 1000) % 86400);
                secs_today * 1000
            }
            TimeCode::Yesterday => {
                let secs_today = (now / 1000) - ((now / 1000) % 86400);
                (secs_today - 86400) * 1000
            }
            TimeCode::LastWeek => now - (7 * 24 * 60 * 60 * 1000),
            TimeCode::LastMonth => now - (30 * 24 * 60 * 60 * 1000),
            TimeCode::LastYear => now - (365 * 24 * 60 * 60 * 1000),
        }
    };
    // Find version at target timestamp using history lookup
}

La propriété .history : session 075

L'opérateur @ donne une seule version. La propriété .history les donne toutes. La session 075 a révélé que chaque couche de la pile avait déjà le support -- les seuls problèmes étaient deux bugs qui faisaient produire des résultats incorrects.

Le bug de duplication : ZeroCore ajoutait la version initiale à l'historique, puis la VM l'ajoutait de nouveau. La correction a établi la règle : l'historique ne contient que les versions passées. La VM est responsable de l'ajout de la version courante.

Le bug des entités non sauvegardées : les entités avec id == 0 retournaient [current] au lieu de []. La correction a ajouté une vérification is_saved.

L'opérateur @ retourne un optionnel

L'accès temporel retourne une valeur optionnelle. La version demandée pourrait ne pas exister :

flinold_user = user @ "2020-01-01"        // User? (might not exist)

{if old_user}
    <p>User existed: {old_user.name}</p>
{else}
    <p>User did not exist yet</p>
{/if}

Cela force les développeurs à gérer le cas d'absence, empêchant les exceptions de pointeur nul.

Comparer à travers le temps

L'opérateur @ se compose naturellement avec les expressions de comparaison :

flin// Did the name change since the last version?
name_changed = user.name != (user @ -1).name

// Did the price increase since yesterday?
price_increased = product.price > (product @ yesterday).price

Ces patterns semblent évidents rétrospectivement, mais ils ne sont possibles que parce que @ retourne une entité complète avec tous les champs remplis.

Le tableau complet

À la fin de la session 081, le système d'accès temporel de FLIN était complet :

  • Trois formes d'accès @ (relatif, absolu, mot-clé).
  • Sept mots-clés temporels.
  • La propriété .history pour les chronologies complètes.
  • Des types de retour optionnels empêchant les erreurs de pointeur nul.
  • Des métadonnées de version sur chaque instantané historique.
  • Une couverture de tests bout en bout du lexeur au HTML rendu.

Pas de table d'audit. Pas de middleware de changelog. Pas de framework d'event sourcing. Deux caractères et un mot-clé.


Ceci est la partie 2 de la série sur le modèle temporel de « How We Built FLIN », documentant la conception et l'implémentation du système de requêtes de voyage dans le temps de FLIN.

Navigation de la série : - [046] Every Entity Remembers Everything: The Temporal Model - [047] Version History and Time Travel Queries (vous êtes ici) - [048] Temporal Integration: From Bugs to 100% Test Coverage - [049] Destroy and Restore: Soft Deletes Done Right - [050] Temporal Filtering and Ordering

Share this article:

Responses

Write a response
0/2000
Loading responses...

Related Articles