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

@ogygie/core

v0.14.0

Published

Ogygie framework core: manifest/spec/error schemas, generic tenancy and gate tooling. Ships TypeScript sources — Ogygie products run tsx/vitest by construction (D1).

Readme

@ogygie/core

Le framework Ogygie : l'outillage et l'infrastructure GÉNÉRIQUES du repo. Règle de séparation (DECISIONS.md D10) — le core ne connaît aucun mot du domaine du produit qui le consomme ; la dépendance va produit → core, jamais l'inverse. Un module qui contient une seule décision propre au produit reste dans apps/.

| Export | Rôle | |---|---| | @ogygie/core/errors | Format OgygieError (code stable, file, cause, suggestedFix) | | @ogygie/core/manifest-owners | Quelle entrée de la carte possède une spec — feature ou package du produit | | @ogygie/core/manifest | Schéma du manifeste ogygie.manifest.json | | @ogygie/core/spec | Frontmatter des specs (parseSpecFrontmatter) et sections canoniques | | @ogygie/core/db · /clock · /tenant · /test-kit | Accès DB, horloge injectable, façade multi-tenant, kit de test | | @ogygie/core/web-lint · /bundle-budget | Parties testables des gates front | | @ogygie/core/vocabulary | Appariement des racines de vocabulaire sur les identifiants machine — les racines appartiennent au produit | | @ogygie/core/vocabulary-audit | Jugement du gate de vocabulaire : alias interdits du code, pureté des fichiers gouvernés | | @ogygie/core/gateway | Passerelle publique dérivée du manifeste (ci-dessous) | | @ogygie/core/client | Client d'API front dérivé du manifeste : transport (fetchImpl injecté), clés de cache et invalidations (D13) | | @ogygie/core/client-react | Les hooks du même client — react et @tanstack/react-query en peers OPTIONNELS, chargés dynamiquement (D13) |

Le test du vocabulaire couvre TOUT le core

Le gate pnpm ogygie:verify-core-vocabulary du repo du framework scanne chaque fichier de ce package — modules, tests, fixtures, JSON d'exemples — et casse la CI à la moindre racine produit (nom du produit, id de feature, mot du domaine, dans les deux langues). Les fixtures fuient le vocabulaire aussi facilement que le code : rien n'est exempté. Les mots interdits, eux, vivent dans scripts/ du repo du framework, JAMAIS ici : ce package est publié (files: ["src"]), donc une liste de mots produit stockée dedans SERAIT la fuite — c'est exactement ainsi que la 0.1.0 a publié le vocabulaire complet d'un domaine produit, dans un fichier de test.

Hypothèses structurelles assumées (non-vocabulaires)

