hono-crud 0.13.10 → 0.13.11

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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.13.11
4
+
5
+ ### Patch Changes
6
+
7
+ - 97e92f5: Dedup batch: the edge-safe in-memory TTL machinery, the cache entry wire format, and the relation-batching control flow each now live in exactly one place.
8
+
9
+ - New internal `MemoryTtlStore` in core (exported via `hono-crud/internal`) owns lazy cleanup-on-access, expiry-on-read, and insertion-order capacity eviction. The cache, rate-limit, and idempotency memory storages compose it, supplying only their entry shapes and domain indices (cache tag index via an eviction hook, idempotency locks as a second store). Public constructor options are unchanged. The logging memory storage intentionally stays standalone — its newest-first ordering is a different structure, not drift.
10
+ - New cache entry codec (`packages/cache/src/entry.ts`, internal): `buildCacheEntry` / `normalizeStoredEntry` / `isCacheEntryExpired` shared by the memory, Redis, and Cloudflare KV backends — including the single canonical legacy-Date migration guard, so already-persisted entries keep reading identically.
11
+ - New relation-batching orchestrator in core (exported via `hono-crud/internal`): the ORM-agnostic control flow (key collection, grouping, map-back, lookup-map dispatch over hasOne/hasMany/belongsTo) is shared; drizzle, prisma, and memory supply only their query adapters. N+1 batching fixes now land in one place.
12
+ - Two deliberate behavior fixes that the dedup surfaced: (1) single-item relation reads in the memory and drizzle adapters now always set the relation key (`null` / `[]` instead of absent), matching the batch path and prisma — this also fixes memory's belongsTo gating on the row's own `id` instead of the foreign key; (2) the rate-limit fixed window no longer slides its stored expiry on within-window increments — the window keeps its original `windowStart + windowMs` end.
13
+ - Internal-only removals (never publicly exported): memory's `loadRelation`, prisma's `loadPrismaRelation`.
14
+
3
15
  ## 0.13.10
4
16
 
5
17
  ### Patch Changes
@@ -1,8 +1,8 @@
1
1
  import { Context } from 'hono';
2
- import { A as AuditConfig, f as AuditLogEntry, g as AuditAction } from '../types-BpA6OtSX.js';
2
+ import { A as AuditConfig, f as AuditLogEntry, g as AuditAction } from '../types-C_OAPSsu.js';
3
3
  import { S as StorageRegistry } from '../registry-3p4qTDGZ.js';
4
4
  import 'zod';
5
- import '../types-DcRAcexC.js';
5
+ import '../types-B5wq2iKZ.js';
6
6
  import '../types-Drjma4gp.js';
7
7
  import '@hono/zod-openapi';
8
8
 
@@ -1,9 +1,9 @@
1
- export { l as APIKeyConfig, m as APIKeyEntry, n as APIKeyLookupResult, o as ActionSource, w as ApprovalConfig, x as ApprovalStorage, y as AuthConfig, z as AuthEnv, B as AuthType, C as AuthUser, D as AuthorizationCheck, Q as EndpointAuthConfig, X as Guard, a4 as JWTAlgorithm, a5 as JWTClaims, a6 as JWTClaimsSchema, a7 as JWTConfig, a8 as JWT_ALGORITHMS, an as OwnershipExtractor, aV as PathPattern, aq as PendingAction, ar as PendingActionStatus, aL as ValidatedJWTClaims, aR as parseJWTClaims, aS as safeParseJWTClaims } from '../types-BpA6OtSX.js';
2
- export { A as AuthEndpointMethods, a as AuthenticatedEndpoint, J as JWTClaimsValidationOptions, M as MemoryAPIKeyStorage, b as MemoryApprovalStorage, P as POLICIES_CONTEXT_KEY, c as allOf, d as allowAll, e as anyOf, f as createAPIKeyMiddleware, g as createAuthMiddleware, h as createJWTMiddleware, i as decodeJWT, j as defaultHashAPIKey, k as denyAll, m as generateAPIKey, n as getAPIKeyStorage, o as hashAPIKey, p as isValidAPIKeyFormat, q as optionalAuth, r as parseIso8601Duration, s as requireAllRoles, t as requireAnyPermission, u as requireApproval, v as requireAuth, w as requireAuthenticated, x as requireAuthentication, y as requireOwnership, z as requireOwnershipOrRole, B as requirePermissions, C as requirePolicy, D as requireRoles, E as setAPIKeyStorage, F as validateAPIKey, G as validateAPIKeyEntry, H as validateJWTClaims, I as verifyJWT, K as withAuth } from '../index-DS6aUfcl.js';
1
+ export { n as APIKeyConfig, o as APIKeyEntry, p as APIKeyLookupResult, q as ActionSource, y as ApprovalConfig, z as ApprovalStorage, B as AuthConfig, C as AuthEnv, D as AuthType, E as AuthUser, G as AuthorizationCheck, U as EndpointAuthConfig, Z as Guard, a5 as JWTAlgorithm, a6 as JWTClaims, a7 as JWTClaimsSchema, a8 as JWTConfig, a9 as JWT_ALGORITHMS, ao as OwnershipExtractor, aW as PathPattern, ar as PendingAction, as as PendingActionStatus, aL as ValidatedJWTClaims, aR as parseJWTClaims, aS as safeParseJWTClaims } from '../types-C_OAPSsu.js';
2
+ export { A as AuthEndpointMethods, a as AuthenticatedEndpoint, J as JWTClaimsValidationOptions, M as MemoryAPIKeyStorage, b as MemoryApprovalStorage, P as POLICIES_CONTEXT_KEY, c as allOf, d as allowAll, e as anyOf, f as createAPIKeyMiddleware, g as createAuthMiddleware, h as createJWTMiddleware, i as decodeJWT, j as defaultHashAPIKey, k as denyAll, m as generateAPIKey, n as getAPIKeyStorage, o as hashAPIKey, p as isValidAPIKeyFormat, q as optionalAuth, r as parseIso8601Duration, s as requireAllRoles, t as requireAnyPermission, u as requireApproval, v as requireAuth, w as requireAuthenticated, x as requireAuthentication, y as requireOwnership, z as requireOwnershipOrRole, B as requirePermissions, C as requirePolicy, D as requireRoles, E as setAPIKeyStorage, F as validateAPIKey, G as validateAPIKeyEntry, H as validateJWTClaims, I as verifyJWT, K as withAuth } from '../index-CZZ12W3j.js';
3
3
  import 'hono';
