tempest-express-sdk 0.8.0 → 0.10.0
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 +5 -4
- package/dist/chunk-U3SXT3KR.js +6 -0
- package/dist/{chunk-JWJJAIXV.js.map → chunk-U3SXT3KR.js.map} +1 -1
- package/dist/cli.cjs +1 -1
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/index.cjs +159 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +227 -86
- package/dist/index.d.ts +227 -86
- package/dist/index.js +157 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-JWJJAIXV.js +0 -6
package/README.md
CHANGED
|
@@ -37,8 +37,8 @@ npm install tempest-express-sdk tempest-db-js express zod
|
|
|
37
37
|
| **sse / websockets** | `SSEBroker`/`sseResponse` (+ `RedisSSEBroker`), transport-agnostic `WebSocketHub` + `attachWebSocketHub` |
|
|
38
38
|
| **flags / storage** | `FeatureFlags` (+ guard), `UploadStorage`/`LocalUploadStorage` |
|
|
39
39
|
| **webpush / email** | `WebPushDispatcher` (VAPID), `EmailUtils` (SMTP) |
|
|
40
|
-
| **server utils** | `TOTPHelper` (MFA), `HTTPClient` (retry + circuit breaker), `MetricsUtils` (+ Prometheus), `getClientIp` |
|
|
41
|
-
| **integrations** | `MessagingProvider` contract; `WhatsAppProvider` (zap-api), `TelegramProvider` (Bot API), `TwilioSmsProvider` (SMS) + webhook receivers |
|
|
40
|
+
| **server utils** | `TOTPHelper` (MFA), `HTTPClient` (retry + circuit breaker), `MetricsUtils` (+ Prometheus, GPU), `makeMetricsRouter`, `getClientIp` |
|
|
41
|
+
| **integrations** | `MessagingProvider` contract; `WhatsAppProvider` (zap-api), `TelegramProvider` (Bot API), `TwilioSmsProvider` (SMS), `EmailProvider` + webhook receivers |
|
|
42
42
|
| **admin** | `AdminSite` + `makeAdminRouter` — JSON admin with auto-derived CRUD + introspection |
|
|
43
43
|
| **api** | `createApp`, `runServer`, `registerExceptionHandlers`, `createOpenApiRegistry`, `generateOpenApiDocument`, `mountSwaggerUi`, `mountRedoc`, `makeHealthRouter` |
|
|
44
44
|
|
|
@@ -98,8 +98,9 @@ Also shipped: the `integrations/` module — a typed WhatsApp client over
|
|
|
98
98
|
[`zap-api`](https://github.com/mauriciobenjamin700) behind a shared
|
|
99
99
|
`MessagingProvider` contract.
|
|
100
100
|
|
|
101
|
-
Planned (see [ROADMAP.md](./ROADMAP.md)):
|
|
102
|
-
|
|
101
|
+
Planned (see [ROADMAP.md](./ROADMAP.md)): additional `MessagingProvider`
|
|
102
|
+
channels (more SMS vendors, a transactional-email provider) under the shared
|
|
103
|
+
contract.
|
|
103
104
|
|
|
104
105
|
## License
|
|
105
106
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/version.ts"],"names":[],"mappings":";AACO,IAAM,OAAA,GAAU","file":"chunk-
|
|
1
|
+
{"version":3,"sources":["../src/version.ts"],"names":[],"mappings":";AACO,IAAM,OAAA,GAAU","file":"chunk-U3SXT3KR.js","sourcesContent":["/** The installed SDK version. Single source of truth for the barrel + CLI. */\nexport const VERSION = \"0.10.0\";\n"]}
|
package/dist/cli.cjs
CHANGED
package/dist/cli.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli/template.ts","../src/version.ts","../src/cli/index.ts"],"names":["join","mkdir","dirname","writeFile","resolve","parseArgs","randomBytes"],"mappings":"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","file":"cli.cjs","sourcesContent":["/**\n * Project-template generator for the `new` command, mirroring `cli.new`.\n *\n * Produces a complete, runnable Express service that follows the SDK's layered\n * architecture (router → controller → service → repository → model) and is\n * pre-wired with `createApp`, native Swagger + Redoc, Zod validation, and a\n * `tempest-db-js` model. {@link projectFiles} returns a `path → contents` map\n * the CLI writes to disk.\n */\n\n/** SDK version the generated project depends on (kept in sync at release). */\nconst SDK_VERSION = \"^0.1.0\";\n\n/**\n * Build the file map for a new service named `name`.\n *\n * @param name - The project (and package) name.\n * @returns A map of relative file path to file contents.\n */\nexport function projectFiles(name: string): Record<string, string> {\n return {\n \"package.json\": `${JSON.stringify(\n {\n name,\n version: \"0.1.0\",\n private: true,\n type: \"module\",\n scripts: {\n dev: \"tsx watch main.ts\",\n start: \"tsx main.ts\",\n build: \"tsc -p tsconfig.json\",\n typecheck: \"tsc --noEmit\",\n },\n dependencies: {\n express: \"^5.1.0\",\n \"tempest-db-js\": \"^0.3.0\",\n \"tempest-express-sdk\": SDK_VERSION,\n zod: \"^3.24.1\",\n },\n devDependencies: {\n \"@types/express\": \"^5.0.0\",\n \"@types/node\": \"^22.10.0\",\n tsx: \"^4.19.2\",\n typescript: \"^5.7.0\",\n },\n },\n null,\n 2,\n )}\\n`,\n\n \"tsconfig.json\": `${JSON.stringify(\n {\n compilerOptions: {\n target: \"ES2022\",\n module: \"ESNext\",\n moduleResolution: \"Bundler\",\n lib: [\"ES2022\"],\n strict: true,\n noUncheckedIndexedAccess: true,\n verbatimModuleSyntax: true,\n esModuleInterop: true,\n skipLibCheck: true,\n forceConsistentCasingInFileNames: true,\n outDir: \"dist\",\n paths: { \"@/*\": [\"./src/*\"] },\n },\n include: [\"src\", \"main.ts\"],\n },\n null,\n 2,\n )}\\n`,\n\n \".gitignore\": \"node_modules\\ndist\\n.env\\n*.log\\n\",\n\n \".env.example\":\n \"HOST=127.0.0.1\\nPORT=8000\\nDEBUG=false\\nDATABASE_URL=sqlite://./app.db\\nCORS_ORIGINS=*\\n\",\n\n \"main.ts\": `import { run } from \"@/index\";\n\nrun();\n`,\n\n \"src/index.ts\": `import { run } from \"@/server\";\n\nexport { run };\n`,\n\n \"src/core/settings.ts\": `import { baseAppSettingsShape, loadSettings, z } from \"tempest-express-sdk\";\n\n/** Application settings — extend the SDK base shape with project fields. */\nexport const settingsSchema = z.object({\n ...baseAppSettingsShape,\n});\n\nexport const settings = loadSettings(settingsSchema);\n`,\n\n \"src/db/models/itemModel.ts\": `import { BaseModel, column, tableNameFor } from \"tempest-express-sdk\";\n\n/** A sample domain model. Replace with your own. */\nexport class ItemModel extends BaseModel {\n static tablename = tableNameFor(\"ItemModel\");\n name = column.text().notNull();\n price = column.integer().notNull();\n}\n`,\n\n \"src/schemas/item.ts\": `import { baseResponseSchema, z } from \"tempest-express-sdk\";\n\n/** Request payload to create an item. */\nexport const itemCreateSchema = z\n .object({\n name: z.string().min(1).openapi({ description: \"The item name.\" }),\n price: z.number().int().min(0).openapi({ description: \"Price in cents.\" }),\n })\n .openapi(\"ItemCreate\");\n\n/** Response payload for an item (base columns + domain fields). */\nexport const itemResponseSchema = baseResponseSchema\n .extend({\n name: z.string(),\n price: z.number().int(),\n })\n .openapi(\"Item\");\n\nexport type ItemCreate = z.infer<typeof itemCreateSchema>;\nexport type ItemResponse = z.infer<typeof itemResponseSchema>;\n`,\n\n \"src/db/repositories/itemRepository.ts\": `import { type AsyncSession, BaseRepository } from \"tempest-express-sdk\";\nimport { ItemModel } from \"@/db/models/itemModel\";\n\n/** Data-access layer for items. */\nexport class ItemRepository extends BaseRepository<typeof ItemModel> {\n constructor(session: AsyncSession) {\n super(ItemModel, session);\n }\n}\n`,\n\n \"src/services/itemService.ts\": `import { BaseService } from \"tempest-express-sdk\";\nimport type { ItemRepository } from \"@/db/repositories/itemRepository\";\nimport type { ItemModel } from \"@/db/models/itemModel\";\nimport type { ItemResponse } from \"@/schemas/item\";\n\n/** Business logic for items. Maps raw rows to the response shape. */\nexport class ItemService extends BaseService<typeof ItemModel, ItemResponse> {\n constructor(repository: ItemRepository) {\n super(repository, (row) => ({\n id: row.id,\n isActive: row.isActive,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n name: row.name,\n price: row.price,\n }));\n }\n}\n`,\n\n \"src/controllers/itemController.ts\": `import { BaseController } from \"tempest-express-sdk\";\nimport type { ItemModel } from \"@/db/models/itemModel\";\nimport type { ItemResponse } from \"@/schemas/item\";\nimport type { ItemService } from \"@/services/itemService\";\n\n/** Orchestration boundary between the router and the item service. */\nexport class ItemController extends BaseController<typeof ItemModel, ItemResponse> {\n constructor(service: ItemService) {\n super(service);\n }\n}\n`,\n\n \"src/api/routers/items.ts\": `import { Router } from \"express\";\nimport type { OpenAPIRegistry } from \"tempest-express-sdk\";\nimport { itemCreateSchema, itemResponseSchema } from \"@/schemas/item\";\n\n/**\n * Build the items router and register its OpenAPI paths.\n *\n * NOTE: wire a real controller (with a DB session) here. This stub returns\n * static data so a freshly generated project boots without a database.\n */\nexport function makeItemsRouter(registry: OpenAPIRegistry): Router {\n const router = Router();\n\n registry.registerPath({\n method: \"get\",\n path: \"/api/items\",\n summary: \"List items\",\n responses: {\n 200: {\n description: \"The items\",\n content: { \"application/json\": { schema: itemResponseSchema.array() } },\n },\n },\n });\n\n registry.registerPath({\n method: \"post\",\n path: \"/api/items\",\n summary: \"Create an item\",\n request: {\n body: { content: { \"application/json\": { schema: itemCreateSchema } } },\n },\n responses: {\n 201: {\n description: \"The created item\",\n content: { \"application/json\": { schema: itemResponseSchema } },\n },\n },\n });\n\n router.get(\"/api/items\", (_req, res) => {\n res.json([]);\n });\n\n router.post(\"/api/items\", (req, res) => {\n const data = itemCreateSchema.parse(req.body);\n res.status(201).json({\n id: \"00000000-0000-0000-0000-000000000000\",\n isActive: true,\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n ...data,\n });\n });\n\n return router;\n}\n`,\n\n \"src/api/app.ts\": `import type { Express } from \"express\";\nimport { createApp, createOpenApiRegistry } from \"tempest-express-sdk\";\nimport { settings } from \"@/core/settings\";\nimport { makeItemsRouter } from \"@/api/routers/items\";\n\n/** Build the configured Express application. */\nexport async function makeApp(): Promise<Express> {\n const registry = createOpenApiRegistry();\n\n return createApp({\n corsOrigins: settings.CORS_ORIGINS,\n openapi: {\n registry,\n info: { title: \"${name}\", version: \"0.1.0\", description: \"Powered by tempest-express-sdk.\" },\n },\n configure: (app) => {\n app.use(makeItemsRouter(registry));\n },\n });\n}\n`,\n\n \"src/server.ts\": `import { runServer } from \"tempest-express-sdk\";\nimport { settings } from \"@/core/settings\";\nimport { makeApp } from \"@/api/app\";\n\n/** Build the app and start listening. */\nexport async function run(): Promise<void> {\n const app = await makeApp();\n await runServer(app, { host: settings.HOST, port: settings.PORT });\n}\n`,\n\n \"docker-compose.yml\": dockerComposeFile(name),\n\n \"README.md\": `# ${name}\n\nGenerated with \\`tempest-express new\\` — an Express + Zod + tempest-db-js service.\n\n## Develop\n\n\\`\\`\\`bash\nnpm install\ncp .env.example .env\nnpm run dev\n\\`\\`\\`\n\n- API: http://127.0.0.1:8000/api/items\n- Swagger UI: http://127.0.0.1:8000/docs\n- Redoc: http://127.0.0.1:8000/redoc\n- Health: http://127.0.0.1:8000/health\n\\`\\`\\`\n`,\n };\n}\n\n/** Lowercase the first letter (PascalCase → camelCase). */\nfunction toCamel(name: string): string {\n return name.charAt(0).toLowerCase() + name.slice(1);\n}\n\n/** Convert `CamelCase` to `snake_case`. */\nfunction toSnake(name: string): string {\n return name.replace(/(?<!^)(?=[A-Z])/g, \"_\").toLowerCase();\n}\n\n/**\n * Build a full CRUD resource file map for a PascalCase resource name.\n *\n * Generates model + schema + repository + service + controller + router under\n * `src/`, mirroring the layered slice the `new` template ships with.\n *\n * @param pascal - The resource name in PascalCase (e.g. `Product`).\n * @returns A map of relative file path to file contents.\n */\nexport function resourceFiles(pascal: string): Record<string, string> {\n const camel = toCamel(pascal);\n const table = toSnake(pascal);\n\n return {\n [`src/db/models/${camel}Model.ts`]: `import { BaseModel, column, tableNameFor } from \"tempest-express-sdk\";\n\n/** The ${pascal} domain model. */\nexport class ${pascal}Model extends BaseModel {\n static tablename = tableNameFor(\"${pascal}Model\"); // \"${table}\"\n name = column.text().notNull();\n}\n`,\n\n [`src/schemas/${camel}.ts`]: `import { baseResponseSchema, z } from \"tempest-express-sdk\";\n\n/** Request payload to create a ${pascal}. */\nexport const ${camel}CreateSchema = z\n .object({ name: z.string().min(1) })\n .openapi(\"${pascal}Create\");\n\n/** Response payload for a ${pascal}. */\nexport const ${camel}ResponseSchema = baseResponseSchema\n .extend({ name: z.string() })\n .openapi(\"${pascal}\");\n\nexport type ${pascal}Create = z.infer<typeof ${camel}CreateSchema>;\nexport type ${pascal}Response = z.infer<typeof ${camel}ResponseSchema>;\n`,\n\n [`src/db/repositories/${camel}Repository.ts`]: `import { type AsyncSession, BaseRepository } from \"tempest-express-sdk\";\nimport { ${pascal}Model } from \"@/db/models/${camel}Model\";\n\n/** Data-access layer for ${pascal}. */\nexport class ${pascal}Repository extends BaseRepository<typeof ${pascal}Model> {\n constructor(session: AsyncSession) {\n super(${pascal}Model, session);\n }\n}\n`,\n\n [`src/services/${camel}Service.ts`]: `import { BaseService } from \"tempest-express-sdk\";\nimport type { ${pascal}Model } from \"@/db/models/${camel}Model\";\nimport type { ${pascal}Repository } from \"@/db/repositories/${camel}Repository\";\nimport type { ${pascal}Response } from \"@/schemas/${camel}\";\n\n/** Business logic for ${pascal}. */\nexport class ${pascal}Service extends BaseService<typeof ${pascal}Model, ${pascal}Response> {\n constructor(repository: ${pascal}Repository) {\n super(repository, (row) => ({\n id: row.id,\n isActive: row.isActive,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n name: row.name,\n }));\n }\n}\n`,\n\n [`src/controllers/${camel}Controller.ts`]: `import { BaseController } from \"tempest-express-sdk\";\nimport type { ${pascal}Model } from \"@/db/models/${camel}Model\";\nimport type { ${pascal}Response } from \"@/schemas/${camel}\";\nimport type { ${pascal}Service } from \"@/services/${camel}Service\";\n\n/** Orchestration boundary for ${pascal}. */\nexport class ${pascal}Controller extends BaseController<typeof ${pascal}Model, ${pascal}Response> {\n constructor(service: ${pascal}Service) {\n super(service);\n }\n}\n`,\n\n [`src/api/routers/${camel}s.ts`]: `import { Router } from \"express\";\nimport type { OpenAPIRegistry } from \"tempest-express-sdk\";\nimport { ${camel}CreateSchema, ${camel}ResponseSchema } from \"@/schemas/${camel}\";\n\n/** Build the ${pascal} router and register its OpenAPI paths. */\nexport function make${pascal}sRouter(registry: OpenAPIRegistry): Router {\n const router = Router();\n\n registry.registerPath({\n method: \"get\",\n path: \"/api/${camel}s\",\n summary: \"List ${camel}s\",\n responses: {\n 200: {\n description: \"OK\",\n content: { \"application/json\": { schema: ${camel}ResponseSchema.array() } },\n },\n },\n });\n registry.registerPath({\n method: \"post\",\n path: \"/api/${camel}s\",\n summary: \"Create a ${camel}\",\n request: {\n body: { content: { \"application/json\": { schema: ${camel}CreateSchema } } },\n },\n responses: {\n 201: {\n description: \"Created\",\n content: { \"application/json\": { schema: ${camel}ResponseSchema } },\n },\n },\n });\n\n router.get(\"/api/${camel}s\", (_req, res) => res.json([]));\n router.post(\"/api/${camel}s\", (req, res) => {\n const data = ${camel}CreateSchema.parse(req.body);\n res.status(201).json({\n id: \"00000000-0000-0000-0000-000000000000\",\n isActive: true,\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n ...data,\n });\n });\n\n return router;\n}\n`,\n };\n}\n\n/** Build a `docker-compose.yml` with Postgres + Redis for local dev. */\nexport function dockerComposeFile(name: string): string {\n return `services:\n postgres:\n image: postgres:16-alpine\n environment:\n POSTGRES_USER: ${name}\n POSTGRES_PASSWORD: ${name}\n POSTGRES_DB: ${name}\n ports:\n - \"5432:5432\"\n volumes:\n - pgdata:/var/lib/postgresql/data\n redis:\n image: redis:7-alpine\n ports:\n - \"6379:6379\"\n\nvolumes:\n pgdata:\n`;\n}\n","/** The installed SDK version. Single source of truth for the barrel + CLI. */\nexport const VERSION = \"0.8.0\";\n","#!/usr/bin/env node\n/**\n * `tempest-express` command-line interface, mirroring `cli.main`.\n *\n * Commands:\n * - `new <name>` — scaffold a runnable Express service from the SDK template.\n * - `--version` — print the CLI/SDK version.\n * - `--help` — print usage.\n */\n\nimport { randomBytes } from \"node:crypto\";\nimport { mkdir, writeFile } from \"node:fs/promises\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { parseArgs } from \"node:util\";\nimport { dockerComposeFile, projectFiles, resourceFiles } from \"@/cli/template\";\nimport { VERSION } from \"@/version\";\n\nconst USAGE = `tempest-express — Express SDK CLI\n\nUsage:\n tempest-express new <name> [--dir <path>] Scaffold a new service\n tempest-express generate <Name> [--dir <path>] Scaffold a CRUD resource\n tempest-express secret [--bytes <n>] Print a random secret\n tempest-express docker-compose [--dir <path>] Write a docker-compose.yml\n tempest-express db Migration guidance\n tempest-express --version Print the version\n tempest-express --help Show this help\n`;\n\nconst DB_HELP = `Migrations are handled by tempest-db-js (programmatic API):\n\n import { ... } from \"tempest-db-js/migrations\";\n\nSee https://www.npmjs.com/package/tempest-db-js for the migration workflow.\n`;\n\n/** Write a project file map under `root`, creating directories as needed. */\nasync function writeFiles(root: string, files: Record<string, string>): Promise<void> {\n for (const [relative, contents] of Object.entries(files)) {\n const target = join(root, relative);\n await mkdir(dirname(target), { recursive: true });\n await writeFile(target, contents, \"utf8\");\n }\n}\n\n/** Scaffold a new project named `name` into `dir/name`. */\nasync function runNew(name: string, dir: string): Promise<void> {\n if (!/^[a-z0-9][a-z0-9-_]*$/i.test(name)) {\n throw new Error(`Invalid project name: ${JSON.stringify(name)}`);\n }\n const root = resolve(dir, name);\n await writeFiles(root, projectFiles(name));\n process.stdout.write(\n `Created ${name} at ${root}\\n\\nNext steps:\\n cd ${name}\\n npm install\\n cp .env.example .env\\n npm run dev\\n`,\n );\n}\n\n/** Scaffold a CRUD resource into `dir` (PascalCase name required). */\nasync function runGenerate(name: string, dir: string): Promise<void> {\n if (!/^[A-Z][A-Za-z0-9]*$/.test(name)) {\n throw new Error(`Resource name must be PascalCase, got ${JSON.stringify(name)}`);\n }\n const files = resourceFiles(name);\n await writeFiles(resolve(dir), files);\n const written = Object.keys(files)\n .map((path) => ` ${path}`)\n .join(\"\\n\");\n process.stdout.write(`Generated ${name} resource:\\n${written}\\n`);\n}\n\n/**\n * Parse `argv` and dispatch to the matching command.\n *\n * @param argv - Arguments after `node script` (defaults to `process.argv`).\n * @returns The process exit code.\n */\nexport async function main(argv: string[] = process.argv.slice(2)): Promise<number> {\n const { values, positionals } = parseArgs({\n args: argv,\n allowPositionals: true,\n options: {\n version: { type: \"boolean\", short: \"v\" },\n help: { type: \"boolean\", short: \"h\" },\n dir: { type: \"string\", default: \".\" },\n bytes: { type: \"string\", default: \"32\" },\n },\n });\n\n if (values.version) {\n process.stdout.write(`${VERSION}\\n`);\n return 0;\n }\n\n const [command, ...rest] = positionals;\n\n if (values.help || command === undefined || command === \"help\") {\n process.stdout.write(USAGE);\n return 0;\n }\n\n const dir = values.dir ?? \".\";\n\n if (command === \"new\") {\n const name = rest[0];\n if (!name) {\n process.stderr.write(`error: \\`new\\` requires a project name\\n\\n${USAGE}`);\n return 1;\n }\n await runNew(name, dir);\n return 0;\n }\n\n if (command === \"generate\" || command === \"g\") {\n const name = rest[0];\n if (!name) {\n process.stderr.write(`error: \\`generate\\` requires a resource name\\n\\n${USAGE}`);\n return 1;\n }\n await runGenerate(name, dir);\n return 0;\n }\n\n if (command === \"secret\") {\n const nbytes = Number.parseInt(values.bytes ?? \"32\", 10);\n if (!Number.isInteger(nbytes) || nbytes < 16) {\n process.stderr.write(\"error: --bytes must be an integer >= 16\\n\");\n return 1;\n }\n process.stdout.write(`${randomBytes(nbytes).toString(\"base64url\")}\\n`);\n return 0;\n }\n\n if (command === \"docker-compose\") {\n await writeFiles(resolve(dir), { \"docker-compose.yml\": dockerComposeFile(\"app\") });\n process.stdout.write(`Wrote ${resolve(dir, \"docker-compose.yml\")}\\n`);\n return 0;\n }\n\n if (command === \"db\") {\n process.stdout.write(DB_HELP);\n return 0;\n }\n\n process.stderr.write(`error: unknown command ${JSON.stringify(command)}\\n\\n${USAGE}`);\n return 1;\n}\n\nmain()\n .then((code) => {\n process.exitCode = code;\n })\n .catch((error: unknown) => {\n const message = error instanceof Error ? error.message : String(error);\n process.stderr.write(`error: ${message}\\n`);\n process.exitCode = 1;\n });\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli/template.ts","../src/version.ts","../src/cli/index.ts"],"names":["join","mkdir","dirname","writeFile","resolve","parseArgs","randomBytes"],"mappings":"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","file":"cli.cjs","sourcesContent":["/**\n * Project-template generator for the `new` command, mirroring `cli.new`.\n *\n * Produces a complete, runnable Express service that follows the SDK's layered\n * architecture (router → controller → service → repository → model) and is\n * pre-wired with `createApp`, native Swagger + Redoc, Zod validation, and a\n * `tempest-db-js` model. {@link projectFiles} returns a `path → contents` map\n * the CLI writes to disk.\n */\n\n/** SDK version the generated project depends on (kept in sync at release). */\nconst SDK_VERSION = \"^0.1.0\";\n\n/**\n * Build the file map for a new service named `name`.\n *\n * @param name - The project (and package) name.\n * @returns A map of relative file path to file contents.\n */\nexport function projectFiles(name: string): Record<string, string> {\n return {\n \"package.json\": `${JSON.stringify(\n {\n name,\n version: \"0.1.0\",\n private: true,\n type: \"module\",\n scripts: {\n dev: \"tsx watch main.ts\",\n start: \"tsx main.ts\",\n build: \"tsc -p tsconfig.json\",\n typecheck: \"tsc --noEmit\",\n },\n dependencies: {\n express: \"^5.1.0\",\n \"tempest-db-js\": \"^0.3.0\",\n \"tempest-express-sdk\": SDK_VERSION,\n zod: \"^3.24.1\",\n },\n devDependencies: {\n \"@types/express\": \"^5.0.0\",\n \"@types/node\": \"^22.10.0\",\n tsx: \"^4.19.2\",\n typescript: \"^5.7.0\",\n },\n },\n null,\n 2,\n )}\\n`,\n\n \"tsconfig.json\": `${JSON.stringify(\n {\n compilerOptions: {\n target: \"ES2022\",\n module: \"ESNext\",\n moduleResolution: \"Bundler\",\n lib: [\"ES2022\"],\n strict: true,\n noUncheckedIndexedAccess: true,\n verbatimModuleSyntax: true,\n esModuleInterop: true,\n skipLibCheck: true,\n forceConsistentCasingInFileNames: true,\n outDir: \"dist\",\n paths: { \"@/*\": [\"./src/*\"] },\n },\n include: [\"src\", \"main.ts\"],\n },\n null,\n 2,\n )}\\n`,\n\n \".gitignore\": \"node_modules\\ndist\\n.env\\n*.log\\n\",\n\n \".env.example\":\n \"HOST=127.0.0.1\\nPORT=8000\\nDEBUG=false\\nDATABASE_URL=sqlite://./app.db\\nCORS_ORIGINS=*\\n\",\n\n \"main.ts\": `import { run } from \"@/index\";\n\nrun();\n`,\n\n \"src/index.ts\": `import { run } from \"@/server\";\n\nexport { run };\n`,\n\n \"src/core/settings.ts\": `import { baseAppSettingsShape, loadSettings, z } from \"tempest-express-sdk\";\n\n/** Application settings — extend the SDK base shape with project fields. */\nexport const settingsSchema = z.object({\n ...baseAppSettingsShape,\n});\n\nexport const settings = loadSettings(settingsSchema);\n`,\n\n \"src/db/models/itemModel.ts\": `import { BaseModel, column, tableNameFor } from \"tempest-express-sdk\";\n\n/** A sample domain model. Replace with your own. */\nexport class ItemModel extends BaseModel {\n static tablename = tableNameFor(\"ItemModel\");\n name = column.text().notNull();\n price = column.integer().notNull();\n}\n`,\n\n \"src/schemas/item.ts\": `import { baseResponseSchema, z } from \"tempest-express-sdk\";\n\n/** Request payload to create an item. */\nexport const itemCreateSchema = z\n .object({\n name: z.string().min(1).openapi({ description: \"The item name.\" }),\n price: z.number().int().min(0).openapi({ description: \"Price in cents.\" }),\n })\n .openapi(\"ItemCreate\");\n\n/** Response payload for an item (base columns + domain fields). */\nexport const itemResponseSchema = baseResponseSchema\n .extend({\n name: z.string(),\n price: z.number().int(),\n })\n .openapi(\"Item\");\n\nexport type ItemCreate = z.infer<typeof itemCreateSchema>;\nexport type ItemResponse = z.infer<typeof itemResponseSchema>;\n`,\n\n \"src/db/repositories/itemRepository.ts\": `import { type AsyncSession, BaseRepository } from \"tempest-express-sdk\";\nimport { ItemModel } from \"@/db/models/itemModel\";\n\n/** Data-access layer for items. */\nexport class ItemRepository extends BaseRepository<typeof ItemModel> {\n constructor(session: AsyncSession) {\n super(ItemModel, session);\n }\n}\n`,\n\n \"src/services/itemService.ts\": `import { BaseService } from \"tempest-express-sdk\";\nimport type { ItemRepository } from \"@/db/repositories/itemRepository\";\nimport type { ItemModel } from \"@/db/models/itemModel\";\nimport type { ItemResponse } from \"@/schemas/item\";\n\n/** Business logic for items. Maps raw rows to the response shape. */\nexport class ItemService extends BaseService<typeof ItemModel, ItemResponse> {\n constructor(repository: ItemRepository) {\n super(repository, (row) => ({\n id: row.id,\n isActive: row.isActive,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n name: row.name,\n price: row.price,\n }));\n }\n}\n`,\n\n \"src/controllers/itemController.ts\": `import { BaseController } from \"tempest-express-sdk\";\nimport type { ItemModel } from \"@/db/models/itemModel\";\nimport type { ItemResponse } from \"@/schemas/item\";\nimport type { ItemService } from \"@/services/itemService\";\n\n/** Orchestration boundary between the router and the item service. */\nexport class ItemController extends BaseController<typeof ItemModel, ItemResponse> {\n constructor(service: ItemService) {\n super(service);\n }\n}\n`,\n\n \"src/api/routers/items.ts\": `import { Router } from \"express\";\nimport type { OpenAPIRegistry } from \"tempest-express-sdk\";\nimport { itemCreateSchema, itemResponseSchema } from \"@/schemas/item\";\n\n/**\n * Build the items router and register its OpenAPI paths.\n *\n * NOTE: wire a real controller (with a DB session) here. This stub returns\n * static data so a freshly generated project boots without a database.\n */\nexport function makeItemsRouter(registry: OpenAPIRegistry): Router {\n const router = Router();\n\n registry.registerPath({\n method: \"get\",\n path: \"/api/items\",\n summary: \"List items\",\n responses: {\n 200: {\n description: \"The items\",\n content: { \"application/json\": { schema: itemResponseSchema.array() } },\n },\n },\n });\n\n registry.registerPath({\n method: \"post\",\n path: \"/api/items\",\n summary: \"Create an item\",\n request: {\n body: { content: { \"application/json\": { schema: itemCreateSchema } } },\n },\n responses: {\n 201: {\n description: \"The created item\",\n content: { \"application/json\": { schema: itemResponseSchema } },\n },\n },\n });\n\n router.get(\"/api/items\", (_req, res) => {\n res.json([]);\n });\n\n router.post(\"/api/items\", (req, res) => {\n const data = itemCreateSchema.parse(req.body);\n res.status(201).json({\n id: \"00000000-0000-0000-0000-000000000000\",\n isActive: true,\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n ...data,\n });\n });\n\n return router;\n}\n`,\n\n \"src/api/app.ts\": `import type { Express } from \"express\";\nimport { createApp, createOpenApiRegistry } from \"tempest-express-sdk\";\nimport { settings } from \"@/core/settings\";\nimport { makeItemsRouter } from \"@/api/routers/items\";\n\n/** Build the configured Express application. */\nexport async function makeApp(): Promise<Express> {\n const registry = createOpenApiRegistry();\n\n return createApp({\n corsOrigins: settings.CORS_ORIGINS,\n openapi: {\n registry,\n info: { title: \"${name}\", version: \"0.1.0\", description: \"Powered by tempest-express-sdk.\" },\n },\n configure: (app) => {\n app.use(makeItemsRouter(registry));\n },\n });\n}\n`,\n\n \"src/server.ts\": `import { runServer } from \"tempest-express-sdk\";\nimport { settings } from \"@/core/settings\";\nimport { makeApp } from \"@/api/app\";\n\n/** Build the app and start listening. */\nexport async function run(): Promise<void> {\n const app = await makeApp();\n await runServer(app, { host: settings.HOST, port: settings.PORT });\n}\n`,\n\n \"docker-compose.yml\": dockerComposeFile(name),\n\n \"README.md\": `# ${name}\n\nGenerated with \\`tempest-express new\\` — an Express + Zod + tempest-db-js service.\n\n## Develop\n\n\\`\\`\\`bash\nnpm install\ncp .env.example .env\nnpm run dev\n\\`\\`\\`\n\n- API: http://127.0.0.1:8000/api/items\n- Swagger UI: http://127.0.0.1:8000/docs\n- Redoc: http://127.0.0.1:8000/redoc\n- Health: http://127.0.0.1:8000/health\n\\`\\`\\`\n`,\n };\n}\n\n/** Lowercase the first letter (PascalCase → camelCase). */\nfunction toCamel(name: string): string {\n return name.charAt(0).toLowerCase() + name.slice(1);\n}\n\n/** Convert `CamelCase` to `snake_case`. */\nfunction toSnake(name: string): string {\n return name.replace(/(?<!^)(?=[A-Z])/g, \"_\").toLowerCase();\n}\n\n/**\n * Build a full CRUD resource file map for a PascalCase resource name.\n *\n * Generates model + schema + repository + service + controller + router under\n * `src/`, mirroring the layered slice the `new` template ships with.\n *\n * @param pascal - The resource name in PascalCase (e.g. `Product`).\n * @returns A map of relative file path to file contents.\n */\nexport function resourceFiles(pascal: string): Record<string, string> {\n const camel = toCamel(pascal);\n const table = toSnake(pascal);\n\n return {\n [`src/db/models/${camel}Model.ts`]: `import { BaseModel, column, tableNameFor } from \"tempest-express-sdk\";\n\n/** The ${pascal} domain model. */\nexport class ${pascal}Model extends BaseModel {\n static tablename = tableNameFor(\"${pascal}Model\"); // \"${table}\"\n name = column.text().notNull();\n}\n`,\n\n [`src/schemas/${camel}.ts`]: `import { baseResponseSchema, z } from \"tempest-express-sdk\";\n\n/** Request payload to create a ${pascal}. */\nexport const ${camel}CreateSchema = z\n .object({ name: z.string().min(1) })\n .openapi(\"${pascal}Create\");\n\n/** Response payload for a ${pascal}. */\nexport const ${camel}ResponseSchema = baseResponseSchema\n .extend({ name: z.string() })\n .openapi(\"${pascal}\");\n\nexport type ${pascal}Create = z.infer<typeof ${camel}CreateSchema>;\nexport type ${pascal}Response = z.infer<typeof ${camel}ResponseSchema>;\n`,\n\n [`src/db/repositories/${camel}Repository.ts`]: `import { type AsyncSession, BaseRepository } from \"tempest-express-sdk\";\nimport { ${pascal}Model } from \"@/db/models/${camel}Model\";\n\n/** Data-access layer for ${pascal}. */\nexport class ${pascal}Repository extends BaseRepository<typeof ${pascal}Model> {\n constructor(session: AsyncSession) {\n super(${pascal}Model, session);\n }\n}\n`,\n\n [`src/services/${camel}Service.ts`]: `import { BaseService } from \"tempest-express-sdk\";\nimport type { ${pascal}Model } from \"@/db/models/${camel}Model\";\nimport type { ${pascal}Repository } from \"@/db/repositories/${camel}Repository\";\nimport type { ${pascal}Response } from \"@/schemas/${camel}\";\n\n/** Business logic for ${pascal}. */\nexport class ${pascal}Service extends BaseService<typeof ${pascal}Model, ${pascal}Response> {\n constructor(repository: ${pascal}Repository) {\n super(repository, (row) => ({\n id: row.id,\n isActive: row.isActive,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n name: row.name,\n }));\n }\n}\n`,\n\n [`src/controllers/${camel}Controller.ts`]: `import { BaseController } from \"tempest-express-sdk\";\nimport type { ${pascal}Model } from \"@/db/models/${camel}Model\";\nimport type { ${pascal}Response } from \"@/schemas/${camel}\";\nimport type { ${pascal}Service } from \"@/services/${camel}Service\";\n\n/** Orchestration boundary for ${pascal}. */\nexport class ${pascal}Controller extends BaseController<typeof ${pascal}Model, ${pascal}Response> {\n constructor(service: ${pascal}Service) {\n super(service);\n }\n}\n`,\n\n [`src/api/routers/${camel}s.ts`]: `import { Router } from \"express\";\nimport type { OpenAPIRegistry } from \"tempest-express-sdk\";\nimport { ${camel}CreateSchema, ${camel}ResponseSchema } from \"@/schemas/${camel}\";\n\n/** Build the ${pascal} router and register its OpenAPI paths. */\nexport function make${pascal}sRouter(registry: OpenAPIRegistry): Router {\n const router = Router();\n\n registry.registerPath({\n method: \"get\",\n path: \"/api/${camel}s\",\n summary: \"List ${camel}s\",\n responses: {\n 200: {\n description: \"OK\",\n content: { \"application/json\": { schema: ${camel}ResponseSchema.array() } },\n },\n },\n });\n registry.registerPath({\n method: \"post\",\n path: \"/api/${camel}s\",\n summary: \"Create a ${camel}\",\n request: {\n body: { content: { \"application/json\": { schema: ${camel}CreateSchema } } },\n },\n responses: {\n 201: {\n description: \"Created\",\n content: { \"application/json\": { schema: ${camel}ResponseSchema } },\n },\n },\n });\n\n router.get(\"/api/${camel}s\", (_req, res) => res.json([]));\n router.post(\"/api/${camel}s\", (req, res) => {\n const data = ${camel}CreateSchema.parse(req.body);\n res.status(201).json({\n id: \"00000000-0000-0000-0000-000000000000\",\n isActive: true,\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n ...data,\n });\n });\n\n return router;\n}\n`,\n };\n}\n\n/** Build a `docker-compose.yml` with Postgres + Redis for local dev. */\nexport function dockerComposeFile(name: string): string {\n return `services:\n postgres:\n image: postgres:16-alpine\n environment:\n POSTGRES_USER: ${name}\n POSTGRES_PASSWORD: ${name}\n POSTGRES_DB: ${name}\n ports:\n - \"5432:5432\"\n volumes:\n - pgdata:/var/lib/postgresql/data\n redis:\n image: redis:7-alpine\n ports:\n - \"6379:6379\"\n\nvolumes:\n pgdata:\n`;\n}\n","/** The installed SDK version. Single source of truth for the barrel + CLI. */\nexport const VERSION = \"0.10.0\";\n","#!/usr/bin/env node\n/**\n * `tempest-express` command-line interface, mirroring `cli.main`.\n *\n * Commands:\n * - `new <name>` — scaffold a runnable Express service from the SDK template.\n * - `--version` — print the CLI/SDK version.\n * - `--help` — print usage.\n */\n\nimport { randomBytes } from \"node:crypto\";\nimport { mkdir, writeFile } from \"node:fs/promises\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { parseArgs } from \"node:util\";\nimport { dockerComposeFile, projectFiles, resourceFiles } from \"@/cli/template\";\nimport { VERSION } from \"@/version\";\n\nconst USAGE = `tempest-express — Express SDK CLI\n\nUsage:\n tempest-express new <name> [--dir <path>] Scaffold a new service\n tempest-express generate <Name> [--dir <path>] Scaffold a CRUD resource\n tempest-express secret [--bytes <n>] Print a random secret\n tempest-express docker-compose [--dir <path>] Write a docker-compose.yml\n tempest-express db Migration guidance\n tempest-express --version Print the version\n tempest-express --help Show this help\n`;\n\nconst DB_HELP = `Migrations are handled by tempest-db-js (programmatic API):\n\n import { ... } from \"tempest-db-js/migrations\";\n\nSee https://www.npmjs.com/package/tempest-db-js for the migration workflow.\n`;\n\n/** Write a project file map under `root`, creating directories as needed. */\nasync function writeFiles(root: string, files: Record<string, string>): Promise<void> {\n for (const [relative, contents] of Object.entries(files)) {\n const target = join(root, relative);\n await mkdir(dirname(target), { recursive: true });\n await writeFile(target, contents, \"utf8\");\n }\n}\n\n/** Scaffold a new project named `name` into `dir/name`. */\nasync function runNew(name: string, dir: string): Promise<void> {\n if (!/^[a-z0-9][a-z0-9-_]*$/i.test(name)) {\n throw new Error(`Invalid project name: ${JSON.stringify(name)}`);\n }\n const root = resolve(dir, name);\n await writeFiles(root, projectFiles(name));\n process.stdout.write(\n `Created ${name} at ${root}\\n\\nNext steps:\\n cd ${name}\\n npm install\\n cp .env.example .env\\n npm run dev\\n`,\n );\n}\n\n/** Scaffold a CRUD resource into `dir` (PascalCase name required). */\nasync function runGenerate(name: string, dir: string): Promise<void> {\n if (!/^[A-Z][A-Za-z0-9]*$/.test(name)) {\n throw new Error(`Resource name must be PascalCase, got ${JSON.stringify(name)}`);\n }\n const files = resourceFiles(name);\n await writeFiles(resolve(dir), files);\n const written = Object.keys(files)\n .map((path) => ` ${path}`)\n .join(\"\\n\");\n process.stdout.write(`Generated ${name} resource:\\n${written}\\n`);\n}\n\n/**\n * Parse `argv` and dispatch to the matching command.\n *\n * @param argv - Arguments after `node script` (defaults to `process.argv`).\n * @returns The process exit code.\n */\nexport async function main(argv: string[] = process.argv.slice(2)): Promise<number> {\n const { values, positionals } = parseArgs({\n args: argv,\n allowPositionals: true,\n options: {\n version: { type: \"boolean\", short: \"v\" },\n help: { type: \"boolean\", short: \"h\" },\n dir: { type: \"string\", default: \".\" },\n bytes: { type: \"string\", default: \"32\" },\n },\n });\n\n if (values.version) {\n process.stdout.write(`${VERSION}\\n`);\n return 0;\n }\n\n const [command, ...rest] = positionals;\n\n if (values.help || command === undefined || command === \"help\") {\n process.stdout.write(USAGE);\n return 0;\n }\n\n const dir = values.dir ?? \".\";\n\n if (command === \"new\") {\n const name = rest[0];\n if (!name) {\n process.stderr.write(`error: \\`new\\` requires a project name\\n\\n${USAGE}`);\n return 1;\n }\n await runNew(name, dir);\n return 0;\n }\n\n if (command === \"generate\" || command === \"g\") {\n const name = rest[0];\n if (!name) {\n process.stderr.write(`error: \\`generate\\` requires a resource name\\n\\n${USAGE}`);\n return 1;\n }\n await runGenerate(name, dir);\n return 0;\n }\n\n if (command === \"secret\") {\n const nbytes = Number.parseInt(values.bytes ?? \"32\", 10);\n if (!Number.isInteger(nbytes) || nbytes < 16) {\n process.stderr.write(\"error: --bytes must be an integer >= 16\\n\");\n return 1;\n }\n process.stdout.write(`${randomBytes(nbytes).toString(\"base64url\")}\\n`);\n return 0;\n }\n\n if (command === \"docker-compose\") {\n await writeFiles(resolve(dir), { \"docker-compose.yml\": dockerComposeFile(\"app\") });\n process.stdout.write(`Wrote ${resolve(dir, \"docker-compose.yml\")}\\n`);\n return 0;\n }\n\n if (command === \"db\") {\n process.stdout.write(DB_HELP);\n return 0;\n }\n\n process.stderr.write(`error: unknown command ${JSON.stringify(command)}\\n\\n${USAGE}`);\n return 1;\n}\n\nmain()\n .then((code) => {\n process.exitCode = code;\n })\n .catch((error: unknown) => {\n const message = error instanceof Error ? error.message : String(error);\n process.stderr.write(`error: ${message}\\n`);\n process.exitCode = 1;\n });\n"]}
|
package/dist/cli.js
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -5,7 +5,9 @@ var zodToOpenapi = require('@asteasolutions/zod-to-openapi');
|
|
|
5
5
|
var zod = require('zod');
|
|
6
6
|
var tempestDbJs = require('tempest-db-js');
|
|
7
7
|
var crypto = require('crypto');
|
|
8
|
+
var child_process = require('child_process');
|
|
8
9
|
var os = require('os');
|
|
10
|
+
var util = require('util');
|
|
9
11
|
var promises = require('fs/promises');
|
|
10
12
|
var path = require('path');
|
|
11
13
|
var express2 = require('express');
|
|
@@ -7011,6 +7013,7 @@ var HTTPClient = class {
|
|
|
7011
7013
|
return this.request("DELETE", url, init);
|
|
7012
7014
|
}
|
|
7013
7015
|
};
|
|
7016
|
+
var execFileAsync = util.promisify(child_process.execFile);
|
|
7014
7017
|
function readCpu() {
|
|
7015
7018
|
const cores = os.cpus().length;
|
|
7016
7019
|
const load1 = os.loadavg()[0] ?? 0;
|
|
@@ -7039,7 +7042,27 @@ function readSystem() {
|
|
|
7039
7042
|
uptimeSeconds: process.uptime()
|
|
7040
7043
|
};
|
|
7041
7044
|
}
|
|
7042
|
-
function
|
|
7045
|
+
async function readGpus() {
|
|
7046
|
+
try {
|
|
7047
|
+
const { stdout } = await execFileAsync("nvidia-smi", [
|
|
7048
|
+
"--query-gpu=index,utilization.gpu,memory.used,memory.total,temperature.gpu",
|
|
7049
|
+
"--format=csv,noheader,nounits"
|
|
7050
|
+
]);
|
|
7051
|
+
return stdout.trim().split("\n").filter((line) => line.trim().length > 0).map((line) => {
|
|
7052
|
+
const [index, util, used, total, temp] = line.split(",").map((v) => Number(v.trim()));
|
|
7053
|
+
return {
|
|
7054
|
+
index: index ?? 0,
|
|
7055
|
+
utilizationPercent: util ?? 0,
|
|
7056
|
+
memoryUsedMb: used ?? 0,
|
|
7057
|
+
memoryTotalMb: total ?? 0,
|
|
7058
|
+
temperatureC: temp ?? 0
|
|
7059
|
+
};
|
|
7060
|
+
});
|
|
7061
|
+
} catch {
|
|
7062
|
+
return [];
|
|
7063
|
+
}
|
|
7064
|
+
}
|
|
7065
|
+
function toPrometheus(snapshot = readSystem(), gpus = []) {
|
|
7043
7066
|
const lines = [
|
|
7044
7067
|
"# HELP process_cpu_load_percent 1-minute load average as percent of cores",
|
|
7045
7068
|
"# TYPE process_cpu_load_percent gauge",
|
|
@@ -7054,6 +7077,29 @@ function toPrometheus(snapshot = readSystem()) {
|
|
|
7054
7077
|
"# TYPE process_uptime_seconds counter",
|
|
7055
7078
|
`process_uptime_seconds ${snapshot.uptimeSeconds}`
|
|
7056
7079
|
];
|
|
7080
|
+
if (gpus.length > 0) {
|
|
7081
|
+
lines.push(
|
|
7082
|
+
"# HELP gpu_utilization_percent GPU utilization percent",
|
|
7083
|
+
"# TYPE gpu_utilization_percent gauge"
|
|
7084
|
+
);
|
|
7085
|
+
for (const gpu of gpus) {
|
|
7086
|
+
lines.push(`gpu_utilization_percent{gpu="${gpu.index}"} ${gpu.utilizationPercent}`);
|
|
7087
|
+
}
|
|
7088
|
+
lines.push(
|
|
7089
|
+
"# HELP gpu_memory_used_mb GPU memory used in MiB",
|
|
7090
|
+
"# TYPE gpu_memory_used_mb gauge"
|
|
7091
|
+
);
|
|
7092
|
+
for (const gpu of gpus) {
|
|
7093
|
+
lines.push(`gpu_memory_used_mb{gpu="${gpu.index}"} ${gpu.memoryUsedMb}`);
|
|
7094
|
+
}
|
|
7095
|
+
lines.push(
|
|
7096
|
+
"# HELP gpu_temperature_celsius GPU core temperature",
|
|
7097
|
+
"# TYPE gpu_temperature_celsius gauge"
|
|
7098
|
+
);
|
|
7099
|
+
for (const gpu of gpus) {
|
|
7100
|
+
lines.push(`gpu_temperature_celsius{gpu="${gpu.index}"} ${gpu.temperatureC}`);
|
|
7101
|
+
}
|
|
7102
|
+
}
|
|
7057
7103
|
return `${lines.join("\n")}
|
|
7058
7104
|
`;
|
|
7059
7105
|
}
|
|
@@ -7061,6 +7107,7 @@ var MetricsUtils = {
|
|
|
7061
7107
|
cpu: readCpu,
|
|
7062
7108
|
memory: readMemory,
|
|
7063
7109
|
system: readSystem,
|
|
7110
|
+
gpus: readGpus,
|
|
7064
7111
|
toPrometheus
|
|
7065
7112
|
};
|
|
7066
7113
|
|
|
@@ -8537,6 +8584,50 @@ function makeTwilioWebhookRouter(options) {
|
|
|
8537
8584
|
return router;
|
|
8538
8585
|
}
|
|
8539
8586
|
|
|
8587
|
+
// src/integrations/email.ts
|
|
8588
|
+
var EmailProvider = class {
|
|
8589
|
+
email;
|
|
8590
|
+
subject;
|
|
8591
|
+
/**
|
|
8592
|
+
* @param options - The email sender and default subject.
|
|
8593
|
+
*/
|
|
8594
|
+
constructor(options) {
|
|
8595
|
+
this.email = options.email;
|
|
8596
|
+
this.subject = options.subject ?? "Notification";
|
|
8597
|
+
}
|
|
8598
|
+
/**
|
|
8599
|
+
* Send a plain-text email.
|
|
8600
|
+
*
|
|
8601
|
+
* @param to - Recipient address.
|
|
8602
|
+
* @param text - Body text (also used as HTML).
|
|
8603
|
+
* @returns A sent result.
|
|
8604
|
+
*/
|
|
8605
|
+
async sendText(to, text) {
|
|
8606
|
+
await this.email.send({ to, subject: this.subject, text });
|
|
8607
|
+
return { status: "sent" };
|
|
8608
|
+
}
|
|
8609
|
+
/**
|
|
8610
|
+
* Send an email linking to the media (caption becomes the lead text).
|
|
8611
|
+
*
|
|
8612
|
+
* @param to - Recipient address.
|
|
8613
|
+
* @param media - The media reference (URL) + optional caption.
|
|
8614
|
+
* @returns A sent result.
|
|
8615
|
+
*/
|
|
8616
|
+
async sendMedia(to, media) {
|
|
8617
|
+
const caption = media.caption ?? "";
|
|
8618
|
+
await this.email.send({
|
|
8619
|
+
to,
|
|
8620
|
+
subject: this.subject,
|
|
8621
|
+
html: `${caption ? `<p>${caption}</p>` : ""}<p><a href="${media.media}">${media.media}</a></p>`
|
|
8622
|
+
});
|
|
8623
|
+
return { status: "sent" };
|
|
8624
|
+
}
|
|
8625
|
+
/** Always `"connected"` — SMTP reachability is verified on first send. */
|
|
8626
|
+
async status() {
|
|
8627
|
+
return "connected";
|
|
8628
|
+
}
|
|
8629
|
+
};
|
|
8630
|
+
|
|
8540
8631
|
// src/admin/site.ts
|
|
8541
8632
|
var AdminSite = class {
|
|
8542
8633
|
/**
|
|
@@ -8676,6 +8767,10 @@ var mfaEnrollResponseSchema = zod.z.object({
|
|
|
8676
8767
|
otpauthUri: zod.z.string().openapi({ description: "otpauth:// URI to render as QR." })
|
|
8677
8768
|
}).openapi("MfaEnrollResponse");
|
|
8678
8769
|
var mfaCodeSchema = zod.z.object({ code: zod.z.string().min(1).openapi({ description: "Authenticator code." }) }).openapi("MfaCode");
|
|
8770
|
+
var mfaChallengeSchema = zod.z.object({
|
|
8771
|
+
mfaToken: zod.z.string().min(1).openapi({ description: "Challenge token from login." }),
|
|
8772
|
+
code: zod.z.string().min(1).openapi({ description: "Authenticator code." })
|
|
8773
|
+
}).openapi("MfaChallenge");
|
|
8679
8774
|
var activationSchema = zod.z.object({ token: zod.z.string().min(1).openapi({ description: "Activation token." }) }).openapi("Activation");
|
|
8680
8775
|
var passwordResetRequestSchema = zod.z.object({ email: zod.z.string().email().openapi({ description: "Account email." }) }).openapi("PasswordResetRequest");
|
|
8681
8776
|
var passwordResetConfirmSchema = zod.z.object({
|
|
@@ -8700,6 +8795,8 @@ var UserAuthService = class {
|
|
|
8700
8795
|
passwordMinLength;
|
|
8701
8796
|
accessTtlSeconds;
|
|
8702
8797
|
refreshTtlSeconds;
|
|
8798
|
+
mfa;
|
|
8799
|
+
mfaChallengeTtlSeconds;
|
|
8703
8800
|
/**
|
|
8704
8801
|
* @param options - Store, password/JWT helpers and token policy.
|
|
8705
8802
|
*/
|
|
@@ -8710,6 +8807,8 @@ var UserAuthService = class {
|
|
|
8710
8807
|
this.passwordMinLength = options.passwordMinLength ?? 12;
|
|
8711
8808
|
this.accessTtlSeconds = options.accessTtlSeconds ?? 3600;
|
|
8712
8809
|
this.refreshTtlSeconds = options.refreshTtlSeconds ?? 60 * 60 * 24 * 14;
|
|
8810
|
+
this.mfa = options.mfa;
|
|
8811
|
+
this.mfaChallengeTtlSeconds = options.mfaChallengeTtlSeconds ?? 300;
|
|
8713
8812
|
}
|
|
8714
8813
|
/** Mint a signed access + refresh token pair for `user`. */
|
|
8715
8814
|
async issueTokens(user) {
|
|
@@ -8759,7 +8858,7 @@ var UserAuthService = class {
|
|
|
8759
8858
|
* Authenticate a user by email + password.
|
|
8760
8859
|
*
|
|
8761
8860
|
* @param data - Validated login payload.
|
|
8762
|
-
* @returns
|
|
8861
|
+
* @returns Full auth, or an {@link MfaChallenge} when MFA is enabled.
|
|
8763
8862
|
* @throws {UnauthorizedException} On bad credentials or inactive account.
|
|
8764
8863
|
*/
|
|
8765
8864
|
async login(data) {
|
|
@@ -8771,6 +8870,37 @@ var UserAuthService = class {
|
|
|
8771
8870
|
if (!user.isActive) {
|
|
8772
8871
|
throw new UnauthorizedException({ message: "Account is inactive" });
|
|
8773
8872
|
}
|
|
8873
|
+
if (this.mfa && await this.mfa.isEnabled(user.id)) {
|
|
8874
|
+
const mfaToken = await this.jwt.encode(
|
|
8875
|
+
{ sub: user.id, type: "mfa" },
|
|
8876
|
+
{ ttlSeconds: this.mfaChallengeTtlSeconds }
|
|
8877
|
+
);
|
|
8878
|
+
return { mfaRequired: true, mfaToken };
|
|
8879
|
+
}
|
|
8880
|
+
return { user: toPublic(user), tokens: await this.issueTokens(user) };
|
|
8881
|
+
}
|
|
8882
|
+
/**
|
|
8883
|
+
* Complete an MFA login challenge: verify the code and issue tokens.
|
|
8884
|
+
*
|
|
8885
|
+
* @param mfaToken - The challenge token from {@link login}.
|
|
8886
|
+
* @param code - The authenticator code.
|
|
8887
|
+
* @returns The public user and a fresh token pair.
|
|
8888
|
+
* @throws {UnauthorizedException} When the challenge/code is invalid, MFA is
|
|
8889
|
+
* not configured, or the account no longer exists / is inactive.
|
|
8890
|
+
*/
|
|
8891
|
+
async verifyMfaChallenge(mfaToken, code) {
|
|
8892
|
+
if (!this.mfa) throw new UnauthorizedException({ message: "MFA not configured" });
|
|
8893
|
+
const claims = await this.jwt.decodeOrNull(mfaToken);
|
|
8894
|
+
if (!claims || claims.type !== "mfa" || typeof claims.sub !== "string") {
|
|
8895
|
+
throw new UnauthorizedException({ message: "Invalid MFA challenge" });
|
|
8896
|
+
}
|
|
8897
|
+
if (!await this.mfa.verify(claims.sub, code)) {
|
|
8898
|
+
throw new UnauthorizedException({ message: "Invalid MFA code" });
|
|
8899
|
+
}
|
|
8900
|
+
const user = await this.store.findById(claims.sub);
|
|
8901
|
+
if (!user || !user.isActive) {
|
|
8902
|
+
throw new UnauthorizedException({ message: "Account is inactive" });
|
|
8903
|
+
}
|
|
8774
8904
|
return { user: toPublic(user), tokens: await this.issueTokens(user) };
|
|
8775
8905
|
}
|
|
8776
8906
|
/**
|
|
@@ -8844,6 +8974,15 @@ var MfaService = class {
|
|
|
8844
8974
|
const secret = await this.store.getSecret(userId);
|
|
8845
8975
|
return secret ? this.totp.verify(secret, code) : false;
|
|
8846
8976
|
}
|
|
8977
|
+
/**
|
|
8978
|
+
* Whether MFA is enabled for a user (used to gate the login challenge).
|
|
8979
|
+
*
|
|
8980
|
+
* @param userId - The user.
|
|
8981
|
+
* @returns `true` when MFA is enabled.
|
|
8982
|
+
*/
|
|
8983
|
+
async isEnabled(userId) {
|
|
8984
|
+
return this.store.isEnabled(userId);
|
|
8985
|
+
}
|
|
8847
8986
|
/**
|
|
8848
8987
|
* Disable MFA after verifying a code.
|
|
8849
8988
|
*
|
|
@@ -9091,6 +9230,10 @@ function makeAuthRouter(options) {
|
|
|
9091
9230
|
}
|
|
9092
9231
|
if (options.mfa) {
|
|
9093
9232
|
const mfa = options.mfa;
|
|
9233
|
+
router.post(`${prefix}/mfa/challenge`, async (req, res) => {
|
|
9234
|
+
const { mfaToken, code } = mfaChallengeSchema.parse(req.body);
|
|
9235
|
+
res.json(await service.verifyMfaChallenge(mfaToken, code));
|
|
9236
|
+
});
|
|
9094
9237
|
const requireUser = (req) => {
|
|
9095
9238
|
const claims = getAuth(req);
|
|
9096
9239
|
if (!claims || typeof claims.sub !== "string") {
|
|
@@ -9317,6 +9460,16 @@ function makeHealthRouter(options = {}) {
|
|
|
9317
9460
|
});
|
|
9318
9461
|
return router;
|
|
9319
9462
|
}
|
|
9463
|
+
function makeMetricsRouter(options = {}) {
|
|
9464
|
+
const path = options.path ?? "/metrics";
|
|
9465
|
+
const router = express2.Router();
|
|
9466
|
+
if (options.guard) router.use(path, options.guard);
|
|
9467
|
+
router.get(path, async (_req, res) => {
|
|
9468
|
+
const gpus = options.includeGpu ? await MetricsUtils.gpus() : [];
|
|
9469
|
+
res.type("text/plain").send(MetricsUtils.toPrometheus(MetricsUtils.system(), gpus));
|
|
9470
|
+
});
|
|
9471
|
+
return router;
|
|
9472
|
+
}
|
|
9320
9473
|
var logger3 = new JSONLogger("tempest_express_sdk.api.server");
|
|
9321
9474
|
function corsMiddleware(origins) {
|
|
9322
9475
|
const allowAll = origins === "*";
|
|
@@ -9383,7 +9536,7 @@ function runServer(app, options = {}) {
|
|
|
9383
9536
|
}
|
|
9384
9537
|
|
|
9385
9538
|
// src/version.ts
|
|
9386
|
-
var VERSION = "0.
|
|
9539
|
+
var VERSION = "0.10.0";
|
|
9387
9540
|
|
|
9388
9541
|
Object.defineProperty(exports, "OpenAPIRegistry", {
|
|
9389
9542
|
enumerable: true,
|
|
@@ -9551,6 +9704,7 @@ exports.CircuitOpenError = CircuitOpenError;
|
|
|
9551
9704
|
exports.CompositeFeatureFlagBackend = CompositeFeatureFlagBackend;
|
|
9552
9705
|
exports.ConflictException = ConflictException;
|
|
9553
9706
|
exports.DEFAULT_LOCALE = DEFAULT_LOCALE;
|
|
9707
|
+
exports.EmailProvider = EmailProvider;
|
|
9554
9708
|
exports.EmailUtils = EmailUtils;
|
|
9555
9709
|
exports.EnvFeatureFlagBackend = EnvFeatureFlagBackend;
|
|
9556
9710
|
exports.EventStream = EventStream;
|
|
@@ -9654,10 +9808,12 @@ exports.makeAuthRouter = makeAuthRouter;
|
|
|
9654
9808
|
exports.makeFlagGuard = makeFlagGuard;
|
|
9655
9809
|
exports.makeHealthRouter = makeHealthRouter;
|
|
9656
9810
|
exports.makeJwtAuthMiddleware = makeJwtAuthMiddleware;
|
|
9811
|
+
exports.makeMetricsRouter = makeMetricsRouter;
|
|
9657
9812
|
exports.makeSessionMiddleware = makeSessionMiddleware;
|
|
9658
9813
|
exports.makeTwilioWebhookRouter = makeTwilioWebhookRouter;
|
|
9659
9814
|
exports.makeUnhandledExceptionHandler = makeUnhandledExceptionHandler;
|
|
9660
9815
|
exports.makeWhatsAppWebhookRouter = makeWhatsAppWebhookRouter;
|
|
9816
|
+
exports.mfaChallengeSchema = mfaChallengeSchema;
|
|
9661
9817
|
exports.mfaCodeSchema = mfaCodeSchema;
|
|
9662
9818
|
exports.mfaEnrollResponseSchema = mfaEnrollResponseSchema;
|
|
9663
9819
|
exports.modifyDict = modifyDict;
|