@rdlabo/workers-hono-kit 0.4.2 → 0.5.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 +135 -25
- package/dist/business-time/index.d.ts +98 -21
- package/dist/business-time/index.js +99 -22
- package/dist/business-time/types.d.ts +3 -3
- package/dist/business-time/types.js +1 -1
- package/dist/db/columns.d.ts +8 -6
- package/dist/db/columns.js +25 -19
- package/dist/db/decimal.d.ts +17 -9
- package/dist/db/decimal.js +17 -9
- package/dist/db/jst.d.ts +11 -6
- package/dist/db/jst.js +11 -6
- package/dist/db/migrate.d.ts +21 -21
- package/dist/db/migrate.js +34 -29
- package/dist/db/orm-config.d.ts +10 -6
- package/dist/db/orm-config.js +15 -11
- package/dist/http/mysql-driver-error.d.ts +25 -0
- package/dist/http/mysql-driver-error.js +35 -0
- package/dist/http/nest-error.js +7 -1
- package/dist/http/query-failed-error.d.ts +29 -0
- package/dist/http/query-failed-error.js +43 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +3 -0
- package/dist/middleware/perf-log.d.ts +77 -0
- package/dist/middleware/perf-log.js +97 -0
- package/package.json +1 -1
- package/scripts/db-baseline.mjs +10 -9
package/README.md
CHANGED
|
@@ -35,13 +35,13 @@ npm install ai ai-gateway-provider # createAiGatewayProvider
|
|
|
35
35
|
> **Compiled ESM, with types.** The package is published as compiled ES modules (`./dist/*.js`) plus
|
|
36
36
|
> declaration files (`./dist/*.d.ts`) via the `exports` field. It depends only on Web-standard APIs
|
|
37
37
|
> (`fetch`, `crypto.subtle`, `Response`) available on Cloudflare Workers (`workerd`) and other edge
|
|
38
|
-
> runtimes, and requires Node.js ≥ 20 for tooling.
|
|
38
|
+
> runtimes, and requires Node.js ≥ 20 for tooling. Four entry points are exposed:
|
|
39
39
|
>
|
|
40
40
|
> | Subpath | Import | Use |
|
|
41
41
|
> | --- | --- | --- |
|
|
42
42
|
> | `.` | `@rdlabo/workers-hono-kit` | Web-standard helpers (middleware, HTTP, Firebase, AWS, AI, Stripe, KV). |
|
|
43
43
|
> | `./db` | `@rdlabo/workers-hono-kit/db` | MySQL data layer (mysql2 + Drizzle). |
|
|
44
|
-
> | `./business-time` | `@rdlabo/workers-hono-kit/business-time` | JST
|
|
44
|
+
> | `./business-time` | `@rdlabo/workers-hono-kit/business-time` | JST business-time API (`toBusinessDateTime` / `normalizeBusinessDate` / `formatBusinessDateTime`, etc.). |
|
|
45
45
|
> | `./testing` | `@rdlabo/workers-hono-kit/testing` | Test helpers (mysql2 + Drizzle + fakes/fixtures). |
|
|
46
46
|
|
|
47
47
|
## API
|
|
@@ -65,16 +65,19 @@ npm install ai ai-gateway-provider # createAiGatewayProvider
|
|
|
65
65
|
| `getAppInfo(c)` / `AppInfo` | Read `x-amz-meta-version` / `x-amz-meta-uuid`. |
|
|
66
66
|
| `resolveAppEnv(env)` / `isProductionEnv(env)` / `AppEnv` | Resolve `'development'` / `'production'` from `env.APP_ENV` (defaults to `'production'` for safety). |
|
|
67
67
|
| `HttpStatus` | HTTP status enum identical to NestJS `@nestjs/common`. |
|
|
68
|
-
| `createNestErrorHandler(options?)` / `NestErrorHandlerOptions` | `app.onError()` handler that maps a thrown `HTTPException` to the NestJS exception-filter body (`{ statusCode, message, error? }`; `401` omits `error`). Configurable field order, reason phrases, error predicate, and unhandled-error report hook. |
|
|
68
|
+
| `createNestErrorHandler(options?)` / `NestErrorHandlerOptions` | `app.onError()` handler that maps a thrown `HTTPException` to the NestJS exception-filter body (`{ statusCode, message, error? }`; `401` omits `error`). Configurable field order, reason phrases, error predicate, and unhandled-error report hook. Unhandled errors log via `console.error` (mysql2 errors include `sqlMessage` / `errno` when detectable). |
|
|
69
|
+
| `createQueryFailedNestErrorHandler(options)` / `QueryFailedClassifier` / `ClassifiedDbError` | Compose a NestJS `QueryFailedExceptionFilter` analog **before** `createNestErrorHandler`: consumer supplies `classify(err)` (parity-critical messages stay in the app); classified DB errors log (`warn` for 400, `error` for 500) and trigger `onUnhandledError` on 500 only. |
|
|
70
|
+
| `findMysqlDriverError(err)` / `logMysqlDriverError(err, statusCode)` / `reportClassifiedDbError(...)` | Low-level mysql2 driver-error detection (follows `err.cause`), structured logging, and classify-path reporting helpers. |
|
|
69
71
|
| `nestNotFoundHandler(c)` | `app.notFound()` handler with the Express/Nest default `{ message: 'Cannot METHOD path', error, statusCode }` 404 body. |
|
|
70
72
|
| `normalizeTrailingSlash(request)` | Strip trailing slash(es) from the request URL before routing (Express/Nest parity). Does **not** 301-redirect — preserves POST/PUT/DELETE bodies. |
|
|
71
73
|
| `NEST_REASON_PHRASES` | `{ 400, 401, 403, 404 }` → NestJS reason phrases. |
|
|
72
74
|
| `createAuthMiddleware(options)` / `AuthMiddlewareOptions` | Factory for a Firebase-token auth middleware: reads the token header, verifies, resolves the DB user id, and stashes the result on the context. Omit `resolveUserId` for a token-only (login) guard. |
|
|
75
|
+
| `perfLog(options?)` / `PerfLogOptions` / `AnalyticsEngineDatasetLike` | Middleware that records one per-request latency data point (`t_app`, colo, cold/warm, route, status) and emits it to **Workers Logs** (`console.log`) and/or **Workers Analytics Engine** (`writeDataPoint`). Lets you measure low-traffic Workers without a live `wrangler tail`. |
|
|
73
76
|
| `ErrorReporter` / `ErrorReportContext` | Types for a `reportError`-style unhandled-error reporter (e.g. wired to Sentry), paired with `createNestErrorHandler`'s `onUnhandledError`. |
|
|
74
77
|
| `createAiGatewayProvider(config)` / `AiGatewayConfig` / `AiGatewayProvider` | Route `@ai-sdk` models through the Cloudflare AI Gateway, via either a Workers `AI` binding or REST credentials (`accountId` / `gateway` / `token`). |
|
|
75
78
|
| `KVCache` / `KVNamespace` / `KVCacheOptions` | Workers-KV cache-aside helper (key `appName+version+table_type_column`, sha256 for string ids, TTL clamped ≥60s). Set `appName` / `version` per application. |
|
|
76
79
|
| `createStripeClient(secret, opts?)` / `verifyStripeWebhook(...)` / `CreateStripeClientOptions` | Workers-native Stripe client (fetch transport) + async webhook verification (SubtleCrypto). `apiVersion` optional (pin to a fixed Stripe API version). |
|
|
77
|
-
| `sendInChunks(queue, messages,
|
|
80
|
+
| `sendInChunks(queue, messages, options?)` / `QueueLike` / `QueueSendMessage` | Send queue messages in bounded chunks to stay under the Workers subrequest cap per invocation. `options.chunkSize` sets the per-batch size (defaults to and is capped at 100). |
|
|
78
81
|
| `processBatch(batch, handler, options?)` / `MessageBatchLike` / `QueueMessageLike` / `ProcessBatchOptions` / `ProcessBatchResult` | Process a queue batch with bounded concurrency (consumer-side counterpart to `sendInChunks`). |
|
|
79
82
|
| `ExecutionContextLike` | Minimal `waitUntil`-only Workers execution context shape (for `withMysqlConnections` in worker entry modules without importing `./db`). |
|
|
80
83
|
|
|
@@ -88,45 +91,83 @@ Requires the `drizzle-orm` and `mysql2` peers. Reads run against a replica via r
|
|
|
88
91
|
| `createMysqlDatabase(options)` | Assemble a `Database` from an already-connected Drizzle ORM + replica `QueryRunner`. |
|
|
89
92
|
| `databaseFrom(orm, replica)` | Build a `Database` from an existing Drizzle instance + replica handle. |
|
|
90
93
|
| `Database` / `DisposableDatabase` / `QueryRunner` / `TxOf` | The `read` / `write` / `transaction` API and its supporting types. |
|
|
91
|
-
| `hyperdriveConnectionOptions(hyperdrive, overrides?)` / `HyperdriveLike` / `ExecutionContextLike` | Build mysql2 `createConnection` options from a Hyperdrive binding (`disableEval`, `decimalNumbers`, `timezone '+09:00'` by default). |
|
|
94
|
+
| `hyperdriveConnectionOptions(hyperdrive, overrides?)` / `HyperdriveLike` / `ExecutionContextLike` | Build mysql2 `createConnection` options from a Hyperdrive binding (`disableEval`, `decimalNumbers`, `timezone '+09:00'` by default). `ExecutionContextLike` is the same type as the root export, re-exported here so `withMysqlConnections` callers don't need the root import. |
|
|
92
95
|
| `withMysqlConnections(...)` | Open primary/replica connections, run a function, close them in `finally` (via `ctx.waitUntil`). |
|
|
93
96
|
| `retryWhenDeadlock(fn, retries?, delay?)` | Same deadlock-retry helper as the root export. |
|
|
94
97
|
| `insertIdOf` / `affectedRowsOf` / `insertedIdsOf` / `DzWriteResult` | Extract `insertId` / `affectedRows` (and derive contiguous bulk-insert ids) from a mysql2 write result. |
|
|
95
|
-
| `toJstDate` / `jstTimestampParams` / `jstDatetimeParams` / `jstDateParams` | JST date/time normalization params
|
|
96
|
-
| `
|
|
97
|
-
| `
|
|
98
|
-
| `
|
|
98
|
+
| `toJstDate` / `jstTimestampParams` / `jstDatetimeParams` / `jstDateParams` | JST date/time normalization params (advanced use). |
|
|
99
|
+
| `MYSQL_TIMEZONE` | Default mysql2 connection `timezone` (`'+09:00'`) for the JST DB deployment. |
|
|
100
|
+
| `jstTimestamp` / `jstDatetime` / `jstDate` / `decimalNumber` | Drizzle column helpers (no repo-side wrapper needed). |
|
|
101
|
+
| `jstOnUpdateNow` | SQL expression for `ON UPDATE CURRENT_TIMESTAMP`. The `jstTimestamp` customType (and friends) do not support `.onUpdateNow()`, so pair it with `.$onUpdateFn(() => jstOnUpdateNow(fsp))`. |
|
|
102
|
+
| `coerceDecimalNumber` / `decimalNumberParams` | DECIMAL normalization params (the `decimalNumber` column helper is usually enough). |
|
|
99
103
|
| `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. |
|
|
100
|
-
| `resolveDbSecret(
|
|
104
|
+
| `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. |
|
|
101
105
|
| `baselineMigrations(options)` / `readBaselineEntry(migrationsFolder)` / `BaselineMigrationsOptions` / `BaselineResult` / `BaselineEntry` | Brownfield first-deploy helper: mark an existing `0000_*` migration as applied without re-running DDL. |
|
|
102
106
|
|
|
103
|
-
#### Drizzle
|
|
107
|
+
#### Drizzle column helpers (`jstTimestamp` / `decimalNumber`, etc.)
|
|
104
108
|
|
|
105
|
-
- `drizzle-orm`
|
|
106
|
-
- consumer
|
|
107
|
-
- npm
|
|
108
|
-
-
|
|
109
|
-
-
|
|
109
|
+
- `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).
|
|
110
|
+
- The consumer just keeps `drizzle-orm` in its `dependencies` as usual. **No `overrides` in `package.json` are needed.**
|
|
111
|
+
- The npm-published artifact contains no `devDependencies`, so installing it does not add a kit-specific `drizzle-orm` (there is only the one peer copy).
|
|
112
|
+
- The column helpers `import` the consumer's `drizzle-orm` at runtime, and the types are the `customType` inference as-is (`MySqlCustomColumnBuilder<…>`). No `any` is used, so the column's semantic type propagates to the consumer table's `$inferSelect`.
|
|
113
|
+
- **Precondition: resolve drizzle to a single copy.** Drizzle's `SQL` is a **nominal** type carrying a private field `shouldInlineParams`, so if the kit and the consumer resolve different copies, `jstTimestamp(…).default(sql\`…\`)` fails the whole schema with `TS2345 separate declarations of a private property 'shouldInlineParams'`. Under `file:`-link development, `drizzle-orm` nests under the kit and becomes a second copy, so **pin `drizzle-orm` to the consumer's own single copy in `tsconfig.json`**:
|
|
110
114
|
|
|
111
115
|
```jsonc
|
|
112
|
-
// tsconfig.json compilerOptions
|
|
116
|
+
// tsconfig.json compilerOptions (merge with existing paths if any)
|
|
113
117
|
"paths": {
|
|
114
118
|
"drizzle-orm": ["./node_modules/drizzle-orm"],
|
|
115
119
|
"drizzle-orm/*": ["./node_modules/drizzle-orm/*"]
|
|
116
120
|
}
|
|
117
121
|
```
|
|
118
122
|
|
|
119
|
-
`moduleResolution: "Bundler"
|
|
120
|
-
-
|
|
123
|
+
With `moduleResolution: "Bundler"`, `baseUrl` is not required (if `baseUrl` is already set, drop the leading `./`). On the published package (a single copy) these `paths` are harmless. **No `overrides` needed.**
|
|
124
|
+
- When developing against the kit via a direct `file:` link, run `npm install` in the kit repo itself to satisfy its peers (do not add `overrides` on the consumer side).
|
|
121
125
|
|
|
122
|
-
**`CURRENT_TIMESTAMP`
|
|
126
|
+
**`CURRENT_TIMESTAMP` vs the connection `timezone:'+09:00'`**
|
|
123
127
|
|
|
124
|
-
|
|
|
128
|
+
| Path | Who decides the time | Relationship to JST |
|
|
125
129
|
| --- | --- | --- |
|
|
126
|
-
|
|
|
127
|
-
| `DEFAULT CURRENT_TIMESTAMP` / `ON UPDATE CURRENT_TIMESTAMP` | MySQL
|
|
130
|
+
| The app binds a `Date` (INSERT/UPDATE) | mysql2 + connection `timezone:'+09:00'` | Treated as JST on the wire (`datetime-wire` test) |
|
|
131
|
+
| `DEFAULT CURRENT_TIMESTAMP` / `ON UPDATE CURRENT_TIMESTAMP` | The MySQL server (session `time_zone`) | A **separate path** from the connection option. JST if the RDS `time_zone` is `+09:00`, UTC if UTC |
|
|
128
132
|
|
|
129
|
-
`jstTimestamp` / `jstDatetime`
|
|
133
|
+
`jstTimestamp` / `jstDatetime` only handle read/write pass-through and DATE normalization; they do not change the timezone of server-side defaults. For columns that need `ON UPDATE`, keep the DDL intent with `.$onUpdateFn(() => jstOnUpdateNow(6))`.
|
|
134
|
+
|
|
135
|
+
### Business time — `@rdlabo/workers-hono-kit/business-time`
|
|
136
|
+
|
|
137
|
+
String-level JST business-time conversions (Workers UTC instant ↔ business calendar date / date-time),
|
|
138
|
+
with **no `mysql2` / `drizzle-orm` dependency**. This is a different layer from the `./db` column helpers
|
|
139
|
+
(which handle the MySQL wire format): the DB stays on JST, and the app handles JST explicitly through
|
|
140
|
+
this module instead of relying implicitly on the connection `timezone`.
|
|
141
|
+
|
|
142
|
+
| Export | Description |
|
|
143
|
+
| --- | --- |
|
|
144
|
+
| `today(ref?)` | The JST business calendar date (`YYYY-MM-DD`) of `ref` (defaults to now). |
|
|
145
|
+
| `toBusinessDate(instant)` | UTC instant → JST business calendar date (`YYYY-MM-DD`). |
|
|
146
|
+
| `normalizeBusinessDate(value)` | Normalize a `string` / `Date` / nullish to `YYYY-MM-DD`; a `YYYY-MM-DD` string passes through unchanged, nullish/empty/invalid → `null`. |
|
|
147
|
+
| `toBusinessDateTime(instant)` | UTC instant → JST business date-time (`YYYY-MM-DD HH:mm:ss`). |
|
|
148
|
+
| `parseBusinessDateTime(value)` | JST business date-time string → UTC instant (accepts a space or `T` separator). |
|
|
149
|
+
| `formatBusinessDateTime(instant, pattern?)` | Format an instant in the business TZ (Nest `helper.formatDate`-compatible tokens). |
|
|
150
|
+
| `startOfBusinessDay(date)` / `endOfBusinessDay(date)` | UTC instant of `00:00:00` / `23:59:59` on a JST business date. |
|
|
151
|
+
| `businessDateTimeInstant(date, time)` | JST business date + wall-clock time → UTC instant. |
|
|
152
|
+
| `addBusinessDays(date, days)` | Add calendar days to a JST business date. |
|
|
153
|
+
| `ageOnBusinessDate(birthDate, asOfDate?)` | Full years of age on a business date (`asOfDate` defaults to `today()`). |
|
|
154
|
+
| `DEFAULT_BUSINESS_DATETIME_PATTERN` | Default `formatBusinessDateTime` pattern (`YYYY-MM-DDThh:mm:ss`). |
|
|
155
|
+
| `BUSINESS_TIMEZONE` / `BusinessDate` / `BusinessDateTime` | JST timezone constant and the business-date / date-time string types. |
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
import {
|
|
159
|
+
toBusinessDate,
|
|
160
|
+
toBusinessDateTime,
|
|
161
|
+
formatBusinessDateTime,
|
|
162
|
+
addBusinessDays,
|
|
163
|
+
} from '@rdlabo/workers-hono-kit/business-time';
|
|
164
|
+
|
|
165
|
+
const now = new Date('2026-07-05T21:00:00Z');
|
|
166
|
+
toBusinessDate(now); // '2026-07-06' (JST)
|
|
167
|
+
toBusinessDateTime(now); // '2026-07-06 06:00:00'
|
|
168
|
+
formatBusinessDateTime(now); // '2026-07-06T06:00:00'
|
|
169
|
+
addBusinessDays('2026-07-06', 3); // '2026-07-09'
|
|
170
|
+
```
|
|
130
171
|
|
|
131
172
|
### Testing — `@rdlabo/workers-hono-kit/testing`
|
|
132
173
|
|
|
@@ -259,6 +300,24 @@ app.onError(
|
|
|
259
300
|
);
|
|
260
301
|
```
|
|
261
302
|
|
|
303
|
+
**Important:** `Sentry.withSentry` does **not** capture errors handled by `app.onError`. Wire
|
|
304
|
+
`onUnhandledError` → `Sentry.captureException` explicitly (mirrors Nest `SentryGlobalFilter`).
|
|
305
|
+
|
|
306
|
+
Repos with a Nest `QueryFailedExceptionFilter` (e.g. odss-mobile) should use
|
|
307
|
+
`createQueryFailedNestErrorHandler` so classified DB errors still log and report to Sentry:
|
|
308
|
+
|
|
309
|
+
```ts
|
|
310
|
+
import { createQueryFailedNestErrorHandler } from '@rdlabo/workers-hono-kit';
|
|
311
|
+
|
|
312
|
+
app.onError(
|
|
313
|
+
createQueryFailedNestErrorHandler({
|
|
314
|
+
fieldOrder: 'message-first',
|
|
315
|
+
classify: classifyQueryFailed, // app-local parity (Japanese messages, errno rules)
|
|
316
|
+
onUnhandledError: (err, c) => container.reportError?.(err, { requestId: c.get('requestId') }),
|
|
317
|
+
}),
|
|
318
|
+
);
|
|
319
|
+
```
|
|
320
|
+
|
|
262
321
|
### Auth middleware
|
|
263
322
|
|
|
264
323
|
Encodes the shared skeleton (read token header → verify → `getAppInfo` → resolve user id →
|
|
@@ -291,6 +350,47 @@ const tokenAuth = createAuthMiddleware<AppEnv, UserRecord>({
|
|
|
291
350
|
});
|
|
292
351
|
```
|
|
293
352
|
|
|
353
|
+
### Latency instrumentation (`perfLog`)
|
|
354
|
+
|
|
355
|
+
Records one data point per request — `t_app` (time inside the app), `colo`, `cold`/`warm`, matched
|
|
356
|
+
route, `status` — and ships it to **Workers Logs** and/or **Workers Analytics Engine**. This lets you
|
|
357
|
+
measure a low-traffic Worker after the fact (retained + queryable) instead of watching a live
|
|
358
|
+
`wrangler tail`. Register it first so it wraps everything.
|
|
359
|
+
|
|
360
|
+
```ts
|
|
361
|
+
import { perfLog } from '@rdlabo/workers-hono-kit';
|
|
362
|
+
|
|
363
|
+
// A) app served with env (`app.fetch(req, env, ctx)`): bare — reads `PERF` (Analytics Engine
|
|
364
|
+
// dataset binding) and `PERF_LOG === '1'` (Workers Logs) off `c.env`.
|
|
365
|
+
app.use('*', perfLog());
|
|
366
|
+
|
|
367
|
+
// B) app built without Hono env (`createApp(container).fetch(req)`): pass bindings explicitly.
|
|
368
|
+
app.use('*', perfLog({ console: env.PERF_LOG === '1', dataset: env.PERF }));
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
```toml
|
|
372
|
+
# wrangler.toml — dataset is created on first write (no provisioning); needs [observability] for Logs.
|
|
373
|
+
[[analytics_engine_datasets]]
|
|
374
|
+
binding = "PERF"
|
|
375
|
+
dataset = "myapp_perf"
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
Query percentiles by route/colo with the Analytics Engine SQL API:
|
|
379
|
+
|
|
380
|
+
```sql
|
|
381
|
+
SELECT blob1 AS path, blob2 AS colo,
|
|
382
|
+
quantileWeighted(0.5)(double1, _sample_interval) AS p50,
|
|
383
|
+
quantileWeighted(0.9)(double1, _sample_interval) AS p90
|
|
384
|
+
FROM myapp_perf WHERE timestamp > now() - INTERVAL '7' DAY
|
|
385
|
+
GROUP BY path, colo ORDER BY p90 DESC
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
> **Scope of `t_app`**: it covers everything *inside* the app; work done in `fetch` *before* the app
|
|
389
|
+
> (e.g. secrets fetch / DB connect in `createApp(container)` vs. building the container in `fetch`) is
|
|
390
|
+
> not comparable across differently-wired apps. Instrument the `fetch` seam if you need a secrets/connect
|
|
391
|
+
> cold breakdown. On production Workers `Date.now()` only advances at I/O boundaries, so `t_app` ≈ I/O
|
|
392
|
+
> wait, not CPU time.
|
|
393
|
+
|
|
294
394
|
### AI Gateway
|
|
295
395
|
|
|
296
396
|
Route `@ai-sdk` models through the Cloudflare AI Gateway — either with a Workers `AI` binding
|
|
@@ -363,7 +463,7 @@ await testDb.resetSchema();
|
|
|
363
463
|
const pool = testDb.createTestPool();
|
|
364
464
|
|
|
365
465
|
const firebase = new FakeFirebaseVerifier();
|
|
366
|
-
firebase.register('
|
|
466
|
+
firebase.register('token-1', { uid: 'uid-1', email: 'a@example.com' });
|
|
367
467
|
|
|
368
468
|
const gateway = configurableFake<PaymentGateway>({ charge: async () => ({ ok: true }) }, 'PaymentGateway');
|
|
369
469
|
```
|
|
@@ -387,6 +487,16 @@ If you consume this package via a local path (e.g. `"@rdlabo/workers-hono-kit":
|
|
|
387
487
|
|
|
388
488
|
When installed from npm normally, package managers dedupe `zod` to a single copy and this is not needed.
|
|
389
489
|
|
|
490
|
+
## CLI
|
|
491
|
+
|
|
492
|
+
The package ships three `bin` commands (run via `npx` or an npm script in the consuming app):
|
|
493
|
+
|
|
494
|
+
| Command | Use |
|
|
495
|
+
| --- | --- |
|
|
496
|
+
| `workers-hono-kit-sync-dev-aws <wrangler-args…>` | Launch `wrangler` with AWS credentials injected as `--var`, resolved from the active AWS profile (honors `AWS_PROFILE`, supports short-lived SSO/temporary creds). Nothing is written to disk — replaces `.dev.vars`. Wire it as the `dev` script, e.g. `AWS_PROFILE=<p> workers-hono-kit-sync-dev-aws dev --var APP_ENV:development`. |
|
|
497
|
+
| `workers-hono-kit-check-subrequest-fanout [dir…]` | CI gate that greps for per-item external-call fan-outs (`runWithConcurrency(` / `PromisePool` / `.withConcurrency(`) that would eventually exceed the Workers subrequest cap. Annotate a genuinely-safe site with `subrequest-ok`. Scans `src` by default; exits 1 on an un-annotated marker. |
|
|
498
|
+
| `workers-hono-kit-db-baseline [--migrations ./drizzle]` | Brownfield first-deploy helper: record the baseline `0000` migration as *already applied* on an existing MySQL DB without running its DDL (the CLI wrapper around `baselineMigrations` / `readBaselineEntry`). Reads DB credentials from `DB_SECRET` (AWS RDS managed secret) or the individual `DB_*` env vars. |
|
|
499
|
+
|
|
390
500
|
## Development
|
|
391
501
|
|
|
392
502
|
```bash
|
|
@@ -1,49 +1,126 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* JST
|
|
2
|
+
* Explicit JST business-time API (Workers UTC instant ↔ business calendar date / date-time).
|
|
3
3
|
*
|
|
4
4
|
* @remarks
|
|
5
|
-
* - DB
|
|
6
|
-
*
|
|
7
|
-
* -
|
|
5
|
+
* - The DB stays on JST. The app does not implicitly rely on the mysql2 `timezone` option; it goes
|
|
6
|
+
* through this module to handle JST.
|
|
7
|
+
* - Converting to the MySQL wire format is the responsibility of {@link ../db/jst.js | db/jst}.
|
|
8
|
+
* - Do not use a `Date`'s local getters (`getHours`, etc.) for business-time decisions.
|
|
8
9
|
*
|
|
9
10
|
* @packageDocumentation
|
|
10
11
|
*/
|
|
11
12
|
import { BUSINESS_TIMEZONE } from './types.js';
|
|
12
13
|
import type { BusinessDate, BusinessDateTime } from './types.js';
|
|
13
14
|
export { BUSINESS_TIMEZONE, type BusinessDate, type BusinessDateTime };
|
|
14
|
-
/**
|
|
15
|
+
/**
|
|
16
|
+
* The JST business calendar date of a reference instant.
|
|
17
|
+
*
|
|
18
|
+
* @param ref - the instant to read; defaults to now.
|
|
19
|
+
* @returns the business date as `YYYY-MM-DD`.
|
|
20
|
+
* @example
|
|
21
|
+
* today(new Date('2026-07-05T20:00:00Z')); // → '2026-07-06' (JST)
|
|
22
|
+
*/
|
|
15
23
|
export declare function today(ref?: Date): BusinessDate;
|
|
16
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* Convert a UTC instant to a JST business calendar date.
|
|
26
|
+
*
|
|
27
|
+
* @param instant - the UTC instant to convert.
|
|
28
|
+
* @returns the business date as `YYYY-MM-DD`.
|
|
29
|
+
*/
|
|
17
30
|
export declare function toBusinessDate(instant: Date): BusinessDate;
|
|
18
31
|
/**
|
|
19
|
-
*
|
|
32
|
+
* Normalize a client / DB input to a JST business calendar date `YYYY-MM-DD`.
|
|
33
|
+
*
|
|
34
|
+
* - A string already in `YYYY-MM-DD` form is returned as-is, **without** constructing a `Date`
|
|
35
|
+
* (a birthday is a calendar day, not an instant).
|
|
36
|
+
* - ISO 8601 and similar values are converted to a JST calendar date via their instant.
|
|
37
|
+
* - Nullish / empty / invalid inputs yield `null`.
|
|
20
38
|
*
|
|
21
|
-
*
|
|
22
|
-
* -
|
|
23
|
-
*
|
|
39
|
+
* @param value - the string, `Date`, or nullish value to normalize.
|
|
40
|
+
* @returns the business date as `YYYY-MM-DD`, or `null` when the input cannot be resolved.
|
|
41
|
+
* @example
|
|
42
|
+
* normalizeBusinessDate('1990-01-15'); // → '1990-01-15' (unchanged)
|
|
43
|
+
* normalizeBusinessDate('2026-07-05T20:00:00Z'); // → '2026-07-06' (JST)
|
|
44
|
+
* normalizeBusinessDate(''); // → null
|
|
24
45
|
*/
|
|
25
46
|
export declare function normalizeBusinessDate(value: string | Date | null | undefined): BusinessDate | null;
|
|
26
|
-
/**
|
|
47
|
+
/**
|
|
48
|
+
* Convert a UTC instant to a JST business date-time (`YYYY-MM-DD HH:mm:ss`).
|
|
49
|
+
*
|
|
50
|
+
* @param instant - the UTC instant to convert.
|
|
51
|
+
* @returns the business date-time string.
|
|
52
|
+
* @example
|
|
53
|
+
* toBusinessDateTime(new Date('2026-07-05T21:00:00Z')); // → '2026-07-06 06:00:00' (JST)
|
|
54
|
+
*/
|
|
27
55
|
export declare function toBusinessDateTime(instant: Date): BusinessDateTime;
|
|
28
|
-
/** Nest / foodlabel / winecode `helper.formatDate
|
|
56
|
+
/** Default pattern for the Nest / foodlabel / winecode `helper.formatDate`. */
|
|
29
57
|
export declare const DEFAULT_BUSINESS_DATETIME_PATTERN: "YYYY-MM-DDThh:mm:ss";
|
|
30
58
|
/**
|
|
31
|
-
* Nest `helper.formatDate
|
|
32
|
-
*
|
|
59
|
+
* Format an instant in the business TZ, compatible with the Nest `helper.formatDate`.
|
|
60
|
+
*
|
|
61
|
+
* Supported tokens: `YYYY` / `MM` / `DD` / `hh` / `mm` / `ss`, plus `S` for the source instant's
|
|
62
|
+
* milliseconds (matching the Nest reference implementation).
|
|
63
|
+
*
|
|
64
|
+
* @param instant - the UTC instant to format.
|
|
65
|
+
* @param pattern - the format pattern; defaults to {@link DEFAULT_BUSINESS_DATETIME_PATTERN}.
|
|
66
|
+
* @returns the formatted string.
|
|
67
|
+
* @example
|
|
68
|
+
* formatBusinessDateTime(new Date('2026-07-05T21:00:00Z')); // → '2026-07-06T06:00:00' (JST)
|
|
33
69
|
*/
|
|
34
70
|
export declare function formatBusinessDateTime(instant: Date, pattern?: string): string;
|
|
35
|
-
/**
|
|
71
|
+
/**
|
|
72
|
+
* Parse a JST business date-time string into a UTC instant. Accepts a space or `T` separator
|
|
73
|
+
* (`YYYY-MM-DD HH:mm:ss` or `YYYY-MM-DDTHH:mm:ss`).
|
|
74
|
+
*
|
|
75
|
+
* @param value - the business date-time string to parse.
|
|
76
|
+
* @returns the corresponding UTC instant.
|
|
77
|
+
* @throws RangeError when `value` is not a valid business date-time.
|
|
78
|
+
* @example
|
|
79
|
+
* parseBusinessDateTime('2026-07-06 06:00:00'); // → 2026-07-05T21:00:00Z
|
|
80
|
+
*/
|
|
36
81
|
export declare function parseBusinessDateTime(value: BusinessDateTime): Date;
|
|
37
|
-
/**
|
|
82
|
+
/**
|
|
83
|
+
* The UTC instant of `00:00:00` on a JST business calendar date.
|
|
84
|
+
*
|
|
85
|
+
* @param date - the business date as `YYYY-MM-DD`.
|
|
86
|
+
* @returns the UTC instant at the start of that business day.
|
|
87
|
+
*/
|
|
38
88
|
export declare function startOfBusinessDay(date: BusinessDate): Date;
|
|
39
|
-
/**
|
|
89
|
+
/**
|
|
90
|
+
* The UTC instant of `23:59:59` on a JST business calendar date.
|
|
91
|
+
*
|
|
92
|
+
* @param date - the business date as `YYYY-MM-DD`.
|
|
93
|
+
* @returns the UTC instant at the end of that business day.
|
|
94
|
+
*/
|
|
40
95
|
export declare function endOfBusinessDay(date: BusinessDate): Date;
|
|
41
96
|
/**
|
|
42
|
-
* JST
|
|
43
|
-
*
|
|
97
|
+
* Convert a JST business calendar date + wall-clock time to a UTC instant.
|
|
98
|
+
*
|
|
99
|
+
* @param date - the business date as `YYYY-MM-DD`.
|
|
100
|
+
* @param time - the wall-clock time as `HH:mm:ss` (or `HH:mm`).
|
|
101
|
+
* @returns the corresponding UTC instant.
|
|
102
|
+
* @throws RangeError when `date` or `time` is malformed.
|
|
103
|
+
* @example
|
|
104
|
+
* businessDateTimeInstant('2026-07-06', '06:00:00'); // → 2026-07-05T21:00:00Z
|
|
44
105
|
*/
|
|
45
106
|
export declare function businessDateTimeInstant(date: BusinessDate, time: string): Date;
|
|
46
|
-
/**
|
|
107
|
+
/**
|
|
108
|
+
* Add a number of calendar days to a JST business calendar date.
|
|
109
|
+
*
|
|
110
|
+
* @param date - the starting business date as `YYYY-MM-DD`.
|
|
111
|
+
* @param days - the number of calendar days to add (may be negative).
|
|
112
|
+
* @returns the resulting business date as `YYYY-MM-DD`.
|
|
113
|
+
* @example
|
|
114
|
+
* addBusinessDays('2026-07-06', 3); // → '2026-07-09'
|
|
115
|
+
*/
|
|
47
116
|
export declare function addBusinessDays(date: BusinessDate, days: number): BusinessDate;
|
|
48
|
-
/**
|
|
117
|
+
/**
|
|
118
|
+
* The full years of age on a business calendar date (a birthday is a `BusinessDate`, not an instant).
|
|
119
|
+
*
|
|
120
|
+
* @param birthDate - the birth date as `YYYY-MM-DD`.
|
|
121
|
+
* @param asOfDate - the reference business date; defaults to {@link today}.
|
|
122
|
+
* @returns the age in completed years.
|
|
123
|
+
* @example
|
|
124
|
+
* ageOnBusinessDate('1990-07-10', '2026-07-06'); // → 35
|
|
125
|
+
*/
|
|
49
126
|
export declare function ageOnBusinessDate(birthDate: BusinessDate, asOfDate?: BusinessDate): number;
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* JST
|
|
2
|
+
* Explicit JST business-time API (Workers UTC instant ↔ business calendar date / date-time).
|
|
3
3
|
*
|
|
4
4
|
* @remarks
|
|
5
|
-
* - DB
|
|
6
|
-
*
|
|
7
|
-
* -
|
|
5
|
+
* - The DB stays on JST. The app does not implicitly rely on the mysql2 `timezone` option; it goes
|
|
6
|
+
* through this module to handle JST.
|
|
7
|
+
* - Converting to the MySQL wire format is the responsibility of {@link ../db/jst.js | db/jst}.
|
|
8
|
+
* - Do not use a `Date`'s local getters (`getHours`, etc.) for business-time decisions.
|
|
8
9
|
*
|
|
9
10
|
* @packageDocumentation
|
|
10
11
|
*/
|
|
11
12
|
import { BUSINESS_TIMEZONE } from './types.js';
|
|
12
13
|
export { BUSINESS_TIMEZONE };
|
|
13
14
|
const pad2 = (n) => String(n).padStart(2, '0');
|
|
14
|
-
/** instant
|
|
15
|
+
/** Shift an instant so it can be read as a business-TZ wall clock (extract fields with `getUTC*`). */
|
|
15
16
|
function toWallClock(instant) {
|
|
16
17
|
return new Date(instant.getTime() + BUSINESS_TIMEZONE.offsetMinutes * 60_000);
|
|
17
18
|
}
|
|
@@ -29,21 +30,41 @@ function parseHms(time) {
|
|
|
29
30
|
}
|
|
30
31
|
return [Number(m[1]), Number(m[2]), Number(m[3] || 0)];
|
|
31
32
|
}
|
|
32
|
-
/**
|
|
33
|
+
/**
|
|
34
|
+
* The JST business calendar date of a reference instant.
|
|
35
|
+
*
|
|
36
|
+
* @param ref - the instant to read; defaults to now.
|
|
37
|
+
* @returns the business date as `YYYY-MM-DD`.
|
|
38
|
+
* @example
|
|
39
|
+
* today(new Date('2026-07-05T20:00:00Z')); // → '2026-07-06' (JST)
|
|
40
|
+
*/
|
|
33
41
|
export function today(ref = new Date()) {
|
|
34
42
|
return toBusinessDate(ref);
|
|
35
43
|
}
|
|
36
|
-
/**
|
|
44
|
+
/**
|
|
45
|
+
* Convert a UTC instant to a JST business calendar date.
|
|
46
|
+
*
|
|
47
|
+
* @param instant - the UTC instant to convert.
|
|
48
|
+
* @returns the business date as `YYYY-MM-DD`.
|
|
49
|
+
*/
|
|
37
50
|
export function toBusinessDate(instant) {
|
|
38
51
|
const wall = toWallClock(instant);
|
|
39
52
|
return `${wall.getUTCFullYear()}-${pad2(wall.getUTCMonth() + 1)}-${pad2(wall.getUTCDate())}`;
|
|
40
53
|
}
|
|
41
54
|
/**
|
|
42
|
-
*
|
|
55
|
+
* Normalize a client / DB input to a JST business calendar date `YYYY-MM-DD`.
|
|
56
|
+
*
|
|
57
|
+
* - A string already in `YYYY-MM-DD` form is returned as-is, **without** constructing a `Date`
|
|
58
|
+
* (a birthday is a calendar day, not an instant).
|
|
59
|
+
* - ISO 8601 and similar values are converted to a JST calendar date via their instant.
|
|
60
|
+
* - Nullish / empty / invalid inputs yield `null`.
|
|
43
61
|
*
|
|
44
|
-
*
|
|
45
|
-
* -
|
|
46
|
-
*
|
|
62
|
+
* @param value - the string, `Date`, or nullish value to normalize.
|
|
63
|
+
* @returns the business date as `YYYY-MM-DD`, or `null` when the input cannot be resolved.
|
|
64
|
+
* @example
|
|
65
|
+
* normalizeBusinessDate('1990-01-15'); // → '1990-01-15' (unchanged)
|
|
66
|
+
* normalizeBusinessDate('2026-07-05T20:00:00Z'); // → '2026-07-06' (JST)
|
|
67
|
+
* normalizeBusinessDate(''); // → null
|
|
47
68
|
*/
|
|
48
69
|
export function normalizeBusinessDate(value) {
|
|
49
70
|
if (value == null) {
|
|
@@ -72,16 +93,31 @@ export function normalizeBusinessDate(value) {
|
|
|
72
93
|
}
|
|
73
94
|
return toBusinessDate(new Date(ms));
|
|
74
95
|
}
|
|
75
|
-
/**
|
|
96
|
+
/**
|
|
97
|
+
* Convert a UTC instant to a JST business date-time (`YYYY-MM-DD HH:mm:ss`).
|
|
98
|
+
*
|
|
99
|
+
* @param instant - the UTC instant to convert.
|
|
100
|
+
* @returns the business date-time string.
|
|
101
|
+
* @example
|
|
102
|
+
* toBusinessDateTime(new Date('2026-07-05T21:00:00Z')); // → '2026-07-06 06:00:00' (JST)
|
|
103
|
+
*/
|
|
76
104
|
export function toBusinessDateTime(instant) {
|
|
77
105
|
const wall = toWallClock(instant);
|
|
78
106
|
return `${wall.getUTCFullYear()}-${pad2(wall.getUTCMonth() + 1)}-${pad2(wall.getUTCDate())} ${pad2(wall.getUTCHours())}:${pad2(wall.getUTCMinutes())}:${pad2(wall.getUTCSeconds())}`;
|
|
79
107
|
}
|
|
80
|
-
/** Nest / foodlabel / winecode `helper.formatDate
|
|
108
|
+
/** Default pattern for the Nest / foodlabel / winecode `helper.formatDate`. */
|
|
81
109
|
export const DEFAULT_BUSINESS_DATETIME_PATTERN = 'YYYY-MM-DDThh:mm:ss';
|
|
82
110
|
/**
|
|
83
|
-
* Nest `helper.formatDate
|
|
84
|
-
*
|
|
111
|
+
* Format an instant in the business TZ, compatible with the Nest `helper.formatDate`.
|
|
112
|
+
*
|
|
113
|
+
* Supported tokens: `YYYY` / `MM` / `DD` / `hh` / `mm` / `ss`, plus `S` for the source instant's
|
|
114
|
+
* milliseconds (matching the Nest reference implementation).
|
|
115
|
+
*
|
|
116
|
+
* @param instant - the UTC instant to format.
|
|
117
|
+
* @param pattern - the format pattern; defaults to {@link DEFAULT_BUSINESS_DATETIME_PATTERN}.
|
|
118
|
+
* @returns the formatted string.
|
|
119
|
+
* @example
|
|
120
|
+
* formatBusinessDateTime(new Date('2026-07-05T21:00:00Z')); // → '2026-07-06T06:00:00' (JST)
|
|
85
121
|
*/
|
|
86
122
|
export function formatBusinessDateTime(instant, pattern = DEFAULT_BUSINESS_DATETIME_PATTERN) {
|
|
87
123
|
const wall = toWallClock(instant);
|
|
@@ -102,7 +138,16 @@ export function formatBusinessDateTime(instant, pattern = DEFAULT_BUSINESS_DATET
|
|
|
102
138
|
}
|
|
103
139
|
return out;
|
|
104
140
|
}
|
|
105
|
-
/**
|
|
141
|
+
/**
|
|
142
|
+
* Parse a JST business date-time string into a UTC instant. Accepts a space or `T` separator
|
|
143
|
+
* (`YYYY-MM-DD HH:mm:ss` or `YYYY-MM-DDTHH:mm:ss`).
|
|
144
|
+
*
|
|
145
|
+
* @param value - the business date-time string to parse.
|
|
146
|
+
* @returns the corresponding UTC instant.
|
|
147
|
+
* @throws RangeError when `value` is not a valid business date-time.
|
|
148
|
+
* @example
|
|
149
|
+
* parseBusinessDateTime('2026-07-06 06:00:00'); // → 2026-07-05T21:00:00Z
|
|
150
|
+
*/
|
|
106
151
|
export function parseBusinessDateTime(value) {
|
|
107
152
|
const normalized = value.includes('T') ? value.replace('T', ' ') : value;
|
|
108
153
|
const m = /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$/.exec(normalized);
|
|
@@ -113,17 +158,33 @@ export function parseBusinessDateTime(value) {
|
|
|
113
158
|
const offsetHours = BUSINESS_TIMEZONE.offsetMinutes / 60;
|
|
114
159
|
return new Date(Date.UTC(Number(y), Number(mo) - 1, Number(d), Number(h) - offsetHours, Number(mi), Number(s), 0));
|
|
115
160
|
}
|
|
116
|
-
/**
|
|
161
|
+
/**
|
|
162
|
+
* The UTC instant of `00:00:00` on a JST business calendar date.
|
|
163
|
+
*
|
|
164
|
+
* @param date - the business date as `YYYY-MM-DD`.
|
|
165
|
+
* @returns the UTC instant at the start of that business day.
|
|
166
|
+
*/
|
|
117
167
|
export function startOfBusinessDay(date) {
|
|
118
168
|
return businessDateTimeInstant(date, '00:00:00');
|
|
119
169
|
}
|
|
120
|
-
/**
|
|
170
|
+
/**
|
|
171
|
+
* The UTC instant of `23:59:59` on a JST business calendar date.
|
|
172
|
+
*
|
|
173
|
+
* @param date - the business date as `YYYY-MM-DD`.
|
|
174
|
+
* @returns the UTC instant at the end of that business day.
|
|
175
|
+
*/
|
|
121
176
|
export function endOfBusinessDay(date) {
|
|
122
177
|
return businessDateTimeInstant(date, '23:59:59');
|
|
123
178
|
}
|
|
124
179
|
/**
|
|
125
|
-
* JST
|
|
126
|
-
*
|
|
180
|
+
* Convert a JST business calendar date + wall-clock time to a UTC instant.
|
|
181
|
+
*
|
|
182
|
+
* @param date - the business date as `YYYY-MM-DD`.
|
|
183
|
+
* @param time - the wall-clock time as `HH:mm:ss` (or `HH:mm`).
|
|
184
|
+
* @returns the corresponding UTC instant.
|
|
185
|
+
* @throws RangeError when `date` or `time` is malformed.
|
|
186
|
+
* @example
|
|
187
|
+
* businessDateTimeInstant('2026-07-06', '06:00:00'); // → 2026-07-05T21:00:00Z
|
|
127
188
|
*/
|
|
128
189
|
export function businessDateTimeInstant(date, time) {
|
|
129
190
|
const [y, mo, d] = parseYmd(date);
|
|
@@ -131,12 +192,28 @@ export function businessDateTimeInstant(date, time) {
|
|
|
131
192
|
const offsetHours = BUSINESS_TIMEZONE.offsetMinutes / 60;
|
|
132
193
|
return new Date(Date.UTC(y, mo - 1, d, h - offsetHours, mi, s, 0));
|
|
133
194
|
}
|
|
134
|
-
/**
|
|
195
|
+
/**
|
|
196
|
+
* Add a number of calendar days to a JST business calendar date.
|
|
197
|
+
*
|
|
198
|
+
* @param date - the starting business date as `YYYY-MM-DD`.
|
|
199
|
+
* @param days - the number of calendar days to add (may be negative).
|
|
200
|
+
* @returns the resulting business date as `YYYY-MM-DD`.
|
|
201
|
+
* @example
|
|
202
|
+
* addBusinessDays('2026-07-06', 3); // → '2026-07-09'
|
|
203
|
+
*/
|
|
135
204
|
export function addBusinessDays(date, days) {
|
|
136
205
|
const anchor = businessDateTimeInstant(date, '12:00:00');
|
|
137
206
|
return toBusinessDate(new Date(anchor.getTime() + days * 24 * 60 * 60 * 1000));
|
|
138
207
|
}
|
|
139
|
-
/**
|
|
208
|
+
/**
|
|
209
|
+
* The full years of age on a business calendar date (a birthday is a `BusinessDate`, not an instant).
|
|
210
|
+
*
|
|
211
|
+
* @param birthDate - the birth date as `YYYY-MM-DD`.
|
|
212
|
+
* @param asOfDate - the reference business date; defaults to {@link today}.
|
|
213
|
+
* @returns the age in completed years.
|
|
214
|
+
* @example
|
|
215
|
+
* ageOnBusinessDate('1990-07-10', '2026-07-06'); // → 35
|
|
216
|
+
*/
|
|
140
217
|
export function ageOnBusinessDate(birthDate, asOfDate) {
|
|
141
218
|
const asOf = asOfDate ?? today();
|
|
142
219
|
const [by, bm, bd] = parseYmd(birthDate);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
/** JST
|
|
1
|
+
/** A JST business calendar date `YYYY-MM-DD` (a calendar day, not an instant). */
|
|
2
2
|
export type BusinessDate = string;
|
|
3
|
-
/** JST
|
|
3
|
+
/** A JST business date-time `YYYY-MM-DD HH:mm:ss` (a wall-clock value, MySQL `DATETIME`-compatible). */
|
|
4
4
|
export type BusinessDateTime = string;
|
|
5
|
-
/** JST
|
|
5
|
+
/** JST business timezone constant. Workers run in UTC instants; business time is made explicit here. */
|
|
6
6
|
export declare const BUSINESS_TIMEZONE: {
|
|
7
7
|
readonly iana: "Asia/Tokyo";
|
|
8
8
|
readonly offsetMinutes: 540;
|