@spfn/core 0.2.0-beta.66 → 0.2.0-beta.68

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.
Files changed (37) hide show
  1. package/README.md +309 -116
  2. package/dist/authz/index.js +398 -3
  3. package/dist/authz/index.js.map +1 -1
  4. package/dist/codegen/index.d.ts +114 -8
  5. package/dist/codegen/index.js +162 -3
  6. package/dist/codegen/index.js.map +1 -1
  7. package/dist/config/index.js +1 -1
  8. package/dist/config/index.js.map +1 -1
  9. package/dist/contract/index.d.ts +288 -0
  10. package/dist/contract/index.js +534 -0
  11. package/dist/contract/index.js.map +1 -0
  12. package/dist/db/index.js +13 -10
  13. package/dist/db/index.js.map +1 -1
  14. package/dist/{define-middleware-DuXD8Hvu.d.ts → define-middleware-B9bFuXVU.d.ts} +1 -1
  15. package/dist/errors/index.js +398 -3
  16. package/dist/errors/index.js.map +1 -1
  17. package/dist/event/index.d.ts +3 -3
  18. package/dist/event/sse/client.d.ts +2 -2
  19. package/dist/event/sse/index.d.ts +4 -4
  20. package/dist/event/sse/index.js +9 -0
  21. package/dist/event/sse/index.js.map +1 -1
  22. package/dist/event/ws/client.d.ts +2 -2
  23. package/dist/event/ws/index.d.ts +3 -3
  24. package/dist/middleware/index.d.ts +108 -11
  25. package/dist/middleware/index.js +769 -632
  26. package/dist/middleware/index.js.map +1 -1
  27. package/dist/route/index.d.ts +8 -552
  28. package/dist/route/index.js +36 -0
  29. package/dist/route/index.js.map +1 -1
  30. package/dist/router-DhvbMhef.d.ts +641 -0
  31. package/dist/server/index.d.ts +3 -3
  32. package/dist/server/index.js +9 -0
  33. package/dist/server/index.js.map +1 -1
  34. package/dist/{token-manager-jKD_EsSE.d.ts → token-manager-vZeqBbtA.d.ts} +7 -0
  35. package/dist/{types-DVjf37yO.d.ts → types-CF-37KAG.d.ts} +1 -1
  36. package/dist/{types-BFB72jbM.d.ts → types-D9uMxeQS.d.ts} +1 -1
  37. package/package.json +11 -9
@@ -1,11 +1,6 @@
1
1
  import { Context, Next, MiddlewareHandler } from 'hono';
2
- import { b as NamedMiddlewareFactory, N as NamedMiddleware } from '../define-middleware-DuXD8Hvu.js';
3
-
4
- /**
5
- * Error Handler Middleware
6
- *
7
- * Handles SerializableError with automatic serialization and standard errors
8
- */
2
+ import { N as NamedMiddlewareFactory, b as NamedMiddleware } from '../define-middleware-B9bFuXVU.js';
3
+ import { Redis, Cluster } from 'ioredis';
9
4
 
10
5
  /**
11
6
  * Options for ErrorHandler middleware
@@ -27,6 +22,21 @@ interface ErrorHandlerOptions {
27
22
  * @default true
28
23
  */
29
24
  enableLogging?: boolean;
