tempest-express-sdk 0.20.1 → 0.22.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tempest-express-sdk",
3
- "version": "0.20.1",
3
+ "version": "0.22.0",
4
4
  "description": "Shared Express/Zod/tempest-db-js building blocks: base schemas, repository, exceptions, pagination, settings and native Swagger + Redoc — the conventions used across Tempest projects, ported from tempest-fastapi-sdk.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -54,10 +54,9 @@
54
54
  "docs:build": "mkdocs build --strict"
55
55
  },
56
56
  "dependencies": {
57
- "@asteasolutions/zod-to-openapi": "^7.3.0",
57
+ "@asteasolutions/zod-to-openapi": "^9.1.0",
58
58
  "express": "^5.1.0",
59
- "swagger-ui-dist": "^5.18.2",
60
- "zod": "^3.24.1"
59
+ "swagger-ui-dist": "^5.18.2"
61
60
  },
62
61
  "peerDependencies": {
63
62
  "amqplib": "^0.10.0",
@@ -67,7 +66,8 @@
67
66
  "nodemailer": "^6.9.0",
68
67
  "tempest-db-js": ">=0.4.0",
69
68
  "web-push": "^3.6.0",
70
- "ws": "^8.0.0"
69
+ "ws": "^8.0.0",
70
+ "zod": "^4.0.0"
71
71
  },