4
4
  import 'zod';
5
- import '../types-DcRAcexC.js';
5
+ import '../types-B5wq2iKZ.js';
6
6
  import '../types-Drjma4gp.js';
7
7
  import '@hono/zod-openapi';
8
- import '../route-DF4Y3iR0.js';
8
+ import '../route-BULuf1Xl.js';
9
9
  import 'hono/utils/http-status';
@@ -0,0 +1 @@
1
+ import {a}from'./chunk-6GO5LUOZ.js';var l={loggingStorage:a.loggingStorage,auditStorage:a.auditStorage,versioningStorage:a.versioningStorage,apiKeyStorage:a.apiKeyStorage,cacheStorage:a.cacheStorage,rateLimitStorage:a.rateLimitStorage,idempotencyStorage:a.idempotencyStorage,eventEmitter:a.eventEmitter};function o(t){return async(e,r)=>{for(let[a,i]of Object.entries(l)){let d=t[a];d&&e.set(i,d);}await r();}}function s(t){return o({loggingStorage:t})}function u(t){return o({auditStorage:t})}function S(t){return o({versioningStorage:t})}function p(t){return o({apiKeyStorage:t})}function c(t){return o({cacheStorage:t})}function v(t){return o({rateLimitStorage:t})}function m(t){return o({idempotencyStorage:t})}var g=class{map=new Map;isExpired;cleanupInterval;maxEntries;onEvict;lastCleanup=0;constructor(e){this.isExpired=e.isExpired,this.cleanupInterval=e.cleanupInterval??0,this.maxEntries=e.maxEntries??0,this.onEvict=e.onEvict;}maybeCleanup(e=Date.now()){this.cleanupInterval<=0||e-this.lastCleanup>=this.cleanupInterval&&(this.lastCleanup=e,this.cleanup(e));}get(e,r=Date.now()){let a=this.map.get(e);if(a!==void 0){if(this.isExpired(a,r)){this.delete(e);return}return a}}peek(e){return this.map.get(e)}has(e,r=Date.now()){return this.get(e,r)!==void 0}set(e,r,a=false){let i=this.map.has(e);if(this.maxEntries>0&&this.map.size>=this.maxEntries&&!i&&this.evictOldest(),i&&a){let d=this.map.get(e);this.map.delete(e),this.onEvict?.(e,d);}this.map.set(e,r);}delete(e){let r=this.map.get(e);return r===void 0?false:(this.map.delete(e),this.onEvict?.(e,r),true)}evictOldest(){let e=this.map.keys().next().value;e!==void 0&&this.delete(e);}cleanup(e=Date.now()){let r=0,a=[];for(let[i,d]of this.map.entries())this.isExpired(d,e)&&a.push(i);for(let i of a)this.delete(i)&&r++;return r}clear(){this.map.clear();}get size(){return this.map.size}getKeys(){return Array.from(this.map.keys())}entries(){return this.map.entries()}};export{o as a,s as b,u as c,S as d,p as e,c as f,v as g,m as h,g as i};
@@ -1,10 +1,10 @@
1
1
  import 'hono';
2
2
  import 'zod';
3
- export { A as AdapterBundle, a as AggregateEndpointConfig, B as BatchCreateEndpointConfig, b as BatchDeleteEndpointConfig, c as BatchRestoreEndpointConfig, d as BatchUpdateEndpointConfig, e as BatchUpsertEndpointConfig, f as CloneEndpointConfig, g as CreateEndpointConfig, D as DeleteEndpointConfig, m as EndpointsConfig, E as ExportEndpointConfig, G as GeneratedEndpoints, I as ImportEndpointConfig, L as ListEndpointConfig, R as ReadEndpointConfig, h as RestoreEndpointConfig, S as SearchEndpointConfig, U as UpdateEndpointConfig, i as UpsertEndpointConfig, s as defineEndpoints } from '../index-CNFFLae5.js';
4
- import '../route-DF4Y3iR0.js';
5
- import '../types-BpA6OtSX.js';
6
- import '../types-BQkdX9O0.js';
3
+ export { A as AdapterBundle, a as AggregateEndpointConfig, B as BatchCreateEndpointConfig, b as BatchDeleteEndpointConfig, c as BatchRestoreEndpointConfig, d as BatchUpdateEndpointConfig, e as BatchUpsertEndpointConfig, f as CloneEndpointConfig, g as CreateEndpointConfig, D as DeleteEndpointConfig, m as EndpointsConfig, E as ExportEndpointConfig, G as GeneratedEndpoints, I as ImportEndpointConfig, L as ListEndpointConfig, R as ReadEndpointConfig, h as RestoreEndpointConfig, S as SearchEndpointConfig, U as UpdateEndpointConfig, i as UpsertEndpointConfig, s as defineEndpoints } from '../index-BWwvlRfM.js';
4
+ import '../route-BULuf1Xl.js';
5
+ import '../types-C_OAPSsu.js';
6
+ import '../types-DXVXRfuq.js';
7
7
  import '@hono/zod-openapi';
