{
  "markdown": "# @mostajs/net — Octonet\n\n> **One schema, 11 transports, 13 databases, multi-tenant auth — out of the box.**\n> Schema-driven multi-protocol API server for the @mostajs ecosystem. Built with TypeScript on Fastify, designed for polyglot consumption (14 native NetClients).\n\n<p align=\"center\">\n  <img src=\"logo/octonet-icon.svg\" width=\"128\" alt=\"Octonet Logo\"/>\n</p>\n\n[![npm](https://img.shields.io/npm/v/@mostajs/net.svg)](https://www.npmjs.com/package/@mostajs/net)\n[![License](https://img.shields.io/badge/license-AGPL--3.0--or--later-blue.svg)](LICENSE)\n[![mcp.so](https://img.shields.io/badge/mcp.so-listed-green.svg)](https://mcp.so/server/octonet-mcp/apolocine)\n\n**Author** : Dr Hamid MADANI \\<drmdh@msn.com\\>\n**Homepage** : [octonet.amia.fr](https://octonet.amia.fr) · [mcp.amia.fr](https://mcp.amia.fr)\n\n---\n\n<p align=\"center\">\n  <img src=\"assets/architecture-orm-net-netclient.png\" width=\"900\" alt=\"Octonet stack as electrical sockets: @mostajs/orm plugs into 13 databases, @mostajs/net (Octonet) re-exposes them over 11 transports, Data-Plug bridges them, and NetClient fans out to 18 language clients consumed by any external app.\"/>\n</p>\n\n> **The socket board.** `@mostajs/orm` connects to **13 databases**; **Octonet**\n> (`@mostajs/net`) re-exposes the same entities over **11 transports** (REST,\n> WebSocket, gRPC, MQTT, AMQP…); **NetClient** then fans out to **18 language\n> clients** — so any external app plugs into your data, in any runtime.\n\n---\n\n## Table of Contents\n\n1. [What is Octonet](#what-is-octonet)\n2. [Try in 2 clicks (T1 sandbox)](#try-in-2-clicks-t1-sandbox)\n3. [3-tier onboarding model](#3-tier-onboarding-model)\n4. [11 transports](#11-transports)\n5. [13 databases](#13-databases)\n6. [Multi-tenant authentication](#multi-tenant-authentication)\n7. [Generic scope registry](#generic-scope-registry)\n8. [Multi-project support](#multi-project-support)\n9. [Quick start (self-host)](#quick-start-self-host)\n10. [Configuration (.env)](#configuration-env)\n11. [API endpoints reference](#api-endpoints-reference)\n12. [Architecture](#architecture)\n13. [Boot sequence](#boot-sequence)\n14. [14 polyglot NetClients](#14-polyglot-netclients)\n15. [Production deploy (amia.fr)](#production-deploy-amiafr)\n16. [License](#license)\n\n---\n\n## What is Octonet\n\nOctonet (`@mostajs/net`) is a Node.js multi-transport server that exposes a single `@mostajs/orm` schema as **11 different network protocols simultaneously** : REST, GraphQL, WebSocket, SSE, JSON-RPC, MCP, gRPC, tRPC, OData, NATS, Arrow Flight.\n\nThe same `User` entity is reachable as :\n\n```bash\ncurl https://octonet.amia.fr/api/v1/User              # REST\ncurl https://octonet.amia.fr/graphql -d '{\"query\":...}'  # GraphQL\nwscat -c wss://octonet.amia.fr/ws                     # WebSocket\ncurl -N https://octonet.amia.fr/mcp                   # MCP (Claude Desktop)\n# … 7 more\n```\n\nBacked by `@mostajs/orm` (13 SGBD dialects). Backed by `@mostajs/rbac` + `@mostajs/auth` + `@mostajs/api-keys` for multi-tenant identity. Backed by `@mostajs/mproject` for multi-project routing.\n\nOctonet is **the second cerebral lobe** of the @mostajs trilogy :\n- **#1 — `@mostajs/orm`** : data persistence (13 databases)\n- **#2 — Octonet** : multi-protocol transport (11 wire protocols)\n- **#3 — NetClients polyglottes** : 14 native client libraries (Java, .NET, Python, Go, Swift, Kotlin, Dart, Rust, PHP, Ruby, Elixir, Lua, Delphi, Unity)\n\n---\n\n## Try in 2 clicks (T1 sandbox)\n\nThe fastest way to test Octonet from any of the 14 runtimes :\n\n1. Open [https://octonet.amia.fr/try](https://octonet.amia.fr/try)\n2. Pick an alias (e.g. `alice-42`)\n3. Get an apikey scoped to your sandbox\n\n```bash\ncurl -X POST https://octonet.amia.fr/try \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"alias\":\"alice-42\"}'\n\n# Response:\n{\n  \"status\": \"ok\",\n  \"data\": {\n    \"alias\": \"alice-42\",\n    \"projectSlug\": \"sandbox-alice-42\",\n    \"apiKey\": \"sk_test_…(64 chars, shown ONCE)\",\n    \"permissions\": { \"projects\":[\"sandbox-alice-42\"], \"operations\":[\"read\",\"write\"], \"transports\":[\"rest\",\"mcp\"] },\n    \"expiresAt\": \"2026-05-02T18:38:19.920Z\",\n    \"quota\": { \"reqPerDay\": 500 },\n    \"exampleCurl\": \"curl https://octonet.amia.fr/api/v1/sandbox-alice-42/User -H \\\"X-API-Key: sk_test_…\\\"\",\n    \"mcpUrl\": \"https://octonet.amia.fr/mcp\"\n  }\n}\n```\n\nYour sandbox is :\n- A private **SQLite file** (isolated from other aliases)\n- Pre-seeded with `User`, `Product`, `Order` entities\n- Read+write CRUD via REST and MCP\n- 500 requests/day quota\n- TTL 7 days (auto-deleted if idle)\n- Free, no email required, anti-abuse rate-limited (10 sandboxes/h/IP)\n\nUse the apikey in any of the [14 NetClients](#14-polyglot-netclients) :\n\n```bash\n# Java / .NET / Python / Go / etc.\nexport MOSTAJS_NET_URL=https://octonet.amia.fr\nexport MOSTAJS_NET_API_KEY=sk_test_…\n```\n\n---\n\n## 3-tier onboarding model\n\n| Tier | Cible | Auth | Quota | Use case |\n|---|---|---|---|---|\n| **T1 — Sandbox publique** [`/try`](https://octonet.amia.fr/try) | \"I want to test in 10 minutes\" | alias seul (pas d'email) | 500 req/jour, TTL 7j | démo NetClients, intégration tests |\n| **T2 — Compte enregistré** [octocloud.amia.fr](https://octocloud.amia.fr) | \"j'utilise vraiment, gratuit\" | email + mot de passe | 10 000 req/jour | apps personnelles, side-projects |\n| **T3 — Self-host** `npx @mostajs/net init` | \"tes données chez toi\" | admin local | aucune | enterprise, on-premise, AGPL |\n\n---\n\n## 11 transports\n\nAuto-generated from your registered schemas. Toggle each via env var (default : all enabled).\n\n| # | Transport | Endpoint | Use case | Notes |\n|---|---|---|---|---|\n| 1 | **REST** | `/api/v1/{Entity}` · `/api/v1/{project}/{Entity}` | universel | 15 routes par entité (CRUD + count + search + aggregate + bulk + relations) |\n| 2 | **GraphQL** | `/graphql` (POST) | front-end riches | schéma + GraphiQL IDE auto-générés via mercurius |\n| 3 | **WebSocket** | `wss://…/ws` | temps réel | events `entity.created/updated/deleted/upserted` broadcast |\n| 4 | **SSE** | `/events` (GET stream) | mobile / browser-friendly | server-sent events |\n| 5 | **JSON-RPC** | `/rpc` (POST) | EVM-adjacent, classic | JSON-RPC 2.0 + method discovery |\n| 6 | **MCP** | `/mcp` (POST/GET SSE) | agents IA (Claude, ChatGPT) | 15 tools/entité auto-générés ([listed on mcp.so](https://mcp.so/server/octonet-mcp/apolocine)) |\n| 7 | **gRPC** | `:50051` | inter-services low-latency | `.proto` auto-généré, 6 RPCs/entité |\n| 8 | **tRPC** | `/trpc/{Entity}.{op}` | TypeScript fullstack | type generation côté client |\n| 9 | **OData** | `/odata/{Collection}` (+ `$metadata`) | SAP/Microsoft Dynamics | OData v4 ($filter, $select, $orderby) |\n| 10 | **NATS** | `mostajs.{Entity}.{op}` | pub/sub edge | request-reply messaging |\n| 11 | **Arrow Flight** | `/arrow/*` | analytics columnaire | streaming zero-copy |\n\n**Le même schéma**, 11 portes d'entrée différentes, 0 codegen.\n\n---\n\n## 13 databases\n\nPersistence dialects fournis par `@mostajs/orm` :\n\n| Catégorie | Bases | Dialect ID |\n|---|---|---|\n| SQL mainstream | PostgreSQL, MySQL, MariaDB, SQLite | `postgres`, `mysql`, `mariadb`, `sqlite` |\n| SQL enterprise | Oracle, SQL Server, DB2, SAP HANA, HSQLDB, Sybase | `oracle`, `mssql`, `db2`, `hana`, `hsqldb`, `sybase` |\n| NewSQL / Cloud | CockroachDB, Google Cloud Spanner | `cockroachdb`, `spanner` |\n| NoSQL | MongoDB | `mongodb` |\n\nSwitch dialect = changer 1 ligne d'env :\n\n```bash\nDB_DIALECT=postgres → DB_DIALECT=mongodb\nSGBD_URI=postgresql://… → SGBD_URI=mongodb://…\n```\n\n---\n\n## Multi-tenant authentication\n\nOctonet utilise un middleware d'authentification basé sur **API keys avec scopes** (orienté machine-to-machine) + RBAC pour la gestion humaine. L'orchestrateur agnostique vit dans `@mostajs/auth/lib/check-request.ts`.\n\n### Flux d'auth d'une requête\n\n```\nHTTP Request\n  │\n  ▼\nauthGuard (Fastify adapter)\n  │\n  ▼\ncheckRequest (@mostajs/auth — framework-agnostic)\n  │\n  ├─ extract X-API-Key from header / Bearer token / ?apikey=\n  ├─ resolveApiKey (@mostajs/api-keys) — DB lookup + bcrypt verify\n  ├─ isScopeAuthorized (@mostajs/api-keys) — check scope.values\n  │     for each (scope, value) pair :\n  │       checks: [{scope:'projects', value:slug},\n  │                {scope:'operations', value:'read'|'write'|'admin'},\n  │                {scope:'transports', value:'rest'|'mcp'|...}]\n  ├─ touchApiKey — fire-and-forget : lastUsedAt, usageCount, lastIp\n  │\n  ▼\normHandler (entité CRUD) ─→ sanitizer (strip password/hash/tokens)\n  │\n  ▼\nHTTP Response\n```\n\n### Scope-based permissions (generic, extensible)\n\nUne apikey a la shape :\n\n```ts\n{\n  permissions: {\n    scopes: {\n      projects:   ['my-project','demo'] | '*',     // declared by @mostajs/mproject\n      operations: ['read','write','admin'] | '*',  // declared by @mostajs/orm\n      transports: ['rest','graphql','mcp'] | '*',  // declared by @mostajs/net\n      // anything: any module can register a new scope\n    },\n    rateLimit: 500,\n  }\n}\n```\n\n`@mostajs/api-keys` ne connaît PAS les noms `projects/operations/transports` — c'est volontaire. Chaque module enregistre ses scopes au boot via `registerScope(dialect, {name, label, …})`. Le check est générique : `isScopeAuthorized(perms, scope, value)`.\n\n### HTTP status mapping\n\n| Cas | HTTP code | Body |\n|---|---|---|\n| Pas d'apikey | 401 | `{error: {code:'UNAUTHORIZED', message:'API key required…'}}` |\n| Apikey invalide / révoquée | 401 | `{error: {code:'UNAUTHORIZED', message:'Invalid or revoked API key'}}` |\n| Apikey valide hors scope | 403 | `{error: {code:'FORBIDDEN', message:'API key not authorized for X=\"Y\"'}}` |\n| Apikey OK | 200/201 | `{status:'ok', data: …}` (sanitized — no password/hash) |\n\n### MCP fallback (compat mcp.so)\n\nL'endpoint `/mcp` autorise un **fallback automatique** sur l'apikey labelée `public-default` quand aucune clé n'est présentée — préserve la compat avec mcp.so / Claude Desktop. La clé publique est **read-only sur le projet `default`** uniquement → écritures bloquées.\n\n### Sanitizer\n\nLes champs `password`, `hash`, `verifyToken`, `resetToken`, `apiKeyHash`, `secret`, `privateKey` sont **automatiquement strippés** de toutes les réponses JSON par un middleware global. Aucun risque de leak à travers `/api/v1/User?limit=1`.\n\n---\n\n## Generic scope registry\n\nLe catalogue des scopes vit en **base de données du projet accueillant** (pas de fichier JSON, pas de constantes hardcodées) :\n\n```sql\n-- Auto-créées au boot via @mostajs/api-keys\nCREATE TABLE api_key_scopes (\n  id, name, label, description, icon, cardinality,\n  valuesSource ('static' | 'dynamic'), dynamicSourceRef\n);\n\nCREATE TABLE api_key_scope_values (\n  id, scopeName, value, label, sortOrder, metadata\n);\n```\n\nChaque module enregistre ses scopes au boot :\n\n```ts\nimport { registerScope } from '@mostajs/api-keys/server'\n\n// Dans mosta-net/src/server.ts (boot)\nawait registerScope(dialect, {\n  name: 'transports', label: 'Network transports',\n  cardinality: 'low', valuesSource: 'static',\n  staticValues: [\n    {value:'rest', sortOrder:1},\n    {value:'graphql', sortOrder:2},\n    // … 9 more\n  ],\n})\n\nawait registerScope(dialect, {\n  name: 'projects', label: 'Projects',\n  cardinality: 'high', valuesSource: 'dynamic',\n  dynamicSourceRef: 'Project.slug',  // queried at runtime\n})\n```\n\nL'admin UI charge le catalogue dynamique via `GET /api/api-keys/scopes` et rend une matrice (composant React `ApiKeyScopeMatrix` dans `@mostajs/api-keys/components/`).\n\n---\n\n## Multi-project support\n\nVia `@mostajs/mproject` — N bases de données isolées sur le même serveur.\n\n### Routes path-prefix\n\n```bash\n# Default project (DB_DIALECT + SGBD_URI au boot)\ncurl http://localhost:4488/api/v1/User\n\n# Project nommé 'analytics'\ncurl http://localhost:4488/api/v1/analytics/events\n\n# Ou via header (équivalent)\ncurl http://localhost:4488/api/v1/events -H \"X-Project: analytics\"\n```\n\n### Ajouter un projet à l'exécution\n\n```bash\ncurl -X POST http://localhost:4488/api/projects \\\n  -H \"X-API-Key: <admin-key>\" \\\n  -d '{\n    \"name\": \"analytics\",\n    \"dialect\": \"mongodb\",\n    \"uri\": \"mongodb://localhost:27017/analytics\",\n    \"schemas\": [{\"name\":\"Event\", \"fields\":{\"type\":\"string\",\"ts\":\"date\"}}]\n  }'\n```\n\nPersisté dans `projects-tree.json` (chemin : `MOSTA_PROJECTS` env var).\n\n---\n\n## Quick start (self-host)\n\n### Install\n\n```bash\nnpm install @mostajs/net @mostajs/orm @mostajs/mproject \\\n            @mostajs/rbac @mostajs/auth @mostajs/api-keys \\\n            @mostajs/config better-sqlite3\n```\n\n### Run with SQLite (zero infra)\n\n```bash\nDB_DIALECT=sqlite \\\nSGBD_URI=./data/octonet.db \\\nDB_SCHEMA_STRATEGY=update \\\nOCTONET_ADMIN_EMAIL=admin@example.com \\\nOCTONET_ADMIN_PASSWORD=ChangeMe123! \\\nnpx mostajs-net serve\n```\n\nConsole output :\n\n```\nLoaded 3 schemas from schemas.json\n[DAL:SQLite] INIT_SCHEMA strategy=update {\"entities\":[\"User\",\"Product\",\"Order\"]}\n✓ Apikey scopes registered (projects, operations, transports)\n✓ RBAC ready — admin=admin@example.com trial=…  public=…\n⚠ public demo apikey emitted ONCE → sk_live_xxxxxxxx…  (save it!)\n✓ Sanitizer middleware on rest, graphql, ws, sse, trpc, mcp, odata, jsonrpc\n✓ ApiKey middleware on rest, graphql, ws, sse, trpc, mcp, odata, jsonrpc\n✓ Protected ormHandler ready (sanitizer + apikey global wrapper)\n✓ T1 sandbox endpoint /try ready (rate-limited 10/h/IP, TTL 7d)\n\n  @mostajs/net  v2.6.x\n  ─────────────────────────────────────────────────\n  Dialect:    sqlite (./data/octonet.db)\n  Entities:   User, Product, Order  (3)\n  Port:       4488\n  Strategy:   update\n  Transports: rest, graphql, ws, sse, trpc, mcp, odata, jsonrpc  (8)\n  Ready.  3 entities × 8 transports = 24 endpoints\n```\n\n### Test\n\n```bash\n# Sans apikey → 401\ncurl http://localhost:4488/api/v1/User\n# {\"error\":{\"code\":\"UNAUTHORIZED\",\"message\":\"API key required…\"}}\n\n# Avec apikey publique (depuis les logs ↑)\ncurl http://localhost:4488/api/v1/User -H \"X-API-Key: sk_live_…\"\n# {\"status\":\"ok\",\"data\":[{\"id\":\"…\",\"email\":\"admin@example.com\",…}]}  ← password absent\n```\n\n---\n\n## Configuration (.env)\n\n### Database (mandatory)\n\n```bash\nDB_DIALECT=postgres                                # any of 13 dialects\nSGBD_URI=postgresql://user:pass@localhost:5432/db\nDB_SCHEMA_STRATEGY=update                          # 'update' (recommandé prod) | 'create' (drop+recreate, dev only)\nDB_SHOW_SQL=false\n```\n\n### System dialect (recommandé prod, optionnel) — v2.7.5+\n\nSépare la base **système** *(apikeys, RBAC users, audit, plans, payments, project-life metadata)* du dialect **métier mutable**. Sans ces variables, alias transparent vers le singleton métier *(rétro-compat mono-base)*.\n\n```bash\nMOSTA_SYSTEM_DIALECT=postgres                       # any of 13 dialects\nMOSTA_SYSTEM_URI=postgresql://user:pass@localhost:5432/octonet_system\n```\n\nVoir section [System dialect](#system-dialect-séparé-du-singleton-métier) ci-dessous pour la motivation et les how-to.\n\n### Server\n\n```bash\nMOSTA_NET_PORT=4488\nMOSTA_PROJECTS=./projects-tree.json                # path to multi-project config\nSCHEMAS_PATH=./schemas                              # directory scanning fallback\n```\n\n### Transports (toggle each)\n\n```bash\nMOSTA_NET_REST_ENABLED=true\nMOSTA_NET_GRAPHQL_ENABLED=true\nMOSTA_NET_WS_ENABLED=true\nMOSTA_NET_SSE_ENABLED=true\nMOSTA_NET_JSONRPC_ENABLED=true\nMOSTA_NET_MCP_ENABLED=true\nMOSTA_NET_TRPC_ENABLED=true\nMOSTA_NET_ODATA_ENABLED=true\nMOSTA_NET_GRPC_ENABLED=false\nMOSTA_NET_NATS_ENABLED=false\nMOSTA_NET_ARROW_ENABLED=false\n\nMOSTA_NET_CORS_ORIGIN=*\nMOSTA_RATE_LIMIT_CLIENT=1000\n```\n\n### RBAC bootstrap (lus via `@mostajs/config` — supports profile cascade `MOSTA_ENV=DEV`)\n\n```bash\nOCTONET_ADMIN_EMAIL=admin@example.com               # 1er boot only ; ignored after\nOCTONET_ADMIN_PASSWORD=secret-12345\nOCTONET_ADMIN_FIRSTNAME=Admin                       # optional\nOCTONET_ADMIN_LASTNAME=Octonet                      # optional\nOCTONET_BOOTSTRAP_VERBOSE=false                     # detailed bootstrap logs\nOCTONET_OPEN_MODE=false                             # dev only — passe sans apikey\n```\n\n### T1 sandbox (optional)\n\n```bash\nOCTONET_TRIAL_DATA_DIR=./data/trials                # SQLite files per alias\n```\n\n### Cloud middleware (optional, futur — partage DB méta avec Octocloud)\n\n```bash\nOCTONET_META_URI=postgresql://user:pass@localhost:5432/octonet_meta   # shared meta DB\nPORTAL_DB_URI=postgresql://user:pass@localhost:5432/octonet_cloud     # legacy octocloud DB\n```\n\n---\n\n## API endpoints reference\n\n### Public\n\n| Endpoint | Méthode | Auth | Description |\n|---|---|---|---|\n| `/health` | GET | open | server status + transports + entities |\n| `/try` | GET | open | T1 sandbox provisioning HTML form |\n| `/try` | POST | open (rate-limit 10/h/IP) | crée sandbox + retourne apikey one-shot |\n| `/api/v1/health` | GET | open | health under api-versioned path |\n\n### Project / entity CRUD (requires apikey)\n\n| Endpoint | Méthode | Op | Description |\n|---|---|---|---|\n| `/api/v1/{Entity}` | GET | findAll | list entities of default project |\n| `/api/v1/{Entity}/:id` | GET | findById | get one |\n| `/api/v1/{Entity}/count` | GET | count | count |\n| `/api/v1/{Entity}/one` | GET | findOne | first match |\n| `/api/v1/{Entity}/search` | GET | search | text search |\n| `/api/v1/{Entity}` | POST | create | insert |\n| `/api/v1/{Entity}/:id` | PUT | update | replace |\n| `/api/v1/{Entity}/:id` | DELETE | delete | remove |\n| `/api/v1/{Entity}/:id/addToSet` | POST | addToSet | add to array |\n| `/api/v1/{Entity}/:id/pull` | POST | pull | remove from array |\n| `/api/v1/{Entity}/:id/increment` | POST | increment | numeric add |\n| `/api/v1/{Entity}/upsert` | POST | upsert | insert or update |\n| `/api/v1/{Entity}/aggregate` | POST | aggregate | pipeline |\n| `/api/v1/{Entity}/updateMany` | POST | updateMany | bulk update |\n| `/api/v1/{Entity}/deleteMany` | POST | deleteMany | bulk delete |\n| `/api/v1/{project}/{Entity}/…` | * | * | same routes scoped to a project |\n\n### Schema management (requires admin apikey)\n\n| Endpoint | Méthode | Description |\n|---|---|---|\n| `/api/upload-schemas-json` | POST | push schemas at runtime (triggers reload) |\n| `/api/apply-schema` | POST | apply schema diff |\n| `/api/compare-schema` | POST | dry-run schema diff |\n| `/api/schemas-config` | GET | current registered schemas |\n\n### Auth & API keys (requires admin)\n\n| Endpoint | Méthode | Description |\n|---|---|---|\n| `/api/api-keys` | GET / POST | list / issue keys |\n| `/api/api-keys/:id` | PUT / DELETE | update / revoke |\n| `/api/api-keys/scopes` | GET | list registered scopes + values (for admin matrix UI) |\n| `/api/api-keys/scopes` | POST | register a new scope (admin) |\n| `/api/api-keys/scopes/:name/values` | PUT / DELETE | manage scope values |\n\n### Transports natifs\n\n| Endpoint | Description |\n|---|---|\n| `/graphql` | GraphQL endpoint + GraphiQL IDE |\n| `/ws` | WebSocket connection (entity events) |\n| `/events` | SSE stream |\n| `/rpc` | JSON-RPC 2.0 |\n| `/mcp` | Model Context Protocol (Claude/Smithery/etc.) |\n| `/trpc/{Entity}.{op}` | tRPC procedure |\n| `/odata/{Collection}` · `/odata/$metadata` | OData v4 |\n\n### Multi-project management\n\n| Endpoint | Méthode | Description |\n|---|---|---|\n| `/api/projects` | GET | list projects |\n| `/api/projects` | POST | add project |\n| `/api/projects/:name` | PUT / DELETE | edit / remove |\n\n### Observability\n\n| Endpoint | Description |\n|---|---|\n| `/api/performance` | live metrics (req/s, p50, p99) |\n| `/api/config-tree` | configuration tree (interactive) |\n| `/api/live-log` | streaming log feed |\n\n---\n\n## Architecture\n\n```\n                                 ┌──────────────────────────────────┐\n                                 │       Fastify (port 4488)        │\n                                 └────────────────┬─────────────────┘\n                                                  │\n       ┌─────────────────────────────────────────┴──────────────────────────────┐\n       │                                                                        │\n       ▼                                                                        ▼\n┌────────────────────────┐                                       ┌────────────────────────┐\n│  Transports (11)       │                                       │  Direct Fastify routes │\n│  - REST                │                                       │  - /try                │\n│  - GraphQL (mercurius) │                                       │  - /api/projects/*     │\n│  - WS                  │                                       │  - /api/api-keys/*     │\n│  - SSE                 │                                       │  - /health, /metrics   │\n│  - JSON-RPC            │                                       └────────────────────────┘\n│  - MCP (SDK Anthropic) │\n│  - gRPC                │              ┌─────────────────────────────────────┐\n│  - tRPC                │              │  Auth pipeline                      │\n│  - OData               │              │  ┌──────────────┐                   │\n│  - NATS                │              │  │ extractAuth  │ ←  HTTP request   │\n│  - Arrow               ├──────────────┤  │ Context      │                   │\n└────────────────────────┘              │  └──────┬───────┘                   │\n                                        │         ▼                           │\n                                        │  ┌──────────────┐                   │\n                                        │  │ checkRequest │ ←  scope-checks   │\n                                        │  │ (mosta-auth) │                   │\n                                        │  └──────┬───────┘                   │\n                                        │         ▼                           │\n                                        │  ┌──────────────┐                   │\n                                        │  │ checkApiKey  │ ←  resolve+verify │\n                                        │  │ (api-keys)   │                   │\n                                        │  └──────┬───────┘                   │\n                                        └─────────┼───────────────────────────┘\n                                                  ▼\n                                        ┌────────────────────────┐\n                                        │ Sanitizer middleware   │\n                                        │ strips password/hash/  │\n                                        │ tokens before response │\n                                        └────────┬───────────────┘\n                                                 ▼\n                                        ┌────────────────────────┐\n                                        │ ormHandler             │\n                                        │ → ProjectManager.resolve│\n                                        │ → EntityService.execute │\n                                        └────────┬───────────────┘\n                                                 ▼\n                                        ┌────────────────────────┐\n                                        │ @mostajs/orm dialect   │\n                                        │ (13 SGBD)              │\n                                        └────────────────────────┘\n```\n\n### Modules de l'écosystème consommés\n\n| Module | Rôle dans Octonet |\n|---|---|\n| `@mostajs/orm` | persistance (13 dialects) |\n| `@mostajs/mproject` | gestion multi-projet (default + sandbox + abonnés) |\n| `@mostajs/rbac` | identité (User, Role, Permission, Account) + AccountSchema + OCTONET_RBAC_SEED |\n| `@mostajs/auth` | `checkRequest` orchestrateur framework-agnostic, hashPassword |\n| `@mostajs/api-keys` | apikey CRUD + scopes (`Scope`/`ScopeValue` schemas) + `checkApiKey` + `ApiKeyScopeMatrix` admin UI |\n| `@mostajs/config` | env var helper avec cascade `MOSTA_ENV` |\n| `@mostajs/cloud-middleware` | quota / abonnement (optionnel — actif si Octocloud connecté) |\n| `@mostajs/replicator` | CQRS multi-replica (optionnel) |\n| `@mostajs/project-life` | persistence schemas Project (optionnel — si stockage SGBD vs JSON) |\n\n---\n\n## System dialect (séparé du singleton métier)\n\n> **Disponible depuis v2.7.5** — résout le bug *« apikeys introuvables après `/api/change-dialect` »* observé en prod.\n\n### Pourquoi deux dialects distincts ?\n\nOctonet expose des routes admin qui mutent la connexion DB au runtime *(`/api/change-dialect`, `/api/reload-config`, `/api/reconnect`)* — légitime côté **métier** *(les entités userland du projet courant peuvent migrer postgres → sqlite → mongodb selon les besoins de l'admin)*.\n\nMais les modules **système** *(apikeys, RBAC users, audit, plans de souscription, payments, project-life metadata)* doivent vivre dans une base **stable** qui ne suit pas ces mutations. Sinon : un changement de dialect métier rend les apikeys introuvables, l'admin se trouve verrouillé hors de l'IHM.\n\n| Rôle | Variable env | Mutable au runtime ? | Lu par |\n|------|--------------|----------------------|--------|\n| **Métier** *(entités userland)* | `DB_DIALECT` + `SGBD_URI` | **Oui** *(IHM admin)* | `EntityService`, transports, routes data |\n| **Système** *(infra Octonet)* | `MOSTA_SYSTEM_DIALECT` + `MOSTA_SYSTEM_URI` | **Non** *(stable)* | RBAC, apikey-middleware, account-scope, auth guards, sandbox /try |\n\n### Configuration recommandée *(prod multi-base)*\n\n```bash\n# Métier — peut bouger via /api/change-dialect\nDB_DIALECT=postgres\nSGBD_URI=postgresql://hmd:***@127.0.0.1:5432/octonet_business\n\n# Système — stable, jamais touché par les routes admin\nMOSTA_SYSTEM_DIALECT=postgres\nMOSTA_SYSTEM_URI=postgresql://hmd:***@127.0.0.1:5432/octonet_system\n```\n\n### Configuration mono-base *(dev / déploiement simple)*\n\nLaisser `MOSTA_SYSTEM_*` vides → alias automatique vers le singleton métier *(rétro-compat 100 %, comportement identique au pré-v2.7.5)*.\n\n### How-to\n\n#### 1. Migrer un déploiement existant vers le mode multi-base\n\n```bash\n# 1. Sauvegarder la base actuelle\npg_dump octonet_business > backup-pre-split.sql\n\n# 2. Créer la base système (vide — RBAC seed re-exécute au boot)\ncreatedb octonet_system\n\n# 3. Ajouter MOSTA_SYSTEM_* dans .env\necho 'MOSTA_SYSTEM_DIALECT=postgres' >> .env.local\necho 'MOSTA_SYSTEM_URI=postgresql://hmd:***@127.0.0.1:5432/octonet_system' >> .env.local\n\n# 4. Restart Octonet — le bootstrap RBAC + scopes seed sur octonet_system\npm2 restart octonet-mcp\n\n# 5. Restaurer les apikeys et RBAC users de l'ancienne base\n#    (à scripter selon convention métier — typiquement export/import des\n#    tables api_keys, users, roles, permissions, scopes, scope_values)\n```\n\n#### 2. Vérifier que le système est bien isolé du métier\n\n```bash\n# Avant /api/change-dialect : apikey doit fonctionner\ncurl -H \"X-API-Key: sk_live_…\" http://localhost:4488/api/auth/verify\n# → 200 OK\n\n# Bouger le dialect métier vers SQLite\ncurl -X POST -H \"Content-Type: application/json\" \\\n  -d '{\"dialect\":\"sqlite\",\"uri\":\":memory:\",\"connect\":true}' \\\n  http://localhost:4488/api/change-dialect\n# → \"Dialecte changé et connecté : sqlite\"\n\n# Re-tester l'apikey — DOIT toujours fonctionner (système intact)\ncurl -H \"X-API-Key: sk_live_…\" http://localhost:4488/api/auth/verify\n# → 200 OK (bug résolu en v2.7.5)\n```\n\nAvant v2.7.5, le 2ᵉ curl retournait `503 metadata DB unavailable` ou `401 PostgreSQL not connected. Call connect() first.`\n\n#### 3. Lire le system dialect depuis du code applicatif\n\n```ts\nimport { getSystemDialect } from '@mostajs/data-plug'\n\nconst sysDialect = await getSystemDialect()\n// Utiliser comme un dialect normal pour requêter les tables système\n// (apikeys, users, roles, audit_log, plans, etc.)\n```\n\n#### 4. Inspecter le bootstrap au démarrage\n\nAu boot d'`octonet-mcp`, deux logs apparaissent :\n\n```\n✓ DB connectée: postgres                            ← métier\n✓ System dialect: postgres (octonet_system)         ← système (si MOSTA_SYSTEM_* défini)\n✓ RBAC ready — admin=… trial=… public=…             ← seed côté système\n✓ ApiKey middleware on REST/SSE/GraphQL/...         ← câblé sur système\n```\n\n### Sites système dans `src/server.ts` *(pour référence)*\n\n18 callers tirent désormais leur dialect via `getSystemDialect()` au lieu du singleton métier :\n\n| Bloc | Sites | Caller |\n|------|-------|--------|\n| RBAC bootstrap | 1 | `bootstrapRbac(systemDialect, …)` |\n| Scopes register | 4 | `registerScope(systemDialect, …)` × 3 + `systemDialect.initSchema(scopeTables)` |\n| Middlewares globaux | 2 | `createApiKeyMiddleware(() => systemDialect, …)` + `createAccountScopeMiddleware(() => systemDialect)` |\n| Middlewares per-transport | 2 | idem appliqués sur chaque `transport.use(…)` |\n| Auth guards transports | 6 | `authGuard(systemDialect, …)` × 6 *(SSE, GraphQL, JSON-RPC, gRPC, tRPC, OData)* |\n| Custom `/api/auth/verify` | 2 | `checkApiKey(systemDialect, …)` + `new UserRepository(systemDialect)` |\n| Sandbox `/try` | 2 | `registerTryRoutes({ dialect: systemDialect, … })` + `startTrialCleanupJob({ dialect: systemDialect, … })` |\n\nLe **dialect métier** *(`dialect`)* reste utilisé légitimement pour :\n- Bootstrap initial du singleton métier *(`L98`)* + `pm.setDefault('default', dialect, …)` *(`L126`, `L240`)*\n- Routes admin `/api/reconnect`, `/api/change-dialect`, `/api/reload-config`, `/api/test-connection`, `/api/truncate-tables`, `/api/drop-tables` — toutes opérations explicitement métier\n- `EntityService` qui sert les entités userland *(opérations CRUD via les transports protégés)*\n\n### Étapes du chantier *(historique)*\n\n| Étape | Repo | Livré |\n|-------|------|-------|\n| 1 | `@mostajs/data-plug` v1.2.2-1.2.4 *(API `getSystemDialect` + façade ORM)* | npm |\n| 2 | `@mostajs/net` v2.7.5 *(`bootstrapSystemDialect` au démarrage)* | git |\n| 3 | `@mostajs/api-keys` 0.2.3, `@mostajs/payment` 0.4.1, `@mostajs/project-life` 0.1.3, `@mostajs/subscriptions-plan` 0.3.5 *(WeakMap repos + façade)* | npm |\n| 4 | `@mostajs/net` v2.7.5 *(consumers basculent sur `getSystemDialect()` — 18 sites)* | git |\n| 5 | Tests d'intégration scénario `/api/change-dialect` postgres → sqlite | ⏳ |\n| 6 | Déploiement amia + smoke test `MOSTA_SYSTEM_URI` | ⏳ |\n\n---\n\n## Boot sequence\n\n1. **Load `.env`** via `@mostajs/config` (profile cascade `MOSTA_ENV`)\n2. **Connect main dialect** (`SGBD_URI`)\n3. **Load schemas** (priority : `getAllSchemas()` registry → `schemas.json` → `SCHEMAS_PATH` directory scan)\n4. **`dialect.initSchema(schemas)`** with strategy `update`/`create`\n5. **Bootstrap RBAC** (`octonet-rbac-bootstrap.ts`) :\n   - register UserSchema/RoleSchema/PermissionSchema/PermissionCategorySchema/AccountSchema/ApiKeySchema\n   - `seedRBAC(OCTONET_RBAC_SEED)` — 6 categories, 25 permissions, 4 roles (admin/subscriber/trial/public)\n   - `createAdmin()` from `OCTONET_ADMIN_EMAIL`/`PASSWORD`\n   - create Account `trial-playground` (type='trial')\n   - create User `public-demo` (role=public)\n   - create Account `public-system` (type='system')\n   - generate ApiKey `public-default` scoped to `default` project (read-only, REST + MCP) — emitted ONCE in clear\n6. **Register canonical scopes** : `projects` (dynamic, `Project.slug`), `operations` (static : read/write/admin), `transports` (static : 11 values)\n7. **Push default project to `projects-tree.json`** (ownerId=admin, visibility=public)\n8. **Load additional projects** from `projects-tree.json`\n9. **Wrap ormHandler** with `composeMiddleware([sanitizer, apikey], ormHandler)` → `protectedOrmHandler`\n10. **Start each enabled transport** :\n    - `transport.use(loggingMiddleware)`\n    - `transport.use(sanitizerMiddleware)`\n    - `transport.use(apiKeyMiddleware)`\n    - `transport.setHandler(ormHandler)`\n    - `transport.start(config)`\n11. **Register Fastify routes** :\n    - `registerDynamicRestRoutes(app, protectedOrmHandler, pm)` — `/api/v1/...`\n    - `registerProjectRoutes(app, pm, protectedOrmHandler)` — `/:project/*`\n    - `registerTryRoutes(app, {dialect, pm})` — `/try` POST\n    - `registerTryPage(app)` — `/try` GET (HTML)\n    - `startTrialCleanupJob({dialect, pm})` — cron horaire TTL 7j\n12. **Listen on port** (`MOSTA_NET_PORT`)\n\n---\n\n## 14 polyglot NetClients\n\nOctonet est consommable depuis 14 runtimes natifs, **avec la même apikey, la même URL** :\n\n| Runtime | Package | Registre |\n|---|---|---|\n| Node/TS | `@mostajs/net/client` (built-in) | npm |\n| Java | `com.mostajs:mostajs-net-client` | Maven Central |\n| Java + Spring Boot | `com.mostajs:mostajs-net-client-spring-boot-starter` | Maven Central |\n| .NET | `MostaJs.Net.Client` | NuGet |\n| Python | `mostajs-net-client` | PyPI |\n| Go | `github.com/apolocine/mosta-net-client-go` | pkg.go.dev |\n| Swift | `mosta-net-client-swift` | Swift Package Index |\n| Kotlin | `io.github.apolocine:mostajs-net-client-kt` | Maven Central |\n| Dart / Flutter | `mostajs_net_client` | pub.dev |\n| Rust | `mostajs-net-client` | crates.io |\n| PHP | `mostajs/net-client` | Packagist |\n| Ruby | `mostajs-net-client` | RubyGems |\n| Elixir | `mostajs_net_client` | Hex.pm |\n| Lua | `mostajs-net-client` | LuaRocks |\n| Delphi | `MostaJsNetClient` | GetIt |\n| Unity (C#) | `com.mostajs.net-client` | OpenUPM |\n\nMonorepo : [github.com/apolocine/mosta-net-clients](https://github.com/apolocine/mosta-net-clients)\n\nChaque NetClient suit le même contrat d'API :\n\n```\nNetClient.create()\n  .url(...)\n  .apiKey(...)\n  .build()\n  .findAll(entity, filter, options)\n  .findById(entity, id)\n  .create(entity, data)\n  .update(entity, id, data)\n  .delete(entity, id)\n  .uploadSchemasJson(schemas)\n  .health()\n```\n\n---\n\n## Production deploy (amia.fr)\n\nLe déploiement de référence tourne sur `octonet.amia.fr` + `mcp.amia.fr` (alias compat) en backend PostgreSQL :\n\n| URL | Rôle |\n|---|---|\n| `https://octonet.amia.fr` | Octonet server (REST, MCP, GraphQL, WS, SSE, tRPC, OData, JSON-RPC) |\n| `https://mcp.amia.fr` | alias DNS de compatibilité — historique mcp.so |\n| `https://octocloud.amia.fr` | Octocloud — portail SaaS Next.js (subscriptions, admin UI) |\n\nLe kit de déploiement complet est dans `Entreprise/octonet-mcp/` :\n- `apache/mcp.amia.fr.conf` — vhost Apache2 (proxy SSE, WS upgrade, certs Let's Encrypt SAN)\n- `ecosystem.config.cjs` — config PM2\n- `deploy.sh` / `install.sh` / `update.sh` — scripts d'orchestration\n- `tests/smoke-all.sh` — suite de smoke tests (DNS, TLS, /health, SSE MCP, REST, /try, NetClient Java)\n\n---\n\n## CLI\n\n```bash\nnpx mostajs-net serve                      # Start server\nnpx mostajs-net mcp                        # MCP-only mode\nnpx mostajs-net generate-apikey <label>    # Émettre une apikey via CLI\nnpx mostajs-net hash-password <password>   # Hash bcrypt\nnpx mostajs-net info                       # JSON config dump\n\nnpx octonet-mcp --dialect=X --uri=Y        # Standalone MCP server (process séparé)\n```\n\n---\n\n## License\n\n[AGPL-3.0-or-later](LICENSE) — usage libre tant que le code dérivé reste open-source.\n**Licence commerciale** disponible : `drmdh@msn.com`. Pricing par projet, pas par seat.\n\n— (c) 2026 Dr Hamid MADANI \\<drmdh@msn.com\\>\n",
  "bytes": 34633,
  "sha": "d72853e3478602276b4bbec3835619aa69d7cf5b6fcff4e3543372023adb6990",
  "repo_slug": "apolocine/mosta-net",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_apolocine_mosta_net_7800c8e3/readme"
}