@rdlabo/workers-hono-kit 0.12.2 → 0.12.3-beta.pr51.sha3289ea609bb4

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 CHANGED
@@ -2,24 +2,9 @@
2
2
 
3
3
  Shared Hono building blocks for Cloudflare Workers APIs: weak ETags, NestJS-shaped validation and
4
4
  error bodies, Firebase auth middleware, AWS helpers, AI Gateway wiring, Stripe, KV, queues,
5
- realtime, and offline contracts. Domain logic and database schemas stay in the consuming
6
- application.
5
+ realtime, and offline contracts.
7
6
 
8
- ## Choose an entry point
9
-
10
- | Import | Responsibility |
11
- | ---------------------------------------- | -------------------------------------------------------------------- |
12
- | `@rdlabo/workers-hono-kit` | HTTP, auth, Firebase, AWS, AI, Stripe, KV, and queue primitives |
13
- | `@rdlabo/workers-hono-kit/mysql` | Hono container adapter for `@rdlabo/workers-mysql` |
14
- | `@rdlabo/workers-hono-kit/offline` | Offline replica wire, cursor, journal, and compatibility contracts |
15
- | `@rdlabo/workers-hono-kit/realtime` | Durable Object WebSocket and retry helpers |
16
- | `@rdlabo/workers-hono-kit/testing` | Auth helpers, fakes, Stripe fixtures, and compatibility test exports |
17
- | `@rdlabo/workers-hono-kit/db` | Deprecated compatibility path for `@rdlabo/workers-mysql` |
18
- | `@rdlabo/workers-hono-kit/business-time` | Deprecated compatibility path for `@rdlabo/workers-timezone` |
19
-
20
- The root entry point does not load MySQL, Drizzle, or Node-only migration modules. MySQL consumers
21
- install the standalone package, which owns `mysql2`; Hono-specific wiring stays in the `/mysql`
22
- adapter.
7
+ [Try a Hono API locally](./docs/quickstart.md): send a health request, inspect its weak ETag, and see the missing-route JSON response. No Cloudflare account or open port is needed for the first exercise.
23
8
 
24
9
  ## Install
25
10
 
@@ -72,6 +57,22 @@ app.get('/health', (c) => c.json({ ok: true }));
72
57
  export default app;