Sans domaine ne veut pas dire sans opinion. Les hypothèses ci-dessous sont GRAVÉES dans les modules du core ; on les DÉCLARE ici plutôt que de les diluer — un produit qui ne peut pas les accepter ne peut pas consommer ce core tel quel.

  1. SaaS multi-tenant à colonne tenant unique. TenantContext (tenantId, userId, db, tenantColumnKey) est le contrat d'accès aux données : chaque table métier porte UNE colonne tenant et tout accès passe par la façade tenantDb (injection à l'écriture, filtre à la lecture, transactions comprises). Seule exception : les référentiels PARTAGÉS, déclarés au manifeste (shared: true) et vérifiés par shared-tables. Le produit nomme son tenant comme il veut (un alias) ; le core impose le MODÈLE.
  2. PostgreSQL, via Drizzle. Database est un PgDatabase (drizzle-orm/pg-core) ; migrations en SQL Postgres ; policy RLS attendue sur toute table tenant (second rempart) ; transactions imbriquées = savepoints Postgres ; test-kit sur PGlite (Postgres en mémoire). Ni MySQL, ni SQLite, ni un autre ORM.
  3. Fastify comme runtime HTTP. manifest-gateway produit une app Fastify (helmet, rate-limit, static) et l'API du produit est supposée être un FastifyInstance décoré (app.db, app.clock).
  4. Zod aux frontières. Toute entrée/sortie a un schéma Zod, source unique de vérité (validation runtime + type inféré + doc) ; le graphe d'un schemas.ts n'autorise par défaut QUE zod (contract-purity, AGENTS.md §Structure — l'exception des CONTRATS).
  5. Une devise de travail par tenant. L'argent est un entier en centimes
    • code ISO 4217 ; aucune conversion de change ne traverse le core. Les futurs helpers monétaires naîtront ici sous cette hypothèse.
  6. Temps en UTC, horloge injectée. Clock est la seule porte d'accès au temps ; les instants persistés sont de l'ISO 8601 UTC.
  7. Repo cartographié par un manifeste. ogygie.manifest.json à la racine décrit apps, features, routes, tables, dépendances — les gates et les dérivations (passerelle, client) le tiennent pour LA carte du repo.
  8. Monorepo TypeScript ESM strict. Modules NodeNext (imports relatifs en .js), pnpm workspace, vitest, TypeScript strict ; les analyses statiques (module-purity, contract-purity) lisent la syntaxe d'import TypeScript — pas un bundler, pas un graphe runtime.
  9. Anglais machine-lisible. Identifiants, codes d'erreur, valeurs d'enum et commentaires en anglais (D11) ; les documents humains du repo consommateur restent en français.

Passerelle dérivée du manifeste — @ogygie/core/gateway

createManifestGateway(manifest, options) dérive une passerelle Fastify d'un manifeste Ogygie : chaque route déclarée devient un relais typé, et rien d'autre n'est atteignable. Zéro ligne de code par feature — livrer une route n'ajoute aucun travail ici, l'oublier au manifeste la rend inatteignable.

C'est un relais pur : il ne décide jamais qui a le droit de quoi (l'autorisation reste dans l'API — un relais qui autorise devient un confused deputy), ne transforme aucun payload, ne détient aucune session.

Usage minimal

import { readFileSync } from "node:fs";
import { createManifestGateway } from "@ogygie/core/gateway";
import * as widgetSchemas from "@my-product/api/features/widgets/schemas";

const manifest: unknown = JSON.parse(readFileSync("ogygie.manifest.json", "utf8"));

const app = await createManifestGateway(manifest, {
  upstreamBaseUrl: "http://127.0.0.1:3000", // l'api, sur le réseau privé
  schemas: { ...widgetSchemas },            // contrats indexés par nom exporté
  staticRoot: "apps/web/dist",              // build du front + fallback SPA
  rateLimit: { max: 300, timeWindow: "1 minute" },
  timeoutMs: 10_000,
  csp: { nonce: true },
  passthroughPrefixes: ["/api/auth/"],      // liste FERMÉE, relais sans validation
});

await app.listen({ port: 8080, host: "0.0.0.0" });
console.log(`${app.gatewayRoutes.length} relais montés depuis le manifeste`);
for (const route of app.gatewayRoutes) console.log(route.method, route.path);

Options

| Option | Requis | Effet | |---|---|---| | upstreamBaseUrl | oui | Adresse privée de l'api. Jamais exposée au client, même en erreur. | | schemas | oui | Record<string, ZodType> indexé par nom exporté. Doit contenir tous les schemaInput/schemaOutput du manifeste, sinon le démarrage est refusé. | | timeoutMs | oui | Délai au-delà duquel l'appel amont est abandonné (→ 504). | | csp.nonce | oui | Pose un nonce CSP par requête et l'estampille sur les <script> de l'index.html servi. | | staticRoot | non | Dossier du build front : fichiers statiques + fallback index.html pour le routage client. Absent = rien n'est servi. | | rateLimit | non | { max, timeWindow } ; défaut 300 / 1 minute. | | passthroughPrefixes | non | Préfixes relayés sans validation de contrat (ex. un catch-all d'auth possédé par l'api, absent du manifeste par nature). Seule brèche à la règle « rien ne passe qui ne soit au manifeste » : liste fermée, chaque entrée justifiée, jamais un motif large. |

Ce que le module N'inclut PAS, et qui reste à la charge de l'application : le registre de schémas (produit-conscient par nature), le contrat d'environnement, le point d'entrée, et le contenu de passthroughPrefixes.

Ce qui est posé pour tout le produit

En-têtes de sécurité possédés par la passerelle (une copie amont ne peut pas les écraser) : CSP stricte sans unsafe-inline, HSTS, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, plus le rate limit. Le statut, le corps et les Set-Cookie de l'amont, eux, traversent octet pour octet — une OgygieError métier arrive au client sans reformatage.

Erreurs

| Code | Statut | Quand | |---|---|---| | GATEWAY_MANIFEST_INVALID | 500, au démarrage | Manifeste illisible, chemin de route malformé, route déclarée deux fois, ou schéma déclaré absent de schemas. Levée en OgygieFailure : le processus ne démarre pas — un démarrage impossible vaut mieux qu'un relais qui ment. | | CONTRACT_VALIDATION_FAILED | 400 | L'entrée ne satisfait pas le schéma déclaré. Émise PAR la passerelle : le trafic invalide n'atteint jamais le réseau privé. Le corps JSON est validé sur POST/PUT/PATCH, { ...query, ...params } sur GET/DELETE. | | GATEWAY_ROUTE_UNKNOWN | 404 | Aucune route déclarée ne correspond (et aucun fallback SPA applicable). | | GATEWAY_UPSTREAM_UNAVAILABLE | 502 | L'amont est injoignable. Aucun détail interne ne fuit : ni URL privée, ni stack. | | GATEWAY_UPSTREAM_TIMEOUT | 504 | L'amont n'a pas répondu dans timeoutMs. |

Client d'API dérivé du manifeste — @ogygie/core/client et /client-react

createManifestClient(manifest, schemas, options) est le pendant front de la passerelle (spec manifest-client, D13) : chaque route déclarée devient une méthode, sous le namespace de sa racine de chemin. Une route absente du manifeste n'existe pas côté client. Les clés de cache et les invalidations sont DÉRIVÉES — un produit n'écrit plus une seule clé à la main, ni un seul fetch.

Usage minimal

import { createManifestClient } from "@ogygie/core/client";
import { createApiHooks } from "@ogygie/core/client-react";
import * as widgetSchemas from "@my-product/api/features/widgets/schemas";

const api = createManifestClient(manifest, { ...widgetSchemas }, {
  fetchImpl: globalThis.fetch,                       // effet injecté, jamais importé au fond d'un helper
  validateResponses: import.meta.env.DEV ? "always" : "never",
});

api.widgets.list();                                  // GET  /api/widgets
api.widgets.get({ widgetId });                       // GET  /api/widgets/:widgetId
api.widgets.list.queryKey();                         // ["widgets", "list"]
api.widgets.create.invalidationRoots;                // ["widgets", ...invalidates du manifeste]

// Les hooks : peers OPTIONNELS, chargés dynamiquement (refus structuré si absents).
const { useApiQuery, useApiMutation } = await createApiHooks(queryClient);
useApiQuery(api.widgets.list);
useApiMutation(api.widgets.create);                  // invalide ["widgets"] + les racines déclarées

Dérivations

| Élément | Règle | |---|---| | Namespace | Premier segment après /api, camelCasé (/api/work-orders/...client.workOrders). | | Opération | Segments statiques suivants (.../:id/archivearchive) ; sinon le VERBE du nom du schéma d'entrée (SearchReportsInputSchemasearch) ; sinon la méthode (GETlist/get, POSTcreate, PATCH/PUTupdate, DELETEdelete). | | Clé de cache | [racine, opération, ...valeurs des params, reste de l'entrée]. L'opération EN FAIT PARTIE : deux GET voisins sur la même entité partageraient sinon une entrée de cache, et le second ne partirait jamais. | | Invalidation | Racine propre + les racines déclarées dans invalidates de la route (vérifiées par verify-manifest : une cible fantôme casse le build). |

