hono-crud 0.13.13 → 0.13.15
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/CHANGELOG.md +155 -0
- package/README.md +11 -10
- package/dist/api-version/index.d.ts +5 -67
- package/dist/api-version/index.js +1 -1
- package/dist/audit/index.d.ts +32 -7
- package/dist/audit/index.js +1 -1
- package/dist/auth/index.d.ts +573 -5
- package/dist/auth/index.js +1 -1
- package/dist/builder/index.d.ts +406 -0
- package/dist/builder/index.js +1 -0
- package/dist/chunk-54F22WTP.js +11 -0
- package/dist/chunk-57MTTJMU.js +1 -0
- package/dist/chunk-5P4RVSHT.js +1 -0
- package/dist/chunk-6GZBIUE2.js +1 -0
- package/dist/chunk-A27HDYSF.js +1 -0
- package/dist/chunk-CTOFQ5RC.js +1 -0
- package/dist/chunk-CWQSQUV4.js +1 -0
- package/dist/chunk-E3MK476S.js +1 -0
- package/dist/chunk-H3VBYIDA.js +1 -0
- package/dist/chunk-HYXDMJ4K.js +1 -0
- package/dist/chunk-L5CVVJQH.js +1 -0
- package/dist/chunk-O62WFEW2.js +1 -0
- package/dist/chunk-P7HU2KIE.js +1 -0
- package/dist/chunk-SDNXN7M5.js +1 -0
- package/dist/chunk-TLI3TRUA.js +1 -0
- package/dist/chunk-V7ABUFW5.js +1 -0
- package/dist/chunk-WBHWKOTP.js +1 -0
- package/dist/chunk-XR6JRDGX.js +1 -0
- package/dist/chunk-YB6AVUPQ.js +1 -0
- package/dist/cloudflare/index.d.ts +78 -0
- package/dist/config/index.d.ts +12 -6
- package/dist/config/index.js +1 -1
- package/dist/context-keys-Dxyh3NuH.d.ts +2313 -0
- package/dist/context-m0qIRK5d.d.ts +21 -0
- package/dist/contracts-C3YJpWrM.d.ts +224 -0
- package/dist/{emitter-B6dLhiMF.d.ts → emitter-B8EL76d3.d.ts} +8 -61
- package/dist/encryption/index.d.ts +2 -2
- package/dist/encryption/index.js +1 -1
- package/dist/events/index.d.ts +12 -6
- package/dist/events/index.js +1 -1
- package/dist/functional/index.d.ts +35 -14
- package/dist/functional/index.js +1 -1
- package/dist/health/index.d.ts +101 -0
- package/dist/health/index.js +1 -0
- package/dist/index-B2punCHK.d.ts +2080 -0
- package/dist/index.d.ts +675 -24
- package/dist/index.js +2 -1
- package/dist/internal.d.ts +26 -51
- package/dist/internal.js +1 -1
- package/dist/jwt-DhJ3yOR6.d.ts +48 -0
- package/dist/logging/index.d.ts +34 -16
- package/dist/logging/index.js +1 -1
- package/dist/memory-DfSspNoL.d.ts +132 -0
- package/dist/memory-ttl-store-D2Bbo-do.d.ts +116 -0
- package/dist/middleware-CrCo6EgQ.d.ts +104 -0
- package/dist/multi-tenant/index.d.ts +28 -8
- package/dist/multi-tenant/index.js +1 -1
- package/dist/path-match-DGLu6wld.d.ts +22 -0
- package/dist/{registry-3p4qTDGZ.d.ts → registry-DYXgzGt0.d.ts} +5 -33
- package/dist/request-info-OQ40JnWp.d.ts +24 -0
- package/dist/{route-C6T6CTON.d.ts → route-CVsl4dg3.d.ts} +1 -1
- package/dist/serialization/index.js +1 -1
- package/dist/storage/index.d.ts +143 -68
- package/dist/storage/index.js +1 -1
- package/dist/{middleware-DBIpdsJ1.d.ts → types-B3XBv6XB.d.ts} +11 -98
- package/dist/{types-zAClTFVv.d.ts → types-BTRpRT28.d.ts} +2 -2
- package/dist/types-COJwpQfh.d.ts +67 -0
- package/dist/types-ChLYHg52.d.ts +55 -0
- package/dist/{types-tthfGTqv.d.ts → types-D72szrPV.d.ts} +107 -43
- package/dist/{types-Drjma4gp.d.ts → types-lAPVBoYa.d.ts} +1 -1
- package/dist/types-xNoq2dk9.d.ts +103 -0
- package/dist/versioning/index.d.ts +28 -7
- package/dist/versioning/index.js +1 -1
- package/dist/wait-until-ClcuhBgw.d.ts +12 -0
- package/package.json +12 -4
- package/dist/chunk-6GO5LUOZ.js +0 -1
- package/dist/chunk-6MS7YXSZ.js +0 -1
- package/dist/chunk-6XVCICWS.js +0 -1
- package/dist/chunk-BSQHHUG2.js +0 -11
- package/dist/chunk-CPIXFQGF.js +0 -1
- package/dist/chunk-EC6JI76L.js +0 -1
- package/dist/chunk-EX4S3Q4M.js +0 -1
- package/dist/chunk-IOWLCLF6.js +0 -1
- package/dist/chunk-JEOLCWK3.js +0 -1
- package/dist/chunk-JITAQDZZ.js +0 -1
- package/dist/chunk-JNRSTMFA.js +0 -1
- package/dist/chunk-MCXQ77DB.js +0 -1
- package/dist/chunk-OCJC5XWY.js +0 -1
- package/dist/chunk-OLSYHJGK.js +0 -1
- package/dist/chunk-PDHKGPGZ.js +0 -1
- package/dist/chunk-RVKM7SXJ.js +0 -1
- package/dist/chunk-STAO4FWC.js +0 -1
- package/dist/chunk-TILULOEV.js +0 -1
- package/dist/chunk-VESRPXGC.js +0 -1
- package/dist/chunk-VJRDAVID.js +0 -1
- package/dist/cloudflare-kv-types-ByUEHhBc.d.ts +0 -30
- package/dist/helpers-CJpjtX-9.d.ts +0 -515
- package/dist/index-DFYo-Us4.d.ts +0 -891
- package/dist/index-DgV651ue.d.ts +0 -683
- package/dist/index-bMu1-qqN.d.ts +0 -4386
- package/dist/types/cloudflare.d.ts +0 -64
- /package/dist/{types/cloudflare.js → cloudflare/index.js} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,160 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.13.15
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 4ba4a85: Owner-scope relation includes (`?include=`) — security fix for cross-tenant exposure.
|
|
8
|
+
|
|
9
|
+
Previously, loading a relation via `?include=` fetched the related rows by foreign key alone, ignoring the **related** model's access scope. A caller who could read a parent row could therefore read a related row in another tenant (or a soft-deleted one) through the include — a cross-tenant data leak.
|
|
10
|
+
|
|
11
|
+
Relations can now declare a `scope` naming the related table's owner and soft-delete columns:
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
relations: {
|
|
15
|
+
post: {
|
|
16
|
+
type: 'belongsTo', model: 'posts', table: posts,
|
|
17
|
+
foreignKey: 'postId', localKey: 'id',
|
|
18
|
+
scope: { tenantField: 'authorId', softDeleteField: 'deletedAt' },
|
|
19
|
+
},
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
When set, included related rows are filtered to the request's resolved tenant id and exclude soft-deleted rows (unless `?withDeleted=true`), so a foreign key pointing at another tenant's row resolves to `null` (belongsTo/hasOne) or is omitted (hasMany). The filtering lives in the core orchestrator (`batchLoadRelations` / `loadRelationsForItem`), so it applies identically across the drizzle, memory, and prisma adapters; the endpoint threads the parent request's tenant id + `withDeleted` into `IncludeOptions.scope` (Read via core; List via each adapter).
|
|
24
|
+
|
|
25
|
+
New public types: `RelationScopeConfig`, `RelationRequestScope`; new fields `RelationConfig.scope` and `IncludeOptions.scope`; new protected `getRelationScope()` on the endpoint base class.
|
|
26
|
+
|
|
27
|
+
Backward-compatible and opt-in: relations without `scope` (or requests that resolve no tenant) behave exactly as before. Declare `scope` on any relation whose related model is access-scoped to close the leak.
|
|
28
|
+
|
|
29
|
+
Note: scoping is applied as a post-fetch filter in the orchestrator (related rows are fetched, then filtered before being mapped back onto the parent — they never reach the response), not yet pushed down to the adapter `WHERE`/`where`. Correct and leak-free; pushing the predicate into the query is a performance follow-up.
|
|
30
|
+
|
|
31
|
+
## 0.13.14
|
|
32
|
+
|
|
33
|
+
### Patch Changes
|
|
34
|
+
|
|
35
|
+
- 376d8d8: Adapter correctness batch:
|
|
36
|
+
|
|
37
|
+
- **Prisma soft-delete**: `read` and `update` now exclude soft-deleted rows (previously deleted data stayed readable and updatable on Prisma only), and `PrismaUpdateEndpoint` implements `findExisting` — restoring write policies, ETag If-Match, versioning, and audit prior-state capture that silently no-oped.
|
|
38
|
+
- **Prisma wiring parity**: new `getPrismaClient` resolver (`_tx` → class field → `CONTEXT_KEYS.prismaClient` context slot) and `createPrismaCrud(prisma, meta)` factory mirroring the Drizzle adapter; the `prisma` class field is now optional.
|
|
39
|
+
- **Upsert match-and-restore**: upsert/import/batchUpsert now match soft-deleted rows and clear the soft-delete field on update, identically across all three adapters (previously single upsert skipped soft-deleted rows — unique-constraint errors on SQL — while batch upsert overwrote them without restoring). Drizzle native ON CONFLICT paths document their divergence.
|
|
40
|
+
- **BatchDelete** responses go through the full finalize pipeline (computed fields, serialization profile, transform) instead of serializer-only.
|
|
41
|
+
- **like/ilike contract**: one cross-adapter definition — literal substring needle (`%` stripped, `_` inert), `like` follows database collation case behavior, `ilike` always case-insensitive. Drizzle no longer passes live user wildcards into SQL LIKE and no longer emits PostgreSQL-only `ILIKE` on sqlite/mysql.
|
|
42
|
+
- **Workers waitUntil**: logging flush, cache invalidation (`withCacheInvalidation`), api-key `updateLastUsed`, and error-handler hooks now register through `waitUntil` so they survive the response on Cloudflare Workers. `emitAsync` removed (zero call sites). One exported `WaitUntil` type (`WaitUntilFn` removed).
|
|
43
|
+
- **Prisma LIKE wildcard leak**: Prisma's `contains` compiles to SQL LIKE without escaping, so user-supplied `_` acted as a live single-char wildcard in like/ilike filters and search (verified against Postgres). Needles are now escaped via `escapeLikeWildcards`.
|
|
44
|
+
- **Prisma `useTransaction` honored**: Create/Update/Delete now wrap `handle()` in `$transaction` when `useTransaction = true` (mirroring Drizzle) — previously the flag existed but did nothing on single-record verbs, so hooks saw no transaction and an after-hook throw did not roll back the write.
|
|
45
|
+
|
|
46
|
+
- a50d497: Core structure consolidation (internal — public import surface unchanged except three dead exports):
|
|
47
|
+
|
|
48
|
+
- One canonical CRUD route table (`CRUD_ROUTES`, exported via `hono-crud/internal`): all 22 endpoint slots as ordered `[name, verb, subPath]` rows with the registration-order invariants documented in one place. `registerCrud`'s 125-line if-chain is now a loop over it; the OpenAPI paths emitter's private duplicate table is gone; `CrudEndpointName` is derived from the table so it can never drift.
|
|
49
|
+
- Health is now a core subpath: `hono-crud/health` replaces the retired `@hono-crud/health` package (same API; zero deps and zero core coupling made a separate package pure overhead).
|
|
50
|
+
- New `hono-crud/cloudflare` module home (merges the former `types/` and `shared/` single-file directories).
|
|
51
|
+
- "Phase E" finished: auth context accessors live in `auth/context.ts` (also exported from `hono-crud/auth`), the back-compat shim `core/context-helpers.ts` is gone, and context reads use `CONTEXT_KEYS` constants instead of string literals.
|
|
52
|
+
- One canonical helper each: `getClientIp` (the `trustProxy` knob is now honored, library-wide default `true` — edge-first; logging middleware previously discarded `trustProxy: false`), one `PathPattern` (auth/logging/rate-limit re-export it), logging's pure delegation shims deleted.
|
|
53
|
+
- Removed dead exports: `createNullableRegistry`, `createRegistryWithDefault`, `PerTenantOpenApiConfig` (use `OpenAPIConfig`).
|
|
54
|
+
|
|
55
|
+
- e121270: Docs truth sweep — every shipped code sample now typechecks against the real API, plus the type-level fixes that pass surfaced:
|
|
56
|
+
|
|
57
|
+
- `withCache` / `withCacheInvalidation` / `withAuth` now accept abstract base classes and return an extendable constructor type. The documented `class X extends withCache(MemoryReadEndpoint)` pattern previously failed to compile for consumers (adapter endpoint classes are abstract, and the old `TBase & Constructor<...>` return type could not be extended — TS2510). Behavior unchanged; types only (`AbstractConstructor` is exported from `hono-crud/internal`).
|
|
58
|
+
- `PendingActionSchema` is now exported from `hono-crud/auth` — its JSDoc already directed storage-adapter authors to validate rows with it, but it was never re-exported.
|
|
59
|
+
- `PrismaClient` (the structural client constraint) is now exported from `@hono-crud/prisma` so consumers can name the type that `prisma = ...` and `createPrismaCrud(...)` accept.
|
|
60
|
+
- Corrected shipped JSDoc: the `withCache` and `AuthenticatedEndpoint` examples no longer show a `handle(ctx)` override (the registrar injects context; `handle()` is parameterless), and the lifecycle-hook docs no longer claim `fire-and-forget` is the default `afterHookMode` (the default is `sequential`).
|
|
61
|
+
|
|
62
|
+
- 55577a9: BREAKING: export-surface rewrite — the root barrel owns only the CRUD core; every feature family is importable only from its subpath.
|
|
63
|
+
|
|
64
|
+
The rule: each `hono-crud/<feature>` subpath barrel is the complete canonical surface of its feature family; the root `'hono-crud'` barrel owns only the CRUD core (model/meta, router/registrar, endpoint classes + result types, exceptions/error-handler, generic context helpers, core infra utils, OpenAPI utilities) and never exports renamed aliases; `hono-crud/internal` is now an explicit curated list (no `export *`).
|
|
65
|
+
|
|
66
|
+
Evicted from the root barrel (import from the subpath instead):
|
|
67
|
+
|
|
68
|
+
- `hono-crud/auth` — all middleware/guards/storage/validators/JWT-claims plus the auth context accessors (`getUser`, `getUserId`, `hasRole`, …) and types (`AuthEnv`, `JWTClaims`, `ApprovalStorage`, …).
|
|
69
|
+
- `hono-crud/logging` — `createLoggingMiddleware`, storage setters/getters, redact/extract/truncate utilities, `MemoryLoggingStorage`, `getRequestStartTime`, logging types. (`getRequestId`/`generateRequestId` remain on the root as generic context helpers.)
|
|
70
|
+
- `hono-crud/storage` — `createStorageMiddleware` + per-feature storage middlewares, `getStorage(ctx, key)` and resolvers, `StorageRegistry`, `StorageEnv`, storage contracts.
|
|
71
|
+
- `hono-crud/events` — `CrudEventEmitter`, emitter setters/getters, `registerWebhooks`, `CRUD_EVENT_TYPES`, event/webhook types.
|
|
72
|
+
- `hono-crud/serialization` — `applyProfile` family, `SerializationProfile`/`SerializationConfig`.
|
|
73
|
+
- `hono-crud/encryption` — encrypt/decrypt helpers, `StaticKeyProvider`, `encryptedValueSchema`, encryption types.
|
|
74
|
+
- `hono-crud/api-version` — `apiVersion`, `getApiVersion`, `getApiVersionConfig`, `apiVersionedResponse`, versioning-strategy types.
|
|
75
|
+
- `hono-crud/audit` — `AuditLogger`, `MemoryAuditLogStorage`, `createAuditLogger`, audit storage setters/getters, and (newly on the barrel) `getAuditConfig` + `calculateChanges`.
|
|
76
|
+
- `hono-crud/versioning` — `VersionManager`, `MemoryVersioningStorage`, `createVersionManager`, versioning storage setters/getters, and (newly on the barrel) `getVersioningConfig`.
|
|
77
|
+
- `hono-crud/multi-tenant` — `multiTenant`, `TenantEnv`, `MultiTenantMiddlewareConfig`, and (newly on the barrel) `getMultiTenantConfig` + `extractTenantId`.
|
|
78
|
+
- `hono-crud/functional` — `createCreate`/`createList`/`createRead`/`createUpdate`/`createDelete` and their config types.
|
|
79
|
+
- `hono-crud/builder` (new subpath) — `crud` and the `*Builder` classes.
|
|
80
|
+
- `hono-crud/config` — the endpoint-config types (`CreateEndpointConfig`, …, `EndpointsConfig`, `AdapterBundle`, `GeneratedEndpoints`). `defineEndpoints` (the function) stays on the root.
|
|
81
|
+
|
|
82
|
+
Deleted outright (no new home):
|
|
83
|
+
|
|
84
|
+
- The rename aliases `getContextRequestId`, `matchLoggingPath`, `extractLoggingUserId`, `LoggingPathPattern` — use `getRequestId` (root), `matchPath`, `extractUserId`, `PathPattern` (all on `hono-crud/logging`).
|
|
85
|
+
- The 17 `Config*Endpoint` type aliases (`ConfigCreateEndpoint`, …) — use the original `*EndpointConfig` names from `hono-crud/config`.
|
|
86
|
+
- `getHandlerForApp` is no longer on the public barrel (it remains module-level in core).
|
|
87
|
+
|
|
88
|
+
`@hono-crud/memory`: the `getStorage` export alias is removed — use `getStore`.
|
|
89
|
+
|
|
90
|
+
`@hono-crud/mcp`: now imports exclusively from `hono-crud/internal` (no behavior change).
|
|
91
|
+
|
|
92
|
+
The model-meta contract types (`AuditConfig`, `VersioningConfig`, `MultiTenantConfig`, `SoftDeleteConfig`, `AuditLogEntry`, `VersionHistoryEntry`, …) stay on the root barrel — they are fields of the model meta, distinct from the feature runtime.
|
|
93
|
+
|
|
94
|
+
(Published as a patch per this repo's pre-1.0 versioning policy.)
|
|
95
|
+
|
|
96
|
+
- fd3895f: Cursor pagination is now real on all three adapters. Previously core advertised cursor query params and `next_cursor`/`prev_cursor` response fields, but only the memory adapter implemented them — Drizzle and Prisma silently fell back to offset pagination.
|
|
97
|
+
|
|
98
|
+
- **Drizzle**: keyset via `WHERE cursorField > decoded ORDER BY cursorField LIMIT n+1`; **Prisma**: native `cursor` + `skip: 1` + `take: n+1`. All three adapters build the cursor-mode `result_info` envelope through one shared core helper, so the shape is byte-identical.
|
|
99
|
+
- **`prev_cursor` removed** (breaking): cursor walks are next-only (Stripe-style) — SQL keyset "previous" requires a reversed query and was only ever implemented in memory.
|
|
100
|
+
- **`order_by` is forced to the cursor field during cursor walks** (documented on the query param) — previously the three adapters could diverge on sort semantics mid-walk.
|
|
101
|
+
- **No silent degradation**: cursor query params and `next_cursor` only appear in the OpenAPI schema when the endpoint enables cursor pagination AND the adapter supports it; enabling it on an unsupporting adapter throws `ConfigurationException` instead of quietly serving offset pages.
|
|
102
|
+
- List-query logic deduplicated per adapter (`executeDrizzleListQuery` / memory store query helper, mirroring Prisma's existing `executePrismaQuery`) and batch OpenAPI scaffolding shared by the three id-keyed batch verbs.
|
|
103
|
+
|
|
104
|
+
- 08e7e95: Naming & config-shape unification (breaking renames, no aliases).
|
|
105
|
+
|
|
106
|
+
**Model feature enablement idiom.** `Model.audit` and `Model.versioning` are now `boolean | Config` like their `softDelete`/`multiTenant` siblings; the required `enabled` field is removed from `AuditConfig` and `VersioningConfig`. Write `audit: true` / `versioning: true` (or a config object — presence enables); `getAuditConfig`/`getVersioningConfig` and the `AuditLogger`/`VersionManager`/`createAuditLogger`/`createVersionManager` config params accept the union. `NormalizedAuditConfig`/`NormalizedVersioningConfig` still carry `enabled`. `fieldEncryption` stays presence-enabled (it has required members).
|
|
107
|
+
|
|
108
|
+
**Path-filter and message vocabulary.** `excludePaths` is the one name for "paths this middleware bypasses": `AuthConfig.skipPaths` → `excludePaths`, `RateLimitConfig.skipPaths` → `excludePaths`, and mcp `AutoOptions.include`/`exclude` → `includePaths`/`excludePaths` (they match `registerCrud` mount paths with the same shared matcher). `AuthConfig.unauthorizedMessage` → `errorMessage`, matching rate-limit and multiTenant.
|
|
109
|
+
|
|
110
|
+
**Duration unit suffixes (renames only — no field changed its unit).**
|
|
111
|
+
|
|
112
|
+
| Old | New | Unit |
|
|
113
|
+
| ------------------------------------------------------------------------ | -------------------------------- | ------- |
|
|
114
|
+
| `CacheConfig.ttl` | `ttlSeconds` | seconds |
|
|
115
|
+
| `IdempotencyConfig.ttl` | `ttlSeconds` | seconds |
|
|
116
|
+
| `IdempotencyConfig.lockTimeout` | `lockTimeoutSeconds` | seconds |
|
|
117
|
+
| `JWTConfig.clockTolerance` / `JWTClaimsValidationOptions.clockTolerance` | `clockToleranceSeconds` | seconds |
|
|
118
|
+
| `SubscribeEndpointConfig.heartbeatInterval` | `heartbeatIntervalMs` | ms |
|
|
119
|
+
| `SubscribeEndpointConfig.connectionTimeout` | `connectionTimeoutMs` | ms |
|
|
120
|
+
| `HealthCheck.timeout` | `timeoutMs` | ms |
|
|
121
|
+
| `HealthConfig.defaultTimeout` | `defaultTimeoutMs` | ms |
|
|
122
|
+
| `WebhookEndpoint.timeout` | `timeoutMs` | ms |
|
|
123
|
+
| `MemoryTtlStoreOptions.cleanupInterval` | `cleanupIntervalMs` | ms |
|
|
124
|
+
| `MemoryLoggingStorageOptions.maxAge` / `.cleanupInterval` | `maxAgeMs` / `cleanupIntervalMs` | ms |
|
|
125
|
+
| `MemoryIdempotencyStorageOptions.cleanupInterval` | `cleanupIntervalMs` | ms |
|
|
126
|
+
| `MemoryRateLimitStorageOptions.cleanupInterval` | `cleanupIntervalMs` | ms |
|
|
127
|
+
|
|
128
|
+
Documented exception: `RateLimitResult.retryAfter` keeps its name (mirrors the HTTP Retry-After header, seconds by RFC).
|
|
129
|
+
|
|
130
|
+
**Env types.** `TenantEnv` now types its tenant variable optional (`string | undefined` until the middleware runs), matching every other `*Env`. `HonoCrudEnv` now folds in `TenantEnv & ApiVersionEnv`, making its "all core context variables" claim true.
|
|
131
|
+
|
|
132
|
+
**Rate-limit extractor nullability.** `extractUserId` and `extractAPIKey` return `string | undefined` (was `string | null`); `KeyExtractor` is `(ctx) => string | undefined` — custom extractors returning `null` must return `undefined`. `extractIP` keeps its `'unknown'` fail-closed sentinel: a falsy key would skip rate limiting entirely, and limits must not fail open when no IP is derivable.
|
|
133
|
+
|
|
134
|
+
- 90ef0da: Storage & middleware-family unification:
|
|
135
|
+
|
|
136
|
+
- **RedisIdempotencyStorage** (`@hono-crud/idempotency`): production idempotency backend whose lock acquisition is ONE atomic `SET key value NX PX ttl` round-trip; compatible with `@upstash/redis` (edge-safe) out of the box. Deliberately no Cloudflare KV backend — KV lacks compare-and-swap, so a KV lock would be advisory only (documented in the package README and the `IdempotencyStorage.lock` contract).
|
|
137
|
+
- **Breaking — idempotency error shape**: the middleware now throws `IdempotencyKeyRequiredException` (400 `IDEMPOTENCY_KEY_REQUIRED`) and `IdempotencyConflictException` (409 `IDEMPOTENCY_CONFLICT`) instead of hand-returning `ctx.json` envelopes, so idempotency errors flow through `createErrorHandler` (ErrorMappers / ErrorHooks / custom `responseEnvelope` / requestId injection) like every sibling middleware. Bodies are unchanged under the default envelope on bare Hono apps; with `createErrorHandler` they now honor your envelope and gain `error.requestId`.
|
|
138
|
+
- **Breaking — Prisma model-mapping registry removed**: `registerPrismaModelMapping` / `registerPrismaModelMappings` / `clearPrismaModelMappings` (module-global mutable state, per-isolate on Workers) are deleted. Set the delegate name statically instead: `defineModel({ tableName: 'people', table: 'person', ... })` or `RelationConfig.table` for relations; the camelCase+singularize derivation remains the fallback.
|
|
139
|
+
- **Missing-storage posture unified**: cache mixins and the idempotency middleware now log a once-per-isolate warning when no storage resolves (rate-limit's existing warning gained the same once-guard); idempotency with `required: true` and no storage throws `ConfigurationException` instead of silently voiding replay protection.
|
|
140
|
+
- **Cache default removed**: `getCacheStorage()` no longer lazily installs a global `MemoryCacheStorage` on read (`lazyDefaultOnGet` retired for cache) — it returns honest `null` until storage is configured. Docs no longer claim a memory default exists.
|
|
141
|
+
- **Approval storage joins the unified injection system**: `CONTEXT_KEYS.approvalStorage` slot, `createStorageMiddleware({ approvalStorage })` / `createApprovalStorageMiddleware()`, and the `setApprovalStorage` / `getApprovalStorage` / `getApprovalStorageRequired` / `resolveApprovalStorage` quartet on `hono-crud/auth`. `requireApproval` resolves storage per request (explicit > context > global > warned in-memory default). **Breaking**: `ApprovalConfig.approvalStorage` renamed to `storage` (matching every sibling config).
|
|
142
|
+
- **Quartet uniformity**: every storage feature now exports its full set/get/getRequired/resolve quartet plus registry from its package/subpath barrel (`resolveRateLimitStorage`, `resolveCacheStorage`, `cacheStorageRegistry`, `rateLimitStorageRegistry`, `idempotencyStorageRegistry`, `eventEmitterRegistry`, `getAPIKeyStorageRequired`, `resolveAPIKeyStorage`, logging's Required/resolve pair on `hono-crud/logging`, …).
|
|
143
|
+
- **ConfigurationException sweep**: request-time misconfiguration (audit/versioning manager without storage, `getDrizzleDb` / `getPrismaClient` resolution, `resetRateLimit`, Prisma `$transaction` capability check, `StorageRegistry.getRequired` / `resolveRequired`) now throws `ConfigurationException` (500 `CONFIGURATION_ERROR`) instead of plain `Error`.
|
|
144
|
+
- `MemoryCacheStorageOptions` and `MemoryIdempotencyStorageOptions` are exported; docs now lead every storage-backed feature with `createStorageMiddleware` (the in-code recommended path) and present `set*Storage` as the long-lived-server option.
|
|
145
|
+
|
|
146
|
+
- 7a7808d: Verb & sugar-surface parity batch:
|
|
147
|
+
|
|
148
|
+
- **Config API verb parity**: `defineEndpoints` gains the 5 missing verbs — `bulkPatch`, `versionHistory`, `versionRead`, `versionCompare`, `versionRollback` — completing all 22 `registerCrud` slots. `AdapterBundle` gains the matching optional slots, and all three first-party bundles (Memory/Drizzle/Prisma) now fill every slot. `GeneratedEndpoints` is derived as a `Pick` over `CrudEndpoints`, so the two types can never drift again.
|
|
149
|
+
- **Loud config failure (BREAKING)**: configuring a verb whose adapter bundle lacks the matching base class now throws a plain `Error` at definition time instead of silently skipping the route. Correct configurations are unaffected.
|
|
150
|
+
- **BulkPatch on every adapter**: new `DrizzleBulkPatchEndpoint` (single `UPDATE ... WHERE` + RETURNING) and `PrismaBulkPatchEndpoint` (`updateMany`; count-only, `returnRecords` unsupported). `MemoryBulkPatchEndpoint` now respects soft-delete visibility (soft-deleted rows are never patched) and bumps managed `updatedAt` — previously it patched deleted rows and skipped the timestamp bump. Core `BulkPatchEndpoint` ships a default `getUpdateSchema()` (model schema minus managed fields, partial) so it works from the config API without subclassing. New conformance cell pins the contract on all three adapters.
|
|
151
|
+
- **OpenAPI schema overrides now actually merge (fix)**: user-supplied `responses`/`request`/`security`/`operationId` previously type-checked but were clobbered by the generated blocks on every surface. A shared `mergeRouteSchema` seam (exported from `hono-crud`) now merges user blocks over the generated schema in every endpoint `getSchema()`. Config `openapi` widened to `Partial<OpenAPIRouteSchema>` and the builder gains `.openapi(schema)`.
|
|
152
|
+
- **`searchFieldName` → `searchParamName` (BREAKING)**: the list-endpoint inline-search query-param knob is renamed everywhere (`ListEndpoint`, `ListFilterParseOptions`, functional `ListConfig`, `NormalizedEndpointConfig`); it names a query parameter, not a model field. Builder `.searchParam()` and config `search.paramName` spellings are unchanged. The default divergence is deliberate and now documented: inline list search defaults to `'search'`, the dedicated `/search` route to `'q'`.
|
|
153
|
+
- **Builder alias removal (BREAKING)**: pre-1.0 back-compat aliases `orderBy()` and `defaultOrder()` are deleted — `.sortable()` / `.defaultSort()` are the only spellings. The builder also no longer hardcodes its own copy of factory defaults (20/100/'search'/'id'/'sequential'/false); unset knobs pass `undefined` through so `generateEndpointClass` is the single source of defaults (behavior identical).
|
|
154
|
+
- **`bodySchema` parity**: functional `CreateConfig`/`UpdateConfig` gain `bodySchema` and the Create/Update builders gain `.bodySchema(schema)`, matching the class and config APIs. Fixed a factory bug where the generated `getBodySchema` override crashed body-schema-less verbs.
|
|
155
|
+
- **Honest hook typing**: the core create/update/delete trio across functional/builder/config now types the hook context as the exported `HookContext` (previously the actively-wrong `tx?: unknown`) and reuses the exported `AfterUpdateHook`/`AfterDeleteHook` aliases. Extended-verb config hook bags are retyped to what is actually passed (upsert gets `isCreate: boolean`, batch-create gets per-item `(item, index)`, batch-update gets `(id, data)`, import gets `(row, rowNumber, mode, tx?)`, clone's `before` gets the prepared clone payload; phantom `tx` params that were never passed are dropped). Previously-dead config hooks now fire: `search.hooks.after` is wired to `afterSearch`, `batchUpsert.hooks.before/after` to `beforeBatch`/`afterBatch`, and `AggregateEndpoint` gains an `after(result)` lifecycle hook so `aggregate.hooks.after` works.
|
|
156
|
+
- **Env-generic config middlewares**: `EndpointsConfig<M, E>` threads `E` so per-endpoint `middlewares` are `MiddlewareHandler<E>[]`, matching the functional and builder surfaces.
|
|
157
|
+
|
|
3
158
|
## 0.13.13
|
|
4
159
|
|
|
5
160
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -29,20 +29,21 @@ You will also want a storage adapter, e.g. `@hono-crud/memory`, `@hono-crud/driz
|
|
|
29
29
|
## Usage
|
|
30
30
|
|
|
31
31
|
```ts
|
|
32
|
+
import { Hono } from 'hono';
|
|
33
|
+
import { defineMeta, defineModel, fromHono, registerCrud } from 'hono-crud';
|
|
34
|
+
import { MemoryCreateEndpoint, MemoryListEndpoint, MemoryReadEndpoint } from '@hono-crud/memory';
|
|
32
35
|
import { z } from 'zod';
|
|
33
|
-
import { fromHono, registerCrud, defineModel, defineMeta } from 'hono-crud';
|
|
34
|
-
import { MemoryCreateEndpoint, MemoryReadEndpoint, MemoryListEndpoint } from '@hono-crud/memory';
|
|
35
36
|
|
|
36
|
-
const UserSchema = z.object({ id: z.
|
|
37
|
-
const
|
|
38
|
-
const
|
|
37
|
+
const UserSchema = z.object({ id: z.uuid(), name: z.string() });
|
|
38
|
+
const UserModel = defineModel({ tableName: 'users', schema: UserSchema, primaryKeys: ['id'] });
|
|
39
|
+
const userMeta = defineMeta({ model: UserModel });
|
|
40
|
+
|
|
41
|
+
class UserCreate extends MemoryCreateEndpoint { _meta = userMeta; }
|
|
42
|
+
class UserRead extends MemoryReadEndpoint { _meta = userMeta; }
|
|
43
|
+
class UserList extends MemoryListEndpoint { _meta = userMeta; }
|
|
39
44
|
|
|
40
45
|
const app = fromHono(new Hono());
|
|
41
|
-
registerCrud(app, '/users', {
|
|
42
|
-
model,
|
|
43
|
-
meta,
|
|
44
|
-
endpoints: { create: MemoryCreateEndpoint, read: MemoryReadEndpoint, list: MemoryListEndpoint },
|
|
45
|
-
});
|
|
46
|
+
registerCrud(app, '/users', { create: UserCreate, read: UserRead, list: UserList });
|
|
46
47
|
```
|
|
47
48
|
|
|
48
49
|
See the [repository README](https://github.com/kshdotdev/hono-crud) for the full guide.
|
|
@@ -1,68 +1,6 @@
|
|
|
1
|
-
import { MiddlewareHandler,
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* Strategy for extracting the API version from requests.
|
|
5
|
-
*/
|
|
6
|
-
type ApiVersionStrategy = 'url' | 'header' | 'query';
|
|
7
|
-
/**
|
|
8
|
-
* A version transformer function that converts request/response data
|
|
9
|
-
* between API versions.
|
|
10
|
-
*/
|
|
11
|
-
type ApiVersionTransformer = (data: Record<string, unknown>) => Record<string, unknown>;
|
|
12
|
-
/**
|
|
13
|
-
* One version entry consumed by apiVersion() via {@link ApiVersioningConfig}.versions
|
|
14
|
-
* — not the record-history config in core/types.
|
|
15
|
-
*/
|
|
16
|
-
interface ApiVersionConfig {
|
|
17
|
-
/** Version identifier (e.g. '1', '2', '2024-01-15') */
|
|
18
|
-
version: string;
|
|
19
|
-
/** Optional middleware to apply for this version */
|
|
20
|
-
middleware?: MiddlewareHandler[];
|
|
21
|
-
/** Transform incoming request body from this version to latest */
|
|
22
|
-
requestTransformer?: ApiVersionTransformer;
|
|
23
|
-
/** Transform outgoing response data from latest to this version */
|
|
24
|
-
responseTransformer?: ApiVersionTransformer;
|
|
25
|
-
/** ISO date string when this version was deprecated */
|
|
26
|
-
deprecated?: string;
|
|
27
|
-
/** ISO date string when this version will be removed */
|
|
28
|
-
sunset?: string;
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Top-level bag for the apiVersion() middleware.
|
|
32
|
-
* NOT to be confused with {@link ApiVersionConfig}, which describes ONE version entry
|
|
33
|
-
* inside `versions`. (Unrelated to the record-history feature's config in core/types,
|
|
34
|
-
* which governs stored record versions, not HTTP API negotiation.)
|
|
35
|
-
*/
|
|
36
|
-
interface ApiVersioningConfig {
|
|
37
|
-
/** Available API versions. First is treated as default if no defaultVersion specified. */
|
|
38
|
-
versions: ApiVersionConfig[];
|
|
39
|
-
/** Default version when none is specified by client */
|
|
40
|
-
defaultVersion?: string;
|
|
41
|
-
/** Version extraction strategy. @default 'header' */
|
|
42
|
-
strategy?: ApiVersionStrategy;
|
|
43
|
-
/** Header name for header strategy. @default 'Accept-Version' */
|
|
44
|
-
headerName?: string;
|
|
45
|
-
/** Query parameter name for query strategy. @default 'version' */
|
|
46
|
-
queryParam?: string;
|
|
47
|
-
/** URL prefix pattern for URL strategy (e.g. '/v{version}'). @default '/v{version}' */
|
|
48
|
-
urlPattern?: string;
|
|
49
|
-
/** Custom version extractor (overrides strategy) */
|
|
50
|
-
extractVersion?: (ctx: Context) => string | undefined;
|
|
51
|
-
/** Whether to add version headers to responses. @default true */
|
|
52
|
-
addHeaders?: boolean;
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Environment type additions for API versioning.
|
|
56
|
-
*
|
|
57
|
-
* Variables are typed optional because they are only set after the api-version
|
|
58
|
-
* middleware has run. Use `extends ApiVersionEnv` on your app's `Env` to opt in.
|
|
59
|
-
*/
|
|
60
|
-
interface ApiVersionEnv extends Env {
|
|
61
|
-
Variables: {
|
|
62
|
-
apiVersion?: string;
|
|
63
|
-
apiVersionConfig?: ApiVersionConfig;
|
|
64
|
-
};
|
|
65
|
-
}
|
|
1
|
+
import { MiddlewareHandler, Context } from 'hono';
|
|
2
|
+
import { a as ApiVersioningConfig, b as ApiVersionConfig } from '../types-COJwpQfh.js';
|
|
3
|
+
export { A as ApiVersionEnv, c as ApiVersionStrategy, d as ApiVersionTransformer } from '../types-COJwpQfh.js';
|
|
66
4
|
|
|
67
5
|
/**
|
|
68
6
|
* Create API versioning middleware.
|
|
@@ -70,7 +8,7 @@ interface ApiVersionEnv extends Env {
|
|
|
70
8
|
* @example
|
|
71
9
|
* ```ts
|
|
72
10
|
* import { Hono } from 'hono';
|
|
73
|
-
* import { apiVersion } from 'hono-crud';
|
|
11
|
+
* import { apiVersion } from 'hono-crud/api-version';
|
|
74
12
|
*
|
|
75
13
|
* const app = new Hono();
|
|
76
14
|
*
|
|
@@ -117,4 +55,4 @@ declare function getApiVersionConfig(ctx: Context): ApiVersionConfig | undefined
|
|
|
117
55
|
*/
|
|
118
56
|
declare function apiVersionedResponse(): MiddlewareHandler;
|
|
119
57
|
|
|
120
|
-
export {
|
|
58
|
+
export { ApiVersionConfig, ApiVersioningConfig, apiVersion, apiVersionedResponse, getApiVersion, getApiVersionConfig };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
import {b,a}from'../chunk-XR6JRDGX.js';function C(e,r){return e.req.header(r)??void 0}function h(e,r){return e.req.query(r)??void 0}function l(e,r){let t=e.req.path,a=r.replace(/[.*+?^${}()|[\]\\]/g,"\\$&").replace("\\{version\\}","([^/]+)"),p=new RegExp(`^${a}`);return t.match(p)?.[1]}function A(e){let{versions:r,strategy:t="header",headerName:d="Accept-Version",queryParam:a$1="version",urlPattern:p="/v{version}",extractVersion:f,addHeaders:c=true}=e,V=e.defaultVersion??r[0]?.version,u=new Map;for(let n of r)u.set(n.version,n);return async(n,m)=>{let o=f?f(n):{header:()=>C(n,d),query:()=>h(n,a$1),url:()=>l(n,p)}[t]();if(o=o??V,!o)throw new b("API version is required",400,"VERSION_REQUIRED");let i=u.get(o);if(!i)throw new b(`Unsupported API version: ${o}`,400,"UNSUPPORTED_VERSION");if(n.set(a.apiVersion,o),n.set(a.apiVersionConfig,i),c&&(n.header("X-API-Version",o),i.deprecated&&n.header("Deprecation",i.deprecated),i.sunset&&n.header("Sunset",i.sunset)),i.middleware&&i.middleware.length>0)for(let y of i.middleware)await y(n,async()=>{});await m();}}function w(e){return e.get(a.apiVersion)}function v(e){return e.get(a.apiVersionConfig)}function E(){return async(e,r)=>{await r();let t=e.get(a.apiVersionConfig);if(!(!t?.responseTransformer||!e.res.headers.get("content-type")?.includes("application/json")))try{let a=await e.res.json(),p=t.responseTransformer(a);e.res=new Response(JSON.stringify(p),{status:e.res.status,headers:e.res.headers});}catch{}}}export{A as apiVersion,E as apiVersionedResponse,w as getApiVersion,v as getApiVersionConfig};
|
package/dist/audit/index.d.ts
CHANGED
|
@@ -1,11 +1,27 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { S as StorageRegistry } from '../registry-DYXgzGt0.js';
|
|
2
|
+
import { Context, Env } from 'hono';
|
|
3
|
+
import { m as AuditFieldChange, l as AuditConfig, Z as NormalizedAuditConfig, n as AuditLogEntry, k as AuditAction } from '../types-D72szrPV.js';
|
|
4
4
|
import 'zod';
|
|
5
|
+
import '../path-match-DGLu6wld.js';
|
|
5
6
|
import '../types-B5wq2iKZ.js';
|
|
6
|
-
import '../types-
|
|
7
|
+
import '../types-lAPVBoYa.js';
|
|
7
8
|
import '@hono/zod-openapi';
|
|
8
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Audit configuration normalization + field-change diffing.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get normalized audit configuration with defaults.
|
|
16
|
+
* Accepts the model-side union: `true` enables with defaults, a config
|
|
17
|
+
* object enables with overrides, `false`/`undefined` disables.
|
|
18
|
+
*/
|
|
19
|
+
declare function getAuditConfig(config?: boolean | AuditConfig): NormalizedAuditConfig;
|
|
20
|
+
/**
|
|
21
|
+
* Calculate field changes between two records.
|
|
22
|
+
*/
|
|
23
|
+
declare function calculateChanges(oldRecord: Record<string, unknown> | undefined, newRecord: Record<string, unknown> | undefined, excludeFields?: string[]): AuditFieldChange[];
|
|
24
|
+
|
|
9
25
|
/**
|
|
10
26
|
* Interface for audit log storage adapters.
|
|
11
27
|
* Implement this to store audit logs in your preferred storage.
|
|
@@ -82,13 +98,22 @@ declare const getAuditStorage: () => AuditLogStorage | null;
|
|
|
82
98
|
* default when none was configured.
|
|
83
99
|
*/
|
|
84
100
|
declare const getAuditStorageRequired: () => AuditLogStorage;
|
|
101
|
+
/**
|
|
102
|
+
* Resolves audit storage with priority: explicit param > context > global.
|
|
103
|
+
* Never creates a default — returns null when nothing is configured.
|
|
104
|
+
*
|
|
105
|
+
* @param ctx - Optional Hono context
|
|
106
|
+
* @param explicitStorage - Optional explicit storage instance
|
|
107
|
+
* @returns The resolved storage, or null when no storage was configured
|
|
108
|
+
*/
|
|
109
|
+
declare function resolveAuditStorage<E extends Env>(ctx?: Context<E>, explicitStorage?: AuditLogStorage): AuditLogStorage | null;
|
|
85
110
|
/**
|
|
86
111
|
* Audit logger class for creating audit log entries.
|
|
87
112
|
*/
|
|
88
113
|
declare class AuditLogger {
|
|
89
114
|
private config;
|
|
90
115
|
private storage;
|
|
91
|
-
constructor(config: AuditConfig | undefined, storage?: AuditLogStorage, ctx?: Context);
|
|
116
|
+
constructor(config: boolean | AuditConfig | undefined, storage?: AuditLogStorage, ctx?: Context);
|
|
92
117
|
private getStorage;
|
|
93
118
|
/**
|
|
94
119
|
* Check if audit logging is enabled for an action.
|
|
@@ -151,6 +176,6 @@ declare class AuditLogger {
|
|
|
151
176
|
* const logger = createAuditLogger(config, myStorage);
|
|
152
177
|
* ```
|
|
153
178
|
*/
|
|
154
|
-
declare function createAuditLogger(config: AuditConfig | undefined, storage?: AuditLogStorage, ctx?: Context): AuditLogger;
|
|
179
|
+
declare function createAuditLogger(config: boolean | AuditConfig | undefined, storage?: AuditLogStorage, ctx?: Context): AuditLogger;
|
|
155
180
|
|
|
156
|
-
export { type AuditLogStorage, AuditLogger, MemoryAuditLogStorage, auditStorageRegistry, createAuditLogger, getAuditStorage, getAuditStorageRequired, setAuditStorage };
|
|
181
|
+
export { type AuditLogStorage, AuditLogger, MemoryAuditLogStorage, auditStorageRegistry, calculateChanges, createAuditLogger, getAuditConfig, getAuditStorage, getAuditStorageRequired, resolveAuditStorage, setAuditStorage };
|
package/dist/audit/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export{
|
|
1
|
+
export{g as AuditLogger,a as MemoryAuditLogStorage,b as auditStorageRegistry,h as createAuditLogger,d as getAuditStorage,e as getAuditStorageRequired,f as resolveAuditStorage,c as setAuditStorage}from'../chunk-WBHWKOTP.js';export{b as calculateChanges,a as getAuditConfig}from'../chunk-L5CVVJQH.js';import'../chunk-5P4RVSHT.js';import'../chunk-TLI3TRUA.js';import'../chunk-XR6JRDGX.js';
|