8
8
  import 'hono/utils/http-status';
9
- import '../types-DcRAcexC.js';
9
+ import '../types-B5wq2iKZ.js';
10
10
  import '../types-Drjma4gp.js';
@@ -1,8 +1,8 @@
1
1
  import { Env, MiddlewareHandler } from 'hono';
2
- import { M as MetaInput, O as OpenAPIRouteSchema, H as HookMode, F as FilterConfig, S as SortSpec, c as SortDirection } from '../types-BpA6OtSX.js';
3
- import { M as ModelObject } from '../types-BQkdX9O0.js';
2
+ import { M as MetaInput, O as OpenAPIRouteSchema, H as HookMode, F as FilterConfig, S as SortSpec, c as SortDirection } from '../types-C_OAPSsu.js';
3
+ import { M as ModelObject } from '../types-DXVXRfuq.js';
4
4
  import 'zod';
5
- import '../types-DcRAcexC.js';
5
+ import '../types-B5wq2iKZ.js';
6
6
  import '../types-Drjma4gp.js';
7
7
  import '@hono/zod-openapi';
8
8
 
@@ -0,0 +1,515 @@
1
+ import { Env, MiddlewareHandler, Context } from 'hono';
2
+ import { S as StorageRegistry } from './registry-3p4qTDGZ.js';
3
+ import { AuditLogStorage } from './audit/index.js';
4
+ import { aV as APIKeyStorage } from './types-C_OAPSsu.js';
5
+ import { e as LoggingStorage } from './middleware-DBIpdsJ1.js';
6
+ import { VersioningStorage } from './versioning/index.js';
7
+ import { a as CrudEventEmitter } from './emitter-B6dLhiMF.js';
8
+
9
+ /**
10
+ * Cross-package storage contracts owned by core.
11
+ *
12
+ * Core owns the three storage interfaces (`CacheStorage`, `RateLimitStorage`,
13
+ * `IdempotencyStorage`) that first-party `@hono-crud/*` plugins implement and
14
+ * that `storage/types.ts` references. Plugins re-export these from
15
+ * `hono-crud/internal` rather than re-declaring them, keeping the dependency
16
+ * graph plugin → core (the only sanctioned cross-package bridge).
17
+ *
18
+ * Storage-boundary convention: durations crossing the storage boundary are
19
+ * milliseconds (`ttlMs`); user-facing config types (e.g. `CacheConfig.ttl`)
20
+ * stay in seconds and live in the plugins.
21
+ */
22
+ /**
23
+ * Cache entry stored in the cache storage.
24
+ */
25
+ interface CacheEntry<T = unknown> {
26
+ data: T;
27
+ /** Epoch milliseconds when the entry was created. */
28
+ createdAt: number;
29
+ /** Epoch milliseconds when the entry expires, or null for no expiration. */
30
+ expiresAt: number | null;
31
+ tags?: string[];
32
+ }
33
+ /**
34
+ * Options for setting cache entries.
35
+ */
36
+ interface CacheSetOptions {
37
+ /** Time-to-live in milliseconds. */
38
+ ttlMs?: number;
39
+ /** Tags for group invalidation. */
40
+ tags?: string[];
41
+ }
42
+ /**
43
+ * Cache storage interface.
44
+ * Implement this interface for custom storage backends.
45
+ */
46
+ interface CacheStorage {
47
+ /**
48
+ * Get a cached entry by key.
49
+ * @returns The cached entry or null if not found/expired.
50
+ */
51
+ get<T>(key: string): Promise<CacheEntry<T> | null>;
52
+ /**
53
+ * Set a cache entry.
54
+ * @param key - The cache key.
55
+ * @param data - The data to cache.
56
+ * @param options - Cache options (ttlMs, tags).
57
+ */
58
+ set<T>(key: string, data: T, options?: CacheSetOptions): Promise<void>;
59
+ /**
60
+ * Delete a cache entry by key.
61
+ * @returns True if the entry was deleted.
62
+ */
63
+ delete(key: string): Promise<boolean>;
64
+ /**
65
+ * Delete entries matching a glob-style pattern.
66
+ * @param pattern - Glob pattern (e.g., "users:*").
67
+ * @returns Number of deleted entries.
68
+ */
69
+ deletePattern(pattern: string): Promise<number>;
70
+ /**
71
+ * Delete all entries with a specific tag.
72
+ * @param tag - The tag to match.
73
+ * @returns Number of deleted entries.
74
+ */
75
+ deleteByTag?(tag: string): Promise<number>;
76
+ /**
77
+ * Check if a key exists in the cache.
78
+ */
79
+ has(key: string): Promise<boolean>;
80
+ /**
81
+ * Clear all cache entries.
82
+ */
83
+ clear(): Promise<void>;
84
+ /**
85
+ * Get cache statistics (optional).
86
+ */
87
+ getStats?(): CacheStats;
88
+ /**
89
+ * Remove expired entries. Optional, edge-safe (no background timers).
90
+ * @returns Number of entries removed.
91
+ */
92
+ cleanup?(): Promise<number>;
93
+ /** Release resources (timers, connections). Optional, edge-safe. */
94
+ destroy?(): void;
95
+ }
96
+ /**
97
+ * Cache statistics.
98
+ */
99
+ interface CacheStats {
100
+ hits: number;
101
+ misses: number;
102
+ size: number;
103
+ }
104
+ /**
105
+ * Entry for fixed window rate limiting.
106
+ * Stores the count and window start time.
107
+ */
108
+ interface FixedWindowEntry {
109
+ /** Number of requests in current window */
110
+ count: number;
111
+ /** Unix timestamp when the window started (ms) */
112
+ windowStart: number;
113
+ }
114
+ /**
115
+ * Entry for sliding window rate limiting.
116
+ * Stores timestamps of requests within the window.
117
+ */
118
+ interface SlidingWindowEntry {
119
+ /** Timestamps of requests within the window (ms) */
120
+ timestamps: number[];
121
+ }
122
+ /**
123
+ * Combined entry type for storage.
124
+ */
125
+ type RateLimitEntry = FixedWindowEntry | SlidingWindowEntry;
126
+ /**
127
+ * Storage interface for rate limiting.
128
+ * Separate from CacheStorage due to different requirements:
129
+ * - Needs atomic increment operations
130
+ * - No need for tags or complex queries
131
+ * - Different data structure (counts/timestamps vs cached data)
132
+ */
133
+ interface RateLimitStorage {
134
+ /**
135
+ * Increment the request count for a key (fixed window).
136
+ * Creates the entry if it doesn't exist.
137
+ * @param key - The rate limit key
138
+ * @param windowMs - Window duration in milliseconds
139
+ * @returns The updated entry with count and window start
140
+ */
141
+ increment(key: string, windowMs: number): Promise<FixedWindowEntry>;
142
+ /**
143
+ * Add a timestamp to the sliding window for a key.
144
+ * Removes timestamps outside the window.
145
+ * @param key - The rate limit key
146
+ * @param windowMs - Window duration in milliseconds
147
+ * @param now - Current timestamp (optional, defaults to Date.now())
148
+ * @returns The updated entry with all timestamps in window
149
+ */
150
+ addTimestamp(key: string, windowMs: number, now?: number): Promise<SlidingWindowEntry>;
151
+ /**
152
+ * Get the current entry for a key.
153
+ * @param key - The rate limit key
154
+ * @returns The entry or null if not found
155
+ */
156
+ get(key: string): Promise<RateLimitEntry | null>;
157
+ /**
158
+ * Reset the rate limit for a key.
159
+ * @param key - The rate limit key
160
+ */
161
+ reset(key: string): Promise<void>;
162
+ /**
163
+ * Clean up expired entries.
164
+ * @returns Number of entries removed
165
+ */
166
+ cleanup(): Promise<number>;
167
+ /**
168
+ * Destroy the storage (cleanup intervals, connections, etc.).
169
+ */
170
+ destroy?(): void;
171
+ }
172
+ /**
173
+ * Stored idempotency response entry.
174
+ */
175
+ interface IdempotencyEntry {
176
+ /** The idempotency key */
177
+ key: string;
178
+ /** HTTP status code of the cached response */
179
+ statusCode: number;
180
+ /** Serialized response body */
181
+ body: string;
182
+ /** Response headers to replay */
183
+ headers: Record<string, string>;
184
+ /** Timestamp when the entry was created */
185
+ createdAt: number;
186
+ }
187
+ /**
188
+ * Storage interface for idempotency keys.
189
+ */
190
+ interface IdempotencyStorage {
191
+ /**
192
+ * Get a stored idempotency entry.
193
+ * Returns null if the key doesn't exist or has expired.
194
+ */
195
+ get(key: string): Promise<IdempotencyEntry | null>;
196
+ /**
197
+ * Store an idempotency entry with a TTL.
198
+ * @param key - The idempotency key
199
+ * @param entry - The response entry to store
200
+ * @param ttlMs - Time-to-live in milliseconds
201
+ */
202
+ set(key: string, entry: IdempotencyEntry, ttlMs: number): Promise<void>;
203
+ /**
204
+ * Check if a key is currently being processed (in-flight lock).
205
+ * Used to prevent concurrent requests with the same key.
206
+ */
207
+ isLocked(key: string): Promise<boolean>;
208
+ /**
209
+ * Acquire a lock for a key being processed.
210
+ * Returns true if the lock was acquired, false if already locked.
211
+ */
212
+ lock(key: string, ttlMs: number): Promise<boolean>;
213
+ /**
214
+ * Release a lock for a key.
215
+ */
216
+ unlock(key: string): Promise<void>;
217
+ /**
218
+ * Remove expired entries and locks. Optional, edge-safe.
219
+ * @returns Number of entries removed.
220
+ */
221
+ cleanup?(): Promise<number>;
222
+ /** Release resources and clear all data. Optional, edge-safe. */
223
+ destroy?(): void;
224
+ }
225
+
226
+ /**
227
+ * Extended Hono environment with storage context variables.
228
+ * Use this type when you want access to context-based storage.
229
+ *
230
+ * @example
231
+ * ```ts
232
+ * import { Hono } from 'hono';
233
+ * import { StorageEnv, createStorageMiddleware } from 'hono-crud';
234
+ *
235
+ * const app = new Hono<StorageEnv>();
236
+ *
237
+ * app.use('/*', createStorageMiddleware({
238
+ * rateLimitStorage: new MemoryRateLimitStorage(),
239
+ * }));
240
+ *
241
+ * app.get('/test', (ctx) => {
242
+ * const storage = ctx.var.rateLimitStorage; // Typed!
243
+ * return ctx.text('ok');
244
+ * });
245
+ * ```
246
+ */
247
+ interface StorageEnv extends Env {
248
+ Variables: {
249
+ loggingStorage?: LoggingStorage;
250
+ auditStorage?: AuditLogStorage;
251
+ versioningStorage?: VersioningStorage;
252
+ apiKeyStorage?: APIKeyStorage;
253
+ cacheStorage?: CacheStorage;
254
+ rateLimitStorage?: RateLimitStorage;
255
+ idempotencyStorage?: IdempotencyStorage;
256
+ eventEmitter?: CrudEventEmitter;
257
+ };
258
+ }
259
+ /**
260
+ * Configuration for the storage middleware.
261
+ * Provide any storage instances you want injected into the Hono context.
262
+ *
263
+ * @example
264
+ * ```ts
265
+ * const config: StorageMiddlewareConfig = {
266
+ * rateLimitStorage: new MemoryRateLimitStorage(),
267
+ * cacheStorage: new MemoryCacheStorage(),
268
+ * };
269
+ * ```
270
+ */
271
+ interface StorageMiddlewareConfig {
272
+ /**
273
+ * Logging storage instance.
274
+ * If provided, will be available as `ctx.var.loggingStorage`.
275
+ */
276
+ loggingStorage?: LoggingStorage;
277
+ /**
278
+ * Audit log storage instance.
279
+ * If provided, will be available as `ctx.var.auditStorage`.
280
+ */
281
+ auditStorage?: AuditLogStorage;
282
+ /**
283
+ * Versioning storage instance.
284
+ * If provided, will be available as `ctx.var.versioningStorage`.
285
+ */
286
+ versioningStorage?: VersioningStorage;
287
+ /**
288
+ * API key storage instance.
289
+ * If provided, will be available as `ctx.var.apiKeyStorage`.
290
+ */
291
+ apiKeyStorage?: APIKeyStorage;
292
+ /**
293
+ * Cache storage instance.
294
+ * If provided, will be available as `ctx.var.cacheStorage`.
295
+ */
296
+ cacheStorage?: CacheStorage;
297
+ /**
298
+ * Rate limit storage instance.
299
+ * If provided, will be available as `ctx.var.rateLimitStorage`.
300
+ */
301
+ rateLimitStorage?: RateLimitStorage;
302
+ /**
303
+ * Idempotency storage instance.
304
+ * If provided, will be available as `ctx.var.idempotencyStorage`.
305
+ */
306
+ idempotencyStorage?: IdempotencyStorage;
307
+ /**
308
+ * CRUD event emitter instance.
309
+ * If provided, will be available as `ctx.var.eventEmitter`.
310
+ */
311
+ eventEmitter?: CrudEventEmitter;
312
+ }
313
+
314
+ /**
315
+ * Creates middleware that injects storage instances into Hono context.
316
+ * This is the recommended approach for serverless environments where
317
+ * global storage can cause issues with cold starts and state persistence.
318
+ *
319
+ * @param config - Storage instances to inject
320
+ * @returns Middleware handler
321
+ *
322
+ * @example
323
+ * ```ts
324
+ * import { Hono } from 'hono';
325
+ * import { createStorageMiddleware, MemoryCacheStorage } from 'hono-crud';
326
+ * import { MemoryRateLimitStorage } from '@hono-crud/rate-limit';
327
+ *
328
+ * const app = new Hono();
329
+ *
330
+ * // Inject storage instances into context
331
+ * app.use('/*', createStorageMiddleware({
332
+ * rateLimitStorage: new MemoryRateLimitStorage(),
333
+ * cacheStorage: new MemoryCacheStorage(),
334
+ * }));
335
+ *
336
+ * // Storage is now available to all downstream middleware and routes
337
+ * ```
338
+ *
339
+ * @example
340
+ * ```ts
341
+ * // Multi-tenant setup with per-tenant storage
342
+ * app.use('/:tenantId/*', async (ctx, next) => {
343
+ * const tenantId = ctx.req.param('tenantId');
344
+ *
345
+ * // Get or create tenant-specific storage
346
+ * const storage = getTenantStorage(tenantId);
347
+ *
348
+ * // Create middleware dynamically
349
+ * const middleware = createStorageMiddleware({
350
+ * rateLimitStorage: storage.rateLimit,
351
+ * cacheStorage: storage.cache,
352
+ * });
353
+ *
354
+ * return middleware(ctx, next);
355
+ * });
356
+ * ```
357
+ */
358
+ declare function createStorageMiddleware<E extends Env = Env>(config: StorageMiddlewareConfig): MiddlewareHandler<E & StorageEnv>;
359
+ /**
360
+ * Creates middleware that injects only logging storage.
361
+ *
362
+ * @param storage - Logging storage instance
363
+ * @returns Middleware handler
364
+ */
365
+ declare function createLoggingStorageMiddleware<E extends Env = Env>(storage: NonNullable<StorageMiddlewareConfig['loggingStorage']>): MiddlewareHandler<E & StorageEnv>;
366
+ /**
367
+ * Creates middleware that injects only audit storage.
368
+ *
369
+ * @param storage - Audit storage instance
370
+ * @returns Middleware handler
371
+ */
372
+ declare function createAuditStorageMiddleware<E extends Env = Env>(storage: NonNullable<StorageMiddlewareConfig['auditStorage']>): MiddlewareHandler<E & StorageEnv>;
373
+ /**
374
+ * Creates middleware that injects only versioning storage.
375
+ *
376
+ * @param storage - Versioning storage instance
377
+ * @returns Middleware handler
378
+ */
379
+ declare function createVersioningStorageMiddleware<E extends Env = Env>(storage: NonNullable<StorageMiddlewareConfig['versioningStorage']>): MiddlewareHandler<E & StorageEnv>;
380
+ /**
381
+ * Creates middleware that injects only API key storage.
382
+ *
383
+ * @param storage - API key storage instance
384
+ * @returns Middleware handler
385
+ */
386
+ declare function createAPIKeyStorageMiddleware<E extends Env = Env>(storage: NonNullable<StorageMiddlewareConfig['apiKeyStorage']>): MiddlewareHandler<E & StorageEnv>;
387
+ /**
388
+ * Creates middleware that injects only cache storage.
389
+ *
390
+ * @param storage - Cache storage instance
391
+ * @returns Middleware handler
392
+ */
393
+ declare function createCacheStorageMiddleware<E extends Env = Env>(storage: NonNullable<StorageMiddlewareConfig['cacheStorage']>): MiddlewareHandler<E & StorageEnv>;
394
+ /**
395
+ * Creates middleware that injects only rate limit storage.
396
+ *
397
+ * @param storage - Rate limit storage instance
398
+ * @returns Middleware handler
399
+ */
400
+ declare function createRateLimitStorageMiddleware<E extends Env = Env>(storage: NonNullable<StorageMiddlewareConfig['rateLimitStorage']>): MiddlewareHandler<E & StorageEnv>;
401
+ /**
402
+ * Creates middleware that injects only idempotency storage.
403
+ *
404
+ * @param storage - Idempotency storage instance
405
+ * @returns Middleware handler
406
+ */
407
+ declare function createIdempotencyStorageMiddleware<E extends Env = Env>(storage: NonNullable<StorageMiddlewareConfig['idempotencyStorage']>): MiddlewareHandler<E & StorageEnv>;
408
+
409
+ /**
410
+ * The set/get/getRequired/resolve quartet every first-party storage feature
411
+ * exposes. `getX(): T | null` returns only explicitly-configured storage and
412
+ * never throws; `getXRequired(): T` throws (or lazy-creates a default when one
413
+ * was configured AND lazyDefaultOnGet is set). `resolveX` applies the
414
+ * explicit > context > global priority chain and NEVER creates a default.
415
+ */
416
+ interface StorageFeature<T> {
417
+ /** The backing registry (exported for advanced use / tests). */
418
+ registry: StorageRegistry<T>;
419
+ /** Set the global storage instance. */
420
+ set(storage: T): void;
421
+ /**
422
+ * Get the explicitly-configured global storage, or null. Never throws.
423
+ * When the feature was created with `lazyDefaultOnGet: true`, this may
424
+ * lazy-create the default (legacy never-null path; cache only).
425
+ */
426
+ get(): T | null;
427
+ /**
428
+ * Get the global storage, throwing if unset. When a defaultFactory was
429
+ * configured, lazy-creates and returns the default (never throws).
430
+ */
431
+ getRequired(): T;
432
+ /** Resolve with priority explicit > context > global. Returns null if none. */
433
+ resolve<E extends Env>(ctx?: Context<E>, explicit?: T): T | null;
434
+ /** Resolve with priority, throwing if none. */
435
+ resolveRequired<E extends Env>(ctx?: Context<E>, explicit?: T): T;
436
+ }
437
+ interface StorageFeatureOptions<T> {
438
+ /** Context-var key (must equal a CONTEXT_KEYS value). */
439
+ contextKey: string;
440
+ /**
441
+ * Optional lazy default factory. When provided, `getRequired()` materializes
442
+ * the default on first access. `resolve*` still never creates hidden state
443
+ * (edge-safe).
444
+ */
445
+ defaultFactory?: () => T;
446
+ /**
447
+ * When true (and a defaultFactory exists), `get()` ALSO lazy-creates the
448
+ * default, preserving a legacy never-null getter. When false/omitted, `get()`
449
+ * returns only explicitly-configured storage (honest `T | null`). Default: false.
450
+ * Only cache sets this true; audit/versioning leave it false.
451
+ */
452
+ lazyDefaultOnGet?: boolean;
453
+ }
454
+ declare function createStorageFeature<T>(options: StorageFeatureOptions<T>): StorageFeature<T>;
455
+
456
+ /**
457
+ * Storage variable keys that can be accessed from context.
458
+ */
459
+ type StorageKey = keyof StorageEnv['Variables'];
460
+ /**
461
+ * Get a storage variable from context using Hono's typed pattern.
462
+ * This is a type-safe alternative to the old getContextVar helper.
463
+ *
464
+ * @param ctx - The Hono context (must be typed with StorageEnv or compatible)
465
+ * @param key - The storage key to retrieve
466
+ * @returns The storage instance or undefined
467
+ *
468
+ * @example
469
+ * ```ts
470
+ * const app = new Hono<StorageEnv>();
471
+ *
472
+ * app.get('/test', (ctx) => {
473
+ * // Option 1: Direct typed access via c.var
474
+ * const storage1 = ctx.var.rateLimitStorage;
475
+ *
476
+ * // Option 2: Using the helper (for generic contexts)
477
+ * const storage2 = getStorage(ctx, 'rateLimitStorage');
478
+ * });
479
+ * ```
480
+ */
481
+ declare function getStorage<K extends StorageKey>(ctx: Context<StorageEnv>, key: K): StorageEnv['Variables'][K];
482
+ /**
483
+ * Resolves logging storage with priority: explicit param > context > global.
484
+ *
485
+ * @param ctx - Optional Hono context
486
+ * @param explicitStorage - Optional explicit storage instance
487
+ * @returns The resolved storage or null if none available
488
+ */
489
+ declare function resolveLoggingStorage<E extends Env>(ctx?: Context<E>, explicitStorage?: LoggingStorage): LoggingStorage | null;
490
+ /**
491
+ * Resolves audit storage with priority: explicit param > context > global.
492
+ *
493
+ * @param ctx - Optional Hono context
494
+ * @param explicitStorage - Optional explicit storage instance
495
+ * @returns The resolved storage, or null when no storage was configured
496
+ */
497
+ declare function resolveAuditStorage<E extends Env>(ctx?: Context<E>, explicitStorage?: AuditLogStorage): AuditLogStorage | null;
498
+ /**
499
+ * Resolves versioning storage with priority: explicit param > context > global.
500
+ *
501
+ * @param ctx - Optional Hono context
502
+ * @param explicitStorage - Optional explicit storage instance
503
+ * @returns The resolved storage, or null when no storage was configured
504
+ */
505
+ declare function resolveVersioningStorage<E extends Env>(ctx?: Context<E>, explicitStorage?: VersioningStorage): VersioningStorage | null;
506
+ /**
507
+ * Resolves API key storage with priority: explicit param > context > global.
508
+ *
509
+ * @param ctx - Optional Hono context
510
+ * @param explicitStorage - Optional explicit storage instance
511
+ * @returns The resolved storage, or null when no storage was configured
512
+ */
513
+ declare function resolveAPIKeyStorage<E extends Env>(ctx?: Context<E>, explicitStorage?: APIKeyStorage): APIKeyStorage | null;
514
+
515
+ export { type CacheEntry as C, type FixedWindowEntry as F, type IdempotencyEntry as I, type RateLimitEntry as R, type SlidingWindowEntry as S, type CacheSetOptions as a, type CacheStats as b, type CacheStorage as c, type IdempotencyStorage as d, type RateLimitStorage as e, type StorageEnv as f, type StorageFeature as g, type StorageFeatureOptions as h, type StorageMiddlewareConfig as i, createAPIKeyStorageMiddleware as j, createAuditStorageMiddleware as k, createCacheStorageMiddleware as l, createIdempotencyStorageMiddleware as m, createLoggingStorageMiddleware as n, createRateLimitStorageMiddleware as o, createStorageFeature as p, createStorageMiddleware as q, createVersioningStorageMiddleware as r, getStorage as s, resolveAPIKeyStorage as t, resolveAuditStorage as u, resolveLoggingStorage as v, resolveVersioningStorage as w };
@@ -1,9 +1,9 @@
1
1
  import { Env, Context, MiddlewareHandler, Hono } from 'hono';
