tempest-express-sdk 0.30.0 → 0.32.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.30.0",
3
+ "version": "0.32.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",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/schemas/base.ts","../src/schemas/fields.ts","../src/settings/base.ts","../src/utils/password.ts","../src/version.ts"],"names":[],"mappings":"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","file":"chunk-DK46733U.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.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 * Ready-made, validated Zod field types, mirroring `utils.fields`.\n *\n * Reusable building blocks for DTOs so you don't re-derive the same constraint\n * everywhere: money in cents, a price string, a percentage, a latitude, a slug,\n * a hex color. Compose them into schemas with `z.object({ price: priceField })`.\n */\n\nimport { z } from \"@/schemas/base\";\n\n// --- Integers ---------------------------------------------------------------\n\n/** A strictly positive integer (`> 0`). */\nexport const positiveIntField = z.number().int().positive();\n/** A non-negative integer (`>= 0`). */\nexport const nonNegativeIntField = z.number().int().min(0);\n/** A monetary amount in the smallest unit (cents), `>= 0`. Avoids float drift. */\nexport const centsField = z.number().int().min(0);\n/** A TCP port (`1..65535`). */\nexport const portField = z.number().int().min(1).max(65535);\n/** A 0–5 star rating. */\nexport const ratingField = z.number().int().min(0).max(5);\n\n// --- Floats -----------------------------------------------------------------\n\n/** A strictly positive float (`> 0`). */\nexport const positiveFloatField = z.number().positive();\n/** A non-negative float (`>= 0`). */\nexport const nonNegativeFloatField = z.number().min(0);\n/** A percentage (`0..100`). */\nexport const percentField = z.number().min(0).max(100);\n/** A ratio (`0..1`). */\nexport const ratioField = z.number().min(0).max(1);\n/** A WGS-84 latitude (`-90..90`). */\nexport const latitudeField = z.number().min(-90).max(90);\n/** A WGS-84 longitude (`-180..180`). */\nexport const longitudeField = z.number().min(-180).max(180);\n\n// --- Strings ----------------------------------------------------------------\n\n/** A non-empty string; whitespace is trimmed before the length check. */\nexport const nonEmptyStrField = z\n .string()\n .transform((value) => value.trim())\n .pipe(z.string().min(1));\n\n/** A URL slug: lowercase alphanumerics separated by single hyphens. */\nexport const slugField = z.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, {\n message: \"Must be a lowercase hyphen-separated slug.\",\n});\n\n/** A hex color: `#rgb` or `#rrggbb`. */\nexport const hexColorField = z.string().regex(/^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/, {\n message: \"Must be a #rgb or #rrggbb hex color.\",\n});\n\n/**\n * A money amount as an exact decimal **string** with up to two decimal places\n * (e.g. `\"19.90\"`). Mirrors `PriceField` — `tempest-db-js` `numeric` columns map\n * to strings, so money stays exact instead of drifting through a float.\n */\nexport const priceField = z.string().regex(/^\\d+(?:\\.\\d{1,2})?$/, {\n message: 'Must be a decimal money string, e.g. \"19.90\".',\n});\n\n// --- Booleans ---------------------------------------------------------------\n\n/** Message shown when a value is neither a boolean nor a boolean-ish token. */\nconst LOOSE_BOOLEAN_ERROR =\n 'Must be a boolean value such as \"true\", \"false\", \"1\", \"0\", \"yes\" or \"no\".';\n\n/**\n * A boolean read from a **textual** source — a query string or an environment\n * variable, where every value arrives as a string.\n *\n * `z.coerce.boolean()` is the wrong tool for those: it is `Boolean(input)`, so\n * every non-empty string is `true` — `\"false\"` and `\"0\"` included — and there is\n * no way to ask for `false` over the wire. This reads the usual textual tokens\n * in both directions (`true`/`1`/`yes`/`on`/`y`/`enabled` and\n * `false`/`0`/`no`/`off`/`n`/`disabled`, case-insensitive, surrounding\n * whitespace trimmed) and **rejects** anything else, so a typo surfaces as a\n * validation error instead of silently becoming `false`.\n *\n * An absent value — and an empty or whitespace-only string, which is how an\n * unset `.env` entry (`DEBUG=`) reaches the schema — falls back to\n * `defaultValue`. Real booleans pass through untouched, so a schema built for\n * `process.env` still parses a synthetic object in tests.\n *\n * The OpenAPI metadata is pinned to `type: boolean` (with the default) so the\n * document describes the field the client actually sends, not the union used to\n * parse it.\n *\n * @param defaultValue - The value used when the field is absent or empty.\n * @returns A zod schema producing a `boolean`.\n */\nexport function looseBoolean(defaultValue: boolean) {\n return z\n .preprocess(\n (value) => (typeof value === \"string\" ? value.trim() || undefined : value),\n z\n .union([z.boolean(), z.stringbool()], { error: LOOSE_BOOLEAN_ERROR })\n .default(defaultValue),\n )\n .openapi({ type: \"boolean\", default: defaultValue });\n}\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\";\nimport { looseBoolean } from \"@/schemas/fields\";\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: looseBoolean(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.ZodType>(\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.30.0\";\n"]}