73
58
  ```
74
59
 
60
+ ## Choose an entry point
61
+
62
+ | Import | Responsibility |
63
+ | ---------------------------------------- | -------------------------------------------------------------------- |
64
+ | `@rdlabo/workers-hono-kit` | HTTP, auth, Firebase, AWS, AI, Stripe, KV, and queue primitives |
65
+ | `@rdlabo/workers-hono-kit/mysql` | Hono container adapter for `@rdlabo/workers-mysql` |
66
+ | `@rdlabo/workers-hono-kit/offline` | Offline replica wire, cursor, journal, and compatibility contracts |
67
+ | `@rdlabo/workers-hono-kit/realtime` | Durable Object WebSocket and retry helpers |
68
+ | `@rdlabo/workers-hono-kit/testing` | Auth helpers, fakes, Stripe fixtures, and compatibility test exports |
69
+ | `@rdlabo/workers-hono-kit/db` | Deprecated compatibility path for `@rdlabo/workers-mysql` |
70
+ | `@rdlabo/workers-hono-kit/business-time` | Deprecated compatibility path for `@rdlabo/workers-timezone` |
71
+
72
+ The root entry point does not load MySQL, Drizzle, or Node-only migration modules. MySQL consumers
73
+ install the standalone package, which owns `mysql2`; Hono-specific wiring stays in the `/mysql`
74
+ adapter.
75
+
75
76
  ### Compatibility import deprecations
76
77
 
77
78
  Kit `/db`, `/business-time`, and the DB-related `/testing` exports (`createTestDb`, pool/noop
@@ -81,6 +82,33 @@ compatibility aliases keep the same runtime identity and signatures; there is no
81
82
  Kit-owned helpers such as `reopenGuardedPaymentFailedSet`, `/mysql` `createContainerRuntime`, and
82
83
  Firebase/auth/KV/Stripe test helpers are not deprecated by this migration.
83
84
 
85
+ ### Social auth (Google / Apple)
86
+
87
+ Root exports `verifyGoogleIdentityToken`, `verifyAppleIdentityToken`,
88
+ `hasFirebaseProviderIdentity`, and `createAppleClientSecret` cover reusable OIDC subject
89
+ verification, Firebase identity matching, and Apple `client_secret` signing. Applications keep
90
+ ownership of HTTP responses, persistence, and provider token exchange or revocation.
91
+
92
+ `hasFirebaseProviderIdentity` expects a **verified** Firebase ID-token payload. Pass
93
+ `requireSignInProvider: true` when an endpoint requires sign-in through that provider
94
+ (subject linked **and** `firebase.sign_in_provider` matches). Leave it `false` (default)
95
+ for link/unlink checks or combined login/link endpoints that also accept sessions established
96
+ through another provider. This policy belongs to the application.
97
+
98
+ ```ts
99
+ import { hasFirebaseProviderIdentity, verifyGoogleIdentityToken } from '@rdlabo/workers-hono-kit';
100
+
101
+ // firebaseVerifier is the application's configured FirebaseVerifier.
102
+ const verifiedFirebaseToken = await firebaseVerifier.verifyIdToken(firebaseIdToken);
103
+ const subject = await verifyGoogleIdentityToken(googleIdToken, GOOGLE_CLIENT_IDS);
104
+ // login: require active Google sign-in
105
+ if (!hasFirebaseProviderIdentity(verifiedFirebaseToken, 'google.com', subject, true)) {
106
+ return c.json({ error: 'Google identity mismatch' }, 401);
107
+ }
108
+ // link / unlink: subject present is enough
109
+ // hasFirebaseProviderIdentity(verifiedFirebaseToken, 'google.com', subject)
110
+ ```
111
+
84
112
  ## Documentation
85
113
 
86
114
  - [HTTP and Authentication](https://docs.rdlabo.dev/projects/workers-hono-kit/docs/http-auth)
@@ -0,0 +1,90 @@
1
+ import type { JWTVerifyGetKey } from 'jose';
2
+ import type { DecodedIdToken } from './firebase-verifier.js';
3
+ /** Apple Sign In OIDC issuer (`iss`) expected by {@link verifyAppleIdentityToken}. */
4
+ export declare const APPLE_IDENTITY_ISSUER = "https://appleid.apple.com";
5
+ /**
6
+ * Google OIDC issuers accepted by {@link verifyGoogleIdentityToken}.
7
+ *
8
+ * Google issues tokens with either the HTTPS form or the host-only form of
9
+ * `accounts.google.com`; both are listed so verification matches either claim.
10
+ */
11
+ export declare const GOOGLE_IDENTITY_ISSUERS: readonly ["https://accounts.google.com", "accounts.google.com"];
12
+ /**
13
+ * Verify an Apple identity token and return its `sub` (Apple user id).
14
+ *
15
+ * Checks signature against Apple's JWKS (or an injected key resolver), enforces
16
+ * {@link APPLE_IDENTITY_ISSUER}, and requires `aud` to equal the configured Services ID /
17
+ * bundle id (`audience`).
18
+ *
19
+ * @param idToken - Raw Apple identity token JWT from Sign in with Apple.
20
+ * @param audience - Expected `aud` claim (Apple Services ID or native bundle id).
21
+ * @param getKey - Optional JWKS / key resolver; defaults to Apple's remote JWKS. Inject a
22
+ * static key in tests to avoid network I/O.
23
+ * @returns The token `sub` (Apple user identifier).
24
+ * @throws If signature, issuer, audience, or expiry fail verification, or `sub` is missing.
25
+ */
26
+ export declare const verifyAppleIdentityToken: (idToken: string, audience: string, getKey?: JWTVerifyGetKey) => Promise<string>;
27
+ /**
28
+ * Verify a Google identity token and return its `sub` (Google user id).
29
+ *
30
+ * Checks signature against Google's OAuth2 certs (or an injected key resolver), accepts either
31
+ * issuer in {@link GOOGLE_IDENTITY_ISSUERS}, and requires `aud` to match the configured OAuth
32
+ * client id(s) (`audience`).
33
+ *
34
+ * @param idToken - Raw Google ID token JWT.
35
+ * @param audience - Expected `aud` claim: a single OAuth client id, or a list when native and
36
+ * web clients share one login endpoint.
37
+ * @param getKey - Optional JWKS / key resolver; defaults to Google's remote certs. Inject a
38
+ * static key in tests to avoid network I/O.
39
+ * @returns The token `sub` (Google user identifier).
40
+ * @throws If signature, issuer, audience, or expiry fail verification, or `sub` is missing.
41
+ */
42
+ export declare const verifyGoogleIdentityToken: (idToken: string, audience: string | readonly string[], getKey?: JWTVerifyGetKey) => Promise<string>;
43
+ /**
44
+ * Return whether a **verified** Firebase ID token already carries the given provider subject.
45
+ *
46
+ * Reads `firebase.identities[providerId]` for `subject`. When `requireSignInProvider` is true,
47
+ * also requires `firebase.sign_in_provider === providerId` so the session was established with
48
+ * that provider (login), not merely that the identity is linked while signed in another way.
49
+ *
50
+ * @remarks
51
+ * `token` must already be a verified Firebase ID-token payload (for example from
52
+ * {@link FirebaseVerifier.verifyIdToken} or auth middleware). This helper does not verify the
53
+ * Firebase JWT itself.
54
+ *
55
+ * Typical policy:
56
+ * - **Login** (`requireSignInProvider: true`): subject present and active sign-in provider matches.
57
+ * - **Link / unlink / linkage checks** (default `false`): subject present in identities only.
58
+ *
59
+ * @param token - Verified Firebase ID-token payload (`DecodedIdToken`).
60
+ * @param providerId - Firebase provider id (e.g. `'google.com'`, `'apple.com'`).
61
+ * @param subject - Provider subject previously returned by {@link verifyGoogleIdentityToken} or
62
+ * {@link verifyAppleIdentityToken}.
63
+ * @param requireSignInProvider - When `true`, also require `sign_in_provider === providerId`.
64
+ * @returns `true` when the identity (and optional active provider) matches.
65
+ */
66
+ export declare const hasFirebaseProviderIdentity: (token: DecodedIdToken, providerId: string, subject: string, requireSignInProvider?: boolean) => boolean;
67
+ /**
68
+ * Apple developer credentials used to mint a Sign in with Apple `client_secret` JWT.
69
+ */
70
+ export interface AppleClientSecretConfig {
71
+ /** PEM-encoded PKCS#8 ES256 private key from the Apple developer key. */
72
+ privateKey: string;
73
+ /** Key id (`kid`) of the Apple developer key. */
74
+ keyId: string;
75
+ /** Apple Team ID used as the JWT `iss` claim. */
76
+ teamId: string;
77
+ }
78
+ /**
79
+ * Create a short-lived Sign in with Apple `client_secret` (ES256 JWT).
80
+ *
81
+ * The JWT is issued for `clientId` as `sub`, audience {@link APPLE_IDENTITY_ISSUER}, and expires
82
+ * 120 seconds after `now`. Used for Apple's token and revoke endpoints.
83
+ *
84
+ * @param config - Apple Team ID, key id, and PKCS#8 private key.
85
+ * @param clientId - Apple Services ID or native bundle id (`sub` claim).
86
+ * @param now - Unix time in seconds for `iat` / `exp`; injectable for deterministic tests.
87
+ * Defaults to the system clock.
88
+ * @returns A compact ES256 JWT suitable as Apple's `client_secret`.
89
+ */
90
+ export declare const createAppleClientSecret: (config: AppleClientSecretConfig, clientId: string, now?: number) => Promise<string>;
@@ -0,0 +1,108 @@
1
+ import { SignJWT, createRemoteJWKSet, importPKCS8, jwtVerify } from 'jose';
2
+ /** Apple Sign In OIDC issuer (`iss`) expected by {@link verifyAppleIdentityToken}. */
3
+ export const APPLE_IDENTITY_ISSUER = 'https://appleid.apple.com';
4
+ /**
5
+ * Google OIDC issuers accepted by {@link verifyGoogleIdentityToken}.
6
+ *
7
+ * Google issues tokens with either the HTTPS form or the host-only form of
8
+ * `accounts.google.com`; both are listed so verification matches either claim.
9
+ */
10
+ export const GOOGLE_IDENTITY_ISSUERS = ['https://accounts.google.com', 'accounts.google.com'];
11
+ const appleJwks = createRemoteJWKSet(new URL(`${APPLE_IDENTITY_ISSUER}/auth/keys`));
12
+ const googleJwks = createRemoteJWKSet(new URL('https://www.googleapis.com/oauth2/v3/certs'));
13
+ /**
14
+ * Verify an Apple identity token and return its `sub` (Apple user id).
15
+ *
16
+ * Checks signature against Apple's JWKS (or an injected key resolver), enforces
17
+ * {@link APPLE_IDENTITY_ISSUER}, and requires `aud` to equal the configured Services ID /
18
+ * bundle id (`audience`).
19
+ *
20
+ * @param idToken - Raw Apple identity token JWT from Sign in with Apple.
21
+ * @param audience - Expected `aud` claim (Apple Services ID or native bundle id).
22
+ * @param getKey - Optional JWKS / key resolver; defaults to Apple's remote JWKS. Inject a
23
+ * static key in tests to avoid network I/O.
24
+ * @returns The token `sub` (Apple user identifier).
25
+ * @throws If signature, issuer, audience, or expiry fail verification, or `sub` is missing.
26
+ */
27
+ export const verifyAppleIdentityToken = async (idToken, audience, getKey = appleJwks) => {
28
+ const { payload } = await jwtVerify(idToken, getKey, { issuer: APPLE_IDENTITY_ISSUER, audience });
29
+ if (!payload.sub) {
30
+ throw new Error('Apple identity token has no subject');
31
+ }
32
+ return payload.sub;
33
+ };
34
+ /**
35
+ * Verify a Google identity token and return its `sub` (Google user id).
36
+ *
37
+ * Checks signature against Google's OAuth2 certs (or an injected key resolver), accepts either
38
+ * issuer in {@link GOOGLE_IDENTITY_ISSUERS}, and requires `aud` to match the configured OAuth
39
+ * client id(s) (`audience`).
40
+ *
41
+ * @param idToken - Raw Google ID token JWT.
42
+ * @param audience - Expected `aud` claim: a single OAuth client id, or a list when native and
43
+ * web clients share one login endpoint.
44
+ * @param getKey - Optional JWKS / key resolver; defaults to Google's remote certs. Inject a
45
+ * static key in tests to avoid network I/O.
46
+ * @returns The token `sub` (Google user identifier).
47
+ * @throws If signature, issuer, audience, or expiry fail verification, or `sub` is missing.
48
+ */
49
+ export const verifyGoogleIdentityToken = async (idToken, audience, getKey = googleJwks) => {
50
+ const { payload } = await jwtVerify(idToken, getKey, {
51
+ issuer: [...GOOGLE_IDENTITY_ISSUERS],
52
+ audience: typeof audience === 'string' ? audience : [...audience],
53
+ });
54
+ if (!payload.sub) {
55
+ throw new Error('Google identity token has no subject');
56
+ }
57
+ return payload.sub;
58
+ };
59
+ /**
60
+ * Return whether a **verified** Firebase ID token already carries the given provider subject.
61
+ *
62
+ * Reads `firebase.identities[providerId]` for `subject`. When `requireSignInProvider` is true,
63
+ * also requires `firebase.sign_in_provider === providerId` so the session was established with
64
+ * that provider (login), not merely that the identity is linked while signed in another way.
65
+ *
66
+ * @remarks
67
+ * `token` must already be a verified Firebase ID-token payload (for example from
68
+ * {@link FirebaseVerifier.verifyIdToken} or auth middleware). This helper does not verify the
69
+ * Firebase JWT itself.
70
+ *
71
+ * Typical policy:
72
+ * - **Login** (`requireSignInProvider: true`): subject present and active sign-in provider matches.
73
+ * - **Link / unlink / linkage checks** (default `false`): subject present in identities only.
74
+ *
75
+ * @param token - Verified Firebase ID-token payload (`DecodedIdToken`).
76
+ * @param providerId - Firebase provider id (e.g. `'google.com'`, `'apple.com'`).
77
+ * @param subject - Provider subject previously returned by {@link verifyGoogleIdentityToken} or
78
+ * {@link verifyAppleIdentityToken}.
79
+ * @param requireSignInProvider - When `true`, also require `sign_in_provider === providerId`.
80
+ * @returns `true` when the identity (and optional active provider) matches.
81
+ */
82
+ export const hasFirebaseProviderIdentity = (token, providerId, subject, requireSignInProvider = false) => {
83
+ const firebase = token;
84
+ const subjects = firebase.firebase?.identities?.[providerId];
85
+ return (Array.isArray(subjects) &&
86
+ subjects.includes(subject) &&
87
+ (!requireSignInProvider || firebase.firebase?.sign_in_provider === providerId));
88
+ };
89
+ /**
90
+ * Create a short-lived Sign in with Apple `client_secret` (ES256 JWT).
91
+ *
92
+ * The JWT is issued for `clientId` as `sub`, audience {@link APPLE_IDENTITY_ISSUER}, and expires
93
+ * 120 seconds after `now`. Used for Apple's token and revoke endpoints.
94
+ *
95
+ * @param config - Apple Team ID, key id, and PKCS#8 private key.
96
+ * @param clientId - Apple Services ID or native bundle id (`sub` claim).
97
+ * @param now - Unix time in seconds for `iat` / `exp`; injectable for deterministic tests.
98
+ * Defaults to the system clock.
99
+ * @returns A compact ES256 JWT suitable as Apple's `client_secret`.
100
+ */
101
+ export const createAppleClientSecret = async (config, clientId, now = Math.floor(Date.now() / 1000)) => new SignJWT({})
102
+ .setProtectedHeader({ alg: 'ES256', kid: config.keyId })
103
+ .setIssuer(config.teamId)
104
+ .setIssuedAt(now)
105
+ .setExpirationTime(now + 120)
106
+ .setAudience(APPLE_IDENTITY_ISSUER)
107
+ .setSubject(clientId)
108
+ .sign(await importPKCS8(config.privateKey, 'ES256'));
package/dist/index.d.ts CHANGED
@@ -91,3 +91,5 @@ export { FirebaseIdTokenValidationError, JoseFirebaseVerifier, SECURETOKEN_JWK_U
91
91
  export { IdentityToolkit } from './firebase/identity-toolkit.js';
92
92
  export type { ServiceAccount } from './firebase/identity-toolkit.js';
93
93
  export { createRemoteFirebaseVerifier, createServiceAccountVerifier } from './firebase/remote-verifier.js';
94
+ export { APPLE_IDENTITY_ISSUER, GOOGLE_IDENTITY_ISSUERS, createAppleClientSecret, hasFirebaseProviderIdentity, verifyAppleIdentityToken, verifyGoogleIdentityToken, } from './firebase/social-auth.js';
95
+ export type { AppleClientSecretConfig } from './firebase/social-auth.js';
package/dist/index.js CHANGED
@@ -67,3 +67,4 @@ export { getTemporaryCredentials } from './aws/sts.js';
67
67
  export { FirebaseIdTokenValidationError, JoseFirebaseVerifier, SECURETOKEN_JWK_URL, } from './firebase/jose-firebase-verifier.js';
68
68
  export { IdentityToolkit } from './firebase/identity-toolkit.js';
69
69
  export { createRemoteFirebaseVerifier, createServiceAccountVerifier } from './firebase/remote-verifier.js';
70
+ export { APPLE_IDENTITY_ISSUER, GOOGLE_IDENTITY_ISSUERS, createAppleClientSecret, hasFirebaseProviderIdentity, verifyAppleIdentityToken, verifyGoogleIdentityToken, } from './firebase/social-auth.js';
package/docs/api-db.md CHANGED
@@ -27,7 +27,7 @@ compatibility_flags = ["nodejs_compat"]
27
27
  | `Database` / `DisposableDatabase` / `HyperdriveDatabase` / `ReadTransaction` / `QueryRunner` / `TxOf` | The `read` / `query` / `readTransaction` / `write` / `transaction` API and its supporting types. |
28
28
  | `hyperdriveConnectionOptions(hyperdrive, overrides?)` / `HyperdriveLike` / `ExecutionContextLike` | Build mysql2 `createConnection` options from a Hyperdrive binding (`disableEval`, `decimalNumbers`, `timezone '+09:00'` by default). `timezone` controls mysql2's JavaScript `Date` conversion; it does not change the MySQL session timezone. |
29
29
  | `withMysqlConnections(...)` | Open primary/replica connections in parallel and run a function. Workers cleans them up at invocation end. |
30
- | `retryWhenDeadlock(fn, retries?, delay?)` | Retry on MySQL `ER_LOCK_DEADLOCK` with exponential backoff. |
30
+ | `retryWhenDeadlock(fn, retries?, delay?)` | Retry on MySQL `ER_LOCK_DEADLOCK` with a wait of `delay × attempt` between attempts. |
31
31
  | `insertIdOf` / `affectedRowsOf` / `insertedIdsOf` / `DzWriteResult` | Extract `insertId` / `affectedRows` (and derive contiguous bulk-insert ids) from a mysql2 write result. |
32
32
  | `toJstDate` / `jstTimestampParams` / `jstDatetimeParams` / `jstDateParams` | JST date/time normalization params (advanced use). |
33
33
  | `MYSQL_TIMEZONE` | Default mysql2 connection `timezone` (`'+09:00'`) for the JST DB deployment. |
@@ -3,11 +3,6 @@
3
3
  Standalone MySQL / Hyperdrive access for Workers, plus the thin Hono container adapter. Fixed
4
4
  `+09:00` storage helpers are independent of IANA display timezones.
5
5
 
6
- - [HTTP and Authentication](./http-auth.md)
7
- - [Realtime and Offline](./realtime-offline.md)
8
- - [Testing and Operations](./testing-operations.md)
9
- - [API reference](./api.md)
10
-
11
6
  Import database helpers from `@rdlabo/workers-mysql`. The package installs `mysql2` directly. Add
12
7
  `drizzle-orm` only when using the `/drizzle` or `/testing` entry point. The old
13
8
  `@rdlabo/workers-hono-kit/db` path is a deprecated compatibility re-export.
@@ -26,27 +21,11 @@ npm install -D @types/node@20
26
21
 
27
22
  For candidate tarball installation, see [Development](./development.md).
28
23
 
29
- ## Migrating from workers-hono-kit
30
-
31
- The package boundary is a breaking change in `0.12.0`. Update these imports before
32
- upgrading:
33
-
34
- | Current import | Replacement |
35
- | ------------------------------------------------------- | ----------------------------------------------------- |
36
- | `createContainerRuntime` from the kit root | `@rdlabo/workers-hono-kit/mysql` |
37
- | `retryWhenDeadlock` from the kit root | `@rdlabo/workers-mysql` |
38
- | DB helpers from `@rdlabo/workers-hono-kit/db` | `@rdlabo/workers-mysql`, `/drizzle`, or `/migrations` |
39
- | DB test helpers from `@rdlabo/workers-hono-kit/testing` | `@rdlabo/workers-mysql/testing` |
40
-
41
- The old `/db` and DB-related `/testing` exports remain available for backward compatibility.
42
- Their individual functions and types carry `@deprecated` notices pointing to the standalone
43
- package. No removal release is scheduled. The kit-owned `/mysql` adapter is not deprecated.
44
- Because `/testing` statically re-exports DB helpers, all kit `/testing` consumers must install the
45
- MySQL package and `drizzle-orm`, including consumers of non-DB helpers such as Firebase or KV fakes.
46
-
47
24
  ## Hyperdrive database
48
25
 
49
- `createHyperdriveDatabase()` lazily opens primary and replica connections from Hyperdrive bindings. `read()` uses the replica query runner; `query()` provides an explicit raw primary SELECT for read-after-write consistency; writes and transactions use the primary Drizzle instance. `readTransaction()` runs Drizzle and raw reads against one primary repeatable-read snapshot. Read transactions are serialized on one separately cached connection so their boundaries cannot mix with each other or with ordinary primary operations. After a fatal mysql2 connection error, a single read or the complete read-only transaction opens a fresh connection and repeats at most once. Writes and write transactions are not repeated because their commit state may be ambiguous. Workers owns connection cleanup at invocation end.
26
+ `createHyperdriveDatabase()` lazily opens primary and replica connections from Hyperdrive bindings.
27
+ For read/write paths, retry boundaries, and invocation lifetime, see
28
+ [Workers MySQL Runtime](https://docs.rdlabo.dev/projects/workers-mysql/docs/runtime).
50
29
 
51
30
  ```ts