25
+ /**
26
+ * Attach the `error` envelope to every error response
27
+ *
28
+ * The envelope is `{ code, message, requestId }` sitting next to the
29
+ * existing `__type` and `message` fields, never replacing them: a web
30
+ * client restores its error class from `__type` while a generated client
31
+ * in another language classifies by `code` alone and cannot read a
32
+ * discriminator it has no registry for.
33
+ *
34
+ * Turn it off only to keep error bodies byte-identical to an older
35
+ * release. A route reached by a generated client needs it.
36
+ *
37
+ * @default true
38
+ */
39
+ errorEnvelope?: boolean;
30
40
  /**
31
41
  * Callback invoked when an error occurs
32
42
  *
@@ -57,6 +67,10 @@ interface OnErrorContext {
57
67
  * SerializableError instances are serialized using their toJSON() method,
58
68
  * preserving custom fields like `resource`, `fields`, etc.
59
69
  *
70
+ * Every response also carries an `error` envelope — `{ code, message,
71
+ * requestId }` — for clients that classify by a single documented field
72
+ * instead of restoring an error class from `__type`.
73
+ *
60
74
  * @param options - Configuration options
61
75
  * @returns Error handler function for Hono's onError hook
62
76
  *
@@ -76,7 +90,8 @@ interface OnErrorContext {
76
90
  * // Throw SerializableError in routes
77
91
  * app.get('/users/:id', (c) => {
78
92
  * throw new NotFoundError({ message: 'User not found', resource: 'User' });
79
- * // Response: { __type: 'NotFoundError', message: 'User not found', resource: 'User' }
93
+ * // Response: { __type: 'NotFoundError', message: 'User not found', resource: 'User',
94
+ * // error: { code: 'NotFoundError', message: 'User not found', requestId: '…' } }
80
95
  * });
81
96
  * ```
82
97
  */
@@ -285,6 +300,8 @@ declare function createCacheNonceStore(cache: CacheClient, prefix?: string): Non
285
300
  */
286
301
  declare function createInMemoryNonceStore(): NonceStore;
287
302
 
303
+ /** Test seam: forget every process-local window. */
304
+ declare function resetMemoryRateLimitStore(): void;
288
305
  /**
289
306
  * One identity dimension to limit on. A bare string uses the top-level `limit`;
290
307
  * an object can carry its own `limit` so dimensions can differ (e.g. a loose
@@ -311,7 +328,11 @@ interface RateLimitOptions {
311
328
  * client IP only.
312
329
  */
313
330
  by?: (c: Context) => (RateLimitDimension | null | undefined)[] | Promise<(RateLimitDimension | null | undefined)[]>;
314
- /** Reject with 429 instead of allowing through when the cache is unavailable. */
331
+ /**
332
+ * Refuse with 429 when the shared cache is unavailable, instead of counting
333
+ * in this process. For a surface where a per-process count is not an
334
+ * acceptable substitute for a shared one.
335
+ */
315
336
  failClosed?: boolean;
316
337
  /** Message for the 429 response. */
317
338
  message?: string;
