npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@astratra/closure

v0.1.0

Published

Closing a period on purpose: a human-owned checklist with blocking and acknowledged items, and an archive that never carries a credential.

Readme

@astratra/closure

Fermer une période volontairement : une liste de clôture portée par des humains, et une archive qui ne transporte jamais un identifiant de connexion.

Année scolaire, exercice comptable, saison, campagne : tout produit à périodes finit par les clore. Et la clôture commence presque toujours comme une échéance subie — une tâche passe à minuit et ferme la période quand une condition tient. Personne ne voit ce qui manque, et personne ne décide vraiment.

Aucune dépendance à l'exécution. Logique pure : ton contrôleur compte, ce package juge.

La liste de clôture

const { createClosureChecklist } = require('@astratra/closure');

const cloture = createClosureChecklist([
  { id: 'deliberations',   label: 'Délibérations à trancher',    blocking: true },
  { id: 'absences',        label: 'Absences sans justificatif',  blocking: false },
  { id: 'impayes',         label: 'Frais impayés',               blocking: false },
]);

// Ton contrôleur compte ce qui reste ; la liste juge.
const liste = cloture.build({ deliberations: 0, absences: 3, impayes: 12 });
// { items: [...], canClose: true, remaining: 2, blocking: 0 }

Bloquant contre reconnu — la distinction qui porte tout

Certains points interdisent de fermer : une délibération non tranchée engage l'avenir d'un enfant. D'autres méritent seulement d'être vus : un impayé se négocie et se reporte.

Fermer une période en laissant des impayés est légitime. Le faire sans l'avoir vu ne l'est pas. C'est le rôle de la reconnaissance :

cloture.canCloseWith(liste, []);
// { ok: false, reason: 'unacknowledged', unseen: ['absences', 'impayes'] }

cloture.canCloseWith(liste, ['absences', 'impayes']);
// { ok: true }

Deux règles tenues par les tests :

Aucune reconnaissance ne passe au-dessus d'un point bloquant. Cocher toutes les cases ne ferme pas une période dont une délibération reste ouverte.

Cocher d'avance ne pré-approuve rien. Une reconnaissance pour un point qui n'en demande pas est ignorée, pas récompensée — sinon on cocherait tout au 1er septembre pour être tranquille en juin.

L'archive

Une archive se transmet, se range, parfois s'envoie par courriel. Deux règles, apprises au prix fort :

Ce qui n'y entre jamais

Mots de passe, jetons, clés d'API : rien de ce qui permet de se connecter à la place de quelqu'un. Le nettoyage se fait à la frontière de l'archive — plutôt que de faire confiance à chaque lecteur pour avoir bien choisi ses champs. C'est la seule version qui survit au prochain qui ajoute une collection.

const { createArchiveBuilder, createScrubber } = require('@astratra/closure');

const builder = createArchiveBuilder({
  scrubber: createScrubber({ alsoNever: ['activationCode'] }),
  sections: [
    { name: 'students', read: (scope) => Student.find({ school: scope.school }).lean() },
    { name: 'results',  read: (scope) => Result.find({ year: scope.yearId }).lean() },
    { name: 'payments', read: (scope) => paymentsCollection.find({ year: scope.yearId }).toArray() },
  ],
});

const archive = await builder.build({ yearId, school });
// { builtAt, sections, counts, failed: [], complete: true }

Une section en échec ne vide pas l'archive — et elle est nommée

Les sections sont lues séparément. Un lecteur qui lève est consigné dans failed avec sa raison, et le reste de l'archive se construit quand même. Le rapport dit exactement ce qui est dedans et ce qui n'y est pas — une archive à laquelle il manque une section en silence a l'air complète et ne l'est pas.

Ce que ce package ne fait pas

  • Il ne compte rien : les requêtes qui dénombrent ce qui reste sont à toi.
  • Il ne stocke pas l'archive — écrire le fichier, le chiffrer, lui donner une durée de vie, c'est ta décision.
  • Il ne connaît ni les écoles, ni les exercices comptables : les points de contrôle et les sections sont ton vocabulaire.

Tests

npm test --workspace @astratra/closure