52
31
  import { createHyperdriveDatabase } from '@rdlabo/workers-mysql';
@@ -84,7 +63,7 @@ import { createContainerRuntime } from '@rdlabo/workers-hono-kit/mysql';
84
63
 
85
64
  ## Writes and retries
86
65
 
87
- - `retryWhenDeadlock()` retries `ER_LOCK_DEADLOCK` with exponential backoff.
66
+ - `retryWhenDeadlock()` retries `ER_LOCK_DEADLOCK` with a wait of `delay × attempt` between attempts.
88
67
  - `insertIdOf()`, `affectedRowsOf()`, and `insertedIdsOf()` normalize mysql2 write results.
89
68
  - `withMysqlConnections()` opens primary and replica connections in parallel for a scoped operation.
90
69
 
@@ -110,6 +89,24 @@ addBusinessDays('2026-07-06', 3);
110
89
  // '2026-07-09'
111
90
  ```
112
91
 
92
+ ## Migrating from workers-hono-kit
93
+
94
+ The package boundary is a breaking change in `0.12.0`. Update these imports before
95
+ upgrading:
96
+
97
+ | Current import | Replacement |
98
+ | ------------------------------------------------------- | ----------------------------------------------------- |
99
+ | `createContainerRuntime` from the kit root | `@rdlabo/workers-hono-kit/mysql` |
100
+ | `retryWhenDeadlock` from the kit root | `@rdlabo/workers-mysql` |
101
+ | DB helpers from `@rdlabo/workers-hono-kit/db` | `@rdlabo/workers-mysql`, `/drizzle`, or `/migrations` |
102
+ | DB test helpers from `@rdlabo/workers-hono-kit/testing` | `@rdlabo/workers-mysql/testing` |
103
+
104
+ The old `/db` and DB-related `/testing` exports remain available for backward compatibility.
105
+ Their individual functions and types carry `@deprecated` notices pointing to the standalone
106
+ package. No removal release is scheduled. The kit-owned `/mysql` adapter is not deprecated.
107
+ Because `/testing` statically re-exports DB helpers, all kit `/testing` consumers must install the
108
+ MySQL package and `drizzle-orm`, including consumers of non-DB helpers such as Firebase or KV fakes.
109
+
113
110
  ## Next step
114
111
 
115
112
  Continue to [Realtime and Offline](./realtime-offline.md), or see
package/docs/http-auth.md CHANGED
@@ -3,11 +3,6 @@
3
3
  Validation, Firebase authentication, shared NestJS-shaped error bodies, and response finalization
4
4
  for Hono Workers APIs.
5
5
 
6
- - [Data Layer](./data-layer.md)
7
- - [Realtime and Offline](./realtime-offline.md)
8
- - [Testing and Operations](./testing-operations.md)
9
- - [API reference](./api.md)
10
-
11
6
  ## Validation
12
7
 
13
8
  `validate(target, schema, options?)` adapts a Zod schema to Hono and returns a NestJS `ValidationPipe`-shaped `400` response. Use `createValidate({ sentry })` to bind optional reporting once.
@@ -0,0 +1,71 @@
1
+ ---
2
+ title: Try a Hono API locally
3
+ ---
4
+
5
+ Give a Hono API consistent HTTP behavior: a health response with a weak ETag and a predictable JSON response for missing routes. You will see both without opening a port or creating a Cloudflare account.
6
+
7
+ ## 1. Create a small project
8
+
9
+ Use Node.js 24 and npm for this exercise. The commands pin the documented kit release. npm installs its required peers; package managers configured to omit peers must follow the [installation requirements](../README.md).
10
+
11
+ ```sh
12
+ mkdir hono-kit-demo
13
+ cd hono-kit-demo
14
+ npm init -y
15
+ npm pkg set type=module
16
+ npm install @rdlabo/workers-hono-kit@0.12.2 hono@4
17
+ npm install --save-dev tsx@4
18
+ ```
19
+
20
+ ## 2. Send two requests
21
+
22
+ Save this as `demo.ts`. `app.request()` exercises the Hono app in the current process.
23
+
24
+ ```ts
25
+ import { Hono } from 'hono';
26
+ import { createAppErrorHandler, finalizeResponse, notFoundHandler } from '@rdlabo/workers-hono-kit';
27
+
28
+ const app = new Hono();
29
+ app.use('*', finalizeResponse());
30
+ app.onError(createAppErrorHandler());
31
+ app.notFound(notFoundHandler);
32
+ app.get('/health', (c) => c.json({ ok: true }));
33
+
34
+ const healthy = await app.request('/health');
35
+ console.log(healthy.status, await healthy.text());
36
+ console.log('weak etag:', healthy.headers.get('etag')?.startsWith('W/'));
37
+
38
+ const missing = await app.request('/missing');
39
+ console.log(missing.status, await missing.text());
40
+ ```
41
+
42
+ ```sh
43
+ npx tsx demo.ts
44
+ ```
45
+
46
+ Expected output (JSON property order does not matter):
47
+
48
+ ```text
49
+ 200 {"ok":true}
50
+ weak etag: true
51
+ 404 {"message":"Cannot GET /missing","error":"Not Found","statusCode":404}
52
+ ```
53
+
54
+ You have checked the HTTP behavior added by the kit. This exercise does not provision Workers bindings, authenticate users, or test a deployed service.
55
+
56
+ ## 3. Move into your application
57
+
58
+ Keep the middleware and route registrations, remove the demonstration requests, and export `app` as your Worker handler. Continue with [HTTP and authentication](./http-auth.md). Add the [MySQL adapter](./data-layer.md) only when the application needs a database.
59
+
60
+ Existing kit users should check [the 0.12 import migration](./data-layer.md) before upgrading. The old `/db` and `/business-time` paths are compatibility exports; new integrations use the standalone packages.
61
+
62
+ ## Next steps
63
+
64
+ | You need | Start with |
65
+ | ------------------------------------------- | ---------------------------------------------------------------------------------------------- |
66
+ | Shared Hono HTTP, auth, or queue behavior | `@rdlabo/workers-hono-kit` |
67
+ | MySQL access, with or without Hono | [Workers MySQL](https://docs.rdlabo.dev/projects/workers-mysql/docs/quickstart) |
68
+ | Timezone conversions and checks on new code | [Workers Timezone + ESLint](https://docs.rdlabo.dev/projects/workers-timezone/docs/quickstart) |
69
+ | Code conventions during development | [ESLint Plugin Rules](https://docs.rdlabo.dev/projects/eslint-plugin-rules/docs/quickstart) |
70
+
71
+ The kit supplies reusable infrastructure. Your routes, domain rules, credentials, and database schema remain in your application. Adopt one helper first; you do not need to adopt every entry point.
@@ -3,11 +3,6 @@
3
3
  Durable Object WebSocket helpers and table-agnostic offline replica contracts. Product schemas,
4
4
  Zod shapes, and domain policy stay in the application.
5
5
 
6
- - [HTTP and Authentication](./http-auth.md)
7
- - [Data Layer](./data-layer.md)
8
- - [Testing and Operations](./testing-operations.md)
9
- - [API reference](./api.md)
10
-
11
6
  ## Durable Object realtime
12
7
 
13
8
  The root and `/realtime` entry points expose the same focused realtime primitives:
@@ -2,11 +2,6 @@
2
2
 
3
3
  Test helpers, queue batching, operational CLIs, and trust boundaries for Hono Workers applications.
4
4
 
5
- - [HTTP and Authentication](./http-auth.md)
6
- - [Data Layer](./data-layer.md)
7
- - [Realtime and Offline](./realtime-offline.md)
8
- - [API reference](./api.md)
9
-
10
5
  ## Testing entry point
11
6
 
12
7
  `@rdlabo/workers-hono-kit/testing` is never loaded by production code. Its DB helpers are deprecated
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rdlabo/workers-hono-kit",
3
- "version": "0.12.2",
3
+ "version": "0.12.3-beta.pr51.sha3289ea609bb4",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -108,8 +108,8 @@
108
108
  },
109
109
  "peerDependencies": {
110
110
  "@hono/zod-validator": "^0.8.0",
111
- "@rdlabo/workers-mysql": "^0.12.2",
112
- "@rdlabo/workers-timezone": "^0.12.2",
111
+ "@rdlabo/workers-mysql": "0.12.3-beta.pr51.sha3289ea609bb4",
112
+ "@rdlabo/workers-timezone": "0.12.3-beta.pr51.sha3289ea609bb4",
113
113
  "ai": "^6.0.0",
114
114
  "ai-gateway-provider": "^3.1.0",
115
115
  "aws4fetch": "^1.0.20",