@@ -333,7 +354,8 @@ interface RateLimitOptions {
333
354
  */
334
355
  declare function getClientIp(c: Context): string;
335
356
  /**
336
- * Redis-backed fixed-window rate limiter.
357
+ * Fixed-window rate limiter — counters in the shared cache when there is one,
358
+ * in this process otherwise.
337
359
  *
338
360
  * @example
339
361
  * ```typescript
@@ -380,4 +402,79 @@ declare function getRateLimitPolicy(name: string): RateLimitOptions | undefined;
380
402
  */
381
403
  declare function rateLimitPolicy(name: string, fallback: RateLimitOptions): NamedMiddleware<string>;
382
404
 
383
- export { type ClientType, ErrorHandler, type ErrorHandlerOptions, type NonceStore, type OnErrorContext, type ProxyGuardConfig, type ProxyGuardMode, type RateLimitDimension, type RateLimitOptions, RequestLogger, type RequestLoggerConfig, type RequestLoggerOptions, createCacheNonceStore, createInMemoryNonceStore, createProxyGuard, getClientIp, getRateLimitPolicy, maskSensitiveData, rateLimit, rateLimitPolicy, setRateLimitFailClosedDefault, setRateLimitPolicies };
405
+ /**
406
+ * The rate limiter's fixed-window counter as a pluggable store — the same
407
+ * pattern the clientProofV1 replay ledger and the SSE token manager already
408
+ * use: an in-memory default, a Redis/Valkey store on top of `getCache()`, and
409
+ * the cache's presence deciding which one a request gets.
410
+ *
411
+ * Why the memory default exists: without it, "no cache configured" and "cache
412
+ * down" both meant the limiter had nowhere to count, so the only choices were
413
+ * letting every request through or refusing every request. Neither is what a
414
+ * limiter is for. Counting in the process is worse than counting in Redis and
415
+ * far better than not counting.
416
+ *
417
+ * What the memory store cannot do is span processes. Behind N instances each
418
+ * keeps its own counters, so the effective limit is N times the configured one.
419
+ * That is the cost of the fallback and the reason a real deployment still
420
+ * points `CACHE_URL` at Redis.
421
+ *
422
+ * @module middleware/rate-limit-store
423
+ */
424
+
425
+ /** One window's state for a key: how many hits, and how long the window has left. */
426
+ interface RateLimitHit {
427
+ count: number;
428
+ /** Milliseconds until the window resets. */
429
+ pttl: number;
430
+ }
431
+ /** What the limiter needs from wherever the counters live. */
432
+ interface RateLimitStore {
433
+ /** Records one hit against `key` and answers the window's state after it. */
434
+ hit(key: string, windowMs: number): Promise<RateLimitHit>;
435
+ }
436
+ /**
437
+ * Counters in Redis — shared by every instance, so the configured limit is the
438
+ * limit no matter how many processes serve the traffic.
439
+ */
440
+ declare class CacheRateLimitStore implements RateLimitStore {
441
+ private readonly cache;
442
+ constructor(cache: Redis | Cluster);
443
+ hit(key: string, windowMs: number): Promise<RateLimitHit>;
444
+ }
445
+ /**
446
+ * Counters in this process.
447
+ *
448
+ * Entries are pruned lazily — a key past its window resets on its next hit, so
449
+ * the common path stays O(1). Keys nobody touches again would otherwise sit
450
+ * there forever, so a full sweep runs once the map crosses `maxKeys`; that
451
+ * bound is what keeps a limiter keyed by client IP from growing without end.
452
+ */
453
+ declare class MemoryRateLimitStore implements RateLimitStore {
454
+ private readonly maxKeys;
455
+ private readonly windows;
456
+ /** How many live windows were dropped to stay under `maxKeys`. */
457
+ private evicted;
458
+ constructor(maxKeys?: number);
459
+ hit(key: string, windowMs: number): Promise<RateLimitHit>;
460
+ /** Drops every window that has already reset. */
461
+ prune(nowMillis: number): void;
462
+ /**
463
+ * Frees a slot for a new key.
464
+ *
465
+ * Expired windows go first. If every window is still live the oldest one is
466
+ * dropped, because a hard bound is the only thing standing between a
467
+ * per-IP limiter and unbounded memory. A dropped window loses its count,
468
+ * so a caller who can mint `maxKeys` distinct identities can push another
469
+ * one out — which is why `evictionCount` is worth watching: it says this
470
+ * process is past what an in-memory limiter should be asked to hold, and
471
+ * the deployment wants a real cache.
472
+ */
473
+ private makeRoom;
474
+ get size(): number;
475
+ /** Live windows dropped for capacity — nonzero means the bound is binding. */
476
+ get evictionCount(): number;
477
+ clear(): void;
478
+ }
479
+
480
+ export { CacheRateLimitStore, type ClientType, ErrorHandler, type ErrorHandlerOptions, MemoryRateLimitStore, type NonceStore, type OnErrorContext, type ProxyGuardConfig, type ProxyGuardMode, type RateLimitDimension, type RateLimitHit, type RateLimitOptions, type RateLimitStore, RequestLogger, type RequestLoggerConfig, type RequestLoggerOptions, createCacheNonceStore, createInMemoryNonceStore, createProxyGuard, getClientIp, getRateLimitPolicy, maskSensitiveData, rateLimit, rateLimitPolicy, resetMemoryRateLimitStore, setRateLimitFailClosedDefault, setRateLimitPolicies };