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

@mostajs/data-plug

v1.2.5

Published

Data access plug — interchangeable backend (ORM direct, REST proxy, …) with multi-dialect registry + auto-register apikey via @mostajs/auth-flow (MOSTA_AUTH_FLOW_URL distinct, forceRefresh, clearCachedApiKey) + system dialect séparé du singleton métier (b

Readme

@mostajs/octoswitcher

Data access switcher — ORM direct or NET transport, one interface, zero config in modules.

Principe

MOSTA_DATA=orm → @mostajs/orm → SQL direct (SQLite, PostgreSQL, Oracle, MSSQL, etc.)
MOSTA_DATA=net → @mostajs/net → Transport distant (REST, GraphQL, gRPC, WS, MCP)

Les modules @mostajs (auth, rbac, audit, settings, ticketing, secu) appellent getDialect() depuis octoswitcher — ils ne savent pas si les données viennent d'une base locale ou d'un serveur distant.

Installation

npm install @mostajs/octoswitcher

Usage dans un module

import { getDialect } from '@mostajs/octoswitcher'
import { UserRepository } from './repositories/user.repository.js'

const dialect = await getDialect()
const repo = new UserRepository(dialect)
const users = await repo.findAll()

Configuration (dans l'app, pas dans le module)

Mode ORM (accès direct base de données)

MOSTA_DATA=orm
DB_DIALECT=sqlite
SGBD_URI=./data/app.db

Mode NET (accès distant via transport)

MOSTA_DATA=net
MOSTA_NET_URL=https://mcp.amia.fr/astro_08/
MOSTA_NET_TRANSPORT=rest

API

getDialect(): Promise<IDataDialect>

Retourne le dialect singleton — ORM ou NET selon MOSTA_DATA.

getDataMode(): DataMode

Retourne 'orm' ou 'net'.

isNetMode(): boolean

Raccourci pour getDataMode() === 'net'.

isOrmMode(): boolean

Raccourci pour getDataMode() === 'orm'.

setDialect(dialect): void

Injecte un dialect externe (pour les tests).

resetDialect(): void

Réinitialise le singleton (pour les tests).

getSystemDialect(): Promise<IDataDialect> (v1.2.2+)

Retourne le dialect système (apikeys, RBAC, audit, plans, payments, project-life). Stable au runtime, jamais muté par les routes admin métier. Si bootstrapSystemDialect() n'a pas été appelé, fallback transparent vers le singleton métier.

bootstrapSystemDialect(): Promise<IDataDialect> (v1.2.2+)

Initialise le dialect système au démarrage de l'app. Lit MOSTA_SYSTEM_DIALECT + MOSTA_SYSTEM_URI ; si présents → ouvre une connexion isolée dédiée via openIsolatedDialect, sinon alias singleton métier (rétro-compat). Idempotent.

setSystemDialect(dialect): void (v1.2.2+)

Injecte un dialect système externe (testing / custom implementation). Pendant naturel de setDialect() côté métier.

resetSystemDialect(): void (v1.2.2+)

Réinitialise le dialect système (testing / teardown). Ne ferme PAS la connexion physique.

Architecture

App (.env.local)
 ├── MOSTA_DATA=net + MOSTA_NET_URL=...
 │
 ├── @mostajs/auth     → @mostajs/rbac → octoswitcher → getDialect()
 ├── @mostajs/rbac     → octoswitcher → getDialect()
 ├── @mostajs/audit    → octoswitcher → getDialect()
 ├── @mostajs/settings → octoswitcher → getDialect()
 ├── @mostajs/ticketing → octoswitcher → getDialect()
 └── @mostajs/secu     → octoswitcher → getDialect()
                              │
                    ┌─────────┴─────────┐
                    │                   │
              MOSTA_DATA=orm      MOSTA_DATA=net
              @mostajs/orm        @mostajs/net
              SQL direct          REST/GraphQL/gRPC

Modules migrés

| Module | Avant | Après | |--------|-------|-------| | rbac | 212 lignes (data-mode.ts + NET factories) | 106 lignes | | audit | 130 lignes (data-mode.ts + NET factories) | 41 lignes | | settings | 119 lignes (data-mode.ts + NET factories) | 41 lignes | | ticketing | hardcodé ORM | 1 import changé | | secu | hardcodé ORM | 1 import changé |

Release Notes

v1.2.5 — 2026-05-04 — bootstrapSystemDialect passe schemaStrategy au dialect isolé

Bug fix (régression du chantier system dialect, détectée au déploiement test sur amia).

Symptôme

Au premier boot d'octonet-mcp avec MOSTA_SYSTEM_DIALECT + MOSTA_SYSTEM_URI définis (mode multi-base), les tables système (RBAC permission_categories, roles, users, …, api_keys, scopes, scope_values) n'étaient jamais créées dans la base système. Logs :

⚠ RBAC bootstrap skipped: relation "permission_categories" does not exist
⚠ Apikey scope registration skipped: relation "api_key_scopes" does not exist

Cause

bootstrapSystemDialect() appelait openIsolatedDialect({dialect, uri}) sans schemaStrategy. Tous les dialects implémentent initSchema(schemas) ainsi :

async initSchema(schemas) {
  const strategy = this.config?.schemaStrategy ?? 'none';
  // si strategy === 'none' → no-op
  // si strategy === 'update'/'create' → crée/migre tables
}

→ Sans schemaStrategy, strategy='none' par défaut → initSchema no-op → tables jamais créées même quand bootstrapRbac ou registerScope les demandent.

Fix

// data-plug v1.2.5
export async function bootstrapSystemDialect() {
  // …
  const schemaStrategy = getEnv('MOSTA_SYSTEM_SCHEMA_STRATEGY', 'update');
  const sys = await openIsolatedDialect({ dialect, uri, schemaStrategy });
  // …
}

Override possible via env var MOSTA_SYSTEM_SCHEMA_STRATEGY (default 'update' — idempotent : crée si absent, sinon migration douce).

Bump

1.2.4 → 1.2.5 (patch — bug fix sans changement d'API).


v1.2.4 — 2026-05-03 — Façade ORM complète (registry helpers)

Complète la façade v1.2.3 en ré-exportant aussi les registry helpers (registre global des schemas). Permet aux modules consumers de résoudre des entités par nom à l'exécution sans import('@mostajs/orm') direct.

export {
  registerSchema, registerSchemas,
  getSchema, getSchemaByCollection,
  getAllSchemas, getEntityNames,
  hasSchema, validateSchemas, clearRegistry,
} from '@mostajs/orm'

Bump 1.2.3 → 1.2.4 (patch — additif pur).


v1.2.3 — 2026-05-03 — Façade ORM (re-exports)

data-plug ré-exporte les primitives @mostajs/orm consommées par les modules applicatifs (api-keys, rbac, audit, project-life, payment, subscriptions-plan, …). Conformément au principe « les modules @mostajs passent par data-plug, jamais hardcoder un dialect ou importer @mostajs/orm directement », data-plug devient le point d'entrée unique data-access.

Exports ajoutés (rétro-compat 100 %, additif pur)

// Class
export { BaseRepository, normalizeDoc, normalizeDocs } from '@mostajs/orm'

// Types
export type {
  IDialect, EntitySchema, FilterQuery, QueryOptions, IRepository,
  FieldDef, FieldType, RelationDef, RelationType, IndexDef,
} from '@mostajs/orm'

Migration des modules consumers

// Avant (couplage dur à orm)
import { BaseRepository } from '@mostajs/orm'
import type { IDialect, EntitySchema } from '@mostajs/orm'

// Après (façade data-plug)
import { BaseRepository } from '@mostajs/data-plug'
import type { IDialect, EntitySchema } from '@mostajs/data-plug'

Bump

1.2.2 → 1.2.3 (patch — pure addition d'exports).


v1.2.2 — 2026-05-03 — System dialect séparé du singleton métier

Introduit getSystemDialect / bootstrapSystemDialect / setSystemDialect / resetSystemDialect pour découpler les modules SYSTÈME (apikeys, RBAC, audit, plans, payments, project-life) du singleton MÉTIER mutable.

Motivation

Le singleton getDialect() est mutable au runtime via /api/change-dialect, /api/reload-config, /api/reconnect — légitime côté métier. Mais les modules système (qui hébergent les apikeys, scopes, RBAC users, plans de souscription, audit) doivent vivre dans une base STABLE qui ne suit pas ces mutations, sinon les apikeys deviennent introuvables après un changement de dialect métier.

API ajoutée (rétro-compat 100 %)

  • getSystemDialect() — retourne le dialect système. Fallback singleton métier si non bootstrappé.
  • bootstrapSystemDialect() — initialise au démarrage. Lit MOSTA_SYSTEM_DIALECT + MOSTA_SYSTEM_URI :
    • présents → connexion isolée dédiée via openIsolatedDialect (prod multi-base)
    • absents → alias singleton métier (rétro-compat mono-base)
  • setSystemDialect(d) — injection (testing / custom implementation, pendant naturel de setDialect côté métier).
  • resetSystemDialect() — reset (testing / teardown).

Configuration cible (prod multi-base)

# Métier (mutable via IHM admin) :
DB_DIALECT=postgres
SGBD_URI=postgresql://hmd:***@127.0.0.1:5432/octonet_business

# Système (stable, jamais touché par /api/change-dialect) :
MOSTA_SYSTEM_DIALECT=postgres
MOSTA_SYSTEM_URI=postgresql://hmd:***@127.0.0.1:5432/octonet_system

Tests

6 / 6 passent (tests-scripts/test-system-dialect.mjs) :

| # | Assertion | |---|-----------| | T1 | fallback alias business | | T2 | getSystemDialect() returns bootstrapped | | T3 | system distinct from business | | T4 | system survives business disconnect() | | T5 | reset then bootstrap re-fallbacks | | T6 | bootstrap idempotent |

Bump

1.2.1 → 1.2.2 (minor — API additive uniquement, aucun breaking change).


v1.2.1 — 2026-05-03 — Trajet 2 (apikey périmée) + URL découplée

3 ajouts purement techniques pour clôturer le flow auto-register E2E. Cohérent avec l'architecture Octonet=cité, Octocloud=maire (cf. memory/reference_octonet_octocloud_architecture.md).

1. MOSTA_AUTH_FLOW_URL env distinct

Le device flow tape sur Octocloud (le maire qui distribue les clés), PAS sur Octonet (la cité qui héberge le data plane). Si MOSTA_AUTH_FLOW_URL est défini, on l'utilise ; sinon fallback MOSTA_NET_URL pour rétro-compat v1.2.0.

# Configuration cible recommandée v1.2.1+ :
MOSTA_AUTH_FLOW_URL=https://octocloud.amia.fr     # device flow (le maire)
MOSTA_NET_URL=https://octonet.amia.fr             # data plane (la cité)

# Rétro-compat v1.2.0 (un seul endpoint pour tout) :
MOSTA_NET_URL=https://octocloud.amia.fr           # auth + data via même endpoint

2. EnsureApiKeyOptions.forceRefresh

Cas d'usage trajet 2 : @mostajs/net-client-js détecte 401 sur un call data plane → callback onApiKeyInvalid → appel à ensureApiKey({ forceRefresh: true }) qui invalide le cache local et redéclenche le device flow vers Octocloud.

import { ensureApiKey } from '@mostajs/data-plug'

netClient.onApiKeyInvalid(async () => {
  return await ensureApiKey({ forceRefresh: true })
})

Comportement avec forceRefresh: true :

  • Ignore le cache ~/.config/<host>/auth.json
  • Ignore MOSTA_NET_API_KEY env (peut être elle-même périmée)
  • Ignore MOSTA_NO_AUTOREGISTER (caller veut absolument refresh, il a déjà eu un 401)
  • Appelle flow.refresh() qui clear le store + lance un nouveau device flow

Note CI / non-interactif : si forceRefresh: true + environnement non-interactif (pas de browser pour approve), le flow timeout naturellement. Le caller doit gérer ce cas.

3. clearCachedApiKey({ host? }) helper exporté

Supprime l'apikey cached localement (~/.config/<host>/auth.json). No-op si pas de cache existant ou si @mostajs/auth-flow non installé.

import { clearCachedApiKey } from '@mostajs/data-plug'

// Sign-out explicite client (orphan-care --logout)
await clearCachedApiKey({ host: 'orphan-care' })

// Le prochain ensureApiKey() redéclenchera un device flow

Cas d'usage :

  • Sign-out explicite côté client
  • Outils admin qui purgent les apikeys locales
  • Tests qui doivent partir d'un état propre

Tests

11 nouveaux assertions dans tests-scripts/test-auto-register.ts (12 → 23 assertions) :

  • T2.A/B/C — MOSTA_AUTH_FLOW_URL priorité + fallback NET_URL + throw si aucun
  • T3.A — forceRefresh ignore MOSTA_NET_API_KEY env
  • T3.B — forceRefresh ignore MOSTA_NO_AUTOREGISTER + cache
  • T4 — clearCachedApiKey() no-op tolérant

Rétro-compat

100% — apps existantes en v1.2.0 (qui utilisent uniquement MOSTA_NET_URL) continuent à fonctionner sans changement. Les 3 ajouts sont opt-in (env var ou option de fonction).

Plan détaillé

Cf. docs/PLAN-V1.2.1.md — checklist d'exécution + tests + commit format.

Récap technique fichier-par-fichier

src/auto-register.ts (modifié) :

  • MOSTA_AUTH_FLOW_URL env distinct (fallback MOSTA_NET_URL pour rétro-compat v1.2.0). Le device flow tape sur Octocloud (le maire qui distribue les clés), PAS sur Octonet (la cité qui héberge le data plane).
  • EnsureApiKeyOptions.forceRefresh — invalide cache + relance fresh device flow.
    • Cas d'usage : net-client-js détecte 401 → callback onApiKeyInvalidensureApiKey({ forceRefresh: true }) → re-auth automatique.
    • Comportement : ignore env MOSTA_NET_API_KEY, ignore MOSTA_NO_AUTOREGISTER, appelle flow.refresh() au lieu de flow.run().
  • clearCachedApiKey({ host }) helper exporté pour sign-out explicite, outils admin, tests propres. No-op tolérant si pas de cache ou @mostajs/auth-flow pas installé.

src/index.ts (modifié) : re-export clearCachedApiKey en plus d'ensureApiKey.

tests-scripts/test-auto-register.ts (modifié) : +11 nouveaux assertions (12 → 23) :

  • T2.A/B/CMOSTA_AUTH_FLOW_URL priorité + fallback NET_URL + throw si aucun
  • T3.AforceRefresh ignore MOSTA_NET_API_KEY env
  • T3.BforceRefresh ignore MOSTA_NO_AUTOREGISTER + cache
  • T4clearCachedApiKey() no-op tolérant

README.md : section Release Notes v1.2.1 + usage exemple trajet 2.

docs/PLAN-V1.2.1.md (nouveau) : plan détaillé d'exécution (checklist 12 items, tests, commit format) — référence pour reprise future.

Bump

1.2.01.2.1.

Note rétro-compat

100% — apps en v1.2.0 ne sont pas affectées (les 3 ajouts sont opt-in via env var ou option de fonction).


v1.2.0 — 2026-05-02 — Auto-register apikey via @mostajs/auth-flow (Session N+4 a)

Première moitié de Session N+4 du chantier auto-register Octonet (cf. Octonet-as-Supabase/11-AUTOREGISTER-FLOW-ROADMAP.md §2.2).

Quand data-plug tourne en mode NET (MOSTA_DATA=net) et que MOSTA_NET_API_KEY n'est pas défini, on déclenche un device flow OAuth 2.0 (RFC 8628) auprès du serveur Octonet pour obtenir une apikey valide. C'est l'étape "data-plug appelle Octonet en premier" du flow end-to-end.

src/auto-register.ts (~140 lignes)

  • ensureApiKey({ store?, clientId?, scope?, host?, onCodeIssued? }) — async
  • Ordre de résolution :
    1. MOSTA_NET_API_KEY env var → court-circuit, return tel quel
    2. Token cached dans ~/.config/<host>/auth.json (60s skew) → return
    3. Si MOSTA_NO_AUTOREGISTER=true → throw avec message explicite
    4. Sinon → device flow auprès de ${MOSTA_NET_URL}/api/v1/auth/device/*
  • @mostajs/auth-flow en LAZY IMPORT (peerDep optional) — data-plug reste utilisable sans lui pour les hôtes ayant leur propre stratégie d'apikey
  • defaultTerminalRenderer : UX CLI propre (URL, user_code, expiration en min)
  • Lecture env via @mostajs/config (cascade MOSTA_ENV=DEV|TEST|PROD honorée pour MOSTA_NET_URL, MOSTA_NET_API_KEY, MOSTA_HOST, MOSTA_CLIENT_ID, MOSTA_NO_AUTOREGISTER)

src/index.ts modifié

  • initNetDialect() : si MOSTA_NET_API_KEY absent, appelle ensureApiKey() AVANT d'instancier NetClient. Pattern lazy-import (./auto-register.js) pour ne pas tirer le code si l'host fournit l'apikey explicitement.
  • Re-export ensureApiKey + EnsureApiKeyOptions au bottom du barrel pour les hôtes qui veulent déclencher manuellement (script CLI bootstrap).

Dépendances

  • @mostajs/auth-flow ajouté en peerDependency optional + devDependency ^0.1.0-alpha.3 (pour les types tsc, pas requis runtime sans device flow).

Tests

12 nouveaux assertions (tests-scripts/test-auto-register.ts) — pattern @mostajs/config :

  • Cascade MOSTA_ENV=TESTTEST_MOSTA_NET_API_KEY priorité sur plain
  • MOSTA_NET_API_KEY explicite → court-circuit
  • Cached token valide via store custom → return direct (URL réelle https://octonet.amia.fr/)
  • MOSTA_NO_AUTOREGISTER=true + pas de cache → throw explicite
  • MOSTA_NO_AUTOREGISTER=true + cached → return cached
  • Sans MOSTA_NET_URL → throw clair
  • MOSTA_CLIENT_ID override via env

URL réelle utilisée dans les tests : https://octonet.amia.fr/ — c'est la cible qu'on attaquera dans les sessions N+4(b/c) côté octonet-cloud routes + /device.

Bump 1.1.01.2.0.

Reste pour clore Session N+4

  • N+4(b) : octonet-cloud routes /api/v1/auth/device/{authorize,token,approve}
  • N+4(c) : octonet-cloud /device page (Server Component branding + consent)

Trajet d'un disconnect()

mosta-net/server.ts → data-plug.disconnect() data-plug/index.ts:139 → orm.disconnectDialect() mosta-orm/core/factory.ts:176 → currentDialect.disconnect() postgres.dialect.ts:92 → pool.end()
this.pool = null
factory.ts:179-180 → currentDialect = null currentConfig = null
index.ts:153 → setGlobalDialect(null) ← cache data-plug vidé

Trajet d'un getDialect() immédiatement après

data-plug.getDialect() index.ts:99 cached = getGlobalDialect() = null index.ts:106 initOrmDialect() index.ts:163 orm.getDialect() factory.ts:134 currentDialect = null factory.ts:138 cfg = getConfigFromEnv() ← relit DB_DIALECT, SGBD_URI factory.ts:143 currentDialect = createDialect() ← nouvelle instance pg factory.ts:144 await connect(cfg) ← nouveau Pool ouvert factory.ts:148 initSchema(getAllSchemas()) index.ts:109 setGlobalDialect(dialect) ← ré-injecté dans le cache global

Conclusion clé : la chaîne de reconnexion est fonctionnellement saine

Usage exemple

// Variables d'environnement (cascade MOSTA_ENV honorée)
// MOSTA_DATA=net
// MOSTA_NET_URL=https://octonet.amia.fr/
// (pas de MOSTA_NET_API_KEY → device flow déclenché au premier appel)

import { getDialect } from '@mostajs/data-plug'

// Au boot, si pas d'apikey en env :
//   1. ~/.config/octonet/auth.json lu — si valide → utilisée
//   2. Sinon → terminal affiche URL + user_code, l'user approuve dans browser,
//      apikey émise + persistée
//   3. NetClient initialisé avec l'apikey, calls DB possibles
const dialect = await getDialect()
const users = await dialect.find(UserSchema, {})

Pour bypass en CI ou scripts non-interactifs :

export MOSTA_NET_API_KEY=ok_existing_apikey
export MOSTA_NO_AUTOREGISTER=true

Licence

AGPL-3.0-or-later — Dr Hamid MADANI [email protected]