emilsoftware-utilities 2.0.0-dev.73 → 2.0.0-dev.74
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.MD +2 -0
- package/dist/accessi/server/standalone.d.ts +2 -4
- package/dist/accessi/server/standalone.js +4 -79
- package/dist/accessi/server/standalone.js.map +1 -1
- package/dist/accessi-module/Console/console.js +2 -1
- package/dist/accessi-module/Console/wiki.js +2 -1
- package/dist/accessi-module/Console/wiki.js.map +1 -1
- package/dist/accessi-module/health/nest.d.ts +31 -0
- package/dist/accessi-module/health/nest.js +89 -0
- package/dist/accessi-module/health/nest.js.map +1 -0
- package/dist/accessi-module/health/readiness.d.ts +53 -0
- package/dist/accessi-module/health/readiness.js +110 -0
- package/dist/accessi-module/health/readiness.js.map +1 -0
- package/dist/accessi-module/index.d.ts +4 -0
- package/dist/accessi-module/index.js +9 -1
- package/dist/accessi-module/index.js.map +1 -1
- package/dist/emilsoftware-utilities-2.0.0-dev.74.tgz +0 -0
- package/docs/proxy-ip.md +99 -0
- package/docs/standalone-service.md +25 -0
- package/package.json +4 -3
- package/dist/emilsoftware-utilities-2.0.0-dev.73.tgz +0 -0
package/README.MD
CHANGED
|
@@ -259,6 +259,7 @@ lista utenti apre un pannello in cui inserire l'URL dell'applicazione e generare
|
|
|
259
259
|
- La backdoor mock `admin`/`demo` non e mai attiva quando `NODE_ENV=production`.
|
|
260
260
|
- Gli allegati sono **protetti di default**: senza `allegatiOptions.authorize` le richieste rispondono 403. Per la migrazione da versioni precedenti impostare `requireAuthorization: false` (sconsigliato in produzione).
|
|
261
261
|
- Upload allegati: limite di 1 file / 90 MB applicato da Multer e validazione dei magic byte oltre al mimetype dichiarato.
|
|
262
|
+
- **IP reali dietro proxy**: `http.trustProxy` deve corrispondere al numero di proxy davanti al servizio (default `false`). Rate limit e audit usano `req.ip`: un valore errato rende i bucket inefficaci o aggirabili via `X-Forwarded-For`. Guida per Nginx/Traefik/Cloudflare: [docs/proxy-ip.md](docs/proxy-ip.md).
|
|
262
263
|
|
|
263
264
|
## Integrazione
|
|
264
265
|
|
|
@@ -267,6 +268,7 @@ lista utenti apre un pannello in cui inserire l'URL dell'applicazione e generare
|
|
|
267
268
|
| Backend Express esistente | `await initializeAccessiModule(app, accessiOptions)` monta le rotte `/api/accessi` su un host Nest isolato |
|
|
268
269
|
| Applicazione NestJS | importa `AccessiModule.forRoot(accessiOptions)` (e/o `AllegatiModule.forRoot(allegatiOptions)`) |
|
|
269
270
|
| Accessi + Allegati insieme | `EmilsoftwareModule.forRoot({ accessiOptions, allegatiOptions })` |
|
|
271
|
+
| Health/readiness in host Nest | `createAccessiHealthModule({ accessiOptions })` espone `/health/live` e `/health/ready` con lo stesso contratto del servizio standalone |
|
|
270
272
|
| Client multi-istanza / multi-tenant | `createAccessiClient({ baseUrl, getToken })` isola base URL e token per istanza (concorrenza sicura); `configureAccessiClient` resta il default globale |
|
|
271
273
|
| Client per scope | `accessi/client/public` (frontend), `accessi/client/admin` (tool admin), `accessi/client/service` (backend fratelli): stesse tipi del client completo, nessuna operazione fuori scope |
|
|
272
274
|
|
|
@@ -2,10 +2,8 @@ import type { Application } from 'express';
|
|
|
2
2
|
import type { Server } from 'http';
|
|
3
3
|
import type { AccessiModuleInitializationResult } from '../../accessi-module';
|
|
4
4
|
import type { AccessiOptions } from '../../accessi-module/AccessiModule';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
check: () => boolean | Promise<boolean>;
|
|
8
|
-
}
|
|
5
|
+
import type { AccessiReadinessCheck } from '../../accessi-module/health/readiness';
|
|
6
|
+
export type { AccessiReadinessCheck } from '../../accessi-module/health/readiness';
|
|
9
7
|
export interface AccessiServiceOptions {
|
|
10
8
|
/** Applicazione Express gia esistente. Default: nuova app. */
|
|
11
9
|
app?: Application;
|
|
@@ -19,8 +19,7 @@ const Logger_1 = require("../../Logger");
|
|
|
19
19
|
const Orm_1 = require("../../Orm");
|
|
20
20
|
const accessi_module_1 = require("../../accessi-module");
|
|
21
21
|
const accessiOptionsFromEnv_1 = require("../../accessi-module/database-updates/accessiOptionsFromEnv");
|
|
22
|
-
const
|
|
23
|
-
const AccessiDatabaseUpdater_1 = require("../../accessi-module/database-updates/AccessiDatabaseUpdater");
|
|
22
|
+
const readiness_1 = require("../../accessi-module/health/readiness");
|
|
24
23
|
const validateOptions_1 = require("../../accessi-module/security/validateOptions");
|
|
25
24
|
function normalizePath(value) {
|
|
26
25
|
return `/${value.replace(/^\/+|\/+$/g, '')}`;
|
|
@@ -66,44 +65,6 @@ function createMetrics() {
|
|
|
66
65
|
};
|
|
67
66
|
return { middleware, render };
|
|
68
67
|
}
|
|
69
|
-
function collectHealthStores(options) {
|
|
70
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
71
|
-
const stores = [];
|
|
72
|
-
const seen = new Set();
|
|
73
|
-
const add = (name, store) => {
|
|
74
|
-
if (!store || seen.has(store) || typeof store.healthCheck !== 'function')
|
|
75
|
-
return;
|
|
76
|
-
seen.add(store);
|
|
77
|
-
stores.push({ name, store });
|
|
78
|
-
};
|
|
79
|
-
add('publicAuthRateLimit', (_a = options.publicAuthRateLimit) === null || _a === void 0 ? void 0 : _a.store);
|
|
80
|
-
add('serviceTokenRateLimit', (_b = options.serviceTokens) === null || _b === void 0 ? void 0 : _b.rateLimitStore);
|
|
81
|
-
add('consoleEntry', (_c = options.consoleEntry) === null || _c === void 0 ? void 0 : _c.store);
|
|
82
|
-
add('edgeRateLimit', (_d = options.edgeRateLimit) === null || _d === void 0 ? void 0 : _d.store);
|
|
83
|
-
add('sessionRevocation', (_e = options.sessionRevocation) === null || _e === void 0 ? void 0 : _e.store);
|
|
84
|
-
add('accessTokenRevocation', (_f = options.accessTokenRevocation) === null || _f === void 0 ? void 0 : _f.store);
|
|
85
|
-
add('refreshTokenRotation', (_g = options.refreshTokenRotation) === null || _g === void 0 ? void 0 : _g.store);
|
|
86
|
-
return stores;
|
|
87
|
-
}
|
|
88
|
-
function checkDatabase(options) {
|
|
89
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
90
|
-
try {
|
|
91
|
-
const version = yield AccessiDatabaseUpdater_1.AccessiDatabaseUpdater.getCurrentVersion(options);
|
|
92
|
-
if (!version) {
|
|
93
|
-
return { ok: false, detail: 'schema Accessi non inizializzato' };
|
|
94
|
-
}
|
|
95
|
-
return {
|
|
96
|
-
ok: true,
|
|
97
|
-
detail: version === accessiSchema_1.ACCESSI_SCHEMA_VERSION
|
|
98
|
-
? version
|
|
99
|
-
: `${version} (atteso ${accessiSchema_1.ACCESSI_SCHEMA_VERSION})`,
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
catch (error) {
|
|
103
|
-
return { ok: false, detail: error instanceof Error ? error.message : String(error) };
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
68
|
/**
|
|
108
69
|
* Crea un servizio Accessi standalone con health, readiness, metriche e shutdown.
|
|
109
70
|
*
|
|
@@ -129,10 +90,7 @@ function createAccessiService() {
|
|
|
129
90
|
let stopping = false;
|
|
130
91
|
app.use(metrics.middleware);
|
|
131
92
|
app.get(`${healthPath}/live`, (_req, res) => {
|
|
132
|
-
res.status(200).json(
|
|
133
|
-
status: 'ok',
|
|
134
|
-
uptimeSeconds: Math.floor(process.uptime()),
|
|
135
|
-
});
|
|
93
|
+
res.status(200).json((0, readiness_1.createAccessiLiveReport)());
|
|
136
94
|
});
|
|
137
95
|
app.get(`${healthPath}/ready`, (_req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
138
96
|
if (!initialized || !nestApp) {
|
|
@@ -142,41 +100,8 @@ function createAccessiService() {
|
|
|
142
100
|
});
|
|
143
101
|
return;
|
|
144
102
|
}
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
database: yield checkDatabase(accessiOptions),
|
|
148
|
-
};
|
|
149
|
-
const stores = collectHealthStores(accessiOptions);
|
|
150
|
-
yield Promise.all(stores.map((_a) => __awaiter(this, [_a], void 0, function* ({ name, store }) {
|
|
151
|
-
var _b;
|
|
152
|
-
try {
|
|
153
|
-
checks[name] = { ok: Boolean(yield ((_b = store.healthCheck) === null || _b === void 0 ? void 0 : _b.call(store))) };
|
|
154
|
-
}
|
|
155
|
-
catch (error) {
|
|
156
|
-
checks[name] = {
|
|
157
|
-
ok: false,
|
|
158
|
-
detail: error instanceof Error ? error.message : String(error),
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
})));
|
|
162
|
-
for (const check of readinessChecks) {
|
|
163
|
-
try {
|
|
164
|
-
const ok = yield check.check();
|
|
165
|
-
checks[check.name] = { ok: Boolean(ok) };
|
|
166
|
-
}
|
|
167
|
-
catch (error) {
|
|
168
|
-
checks[check.name] = {
|
|
169
|
-
ok: false,
|
|
170
|
-
detail: error instanceof Error ? error.message : String(error),
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
const ok = Object.values(checks).every((check) => check.ok);
|
|
175
|
-
res.status(ok ? 200 : 503).json({
|
|
176
|
-
status: ok ? 'ok' : 'error',
|
|
177
|
-
checks,
|
|
178
|
-
timestamp: new Date().toISOString(),
|
|
179
|
-
});
|
|
103
|
+
const report = yield (0, readiness_1.checkAccessiReadiness)(accessiOptions, readinessChecks);
|
|
104
|
+
res.status((0, readiness_1.readinessHttpStatus)(report)).json(report);
|
|
180
105
|
}));
|
|
181
106
|
if (options.metrics !== false) {
|
|
182
107
|
app.get(`${healthPath}/metrics`, (_req, res) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"standalone.js","sourceRoot":"","sources":["../../../src/accessi/server/standalone.ts"],"names":[],"mappings":"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","sourcesContent":["import express from 'express';\nimport type { Application, Request, Response } from 'express';\nimport type { Server } from 'http';\nimport { Logger } from '../../Logger';\nimport { Orm } from '../../Orm';\nimport { initializeAccessiModule } from '../../accessi-module';\nimport type {\n AccessiModuleInitializationResult,\n} from '../../accessi-module';\nimport type { AccessiOptions } from '../../accessi-module/AccessiModule';\nimport { createAccessiOptionsFromEnv } from '../../accessi-module/database-updates/accessiOptionsFromEnv';\nimport {\n ACCESSI_SCHEMA_VERSION,\n} from '../../accessi-module/database-updates/accessiSchema';\nimport { AccessiDatabaseUpdater } from '../../accessi-module/database-updates/AccessiDatabaseUpdater';\nimport { assertValidAccessiOptions, validateAccessiOptions } from '../../accessi-module/security/validateOptions';\n\nexport interface AccessiReadinessCheck {\n name: string;\n check: () => boolean | Promise<boolean>;\n}\n\nexport interface AccessiServiceOptions {\n /** Applicazione Express gia esistente. Default: nuova app. */\n app?: Application;\n /** Configurazione completa. Default: `createAccessiOptionsFromEnv()`. */\n accessiOptions?: AccessiOptions;\n port?: number;\n host?: string;\n /** Path base degli endpoint health. Default: `/health`. */\n healthPath?: string;\n /** Espone `/health/metrics` in formato Prometheus. Default: true. */\n metrics?: boolean;\n /** Check aggiuntivi per la readiness. */\n readinessChecks?: AccessiReadinessCheck[];\n /** Timeout chiusura server. Default: 10000 ms. */\n shutdownTimeoutMs?: number;\n}\n\nexport interface AccessiService {\n app: Application;\n nestApp: AccessiModuleInitializationResult['nestApp'];\n document: AccessiModuleInitializationResult['document'];\n start(): Promise<Server>;\n stop(): Promise<void>;\n}\n\ninterface HealthCheckResult {\n ok: boolean;\n detail?: string;\n}\n\nfunction normalizePath(value: string): string {\n return `/${value.replace(/^\\/+|\\/+$/g, '')}`;\n}\n\nfunction createMetrics() {\n const startedAt = Date.now();\n let requests = 0;\n let inFlight = 0;\n const statuses = new Map<string, number>();\n\n const middleware = (_req: Request, res: Response, next: () => void): void => {\n requests += 1;\n inFlight += 1;\n res.on('finish', () => {\n inFlight = Math.max(0, inFlight - 1);\n const status = String(res.statusCode);\n statuses.set(status, (statuses.get(status) ?? 0) + 1);\n });\n next();\n };\n\n const render = (): string => {\n const memory = process.memoryUsage();\n const lines = [\n '# HELP accessi_http_requests_total Richieste HTTP totali.',\n '# TYPE accessi_http_requests_total counter',\n ...[...statuses.entries()].map(\n ([status, count]) => `accessi_http_requests_total{status=\"${status}\"} ${count}`,\n ),\n '# HELP accessi_http_in_flight Richieste HTTP in corso.',\n '# TYPE accessi_http_in_flight gauge',\n `accessi_http_in_flight ${inFlight}`,\n '# HELP accessi_process_uptime_seconds Uptime del processo.',\n '# TYPE accessi_process_uptime_seconds gauge',\n `accessi_process_uptime_seconds ${Math.floor((Date.now() - startedAt) / 1000)}`,\n '# HELP accessi_process_memory_bytes Memoria del processo.',\n '# TYPE accessi_process_memory_bytes gauge',\n `accessi_process_memory_bytes{type=\"rss\"} ${memory.rss}`,\n `accessi_process_memory_bytes{type=\"heapUsed\"} ${memory.heapUsed}`,\n `accessi_process_memory_bytes{type=\"heapTotal\"} ${memory.heapTotal}`,\n '# HELP accessi_http_requests_started_total Richieste HTTP avviate.',\n '# TYPE accessi_http_requests_started_total counter',\n `accessi_http_requests_started_total ${requests}`,\n ];\n return `${lines.join('\\n')}\\n`;\n };\n\n return { middleware, render };\n}\n\nfunction collectHealthStores(options: AccessiOptions): Array<{ name: string; store: { healthCheck?: () => Promise<boolean> } }> {\n const stores: Array<{ name: string; store: { healthCheck?: () => Promise<boolean> } }> = [];\n const seen = new Set<unknown>();\n\n const add = (name: string, store: { healthCheck?: () => Promise<boolean> } | undefined): void => {\n if (!store || seen.has(store) || typeof store.healthCheck !== 'function') return;\n seen.add(store);\n stores.push({ name, store });\n };\n\n add('publicAuthRateLimit', options.publicAuthRateLimit?.store);\n add('serviceTokenRateLimit', options.serviceTokens?.rateLimitStore);\n add('consoleEntry', options.consoleEntry?.store);\n add('edgeRateLimit', options.edgeRateLimit?.store);\n add('sessionRevocation', options.sessionRevocation?.store);\n add('accessTokenRevocation', options.accessTokenRevocation?.store);\n add('refreshTokenRotation', options.refreshTokenRotation?.store);\n return stores;\n}\n\nasync function checkDatabase(options: AccessiOptions): Promise<HealthCheckResult> {\n try {\n const version = await AccessiDatabaseUpdater.getCurrentVersion(options);\n if (!version) {\n return { ok: false, detail: 'schema Accessi non inizializzato' };\n }\n return {\n ok: true,\n detail: version === ACCESSI_SCHEMA_VERSION\n ? version\n : `${version} (atteso ${ACCESSI_SCHEMA_VERSION})`,\n };\n } catch (error) {\n return { ok: false, detail: error instanceof Error ? error.message : String(error) };\n }\n}\n\n/**\n * Crea un servizio Accessi standalone con health, readiness, metriche e shutdown.\n *\n * Non apre la porta finche non viene chiamato `start()`.\n */\nexport async function createAccessiService(\n options: AccessiServiceOptions = {},\n): Promise<AccessiService> {\n const logger = new Logger('AccessiService');\n const accessiOptions = options.accessiOptions ?? createAccessiOptionsFromEnv();\n assertValidAccessiOptions(accessiOptions);\n for (const warning of validateAccessiOptions(accessiOptions).warnings) {\n logger.warning(`Configurazione Accessi: ${warning.code} - ${warning.message}`);\n }\n\n const app = options.app ?? express();\n const healthPath = normalizePath(options.healthPath ?? '/health');\n const metrics = createMetrics();\n const readinessChecks = options.readinessChecks ?? [];\n let initialized = false;\n let nestApp: AccessiModuleInitializationResult['nestApp'] | undefined;\n let document: AccessiModuleInitializationResult['document'] | undefined;\n let server: Server | undefined;\n let stopping = false;\n\n app.use(metrics.middleware);\n\n app.get(`${healthPath}/live`, (_req: Request, res: Response) => {\n res.status(200).json({\n status: 'ok',\n uptimeSeconds: Math.floor(process.uptime()),\n });\n });\n\n app.get(`${healthPath}/ready`, async (_req: Request, res: Response) => {\n if (!initialized || !nestApp) {\n res.status(503).json({\n status: 'error',\n checks: { bootstrap: { ok: false, detail: 'bootstrap non completato' } },\n });\n return;\n }\n\n const checks: Record<string, HealthCheckResult> = {\n bootstrap: { ok: true },\n database: await checkDatabase(accessiOptions),\n };\n\n const stores = collectHealthStores(accessiOptions);\n await Promise.all(stores.map(async ({ name, store }) => {\n try {\n checks[name] = { ok: Boolean(await store.healthCheck?.()) };\n } catch (error) {\n checks[name] = {\n ok: false,\n detail: error instanceof Error ? error.message : String(error),\n };\n }\n }));\n\n for (const check of readinessChecks) {\n try {\n const ok = await check.check();\n checks[check.name] = { ok: Boolean(ok) };\n } catch (error) {\n checks[check.name] = {\n ok: false,\n detail: error instanceof Error ? error.message : String(error),\n };\n }\n }\n\n const ok = Object.values(checks).every((check) => check.ok);\n res.status(ok ? 200 : 503).json({\n status: ok ? 'ok' : 'error',\n checks,\n timestamp: new Date().toISOString(),\n });\n });\n\n if (options.metrics !== false) {\n app.get(`${healthPath}/metrics`, (_req: Request, res: Response) => {\n res.setHeader('Content-Type', 'text/plain; version=0.0.4');\n res.status(200).send(metrics.render());\n });\n }\n\n const result = await initializeAccessiModule(app, accessiOptions);\n nestApp = result.nestApp;\n document = result.document;\n initialized = true;\n\n const stop = async (): Promise<void> => {\n if (stopping) return;\n stopping = true;\n const timeout = Math.max(1_000, options.shutdownTimeoutMs ?? 10_000);\n await Promise.race([\n new Promise<void>((resolve) => {\n if (!server) return resolve();\n server.close(() => resolve());\n }),\n new Promise<void>((resolve) => setTimeout(resolve, timeout)),\n ]);\n try {\n await nestApp?.close();\n } finally {\n await Orm.closePools();\n }\n };\n\n const start = async (): Promise<Server> => {\n if (server) return server;\n const port = options.port ?? Number(process.env.PORT ?? 3000);\n server = await new Promise<Server>((resolve, reject) => {\n const listener = options.host\n ? app.listen(port, options.host, () => resolve(listener))\n : app.listen(port, () => resolve(listener));\n listener.once('error', reject);\n });\n const address = server.address();\n const actualPort = typeof address === 'object' && address ? address.port : port;\n logger.info(`Accessi Service in ascolto su http://${options.host ?? '0.0.0.0'}:${actualPort}`);\n return server;\n };\n\n return {\n app,\n get nestApp() {\n if (!nestApp) throw new Error('Accessi Service non inizializzato.');\n return nestApp;\n },\n get document() {\n if (!document) throw new Error('Documento OpenAPI non disponibile.');\n return document;\n },\n start,\n stop,\n };\n}\n\n/**\n * Avvia Accessi Service e registra SIGTERM/SIGINT per lo shutdown graceful.\n */\nexport async function startAccessiService(options: AccessiServiceOptions = {}): Promise<AccessiService> {\n const service = await createAccessiService(options);\n await service.start();\n\n let shuttingDown = false;\n const shutdown = async (signal: string): Promise<void> => {\n if (shuttingDown) return;\n shuttingDown = true;\n new Logger('AccessiService').info(`Ricevuto ${signal}: arresto in corso.`);\n await service.stop();\n };\n\n process.once('SIGTERM', () => { void shutdown('SIGTERM'); });\n process.once('SIGINT', () => { void shutdown('SIGINT'); });\n return service;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"standalone.js","sourceRoot":"","sources":["../../../src/accessi/server/standalone.ts"],"names":[],"mappings":"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","sourcesContent":["import express from 'express';\nimport type { Application, Request, Response } from 'express';\nimport type { Server } from 'http';\nimport { Logger } from '../../Logger';\nimport { Orm } from '../../Orm';\nimport { initializeAccessiModule } from '../../accessi-module';\nimport type {\n AccessiModuleInitializationResult,\n} from '../../accessi-module';\nimport type { AccessiOptions } from '../../accessi-module/AccessiModule';\nimport { createAccessiOptionsFromEnv } from '../../accessi-module/database-updates/accessiOptionsFromEnv';\nimport {\n checkAccessiReadiness,\n createAccessiLiveReport,\n readinessHttpStatus,\n} from '../../accessi-module/health/readiness';\nimport type { AccessiReadinessCheck } from '../../accessi-module/health/readiness';\nimport { assertValidAccessiOptions, validateAccessiOptions } from '../../accessi-module/security/validateOptions';\n\nexport type { AccessiReadinessCheck } from '../../accessi-module/health/readiness';\n\n\nexport interface AccessiServiceOptions {\n /** Applicazione Express gia esistente. Default: nuova app. */\n app?: Application;\n /** Configurazione completa. Default: `createAccessiOptionsFromEnv()`. */\n accessiOptions?: AccessiOptions;\n port?: number;\n host?: string;\n /** Path base degli endpoint health. Default: `/health`. */\n healthPath?: string;\n /** Espone `/health/metrics` in formato Prometheus. Default: true. */\n metrics?: boolean;\n /** Check aggiuntivi per la readiness. */\n readinessChecks?: AccessiReadinessCheck[];\n /** Timeout chiusura server. Default: 10000 ms. */\n shutdownTimeoutMs?: number;\n}\n\nexport interface AccessiService {\n app: Application;\n nestApp: AccessiModuleInitializationResult['nestApp'];\n document: AccessiModuleInitializationResult['document'];\n start(): Promise<Server>;\n stop(): Promise<void>;\n}\n\nfunction normalizePath(value: string): string {\n return `/${value.replace(/^\\/+|\\/+$/g, '')}`;\n}\n\nfunction createMetrics() {\n const startedAt = Date.now();\n let requests = 0;\n let inFlight = 0;\n const statuses = new Map<string, number>();\n\n const middleware = (_req: Request, res: Response, next: () => void): void => {\n requests += 1;\n inFlight += 1;\n res.on('finish', () => {\n inFlight = Math.max(0, inFlight - 1);\n const status = String(res.statusCode);\n statuses.set(status, (statuses.get(status) ?? 0) + 1);\n });\n next();\n };\n\n const render = (): string => {\n const memory = process.memoryUsage();\n const lines = [\n '# HELP accessi_http_requests_total Richieste HTTP totali.',\n '# TYPE accessi_http_requests_total counter',\n ...[...statuses.entries()].map(\n ([status, count]) => `accessi_http_requests_total{status=\"${status}\"} ${count}`,\n ),\n '# HELP accessi_http_in_flight Richieste HTTP in corso.',\n '# TYPE accessi_http_in_flight gauge',\n `accessi_http_in_flight ${inFlight}`,\n '# HELP accessi_process_uptime_seconds Uptime del processo.',\n '# TYPE accessi_process_uptime_seconds gauge',\n `accessi_process_uptime_seconds ${Math.floor((Date.now() - startedAt) / 1000)}`,\n '# HELP accessi_process_memory_bytes Memoria del processo.',\n '# TYPE accessi_process_memory_bytes gauge',\n `accessi_process_memory_bytes{type=\"rss\"} ${memory.rss}`,\n `accessi_process_memory_bytes{type=\"heapUsed\"} ${memory.heapUsed}`,\n `accessi_process_memory_bytes{type=\"heapTotal\"} ${memory.heapTotal}`,\n '# HELP accessi_http_requests_started_total Richieste HTTP avviate.',\n '# TYPE accessi_http_requests_started_total counter',\n `accessi_http_requests_started_total ${requests}`,\n ];\n return `${lines.join('\\n')}\\n`;\n };\n\n return { middleware, render };\n}\n\n/**\n * Crea un servizio Accessi standalone con health, readiness, metriche e shutdown.\n *\n * Non apre la porta finche non viene chiamato `start()`.\n */\nexport async function createAccessiService(\n options: AccessiServiceOptions = {},\n): Promise<AccessiService> {\n const logger = new Logger('AccessiService');\n const accessiOptions = options.accessiOptions ?? createAccessiOptionsFromEnv();\n assertValidAccessiOptions(accessiOptions);\n for (const warning of validateAccessiOptions(accessiOptions).warnings) {\n logger.warning(`Configurazione Accessi: ${warning.code} - ${warning.message}`);\n }\n\n const app = options.app ?? express();\n const healthPath = normalizePath(options.healthPath ?? '/health');\n const metrics = createMetrics();\n const readinessChecks = options.readinessChecks ?? [];\n let initialized = false;\n let nestApp: AccessiModuleInitializationResult['nestApp'] | undefined;\n let document: AccessiModuleInitializationResult['document'] | undefined;\n let server: Server | undefined;\n let stopping = false;\n\n app.use(metrics.middleware);\n\n app.get(`${healthPath}/live`, (_req: Request, res: Response) => {\n res.status(200).json(createAccessiLiveReport());\n });\n\n app.get(`${healthPath}/ready`, async (_req: Request, res: Response) => {\n if (!initialized || !nestApp) {\n res.status(503).json({\n status: 'error',\n checks: { bootstrap: { ok: false, detail: 'bootstrap non completato' } },\n });\n return;\n }\n\n const report = await checkAccessiReadiness(accessiOptions, readinessChecks);\n res.status(readinessHttpStatus(report)).json(report);\n });\n\n if (options.metrics !== false) {\n app.get(`${healthPath}/metrics`, (_req: Request, res: Response) => {\n res.setHeader('Content-Type', 'text/plain; version=0.0.4');\n res.status(200).send(metrics.render());\n });\n }\n\n const result = await initializeAccessiModule(app, accessiOptions);\n nestApp = result.nestApp;\n document = result.document;\n initialized = true;\n\n const stop = async (): Promise<void> => {\n if (stopping) return;\n stopping = true;\n const timeout = Math.max(1_000, options.shutdownTimeoutMs ?? 10_000);\n await Promise.race([\n new Promise<void>((resolve) => {\n if (!server) return resolve();\n server.close(() => resolve());\n }),\n new Promise<void>((resolve) => setTimeout(resolve, timeout)),\n ]);\n try {\n await nestApp?.close();\n } finally {\n await Orm.closePools();\n }\n };\n\n const start = async (): Promise<Server> => {\n if (server) return server;\n const port = options.port ?? Number(process.env.PORT ?? 3000);\n server = await new Promise<Server>((resolve, reject) => {\n const listener = options.host\n ? app.listen(port, options.host, () => resolve(listener))\n : app.listen(port, () => resolve(listener));\n listener.once('error', reject);\n });\n const address = server.address();\n const actualPort = typeof address === 'object' && address ? address.port : port;\n logger.info(`Accessi Service in ascolto su http://${options.host ?? '0.0.0.0'}:${actualPort}`);\n return server;\n };\n\n return {\n app,\n get nestApp() {\n if (!nestApp) throw new Error('Accessi Service non inizializzato.');\n return nestApp;\n },\n get document() {\n if (!document) throw new Error('Documento OpenAPI non disponibile.');\n return document;\n },\n start,\n stop,\n };\n}\n\n/**\n * Avvia Accessi Service e registra SIGTERM/SIGINT per lo shutdown graceful.\n */\nexport async function startAccessiService(options: AccessiServiceOptions = {}): Promise<AccessiService> {\n const service = await createAccessiService(options);\n await service.start();\n\n let shuttingDown = false;\n const shutdown = async (signal: string): Promise<void> => {\n if (shuttingDown) return;\n shuttingDown = true;\n new Logger('AccessiService').info(`Ricevuto ${signal}: arresto in corso.`);\n await service.stop();\n };\n\n process.once('SIGTERM', () => { void shutdown('SIGTERM'); });\n process.once('SIGINT', () => { void shutdown('SIGINT'); });\n return service;\n}\n"]}
|
|
@@ -986,7 +986,7 @@ var WIKI_SECTIONS = [
|
|
|
986
986
|
"})",
|
|
987
987
|
"export class AppModule {}"
|
|
988
988
|
].join("\n"), "app.module.ts"),
|
|
989
|
-
p(`Il global prefix api e le rotte /api/accessi/* sono gestiti dal modulo. Le tue rotte Nest si proteggono con JwtSimpleGuard usando il JWT Accessi.`)
|
|
989
|
+
p(`Il global prefix api e le rotte /api/accessi/* sono gestiti dal modulo. Le tue rotte Nest si proteggono con JwtSimpleGuard usando il JWT Accessi. Per health e readiness usare createAccessiHealthModule({ accessiOptions }): espone /health/live e /health/ready con lo stesso contratto del servizio standalone (bootstrap, database, store Redis e check aggiuntivi).`)
|
|
990
990
|
]
|
|
991
991
|
},
|
|
992
992
|
// -------------------------------------------------------------------------
|
|
@@ -1031,6 +1031,7 @@ var WIKI_SECTIONS = [
|
|
|
1031
1031
|
note("warn", `Un solo proprietario`, `Non montare AccessiModule in due backend puntati allo stesso database: migrazioni, bootstrap admin, ticket console e rate limit diventano ambigui.`),
|
|
1032
1032
|
note("info", `Client per scope`, `Usare accessi/client/public nei frontend pubblici, accessi/client/admin nella console e nei tool amministrativi, accessi/client/service solo nei backend fratelli. Gli scope partizionano il client completo e condividono gli stessi tipi; il client completo resta disponibile per compatibilita.`),
|
|
1033
1033
|
note("info", `Split in pacchetti`, `Il package resta unico con entry point per scope: decisione, soglie per rivalutarla e piano di migrazione sono nell'ADR docs/adr/0001-split-package.md.`),
|
|
1034
|
+
note("info", `IP reali dietro proxy`, `Imposta http.trustProxy sul numero di proxy davanti al servizio (1 per Nginx/Traefik/ALB, 2 con Cloudflare + ingress) e lascialo false se il servizio e esposto direttamente. Rate limit e audit usano req.ip: dettagli in docs/proxy-ip.md.`),
|
|
1034
1035
|
note("info", `Prompt di integrazione`, `Il file integration-prompt.md nella root del repository contiene il contesto completo per frontend, backend e agenti AI.`)
|
|
1035
1036
|
]
|
|
1036
1037
|
},
|
|
@@ -119,7 +119,7 @@ exports.WIKI_SECTIONS = [
|
|
|
119
119
|
'})',
|
|
120
120
|
'export class AppModule {}',
|
|
121
121
|
].join('\n'), 'app.module.ts'),
|
|
122
|
-
p(`Il global prefix api e le rotte /api/accessi/* sono gestiti dal modulo. Le tue rotte Nest si proteggono con JwtSimpleGuard usando il JWT Accessi.`),
|
|
122
|
+
p(`Il global prefix api e le rotte /api/accessi/* sono gestiti dal modulo. Le tue rotte Nest si proteggono con JwtSimpleGuard usando il JWT Accessi. Per health e readiness usare createAccessiHealthModule({ accessiOptions }): espone /health/live e /health/ready con lo stesso contratto del servizio standalone (bootstrap, database, store Redis e check aggiuntivi).`),
|
|
123
123
|
],
|
|
124
124
|
},
|
|
125
125
|
// -------------------------------------------------------------------------
|
|
@@ -161,6 +161,7 @@ exports.WIKI_SECTIONS = [
|
|
|
161
161
|
note('warn', `Un solo proprietario`, `Non montare AccessiModule in due backend puntati allo stesso database: migrazioni, bootstrap admin, ticket console e rate limit diventano ambigui.`),
|
|
162
162
|
note('info', `Client per scope`, `Usare accessi/client/public nei frontend pubblici, accessi/client/admin nella console e nei tool amministrativi, accessi/client/service solo nei backend fratelli. Gli scope partizionano il client completo e condividono gli stessi tipi; il client completo resta disponibile per compatibilita.`),
|
|
163
163
|
note('info', `Split in pacchetti`, `Il package resta unico con entry point per scope: decisione, soglie per rivalutarla e piano di migrazione sono nell'ADR docs/adr/0001-split-package.md.`),
|
|
164
|
+
note('info', `IP reali dietro proxy`, `Imposta http.trustProxy sul numero di proxy davanti al servizio (1 per Nginx/Traefik/ALB, 2 con Cloudflare + ingress) e lascialo false se il servizio e esposto direttamente. Rate limit e audit usano req.ip: dettagli in docs/proxy-ip.md.`),
|
|
164
165
|
note('info', `Prompt di integrazione`, `Il file integration-prompt.md nella root del repository contiene il contesto completo per frontend, backend e agenti AI.`),
|
|
165
166
|
],
|
|
166
167
|
},
|