72
72
  "peerDependenciesMeta": {
73
73
  "amqplib": {
@@ -99,8 +99,8 @@
99
99
  "@types/express": "^5.0.0",
100
100
  "@types/jsonwebtoken": "^9.0.7",
101
101
  "@types/node": "^22.10.0",
102
- "@types/swagger-ui-dist": "^3.30.6",
103
102
  "@types/nodemailer": "^6.4.16",
103
+ "@types/swagger-ui-dist": "^3.30.6",
104
104
  "@types/web-push": "^3.6.4",
105
105
  "@types/ws": "^8.5.12",
106
106
  "amqplib": "^0.10.4",
@@ -109,10 +109,11 @@
109
109
  "nodemailer": "^6.9.16",
110
110
  "tempest-db-js": "^0.4.0",
111
111
  "tsup": "^8.3.0",
112
- "ws": "^8.18.0",
113
- "web-push": "^3.6.7",
114
112
  "tsx": "^4.19.2",
115
113
  "typescript": "^5.7.0",
116
- "vitest": "^2.1.9"
114
+ "vitest": "^2.1.9",
115
+ "web-push": "^3.6.7",
116
+ "ws": "^8.18.0",
117
+ "zod": "^4.5.4"
117
118
  }
118
119
  }
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/schemas/base.ts","../src/settings/base.ts","../src/utils/password.ts","../src/version.ts"],"names":[],"mappings":"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","file":"chunk-XGKAZZR2.js","sourcesContent":["/**\n * Zod foundation shared by every DTO, mirroring `schemas.base.BaseSchema`.\n *\n * Pydantic uses class inheritance for shared config; Zod composes instead. This\n * module re-exports a `z` already augmented with `.openapi()` (so every schema\n * can carry OpenAPI metadata) and a {@link toDict} helper matching\n * `BaseSchema.to_dict` (drop nullish, exclude keys, merge extras).\n */\n\nimport { extendZodWithOpenApi } from \"@asteasolutions/zod-to-openapi\";\nimport { z } from \"zod\";\n\n// Augment the shared `z` instance with `.openapi(...)`. Idempotent — calling it\n// more than once across modules is safe.\nextendZodWithOpenApi(z);\n\nexport { z };\n\n/** Options for {@link toDict}. */\nexport interface ToDictOptions {\n /** Field names to drop from the output. */\n exclude?: string[];\n /** Extra entries merged on top (override existing keys). */\n include?: Record<string, unknown>;\n}\n\n/**\n * Serialize a validated object to a plain record, dropping `null`/`undefined`,\n * removing `exclude`d keys and merging `include` on top.\n *\n * @param data - The source object (typically a parsed schema).\n * @param options - Keys to exclude and entries to merge in.\n * @returns The cleaned record.\n */\nexport function toDict(\n data: Record<string, unknown>,\n options: ToDictOptions = {},\n): Record<string, unknown> {\n const exclude = new Set(options.exclude ?? []);\n const out: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(data)) {\n if (exclude.has(key)) continue;\n if (value === null || value === undefined) continue;\n out[key] = value;\n }\n return { ...out, ...(options.include ?? {}) };\n}\n\n/**\n * Response schema fields every ORM record carries (`id`, `isActive`,\n * `createdAt`, `updatedAt`). Mirrors `BaseResponseSchema`. Extend it with\n * `baseResponseSchema.extend({ ... })` to build concrete `*ResponseSchema`s.\n */\nexport const baseResponseSchema = z.object({\n id: z.string().uuid().openapi({ description: \"The unique identifier of the record.\" }),\n isActive: z\n .boolean()\n .openapi({ description: \"Whether the record is active (soft-delete flag).\" }),\n createdAt: z.coerce\n .date()\n .openapi({ description: \"The creation timestamp of the record (UTC).\" }),\n updatedAt: z.coerce\n .date()\n .openapi({ description: \"The last update timestamp of the record (UTC).\" }),\n});\n\n/** The inferred TS type of a {@link baseResponseSchema} payload. */\nexport type BaseResponse = z.infer<typeof baseResponseSchema>;\n","/**\n * Environment-driven settings, mirroring `settings.base.BaseAppSettings`.\n *\n * Pydantic-settings reads env vars into a frozen, validated model. Here a zod\n * schema plays the same role: {@link loadSettings} parses `process.env` (env\n * names are matched case-sensitively, like the FastAPI SDK) and returns a\n * frozen, fully-typed object. Composable fragments ({@link serverSettingsShape}\n * etc.) cover the common server/database/CORS knobs.\n */\n\nimport { z } from \"@/schemas/base\";\n\n/** Server bind/runtime settings. Defaults bind to localhost. */\nexport const serverSettingsShape = {\n HOST: z.string().default(\"127.0.0.1\"),\n PORT: z.coerce.number().int().min(0).max(65535).default(8000),\n DEBUG: z.coerce.boolean().default(false),\n} as const;\n\n/** Database connection settings. */\nexport const databaseSettingsShape = {\n DATABASE_URL: z.string().default(\"sqlite://./app.db\"),\n} as const;\n\n/** CORS settings. `CORS_ORIGINS` is a comma-separated list. */\nexport const corsSettingsShape = {\n CORS_ORIGINS: z\n .string()\n .default(\"*\")\n .transform((value) =>\n value\n .split(\",\")\n .map((origin) => origin.trim())\n .filter((origin) => origin.length > 0),\n ),\n} as const;\n\n/** The combined base settings shape (server + database + CORS). */\nexport const baseAppSettingsShape = {\n ...serverSettingsShape,\n ...databaseSettingsShape,\n ...corsSettingsShape,\n} as const;\n\n/** A zod object built from {@link baseAppSettingsShape}. */\nexport const baseAppSettingsSchema = z.object(baseAppSettingsShape);\n\n/** The parsed shape of {@link baseAppSettingsSchema}. */\nexport type BaseAppSettings = z.infer<typeof baseAppSettingsSchema>;\n\n/**\n * Parse and freeze settings from an environment source.\n *\n * Extend the base shape with project fields:\n *\n * ```ts\n * const settings = loadSettings(\n * z.object({ ...baseAppSettingsShape, JWT_SECRET: z.string() }),\n * );\n * ```\n *\n * @param schema - The settings zod schema.\n * @param env - The environment source (defaults to `process.env`).\n * @returns The validated, frozen settings object.\n * @throws {z.ZodError} When required env vars are missing or malformed.\n */\nexport function loadSettings<S extends z.ZodTypeAny>(\n schema: S,\n env: NodeJS.ProcessEnv = process.env,\n): Readonly<z.infer<S>> {\n return Object.freeze(schema.parse(env));\n}\n","/**\n * Password hashing backed by bcrypt, mirroring `utils.password.PasswordUtils`.\n *\n * `bcryptjs` is an optional peer dependency, imported lazily so that\n * `import \"tempest-express-sdk\"` keeps working when it is not installed — the\n * clear error is deferred to first use. Methods are async (the idiomatic Node\n * bcrypt surface).\n */\n\ntype BcryptModule = typeof import(\"bcryptjs\");\n\nlet cached: BcryptModule | null = null;\n\n/** Lazily load `bcryptjs`, with a clear install hint when missing. */\nasync function loadBcrypt(): Promise<BcryptModule> {\n if (cached) return cached;\n try {\n const mod = (await import(\"bcryptjs\")) as BcryptModule & { default?: BcryptModule };\n cached = mod.default ?? mod;\n } catch (cause) {\n throw new Error(\n \"PasswordUtils requires the 'bcryptjs' peer dependency. Install with `npm i bcryptjs`.\",\n { cause },\n );\n }\n return cached;\n}\n\n/** Hash and verify passwords using bcrypt. Stateless — construct once, reuse. */\nexport class PasswordUtils {\n /**\n * @param rounds - The bcrypt cost factor. Higher is slower and harder to\n * brute-force. Defaults to 12.\n */\n constructor(private readonly rounds: number = 12) {}\n\n /**\n * Hash a plaintext password.\n *\n * @param plain - The plaintext password.\n * @returns The bcrypt hash, ready to persist.\n */\n async hash(plain: string): Promise<string> {\n const bcrypt = await loadBcrypt();\n const salt = await bcrypt.genSalt(this.rounds);\n return bcrypt.hash(plain, salt);\n }\n\n /**\n * Verify a plaintext password against a stored hash. Returns `false` for\n * malformed hashes rather than throwing.\n *\n * @param plain - The plaintext password to verify.\n * @param hashed - The previously stored bcrypt hash.\n * @returns `true` when the password matches.\n */\n async verify(plain: string, hashed: string): Promise<boolean> {\n try {\n const bcrypt = await loadBcrypt();\n return await bcrypt.compare(plain, hashed);\n } catch {\n return false;\n }\n }\n}\n","/** The installed SDK version. Single source of truth for the barrel + CLI. */\nexport const VERSION = \"0.20.1\";\n"]}