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/project-life

v0.1.3

Published

Project lifecycle management (create, pause, resume, delete) for @mostajs

Downloads

215

Readme

@mostajs/project-life

Project lifecycle management (create, pause, resume, delete) pour les hôtes mostajs.

Auteur : Dr Hamid MADANI [email protected] License : AGPL-3.0-or-later


Principe

project-life gère la table de référence des projets d'un hôte multi-tenant : enregistrement, pause, reprise, suppression. C'est un module système (au sens : ses données vivent dans la base système, pas dans la base métier d'un projet).

Distinction claire avec les autres modules de l'écosystème projet :

| Module | Rôle | |--------|------| | @mostajs/project-life | Métadonnées projets (qui existent, leur état, leur config)table système | | @mostajs/mproject | Cycle de vie des ressources d'un projet (connexion DB isolée, EntityService dédié)createIsolatedDialect | | @mostajs/replicator | Réplication des données entre projets — connexions isolées par stream |

project-life est le « registre civil » des projets : il sait qu'ils existent. mproject est le « cadastre » : il sait où ils habitent et leur ouvre la porte.


Install

npm install @mostajs/project-life
# peer deps :
npm install @mostajs/data-plug @mostajs/mproject

Usage

1. Enregistrer le schema système au bootstrap

project-life expose son EntitySchema (ProjectSchema) que l'hôte enregistre auprès du registre global.

import { registerSchemas } from '@mostajs/data-plug'
import { ProjectSchema } from '@mostajs/project-life'

registerSchemas([ProjectSchema])

2. Provisionner un nouveau projet

import { provisionProject } from '@mostajs/project-life/server'
import { getSystemDialect } from '@mostajs/data-plug'
import { ProjectManager } from '@mostajs/mproject'

const dialect = await getSystemDialect()   // dialect SYSTÈME
const pm = new ProjectManager()

const result = await provisionProject(dialect, pm, accountId, {
  name: 'orphan-care',
  slug: 'orphan-care',
  dialect: 'postgres',
  uri: 'postgresql://hmd:***@127.0.0.1:5432/orphan_care_db',
  schemas: [/* EntitySchema[] du projet */],
  transports: ['rest', 'graphql'],
  poolSize: 5,
})

if (result.ok) {
  console.log('Provisioned as mproject:', result.mprojectName)
}

3. Pause / Resume

import { pauseProject, resumeProject } from '@mostajs/project-life/server'

// Pause : ferme la connexion isolée du projet, marque status='paused'
await pauseProject(dialect, pm, projectId)

// Resume : ré-ouvre la connexion + status='active'
// (encKey requis si l'URI est chiffrée — cf. encryptUri ci-dessous)
await resumeProject(dialect, pm, projectId, encKey)

4. Déprovisionner

import { deprovisionProject } from '@mostajs/project-life/server'

await deprovisionProject(dialect, pm, projectId)
// → ferme la connexion isolée, supprime la row dans la table projects

5. Bootstrap au démarrage de l'hôte

Au démarrage, ré-ouvrir toutes les connexions des projets actifs (typiquement après un restart pm2) :

import { bootAllProjects } from '@mostajs/project-life/server'

await bootAllProjects(dialect, pm, encKey)
// → pour chaque project status='active', ouvre une connexion isolée via pm

6. Routes Fastify (optionnel — handlers prêts à l'emploi)

import { createProjectHandlers } from '@mostajs/project-life/server'

const handlers = createProjectHandlers(dialect, pm, encKey)

app.get('/api/projects', handlers.list)
app.post('/api/projects', handlers.create)
app.post('/api/projects/:id/pause', handlers.pause)
app.post('/api/projects/:id/resume', handlers.resume)
app.delete('/api/projects/:id', handlers.delete)

7. Chiffrement des URIs (optionnel)

Pour les déploiements où les credentials DB ne doivent pas être lisibles en clair dans la base système :

import { encryptUri, decryptUri, generateEncryptionKey } from '@mostajs/project-life/server'

// Génération initiale (à mettre en .env : PROJECT_ENCRYPTION_KEY=<key>)
const key = generateEncryptionKey()

// À l'enregistrement
const encryptedUri = encryptUri('postgresql://hmd:***@host/db', key)

// Au resume / boot
const plainUri = decryptUri(encryptedUri, key)

L'env var PROJECT_ENCRYPTION_KEY est lue automatiquement par getEncryptionKey() si pas de clé passée explicitement.


API

Server-side (@mostajs/project-life/server)

| Fonction | Signature | |----------|-----------| | provisionProject | (dialect, pm, accountId, config: ProjectConfig) → Promise<ProvisionResult> | | deprovisionProject | (dialect, pm, projectId) → Promise<{ok, error?}> | | pauseProject | (dialect, pm, projectId) → Promise<{ok, error?}> | | resumeProject | (dialect, pm, projectId, encKey?) → Promise<{ok, error?}> | | bootAllProjects | (dialect, pm, encKey?) → Promise<void> | | getProjectRepo | (dialect) → BaseRepository<ProjectDTO> | | createProjectHandlers | (dialect, pm, encKey?) → { list, create, pause, resume, delete } | | encryptUri / decryptUri | (uri \| encrypted, key?) → string | | generateEncryptionKey | () → string (base64) |

Client-safe (@mostajs/project-life)

export type { ProjectDTO, ProjectConfig, ProvisionResult, ProjectStatus }
export { ProjectSchema, moduleInfo }

Schema ProjectDTO

| Champ | Type | Description | |-------|------|-------------| | id | string | UUID | | account | string | FK → Account propriétaire | | name | string | Nom affichable | | slug | string | Identifiant URL-safe (unique par account) | | dialect | string | postgres, mysql, mongodb, sqlite, … | | uri | string | Connexion DB (en clair ou chiffré via encryptUri) | | schemas | any[] | EntitySchema[] des entités du projet | | transports | string[] | Ex : ['rest', 'graphql', 'mcp'] | | poolSize | number | Taille du pool DB | | status | 'active' \| 'paused' \| 'error' | État courant | | error? | string | Dernière erreur si status='error' | | mprojectName? | string | Nom interne ProjectManager (rempli au provisioning) | | createdAt, updatedAt | string | ISO 8601 |


Architecture

                ┌─────────────────────────────┐
                │  @mostajs/project-life      │
                │  (table système — registre)  │
                │                             │
                │  ProjectDTO row             │
                │  { id, dialect, uri, … }    │
                └──────────────┬──────────────┘
                               │ provisionProject()
                               ▼
                ┌─────────────────────────────┐
                │  @mostajs/mproject          │
                │  (ProjectManager — runtime) │
                │                             │
                │  createIsolatedDialect      │
                │  EntityService dédié        │
                │                             │
                └──────────────┬──────────────┘
                               │
                               ▼
                ┌─────────────────────────────┐
                │  @mostajs/replicator        │
                │  (sync inter-projets)       │
                └─────────────────────────────┘

project-life ne touche pas aux ressources d'un projet (c'est mproject). Il garde juste la trace que le projet existe.


Changelog

v0.1.3 — 2026-05-04 — Découplage @mostajs/orm via façade data-plug + WeakMap repo

Étape 3 du chantier « system dialect séparé » — applique deux fix qui se combinent.

1. Migration @mostajs/orm@mostajs/data-plug (façade)

Conformément au principe « les modules @mostajs passent par data-plug, jamais hardcoder un dialect ou importer @mostajs/orm directement », project-life ne dépend plus de @mostajs/orm en peerDependency. Tous les imports de production passent désormais par @mostajs/data-plug v1.2.4.

6 fichiers migrés :

| Fichier | Symboles | |---------|----------| | src/api/projects.route.ts | IDialect | | src/lib/boot.ts | IDialect | | src/lib/module-info.ts | EntitySchema | | src/lib/project-factory.ts | BaseRepository + IDialect | | src/lib/provisioning.ts | IDialect | | src/schemas/project.schema.ts | EntitySchema | | package.json | peerDep ormdata-plug ^1.2.4 |

@mostajs/orm reste en devDependency uniquement (cohérence pour test-unit éventuel).

2. WeakMap dans project-factory.ts

// Avant — capture la référence du PREMIER dialect, ignore les suivants
let repo: BaseRepository<ProjectDTO> | null = null

// Après — keyed par identité du dialect
const cache = new WeakMap<IDialect, BaseRepository<ProjectDTO>>()

Évite que le repo capture la référence du PREMIER dialect passé et ignore tous les suivants. Lorsque /api/change-dialect (ou rotation système ↔ métier) modifie le dialect courant, le cache miss force la reconstruction du repo avec la nouvelle instance dialect — au lieu de réutiliser un repo pointant vers une connexion morte.

resetProjectRepo() conservé en no-op pour rétro-compat (la WeakMap auto-libère naturellement les entrées dont le dialect n'est plus référencé).

Bump

0.1.2 → 0.1.3 (patch — découplage interne, signatures publiques inchangées).


v0.1.2 et antérieurs

Versions initiales de provisioning + encryption + bootstrap. Pas de release notes archivées.


License

AGPL-3.0-or-later — usage libre tant que le code dérivé reste open-source. Licence commerciale disponible : [email protected]. Pricing par projet, pas par seat.

— (c) 2026 Dr Hamid MADANI <[email protected]>