@rdlabo/workers-hono-kit 0.10.6 → 0.11.1
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/LICENSE +1 -1
- package/README.md +40 -764
- package/dist/db/columns.d.ts +0 -10
- package/dist/db/columns.js +1 -5
- package/dist/db/database.d.ts +34 -9
- package/dist/db/database.js +49 -9
- package/dist/db/index.d.ts +2 -4
- package/dist/db/index.js +1 -2
- package/docs/api-business-time.md +33 -0
- package/docs/api-db.md +49 -0
- package/docs/api-offline.md +67 -0
- package/docs/api-root.md +71 -0
- package/docs/api-testing.md +16 -0
- package/docs/api.md +12 -0
- package/docs/cli.md +33 -0
- package/docs/data-layer.md +45 -0
- package/docs/development.md +32 -0
- package/docs/http-auth.md +36 -0
- package/docs/realtime-offline.md +27 -0
- package/docs/role-policies.md +48 -0
- package/docs/testing-operations.md +25 -0
- package/package.json +5 -4
- package/scripts/check-realtime-bundle.mjs +0 -0
- package/scripts/query-realtime-do-metrics.mjs +0 -0
- package/dist/db/decimal.d.ts +0 -35
- package/dist/db/decimal.js +0 -58
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
## Validation
|
|
2
|
+
|
|
3
|
+
`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.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { createValidate, zNumOptional } from '@rdlabo/workers-hono-kit';
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
|
|
9
|
+
const validate = createValidate({ sentry });
|
|
10
|
+
const querySchema = z.object({ page: zNumOptional() });
|
|
11
|
+
|
|
12
|
+
app.get('/items', validate('query', querySchema), async (c) => {
|
|
13
|
+
const query = c.req.valid('query');
|
|
14
|
+
return c.json(await listItems(query.page));
|
|
15
|
+
});
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Authentication
|
|
19
|
+
|
|
20
|
+
`createAuthMiddleware()` reads a token header, verifies a Firebase ID token, optionally resolves the application user ID, and stores the result on the Hono context. Use `createRemoteFirebaseVerifier(projectId)` for cached remote JWKS verification or `createServiceAccountVerifier()` when Identity Toolkit `getUser` and `deleteUser` operations are required.
|
|
21
|
+
|
|
22
|
+
Keep identity, reauthentication, and feature credential failures distinct with the stable auth-failure body helpers.
|
|
23
|
+
|
|
24
|
+
## Error and routing contracts
|
|
25
|
+
|
|
26
|
+
- `createAppErrorHandler()` composes query failure classification, generic mysql2 classification, and optional reporting.
|
|
27
|
+
- `createHttpErrorHandler()` maps `HTTPException` to the shared JSON error body.
|
|
28
|
+
- `notFoundHandler()` returns `Cannot METHOD path` with a 404 status.
|
|
29
|
+
- `normalizeTrailingSlash()` removes trailing slashes without redirecting, preserving request bodies.
|
|
30
|
+
- `finalizeResponse()` adds weak ETags and handles matching `If-None-Match` requests.
|
|
31
|
+
|
|
32
|
+
Mount `createMaintenanceMiddleware()` after CORS and before container or database middleware so maintenance responses do not initialize expensive infrastructure.
|
|
33
|
+
|
|
34
|
+
## Deferred work and observability
|
|
35
|
+
|
|
36
|
+
`createWaitUntilDefer(ctx)` registers background work through `waitUntil` and logs rejected work. `perfLog()` emits per-request application latency, colo, cold/warm state, route, and status to Workers Logs and optionally Analytics Engine.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
## Durable Object realtime
|
|
2
|
+
|
|
3
|
+
The root and `/realtime` entry points expose the same focused realtime primitives:
|
|
4
|
+
|
|
5
|
+
- `configureHibernationAutoResponse()` configures runtime ping/pong without waking JavaScript.
|
|
6
|
+
- `upgradeHibernationWebSocket()` attaches state before accepting the socket.
|
|
7
|
+
- `broadcastHibernationWebSockets()` broadcasts through sockets restored by `getWebSockets()`.
|
|
8
|
+
- `acknowledgeHibernationWebSocketClose()` and `closeHibernationWebSocket()` normalize close handling.
|
|
9
|
+
- `retryDurableObjectOperation()` retries only errors marked `retryable` and not `overloaded`. Create a fresh stub inside the operation for every attempt.
|
|
10
|
+
- `invokeDurableObjectFetch()` preserves the structured response/error contract for DO calls.
|
|
11
|
+
|
|
12
|
+
WebSocket protocol parsers validate offered subprotocols before upgrade.
|
|
13
|
+
|
|
14
|
+
## Offline replica contracts
|
|
15
|
+
|
|
16
|
+
`@rdlabo/workers-hono-kit/offline` is table-agnostic. Product schemas, Zod objects, public-column allowlists, schema hashes, and domain policy stay in the application.
|
|
17
|
+
|
|
18
|
+
`defineRestDbMethodConverter()` types a pure REST method ↔ table converter. Every represented table and column is required, including nullable/default columns. Omit an auto-increment `id` from the product-owned table scheme when a create method intentionally does not own it.
|
|
19
|
+
|
|
20
|
+
Wire helpers canonicalize values:
|
|
21
|
+
|
|
22
|
+
- `toReplicaIsoDatetime()` → UTC ISO-8601
|
|
23
|
+
- `toReplicaDateOnly()` → `YYYY-MM-DD` or `null`
|
|
24
|
+
- `toTinyIntFlag()` / `fromTinyIntFlag()` → boolean/tinyint conversion
|
|
25
|
+
- `replicaNowIso(clock?)` → injectable current time
|
|
26
|
+
|
|
27
|
+
Journal helpers enforce cursor coverage, retention, mutation transactions, and rebaseline behavior. Wire compatibility helpers let an application accept explicit previous fingerprints while maintaining a canonical current fingerprint.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Storage-agnostic role policies
|
|
2
|
+
|
|
3
|
+
`createRolePolicy` builds pure RBAC checks without coupling the policy to a database schema. The application can resolve roles from a membership table, a `users.role` column, token claims, or any other source.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { createRolePolicy } from '@rdlabo/workers-hono-kit';
|
|
7
|
+
|
|
8
|
+
type Role = 'owner' | 'admin' | 'member' | 'read';
|
|
9
|
+
type Permission = 'organization.manage' | 'resource.write' | 'resource.read';
|
|
10
|
+
|
|
11
|
+
const policy = createRolePolicy<Role, Permission>({
|
|
12
|
+
permissions: {
|
|
13
|
+
owner: ['organization.manage', 'resource.write', 'resource.read'],
|
|
14
|
+
admin: ['resource.write', 'resource.read'],
|
|
15
|
+
member: ['resource.write', 'resource.read'],
|
|
16
|
+
read: ['resource.read'],
|
|
17
|
+
},
|
|
18
|
+
assignableRoles: {
|
|
19
|
+
owner: ['admin', 'member', 'read'],
|
|
20
|
+
admin: ['member', 'read'],
|
|
21
|
+
member: [],
|
|
22
|
+
read: [],
|
|
23
|
+
},
|
|
24
|
+
manageableRoles: {
|
|
25
|
+
owner: ['admin', 'member', 'read'],
|
|
26
|
+
admin: ['member', 'read'],
|
|
27
|
+
member: [],
|
|
28
|
+
read: [],
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Policy fields
|
|
34
|
+
|
|
35
|
+
- `permissions` maps a role to the set of permissions it grants.
|
|
36
|
+
- `assignableRoles` defines which roles an actor may grant to another subject.
|
|
37
|
+
- `manageableRoles` defines which existing subject roles an actor may manage.
|
|
38
|
+
|
|
39
|
+
The resulting `RolePolicy` has four pure checks:
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
policy.hasPermission('member', 'resource.write'); // true
|
|
43
|
+
policy.canAssignRole('admin', 'member'); // true
|
|
44
|
+
policy.canManageRole('owner', 'admin'); // true
|
|
45
|
+
policy.canChangeRole('owner', 'admin', 'member'); // true
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
`canChangeRole(actor, current, next)` is a combination: the actor must be able to manage the subject's current role and also be allowed to assign the next role. Keeping role lookup and policy checks separate means the same policy can be reused no matter where roles are stored.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
## Testing entry point
|
|
2
|
+
|
|
3
|
+
`@rdlabo/workers-hono-kit/testing` requires the database peers and is never loaded by production code.
|
|
4
|
+
|
|
5
|
+
| Helper | Use |
|
|
6
|
+
| --------------------------------------------------------------- | --------------------------------------------------------------------- |
|
|
7
|
+
| `createTestDb()` | Build a Drizzle-migration-backed test database. |
|
|
8
|
+
| `FakeFirebaseVerifier` | Verify registered in-memory Firebase tokens. |
|
|
9
|
+
| `createPoolDatabase()` / `createNoopDatabase()` | Provide database implementations for tests. |
|
|
10
|
+
| `authHeaders()` / `registerFirebaseToken()` / `provisionUser()` | Prepare authenticated route tests. |
|
|
11
|
+
| `configurableFake()` | Create a partial fake that fails explicitly for unconfigured members. |
|
|
12
|
+
| `fakeKv()` / `fakeQueue()` | Use in-memory Workers binding fakes. |
|
|
13
|
+
| Stripe fixture factories | Create typed events, sessions, subscriptions, prices, and intents. |
|
|
14
|
+
|
|
15
|
+
## Queues
|
|
16
|
+
|
|
17
|
+
`sendInChunks()` bounds queue sends under Workers subrequest limits. `processBatch()` handles a message batch sequentially, bounding concurrent subrequests to one; errors explicitly marked with `queueDisposition: 'discard'` are acknowledged, while other failures retry. `createQueueErrorHandler()` adds logging and optional final-attempt reporting.
|
|
18
|
+
|
|
19
|
+
## Operational CLI
|
|
20
|
+
|
|
21
|
+
The package publishes commands for synchronizing development AWS credentials, checking subrequest fanout, creating database baselines, checking realtime bundles, and querying Durable Object metrics. Run the exact CLI shipped with the installed package version and review its `--help` before changing infrastructure.
|
|
22
|
+
|
|
23
|
+
## Trust boundaries
|
|
24
|
+
|
|
25
|
+
AWS, Firebase, AI Gateway, Stripe, and database clients are configured by the consuming application. Do not place domain-specific credentials, schemas, or authorization policy inside the shared kit. Use `createRolePolicy()` only for storage-agnostic role and relation mapping; the application still owns its roles and permissions.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rdlabo/workers-hono-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -11,12 +11,12 @@
|
|
|
11
11
|
"license": "MIT",
|
|
12
12
|
"repository": {
|
|
13
13
|
"type": "git",
|
|
14
|
-
"url": "git+https://github.com/rdlabo-
|
|
14
|
+
"url": "git+https://github.com/rdlabo-dev/workers-hono-kit.git"
|
|
15
15
|
},
|
|
16
16
|
"bugs": {
|
|
17
|
-
"url": "https://github.com/rdlabo-
|
|
17
|
+
"url": "https://github.com/rdlabo-dev/workers-hono-kit/issues"
|
|
18
18
|
},
|
|
19
|
-
"homepage": "https://
|
|
19
|
+
"homepage": "https://docs.rdlabo.dev/projects/workers-hono-kit",
|
|
20
20
|
"keywords": [
|
|
21
21
|
"hono",
|
|
22
22
|
"cloudflare-workers",
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"files": [
|
|
44
44
|
"dist",
|
|
45
45
|
"scripts",
|
|
46
|
+
"docs",
|
|
46
47
|
"!src/**/*.spec.ts"
|
|
47
48
|
],
|
|
48
49
|
"bin": {
|
|
File without changes
|
|
File without changes
|
package/dist/db/decimal.d.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Drizzle `customType` params for a MySQL `DECIMAL` column.
|
|
3
|
-
*
|
|
4
|
-
* @remarks
|
|
5
|
-
* - **Reads (SELECT)**: `fromDriver` unifies the driver value (`number` / `string` / `null`) to a JS
|
|
6
|
-
* `number | null`. Combined with the connection's `decimalNumbers: true`
|
|
7
|
-
* ({@link hyperdriveConnectionOptions} default), it aligns values to numbers even on the Drizzle
|
|
8
|
-
* builder path when strings like `"0"` / `"100.00"` slip in, without dropping `0`.
|
|
9
|
-
* - **Writes (INSERT/UPDATE)**: `toDriver` binds the number to mysql2 as-is (no `String()` conversion).
|
|
10
|
-
* - Raw-SQL `db.read` relies on the connection's `decimalNumbers: true`; the column's `fromDriver` is
|
|
11
|
-
* for the Drizzle `select` path.
|
|
12
|
-
*/
|
|
13
|
-
export interface DecimalNumberConfig {
|
|
14
|
-
precision: number;
|
|
15
|
-
scale: number;
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Normalize a DECIMAL value coming from mysql2 / Drizzle to a JS `number | null`.
|
|
19
|
-
* `0` is preserved as-is so it is not dropped as falsy.
|
|
20
|
-
*
|
|
21
|
-
* @param value - the raw driver value (`number` / `string` / `bigint` / nullish).
|
|
22
|
-
* @returns the coerced finite number, or `null` when it cannot be resolved.
|
|
23
|
-
*/
|
|
24
|
-
export declare function coerceDecimalNumber(value: unknown): number | null;
|
|
25
|
-
/**
|
|
26
|
-
* Params for a `customType`. For advanced use; the {@link decimalNumber} column helper is usually enough.
|
|
27
|
-
*
|
|
28
|
-
* @param config - the DECIMAL `precision` / `scale`.
|
|
29
|
-
* @returns the `customType` params (`dataType` / `fromDriver` / `toDriver`).
|
|
30
|
-
*/
|
|
31
|
-
export declare const decimalNumberParams: (config: DecimalNumberConfig) => {
|
|
32
|
-
dataType: () => string;
|
|
33
|
-
fromDriver: (value: unknown) => number | null;
|
|
34
|
-
toDriver: (value: number | string | null) => number | string | null;
|
|
35
|
-
};
|
package/dist/db/decimal.js
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Drizzle `customType` params for a MySQL `DECIMAL` column.
|
|
3
|
-
*
|
|
4
|
-
* @remarks
|
|
5
|
-
* - **Reads (SELECT)**: `fromDriver` unifies the driver value (`number` / `string` / `null`) to a JS
|
|
6
|
-
* `number | null`. Combined with the connection's `decimalNumbers: true`
|
|
7
|
-
* ({@link hyperdriveConnectionOptions} default), it aligns values to numbers even on the Drizzle
|
|
8
|
-
* builder path when strings like `"0"` / `"100.00"` slip in, without dropping `0`.
|
|
9
|
-
* - **Writes (INSERT/UPDATE)**: `toDriver` binds the number to mysql2 as-is (no `String()` conversion).
|
|
10
|
-
* - Raw-SQL `db.read` relies on the connection's `decimalNumbers: true`; the column's `fromDriver` is
|
|
11
|
-
* for the Drizzle `select` path.
|
|
12
|
-
*/
|
|
13
|
-
/**
|
|
14
|
-
* Normalize a DECIMAL value coming from mysql2 / Drizzle to a JS `number | null`.
|
|
15
|
-
* `0` is preserved as-is so it is not dropped as falsy.
|
|
16
|
-
*
|
|
17
|
-
* @param value - the raw driver value (`number` / `string` / `bigint` / nullish).
|
|
18
|
-
* @returns the coerced finite number, or `null` when it cannot be resolved.
|
|
19
|
-
*/
|
|
20
|
-
export function coerceDecimalNumber(value) {
|
|
21
|
-
if (value === null || value === undefined) {
|
|
22
|
-
return null;
|
|
23
|
-
}
|
|
24
|
-
if (typeof value === 'number') {
|
|
25
|
-
return Number.isFinite(value) ? value : null;
|
|
26
|
-
}
|
|
27
|
-
if (typeof value === 'string') {
|
|
28
|
-
const trimmed = value.trim();
|
|
29
|
-
if (trimmed === '') {
|
|
30
|
-
return null;
|
|
31
|
-
}
|
|
32
|
-
const n = Number(trimmed);
|
|
33
|
-
return Number.isFinite(n) ? n : null;
|
|
34
|
-
}
|
|
35
|
-
if (typeof value === 'bigint') {
|
|
36
|
-
return Number(value);
|
|
37
|
-
}
|
|
38
|
-
return null;
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Params for a `customType`. For advanced use; the {@link decimalNumber} column helper is usually enough.
|
|
42
|
-
*
|
|
43
|
-
* @param config - the DECIMAL `precision` / `scale`.
|
|
44
|
-
* @returns the `customType` params (`dataType` / `fromDriver` / `toDriver`).
|
|
45
|
-
*/
|
|
46
|
-
export const decimalNumberParams = (config) => ({
|
|
47
|
-
dataType: () => `decimal(${config.precision},${config.scale})`,
|
|
48
|
-
fromDriver: (value) => coerceDecimalNumber(value),
|
|
49
|
-
toDriver: (value) => {
|
|
50
|
-
if (value === null) {
|
|
51
|
-
return null;
|
|
52
|
-
}
|
|
53
|
-
if (typeof value === 'number') {
|
|
54
|
-
return value;
|
|
55
|
-
}
|
|
56
|
-
return coerceDecimalNumber(value);
|
|
57
|
-
},
|
|
58
|
-
});
|