Options

| Option | Requis | Effet | |---|---|---| | fetchImpl | oui | L'effet réseau, injecté (globalThis.fetch, ou un faux en test). Le module n'appelle jamais le réseau autrement. | | validateResponses | oui | "always" reparse chaque réponse avec son schemaOutput (dev : un contrat cassé échoue ICI) ; "never" = passthrough. Le PRODUIT choisit par environnement — le core ne lit aucune variable d'env. | | baseUrl | non | Préfixe du chemin dérivé ; vide par défaut, le front appelle son api en relatif. |

Erreurs

| Code | Quand | |---|---| | MANIFEST_CLIENT_INVALID_MANIFEST | À la construction : manifeste illisible, chemin malformé, route déclarée deux fois. | | MANIFEST_CLIENT_UNKNOWN_SCHEMA | À la construction : un schemaInput/schemaOutput du manifeste manque au registre. | | MANIFEST_CLIENT_AMBIGUOUS_OPERATION | À la construction : deux routes dérivent le même nom d'opération — jamais un écrasement silencieux. | | MANIFEST_CLIENT_MISSING_PATH_PARAM | Appel sans le param de chemin que la route exige, avant tout appel réseau. | | NETWORK_ERROR | fetchImpl a rejeté, ou l'api a échoué SANS corps OgygieError (statut conservé). | | CONTRACT_BROKEN | Réponse non-JSON, ou violant schemaOutput en validateResponses: "always". | | CLIENT_REACT_PEER_MISSING | client-react sans @tanstack/react-query installé : refus nommant la commande, jamais une erreur de résolution de module. |

Un corps OgygieError renvoyé par l'api, lui, ressort intact (code, details, suggestedFix, statut) : c'est la seule chose sur laquelle un écran puisse agir.