2
2
  import { ZodObject, ZodRawShape } from 'zod';
3
3
  import { OpenAPIHono } from '@hono/zod-openapi';
4
- import { O as OpenAPIRoute } from './route-DF4Y3iR0.js';
5
- import { O as OpenAPIRouteSchema, a as ResponseEnvelope, M as MetaInput, H as HookMode, F as FilterConfig, c as SortDirection } from './types-BpA6OtSX.js';
6
- import { M as ModelObject } from './types-BQkdX9O0.js';
4
+ import { O as OpenAPIRoute } from './route-BULuf1Xl.js';
5
+ import { O as OpenAPIRouteSchema, a as ResponseEnvelope, M as MetaInput, H as HookMode, F as FilterConfig, c as SortDirection } from './types-C_OAPSsu.js';
6
+ import { M as ModelObject } from './types-DXVXRfuq.js';
7
7
 
8
8
  interface OpenAPIConfig$1 {
9
9
  openapi?: string;
@@ -1,6 +1,6 @@
1
- import { z as AuthEnv, a7 as JWTConfig, a5 as JWTClaims, l as APIKeyConfig, m as APIKeyEntry, y as AuthConfig, X as Guard, w as ApprovalConfig, D as AuthorizationCheck, an as OwnershipExtractor, aa as ModelPolicies, x as ApprovalStorage, aq as PendingAction, C as AuthUser, M as MetaInput, O as OpenAPIRouteSchema, P as Constructor, Q as EndpointAuthConfig, aW as APIKeyStorage, n as APIKeyLookupResult } from './types-BpA6OtSX.js';
1
+ import { C as AuthEnv, a8 as JWTConfig, a6 as JWTClaims, n as APIKeyConfig, o as APIKeyEntry, B as AuthConfig, Z as Guard, y as ApprovalConfig, G as AuthorizationCheck, ao as OwnershipExtractor, ab as ModelPolicies, z as ApprovalStorage, ar as PendingAction, E as AuthUser, M as MetaInput, O as OpenAPIRouteSchema, T as Constructor, U as EndpointAuthConfig, aV as APIKeyStorage, p as APIKeyLookupResult } from './types-C_OAPSsu.js';
2
2
  import { MiddlewareHandler, Context } from 'hono';
3
- import { O as OpenAPIRoute } from './route-DF4Y3iR0.js';
3
+ import { O as OpenAPIRoute } from './route-BULuf1Xl.js';
4
4
 
5
5
  /**
6
6
  * Default function to extract a Bearer token from the `Authorization` header.
@@ -1,8 +1,8 @@
1
- import { aB as SchemaResolveContext, T as ErrorResponse, a as ResponseEnvelope, d as Model, N as ComputedFieldsConfig, s as AggregateField, u as AggregateOptions, aI as SoftDeleteConfig, al as NormalizedSoftDeleteConfig, j as AuditFieldChange, A as AuditConfig, aj as NormalizedAuditConfig, h as VersioningConfig, am as NormalizedVersioningConfig, ak as NormalizedMultiTenantConfig, ac as MultiTenantConfig, aD as SearchFieldConfig, aE as SearchMode, M as MetaInput, aa as ModelPolicies, as as PolicyContext, W as FilterCondition, _ as HookContext, V as ValidatedData, H as HookMode, O as OpenAPIRouteSchema, au as RelationConfig, a1 as IncludeOptions, af as NestedUpdateInput, ah as NestedWriteResult, E as CascadeAction, F as FilterConfig, S as SortSpec, L as ListFilters, ao as PaginatedResult, r as AggregateConfig, v as AggregateResult, aF as SearchOptions, aH as SearchResultItem, aG as SearchResult, c as SortDirection, z as AuthEnv } from './types-BpA6OtSX.js';
1
+ import { aB as SchemaResolveContext, W as ErrorResponse, a as ResponseEnvelope, d as Model, Q as ComputedFieldsConfig, u as AggregateField, w as AggregateOptions, aI as SoftDeleteConfig, am as NormalizedSoftDeleteConfig, j as AuditFieldChange, A as AuditConfig, ak as NormalizedAuditConfig, h as VersioningConfig, an as NormalizedVersioningConfig, al as NormalizedMultiTenantConfig, ad as MultiTenantConfig, aD as SearchFieldConfig, aE as SearchMode, M as MetaInput, ab as ModelPolicies, at as PolicyContext, Y as FilterCondition, a0 as HookContext, V as ValidatedData, H as HookMode, O as OpenAPIRouteSchema, k as RelationConfig, l as IncludeOptions, ag as NestedUpdateInput, ai as NestedWriteResult, J as CascadeAction, F as FilterConfig, S as SortSpec, L as ListFilters, ap as PaginatedResult, t as AggregateConfig, x as AggregateResult, aF as SearchOptions, aH as SearchResultItem, aG as SearchResult, c as SortDirection, C as AuthEnv } from './types-C_OAPSsu.js';
2
2
  import { d as LoggingEnv } from './middleware-DBIpdsJ1.js';
3
- import { StorageEnv } from './storage/index.js';
4
- import { O as OpenAPIRoute } from './route-DF4Y3iR0.js';
5
- import { H as HonoOpenAPIApp, G as GeneratedEndpoints } from './index-CNFFLae5.js';
3
+ import { f as StorageEnv } from './helpers-C3xy6C9u.js';
4
+ import { O as OpenAPIRoute } from './route-BULuf1Xl.js';
5
+ import { H as HonoOpenAPIApp, G as GeneratedEndpoints } from './index-BWwvlRfM.js';
6
6
  import { HTTPException } from 'hono/http-exception';
7
7
  import { ContentfulStatusCode } from 'hono/utils/http-status';
8
8
  import { ZodError, z, ZodObject, ZodRawShape } from 'zod';
@@ -11,14 +11,14 @@ import { AuditLogger } from './audit/index.js';
11
11
  import { VersionManager } from './versioning/index.js';
12
12
  import './multi-tenant/index.js';
13
13
  import { d as CrudEventType, a as CrudEventEmitter, c as CrudEventPayload } from './emitter-B6dLhiMF.js';
14
- import { M as ModelObject, F as FieldSelection } from './types-BQkdX9O0.js';
14
+ import { M as ModelObject, F as FieldSelection } from './types-DXVXRfuq.js';
15
15
  import { Hook } from '@hono/zod-openapi';
16
- import './index-DS6aUfcl.js';
16
+ import './index-CZZ12W3j.js';
17
17
  import './logging/index.js';
18
18
  import './registry-3p4qTDGZ.js';
19
19
  import './events/index.js';
20
20
  import './serialization/index.js';
21
- import './types-DcRAcexC.js';
21
+ import './types-B5wq2iKZ.js';
22
22
  import './encryption/index.js';
23
23
  import './types-Drjma4gp.js';
24
24
  import './api-version/index.js';