From a79d39ed2e9598d901cbb7ae3a9b9c390358b6b9 Mon Sep 17 00:00:00 2001 From: rdlabo Date: Sat, 15 Aug 2026 21:06:58 +0900 Subject: [PATCH] refactor(db): remove decimal number helper --- README.md | 5 ++- src/db/columns.ts | 9 +----- src/db/decimal.spec.ts | 37 ---------------------- src/db/decimal.ts | 71 ------------------------------------------ src/db/index.ts | 5 +-- 5 files changed, 4 insertions(+), 123 deletions(-) delete mode 100644 src/db/decimal.spec.ts delete mode 100644 src/db/decimal.ts diff --git a/README.md b/README.md index 2ef7148..7c449b0 100644 --- a/README.md +++ b/README.md @@ -137,14 +137,13 @@ Requires the `drizzle-orm` and `mysql2` peers. Reads run against a replica via r | `insertIdOf` / `affectedRowsOf` / `insertedIdsOf` / `DzWriteResult` | Extract `insertId` / `affectedRows` (and derive contiguous bulk-insert ids) from a mysql2 write result. | | `toJstDate` / `jstTimestampParams` / `jstDatetimeParams` / `jstDateParams` | JST date/time normalization params (advanced use). | | `MYSQL_TIMEZONE` | Default mysql2 connection `timezone` (`'+09:00'`) for the JST DB deployment. | -| `jstTimestamp` / `jstDatetime` / `jstDate` / `decimalNumber` | Drizzle column helpers (no repo-side wrapper needed). | +| `jstTimestamp` / `jstDatetime` / `jstDate` | Drizzle column helpers (no repo-side wrapper needed). | | `jstOnUpdateNow` | SQL expression for `ON UPDATE CURRENT_TIMESTAMP`. The `jstTimestamp` customType (and friends) do not support `.onUpdateNow()`, so pair it with `.$onUpdateFn(() => jstOnUpdateNow(fsp))`. | -| `coerceDecimalNumber` / `decimalNumberParams` | DECIMAL normalization params (the `decimalNumber` column helper is usually enough). | | `DRIZZLE_ORM_OPTIONS` / `honoDrizzleConfig(options)` / `HonoDrizzleConfigOptions` | Shared Drizzle casing (`snake_case`) for both the runtime `drizzle()` call and `drizzle.config.ts`, keeping config ↔ runtime in sync. | | `resolveDbSecret()` / `ResolvedDbSecret` | Resolve DB connection info from the `DB_SECRET` env var (an AWS RDS managed-secret JSON string) for CI migrate / local tooling. Returns `undefined` when `DB_SECRET` is unset; throws on invalid JSON or a missing required key. | | `baselineMigrations(options)` / `readBaselineEntry(migrationsFolder)` / `BaselineMigrationsOptions` / `BaselineResult` / `BaselineEntry` | Brownfield first-deploy helper: mark an existing `0000_*` migration as applied without re-running DDL. | -#### Drizzle column helpers (`jstTimestamp` / `decimalNumber`, etc.) +#### Drizzle column helpers (`jstTimestamp` / `jstDatetime` / `jstDate`, etc.) - `drizzle-orm` is a **peer** only. The kit does not include `drizzle-orm` as a dependency (even after publishing, it uses the consumer's single copy). - The consumer just keeps `drizzle-orm` in its `dependencies` as usual. **No `overrides` in `package.json` are needed.** diff --git a/src/db/columns.ts b/src/db/columns.ts index af74c3d..a8f6a2b 100644 --- a/src/db/columns.ts +++ b/src/db/columns.ts @@ -5,8 +5,7 @@ * @remarks * `drizzle-orm` is a **peer** (the consumer resolves a single copy); the kit does not bundle it. The * return types are the `customType` inference as-is (`MySqlCustomColumnBuilder<…>`) with no `any`, so - * the column's semantic type (`string | Date`, `number | null`, etc.) propagates to the consumer - * table's `$inferSelect`. + * the column's semantic type (`string | Date`, etc.) propagates to the consumer table's `$inferSelect`. * * **Precondition (a single drizzle copy)**: Drizzle's `SQL` is a **nominal** type carrying a private * field `shouldInlineParams`, so if the kit and the consumer resolve different copies of drizzle, @@ -23,8 +22,6 @@ */ import { sql } from 'drizzle-orm'; import { customType } from 'drizzle-orm/mysql-core'; -import { decimalNumberParams } from './decimal.js'; -import type { DecimalNumberConfig } from './decimal.js'; import { jstDateParams, jstDatetimeParams, jstTimestampParams } from './jst.js'; /** @@ -47,7 +44,3 @@ export const jstDatetime = (name: string, opts?: { fsp?: number }) => /** MySQL `date` — on INSERT/UPDATE, normalizes ISO / empty strings to `YYYY-MM-DD` (via `toDriver`). */ export const jstDate = (name: string) => customType<{ data: string | null; driverData: string | null }>(jstDateParams())(name); - -/** MySQL `decimal` — SELECT coerces string→number via `fromDriver`; writes bind the number as-is. */ -export const decimalNumber = (name: string, config: DecimalNumberConfig) => - customType<{ data: number | null; driverData: number | string | null }>(decimalNumberParams(config))(name); diff --git a/src/db/decimal.spec.ts b/src/db/decimal.spec.ts deleted file mode 100644 index c2b6b21..0000000 --- a/src/db/decimal.spec.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { describe, expect, it } from 'vitest'; -import { coerceDecimalNumber, decimalNumberParams } from './decimal.js'; - -describe('coerceDecimalNumber', () => { - it('number をそのまま返す(0 を含む)', () => { - expect(coerceDecimalNumber(0)).toBe(0); - expect(coerceDecimalNumber(100.5)).toBe(100.5); - }); - - it('文字列 DECIMAL を number に変換する', () => { - expect(coerceDecimalNumber('0')).toBe(0); - expect(coerceDecimalNumber('100.00')).toBe(100); - expect(coerceDecimalNumber(' 42.5 ')).toBe(42.5); - }); - - it('null / undefined / 空文字は null', () => { - expect(coerceDecimalNumber(null)).toBeNull(); - expect(coerceDecimalNumber(undefined)).toBeNull(); - expect(coerceDecimalNumber('')).toBeNull(); - expect(coerceDecimalNumber(' ')).toBeNull(); - }); -}); - -describe('decimalNumberParams', () => { - const params = decimalNumberParams({ precision: 10, scale: 2 }); - - it('dataType は precision/scale を含む', () => { - expect(params.dataType()).toBe('decimal(10,2)'); - }); - - it('fromDriver / toDriver が読書を number 域に揃える', () => { - expect(params.fromDriver('0')).toBe(0); - expect(params.fromDriver(12.34)).toBe(12.34); - expect(params.toDriver(0)).toBe(0); - expect(params.toDriver('99.9')).toBe(99.9); - }); -}); diff --git a/src/db/decimal.ts b/src/db/decimal.ts deleted file mode 100644 index e16aa7b..0000000 --- a/src/db/decimal.ts +++ /dev/null @@ -1,71 +0,0 @@ -/** - * Drizzle `customType` params for a MySQL `DECIMAL` column. - * - * @remarks - * - **Reads (SELECT)**: `fromDriver` unifies the driver value (`number` / `string` / `null`) to a JS - * `number | null`. Combined with the connection's `decimalNumbers: true` - * ({@link hyperdriveConnectionOptions} default), it aligns values to numbers even on the Drizzle - * builder path when strings like `"0"` / `"100.00"` slip in, without dropping `0`. - * - **Writes (INSERT/UPDATE)**: `toDriver` binds the number to mysql2 as-is (no `String()` conversion). - * - Raw-SQL `db.read` relies on the connection's `decimalNumbers: true`; the column's `fromDriver` is - * for the Drizzle `select` path. - */ - -export interface DecimalNumberConfig { - precision: number; - scale: number; -} - -/** - * Normalize a DECIMAL value coming from mysql2 / Drizzle to a JS `number | null`. - * `0` is preserved as-is so it is not dropped as falsy. - * - * @param value - the raw driver value (`number` / `string` / `bigint` / nullish). - * @returns the coerced finite number, or `null` when it cannot be resolved. - */ -export function coerceDecimalNumber(value: unknown): number | null { - if (value === null || value === undefined) { - return null; - } - if (typeof value === 'number') { - return Number.isFinite(value) ? value : null; - } - if (typeof value === 'string') { - const trimmed = value.trim(); - if (trimmed === '') { - return null; - } - const n = Number(trimmed); - return Number.isFinite(n) ? n : null; - } - if (typeof value === 'bigint') { - return Number(value); - } - return null; -} - -/** - * Params for a `customType`. For advanced use; the {@link decimalNumber} column helper is usually enough. - * - * @param config - the DECIMAL `precision` / `scale`. - * @returns the `customType` params (`dataType` / `fromDriver` / `toDriver`). - */ -export const decimalNumberParams = ( - config: DecimalNumberConfig, -): { - dataType: () => string; - fromDriver: (value: unknown) => number | null; - toDriver: (value: number | string | null) => number | string | null; -} => ({ - dataType: () => `decimal(${config.precision},${config.scale})`, - fromDriver: (value: unknown) => coerceDecimalNumber(value), - toDriver: (value: number | string | null) => { - if (value === null) { - return null; - } - if (typeof value === 'number') { - return value; - } - return coerceDecimalNumber(value); - }, -}); diff --git a/src/db/index.ts b/src/db/index.ts index 4f93773..e4a34a6 100644 --- a/src/db/index.ts +++ b/src/db/index.ts @@ -32,10 +32,7 @@ export type { HyperdriveLike, ExecutionContextLike } from './connection.js'; export { MYSQL_TIMEZONE, toJstDate, jstTimestampParams, jstDatetimeParams, jstDateParams } from './jst.js'; -export { coerceDecimalNumber, decimalNumberParams } from './decimal.js'; -export type { DecimalNumberConfig } from './decimal.js'; - -export { jstTimestamp, jstDatetime, jstDate, decimalNumber, jstOnUpdateNow } from './columns.js'; +export { jstTimestamp, jstDatetime, jstDate, jstOnUpdateNow } from './columns.js'; export { DRIZZLE_ORM_OPTIONS, honoDrizzleConfig, resolveDbSecret } from './orm-config.js'; export type { HonoDrizzleConfigOptions, ResolvedDbSecret } from './orm-config.js';