@shware/http 2.5.0 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -47,4 +47,31 @@ app.get('/', () => {
47
47
  serve({ fetch: app.fetch, port: 3000 });
48
48
  ```
49
49
 
50
+ ## Custom ErrorReason
51
+
52
+ By default, `ErrorInfo.reason` accepts any `string`. You can use TypeScript [declaration merging](https://www.typescriptlang.org/docs/handbook/declaration-merging.html) to constrain it to a set of known keys:
53
+
54
+ ```typescript
55
+ // e.g. shware-http.d.ts
56
+ declare module '@shware/http' {
57
+ interface ErrorReason {
58
+ ACCOUNT_BLOCKED: string;
59
+ ACCOUNT_LOCKED: string;
60
+ SUBSCRIPTION_EXPIRED: string;
61
+ }
62
+ }
63
+ ```
64
+
65
+ Once declared, `Details.errorInfo({ reason })` will only accept the keys you defined, with full autocomplete support:
66
+
67
+ ```typescript
68
+ import { Details } from '@shware/http';
69
+
70
+ // OK
71
+ Details.new().errorInfo({ reason: 'ACCOUNT_BLOCKED' });
72
+
73
+ // Type error: Type '"INVALID"' is not assignable to type 'keyof ErrorReason'
74
+ Details.new().errorInfo({ reason: 'INVALID' });
75
+ ```
76
+
50
77
  ## nestjs example
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/error/detail.ts"],"sourcesContent":["/** reference: https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto */\nimport type { ErrorReason } from './reason';\n\nexport enum DetailType {\n ERROR_INFO = 'type.googleapis.com/google.rpc.ErrorInfo',\n RETRY_INFO = 'type.googleapis.com/google.rpc.RetryInfo',\n DEBUG_INFO = 'type.googleapis.com/google.rpc.DebugInfo',\n QUOTA_FAILURE = 'type.googleapis.com/google.rpc.QuotaFailure',\n PRECONDITION_FAILURE = 'type.googleapis.com/google.rpc.PreconditionFailure',\n BAD_REQUEST = 'type.googleapis.com/google.rpc.BadRequest',\n REQUEST_INFO = 'type.googleapis.com/google.rpc.RequestInfo',\n RESOURCE_INFO = 'type.googleapis.com/google.rpc.ResourceInfo',\n HELP = 'type.googleapis.com/google.rpc.Help',\n LOCALIZED_MESSAGE = 'type.googleapis.com/google.rpc.LocalizedMessage',\n}\n\nexport interface ErrorInfo {\n '@type': DetailType.ERROR_INFO;\n reason: keyof ErrorReason | (string & {});\n domain?: string;\n metadata?: Record<string, string>;\n}\n\nexport interface RetryInfo {\n '@type': DetailType.RETRY_INFO;\n retryDelay: number;\n}\n\nexport interface DebugInfo {\n '@type': DetailType.DEBUG_INFO;\n stackEntries: string[];\n detail: string;\n}\n\nexport interface QuotaFailure {\n '@type': DetailType.QUOTA_FAILURE;\n violations: {\n subject: string;\n description: string;\n apiService: string;\n quoteId: string;\n quoteValue: number;\n quotaMetric: string;\n quotaDimensions: Record<string, string>;\n futureQuotaValue: number;\n }[];\n}\n\nexport interface PreconditionFailure {\n '@type': DetailType.PRECONDITION_FAILURE;\n violations: { type: string; subject: string; description: string }[];\n}\n\nexport interface BadRequest {\n '@type': DetailType.BAD_REQUEST;\n fieldViolations: {\n field: string;\n description: string;\n reason: string;\n localizedMessage: Omit<LocalizedMessage, '@type'>;\n }[];\n}\n\nexport interface RequestInfo {\n '@type': DetailType.REQUEST_INFO;\n requestId: string;\n servingData: string;\n}\n\nexport interface ResourceInfo {\n '@type': DetailType.RESOURCE_INFO;\n resourceType: string;\n resourceName: string;\n owner: string;\n description: string;\n}\n\nexport interface Help {\n '@type': DetailType.HELP;\n links: { url: string; description: string }[];\n}\n\nexport interface LocalizedMessage {\n '@type': DetailType.LOCALIZED_MESSAGE;\n locale: string;\n message: string;\n}\n\nexport type Detail =\n | RetryInfo\n | DebugInfo\n | QuotaFailure\n | ErrorInfo\n | PreconditionFailure\n | BadRequest\n | RequestInfo\n | ResourceInfo\n | Help\n | LocalizedMessage;\n\n/**\n * Example usage:\n * const details = Details.new()\n * .requestInfo({ requestId: '1234567890', servingData: '/v1/tests' })\n * .errorInfo({ reason: 'ACCOUNT_LOCKED' });\n * */\nexport class Details {\n readonly list: Detail[] = [];\n private constructor() {}\n\n static new() {\n return new Details();\n }\n\n errorInfo(detail: Omit<ErrorInfo, '@type'>) {\n this.list.push({ '@type': DetailType.ERROR_INFO, ...detail });\n return this;\n }\n\n retryInfo(detail: Omit<RetryInfo, '@type'>) {\n this.list.push({ '@type': DetailType.RETRY_INFO, ...detail });\n return this;\n }\n\n debugInfo(detail: Omit<DebugInfo, '@type'>) {\n this.list.push({ '@type': DetailType.DEBUG_INFO, ...detail });\n return this;\n }\n\n quotaFailure(detail: Omit<QuotaFailure, '@type'>) {\n this.list.push({ '@type': DetailType.QUOTA_FAILURE, ...detail });\n return this;\n }\n\n preconditionFailure(detail: Omit<PreconditionFailure, '@type'>) {\n this.list.push({ '@type': DetailType.PRECONDITION_FAILURE, ...detail });\n return this;\n }\n\n badRequest(detail: Omit<BadRequest, '@type'>) {\n this.list.push({ '@type': DetailType.BAD_REQUEST, ...detail });\n return this;\n }\n\n requestInfo(detail: Omit<RequestInfo, '@type'>) {\n this.list.push({ '@type': DetailType.REQUEST_INFO, ...detail });\n return this;\n }\n\n resourceInfo(detail: Omit<ResourceInfo, '@type'>) {\n this.list.push({ '@type': DetailType.RESOURCE_INFO, ...detail });\n return this;\n }\n\n help(detail: Omit<Help, '@type'>) {\n this.list.push({ '@type': DetailType.HELP, ...detail });\n return this;\n }\n\n localizedMessage(detail: Omit<LocalizedMessage, '@type'>) {\n this.list.push({ '@type': DetailType.LOCALIZED_MESSAGE, ...detail });\n return this;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAGO,IAAK,aAAL,kBAAKA,gBAAL;AACL,EAAAA,YAAA,gBAAa;AACb,EAAAA,YAAA,gBAAa;AACb,EAAAA,YAAA,gBAAa;AACb,EAAAA,YAAA,mBAAgB;AAChB,EAAAA,YAAA,0BAAuB;AACvB,EAAAA,YAAA,iBAAc;AACd,EAAAA,YAAA,kBAAe;AACf,EAAAA,YAAA,mBAAgB;AAChB,EAAAA,YAAA,UAAO;AACP,EAAAA,YAAA,uBAAoB;AAVV,SAAAA;AAAA,GAAA;AAuGL,IAAM,UAAN,MAAM,SAAQ;AAAA,EACV,OAAiB,CAAC;AAAA,EACnB,cAAc;AAAA,EAAC;AAAA,EAEvB,OAAO,MAAM;AACX,WAAO,IAAI,SAAQ;AAAA,EACrB;AAAA,EAEA,UAAU,QAAkC;AAC1C,SAAK,KAAK,KAAK,EAAE,SAAS,6DAAuB,GAAG,OAAO,CAAC;AAC5D,WAAO;AAAA,EACT;AAAA,EAEA,UAAU,QAAkC;AAC1C,SAAK,KAAK,KAAK,EAAE,SAAS,6DAAuB,GAAG,OAAO,CAAC;AAC5D,WAAO;AAAA,EACT;AAAA,EAEA,UAAU,QAAkC;AAC1C,SAAK,KAAK,KAAK,EAAE,SAAS,6DAAuB,GAAG,OAAO,CAAC;AAC5D,WAAO;AAAA,EACT;AAAA,EAEA,aAAa,QAAqC;AAChD,SAAK,KAAK,KAAK,EAAE,SAAS,mEAA0B,GAAG,OAAO,CAAC;AAC/D,WAAO;AAAA,EACT;AAAA,EAEA,oBAAoB,QAA4C;AAC9D,SAAK,KAAK,KAAK,EAAE,SAAS,iFAAiC,GAAG,OAAO,CAAC;AACtE,WAAO;AAAA,EACT;AAAA,EAEA,WAAW,QAAmC;AAC5C,SAAK,KAAK,KAAK,EAAE,SAAS,+DAAwB,GAAG,OAAO,CAAC;AAC7D,WAAO;AAAA,EACT;AAAA,EAEA,YAAY,QAAoC;AAC9C,SAAK,KAAK,KAAK,EAAE,SAAS,iEAAyB,GAAG,OAAO,CAAC;AAC9D,WAAO;AAAA,EACT;AAAA,EAEA,aAAa,QAAqC;AAChD,SAAK,KAAK,KAAK,EAAE,SAAS,mEAA0B,GAAG,OAAO,CAAC;AAC/D,WAAO;AAAA,EACT;AAAA,EAEA,KAAK,QAA6B;AAChC,SAAK,KAAK,KAAK,EAAE,SAAS,kDAAiB,GAAG,OAAO,CAAC;AACtD,WAAO;AAAA,EACT;AAAA,EAEA,iBAAiB,QAAyC;AACxD,SAAK,KAAK,KAAK,EAAE,SAAS,2EAA8B,GAAG,OAAO,CAAC;AACnE,WAAO;AAAA,EACT;AACF;","names":["DetailType"]}
1
+ {"version":3,"sources":["../../src/error/detail.ts"],"sourcesContent":["/** reference: https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto */\nimport type { ResolvedErrorReason } from './reason';\n\nexport enum DetailType {\n ERROR_INFO = 'type.googleapis.com/google.rpc.ErrorInfo',\n RETRY_INFO = 'type.googleapis.com/google.rpc.RetryInfo',\n DEBUG_INFO = 'type.googleapis.com/google.rpc.DebugInfo',\n QUOTA_FAILURE = 'type.googleapis.com/google.rpc.QuotaFailure',\n PRECONDITION_FAILURE = 'type.googleapis.com/google.rpc.PreconditionFailure',\n BAD_REQUEST = 'type.googleapis.com/google.rpc.BadRequest',\n REQUEST_INFO = 'type.googleapis.com/google.rpc.RequestInfo',\n RESOURCE_INFO = 'type.googleapis.com/google.rpc.ResourceInfo',\n HELP = 'type.googleapis.com/google.rpc.Help',\n LOCALIZED_MESSAGE = 'type.googleapis.com/google.rpc.LocalizedMessage',\n}\n\nexport interface ErrorInfo {\n '@type': DetailType.ERROR_INFO;\n reason: ResolvedErrorReason;\n domain?: string;\n metadata?: Record<string, string>;\n}\n\nexport interface RetryInfo {\n '@type': DetailType.RETRY_INFO;\n retryDelay: number;\n}\n\nexport interface DebugInfo {\n '@type': DetailType.DEBUG_INFO;\n stackEntries: string[];\n detail: string;\n}\n\nexport interface QuotaFailure {\n '@type': DetailType.QUOTA_FAILURE;\n violations: {\n subject: string;\n description: string;\n apiService: string;\n quoteId: string;\n quoteValue: number;\n quotaMetric: string;\n quotaDimensions: Record<string, string>;\n futureQuotaValue: number;\n }[];\n}\n\nexport interface PreconditionFailure {\n '@type': DetailType.PRECONDITION_FAILURE;\n violations: { type: string; subject: string; description: string }[];\n}\n\nexport interface BadRequest {\n '@type': DetailType.BAD_REQUEST;\n fieldViolations: {\n field: string;\n description: string;\n reason: string;\n localizedMessage: Omit<LocalizedMessage, '@type'>;\n }[];\n}\n\nexport interface RequestInfo {\n '@type': DetailType.REQUEST_INFO;\n requestId: string;\n servingData: string;\n}\n\nexport interface ResourceInfo {\n '@type': DetailType.RESOURCE_INFO;\n resourceType: string;\n resourceName: string;\n owner: string;\n description: string;\n}\n\nexport interface Help {\n '@type': DetailType.HELP;\n links: { url: string; description: string }[];\n}\n\nexport interface LocalizedMessage {\n '@type': DetailType.LOCALIZED_MESSAGE;\n locale: string;\n message: string;\n}\n\nexport type Detail =\n | RetryInfo\n | DebugInfo\n | QuotaFailure\n | ErrorInfo\n | PreconditionFailure\n | BadRequest\n | RequestInfo\n | ResourceInfo\n | Help\n | LocalizedMessage;\n\n/**\n * Example usage:\n * const details = Details.new()\n * .requestInfo({ requestId: '1234567890', servingData: '/v1/tests' })\n * .errorInfo({ reason: 'ACCOUNT_LOCKED' });\n * */\nexport class Details {\n readonly list: Detail[] = [];\n private constructor() {}\n\n static new() {\n return new Details();\n }\n\n errorInfo(detail: Omit<ErrorInfo, '@type'>) {\n this.list.push({ '@type': DetailType.ERROR_INFO, ...detail });\n return this;\n }\n\n retryInfo(detail: Omit<RetryInfo, '@type'>) {\n this.list.push({ '@type': DetailType.RETRY_INFO, ...detail });\n return this;\n }\n\n debugInfo(detail: Omit<DebugInfo, '@type'>) {\n this.list.push({ '@type': DetailType.DEBUG_INFO, ...detail });\n return this;\n }\n\n quotaFailure(detail: Omit<QuotaFailure, '@type'>) {\n this.list.push({ '@type': DetailType.QUOTA_FAILURE, ...detail });\n return this;\n }\n\n preconditionFailure(detail: Omit<PreconditionFailure, '@type'>) {\n this.list.push({ '@type': DetailType.PRECONDITION_FAILURE, ...detail });\n return this;\n }\n\n badRequest(detail: Omit<BadRequest, '@type'>) {\n this.list.push({ '@type': DetailType.BAD_REQUEST, ...detail });\n return this;\n }\n\n requestInfo(detail: Omit<RequestInfo, '@type'>) {\n this.list.push({ '@type': DetailType.REQUEST_INFO, ...detail });\n return this;\n }\n\n resourceInfo(detail: Omit<ResourceInfo, '@type'>) {\n this.list.push({ '@type': DetailType.RESOURCE_INFO, ...detail });\n return this;\n }\n\n help(detail: Omit<Help, '@type'>) {\n this.list.push({ '@type': DetailType.HELP, ...detail });\n return this;\n }\n\n localizedMessage(detail: Omit<LocalizedMessage, '@type'>) {\n this.list.push({ '@type': DetailType.LOCALIZED_MESSAGE, ...detail });\n return this;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAGO,IAAK,aAAL,kBAAKA,gBAAL;AACL,EAAAA,YAAA,gBAAa;AACb,EAAAA,YAAA,gBAAa;AACb,EAAAA,YAAA,gBAAa;AACb,EAAAA,YAAA,mBAAgB;AAChB,EAAAA,YAAA,0BAAuB;AACvB,EAAAA,YAAA,iBAAc;AACd,EAAAA,YAAA,kBAAe;AACf,EAAAA,YAAA,mBAAgB;AAChB,EAAAA,YAAA,UAAO;AACP,EAAAA,YAAA,uBAAoB;AAVV,SAAAA;AAAA,GAAA;AAuGL,IAAM,UAAN,MAAM,SAAQ;AAAA,EACV,OAAiB,CAAC;AAAA,EACnB,cAAc;AAAA,EAAC;AAAA,EAEvB,OAAO,MAAM;AACX,WAAO,IAAI,SAAQ;AAAA,EACrB;AAAA,EAEA,UAAU,QAAkC;AAC1C,SAAK,KAAK,KAAK,EAAE,SAAS,6DAAuB,GAAG,OAAO,CAAC;AAC5D,WAAO;AAAA,EACT;AAAA,EAEA,UAAU,QAAkC;AAC1C,SAAK,KAAK,KAAK,EAAE,SAAS,6DAAuB,GAAG,OAAO,CAAC;AAC5D,WAAO;AAAA,EACT;AAAA,EAEA,UAAU,QAAkC;AAC1C,SAAK,KAAK,KAAK,EAAE,SAAS,6DAAuB,GAAG,OAAO,CAAC;AAC5D,WAAO;AAAA,EACT;AAAA,EAEA,aAAa,QAAqC;AAChD,SAAK,KAAK,KAAK,EAAE,SAAS,mEAA0B,GAAG,OAAO,CAAC;AAC/D,WAAO;AAAA,EACT;AAAA,EAEA,oBAAoB,QAA4C;AAC9D,SAAK,KAAK,KAAK,EAAE,SAAS,iFAAiC,GAAG,OAAO,CAAC;AACtE,WAAO;AAAA,EACT;AAAA,EAEA,WAAW,QAAmC;AAC5C,SAAK,KAAK,KAAK,EAAE,SAAS,+DAAwB,GAAG,OAAO,CAAC;AAC7D,WAAO;AAAA,EACT;AAAA,EAEA,YAAY,QAAoC;AAC9C,SAAK,KAAK,KAAK,EAAE,SAAS,iEAAyB,GAAG,OAAO,CAAC;AAC9D,WAAO;AAAA,EACT;AAAA,EAEA,aAAa,QAAqC;AAChD,SAAK,KAAK,KAAK,EAAE,SAAS,mEAA0B,GAAG,OAAO,CAAC;AAC/D,WAAO;AAAA,EACT;AAAA,EAEA,KAAK,QAA6B;AAChC,SAAK,KAAK,KAAK,EAAE,SAAS,kDAAiB,GAAG,OAAO,CAAC;AACtD,WAAO;AAAA,EACT;AAAA,EAEA,iBAAiB,QAAyC;AACxD,SAAK,KAAK,KAAK,EAAE,SAAS,2EAA8B,GAAG,OAAO,CAAC;AACnE,WAAO;AAAA,EACT;AACF;","names":["DetailType"]}
@@ -1,4 +1,4 @@
1
- import { ErrorReason } from './reason.cjs';
1
+ import { ResolvedErrorReason } from './reason.cjs';
2
2
 
3
3
  /** reference: https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto */
4
4
 
@@ -16,7 +16,7 @@ declare enum DetailType {
16
16
  }
17
17
  interface ErrorInfo {
18
18
  '@type': DetailType.ERROR_INFO;
19
- reason: keyof ErrorReason | (string & {});
19
+ reason: ResolvedErrorReason;
20
20
  domain?: string;
21
21
  metadata?: Record<string, string>;
22
22
  }
@@ -1,4 +1,4 @@
1
- import { ErrorReason } from './reason.js';
1
+ import { ResolvedErrorReason } from './reason.js';
2
2
 
3
3
  /** reference: https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto */
4
4
 
@@ -16,7 +16,7 @@ declare enum DetailType {
16
16
  }
17
17
  interface ErrorInfo {
18
18
  '@type': DetailType.ERROR_INFO;
19
- reason: keyof ErrorReason | (string & {});
19
+ reason: ResolvedErrorReason;
20
20
  domain?: string;
21
21
  metadata?: Record<string, string>;
22
22
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/error/detail.ts"],"sourcesContent":["/** reference: https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto */\nimport type { ErrorReason } from './reason';\n\nexport enum DetailType {\n ERROR_INFO = 'type.googleapis.com/google.rpc.ErrorInfo',\n RETRY_INFO = 'type.googleapis.com/google.rpc.RetryInfo',\n DEBUG_INFO = 'type.googleapis.com/google.rpc.DebugInfo',\n QUOTA_FAILURE = 'type.googleapis.com/google.rpc.QuotaFailure',\n PRECONDITION_FAILURE = 'type.googleapis.com/google.rpc.PreconditionFailure',\n BAD_REQUEST = 'type.googleapis.com/google.rpc.BadRequest',\n REQUEST_INFO = 'type.googleapis.com/google.rpc.RequestInfo',\n RESOURCE_INFO = 'type.googleapis.com/google.rpc.ResourceInfo',\n HELP = 'type.googleapis.com/google.rpc.Help',\n LOCALIZED_MESSAGE = 'type.googleapis.com/google.rpc.LocalizedMessage',\n}\n\nexport interface ErrorInfo {\n '@type': DetailType.ERROR_INFO;\n reason: keyof ErrorReason | (string & {});\n domain?: string;\n metadata?: Record<string, string>;\n}\n\nexport interface RetryInfo {\n '@type': DetailType.RETRY_INFO;\n retryDelay: number;\n}\n\nexport interface DebugInfo {\n '@type': DetailType.DEBUG_INFO;\n stackEntries: string[];\n detail: string;\n}\n\nexport interface QuotaFailure {\n '@type': DetailType.QUOTA_FAILURE;\n violations: {\n subject: string;\n description: string;\n apiService: string;\n quoteId: string;\n quoteValue: number;\n quotaMetric: string;\n quotaDimensions: Record<string, string>;\n futureQuotaValue: number;\n }[];\n}\n\nexport interface PreconditionFailure {\n '@type': DetailType.PRECONDITION_FAILURE;\n violations: { type: string; subject: string; description: string }[];\n}\n\nexport interface BadRequest {\n '@type': DetailType.BAD_REQUEST;\n fieldViolations: {\n field: string;\n description: string;\n reason: string;\n localizedMessage: Omit<LocalizedMessage, '@type'>;\n }[];\n}\n\nexport interface RequestInfo {\n '@type': DetailType.REQUEST_INFO;\n requestId: string;\n servingData: string;\n}\n\nexport interface ResourceInfo {\n '@type': DetailType.RESOURCE_INFO;\n resourceType: string;\n resourceName: string;\n owner: string;\n description: string;\n}\n\nexport interface Help {\n '@type': DetailType.HELP;\n links: { url: string; description: string }[];\n}\n\nexport interface LocalizedMessage {\n '@type': DetailType.LOCALIZED_MESSAGE;\n locale: string;\n message: string;\n}\n\nexport type Detail =\n | RetryInfo\n | DebugInfo\n | QuotaFailure\n | ErrorInfo\n | PreconditionFailure\n | BadRequest\n | RequestInfo\n | ResourceInfo\n | Help\n | LocalizedMessage;\n\n/**\n * Example usage:\n * const details = Details.new()\n * .requestInfo({ requestId: '1234567890', servingData: '/v1/tests' })\n * .errorInfo({ reason: 'ACCOUNT_LOCKED' });\n * */\nexport class Details {\n readonly list: Detail[] = [];\n private constructor() {}\n\n static new() {\n return new Details();\n }\n\n errorInfo(detail: Omit<ErrorInfo, '@type'>) {\n this.list.push({ '@type': DetailType.ERROR_INFO, ...detail });\n return this;\n }\n\n retryInfo(detail: Omit<RetryInfo, '@type'>) {\n this.list.push({ '@type': DetailType.RETRY_INFO, ...detail });\n return this;\n }\n\n debugInfo(detail: Omit<DebugInfo, '@type'>) {\n this.list.push({ '@type': DetailType.DEBUG_INFO, ...detail });\n return this;\n }\n\n quotaFailure(detail: Omit<QuotaFailure, '@type'>) {\n this.list.push({ '@type': DetailType.QUOTA_FAILURE, ...detail });\n return this;\n }\n\n preconditionFailure(detail: Omit<PreconditionFailure, '@type'>) {\n this.list.push({ '@type': DetailType.PRECONDITION_FAILURE, ...detail });\n return this;\n }\n\n badRequest(detail: Omit<BadRequest, '@type'>) {\n this.list.push({ '@type': DetailType.BAD_REQUEST, ...detail });\n return this;\n }\n\n requestInfo(detail: Omit<RequestInfo, '@type'>) {\n this.list.push({ '@type': DetailType.REQUEST_INFO, ...detail });\n return this;\n }\n\n resourceInfo(detail: Omit<ResourceInfo, '@type'>) {\n this.list.push({ '@type': DetailType.RESOURCE_INFO, ...detail });\n return this;\n }\n\n help(detail: Omit<Help, '@type'>) {\n this.list.push({ '@type': DetailType.HELP, ...detail });\n return this;\n }\n\n localizedMessage(detail: Omit<LocalizedMessage, '@type'>) {\n this.list.push({ '@type': DetailType.LOCALIZED_MESSAGE, ...detail });\n return this;\n }\n}\n"],"mappings":";AAGO,IAAK,aAAL,kBAAKA,gBAAL;AACL,EAAAA,YAAA,gBAAa;AACb,EAAAA,YAAA,gBAAa;AACb,EAAAA,YAAA,gBAAa;AACb,EAAAA,YAAA,mBAAgB;AAChB,EAAAA,YAAA,0BAAuB;AACvB,EAAAA,YAAA,iBAAc;AACd,EAAAA,YAAA,kBAAe;AACf,EAAAA,YAAA,mBAAgB;AAChB,EAAAA,YAAA,UAAO;AACP,EAAAA,YAAA,uBAAoB;AAVV,SAAAA;AAAA,GAAA;AAuGL,IAAM,UAAN,MAAM,SAAQ;AAAA,EACV,OAAiB,CAAC;AAAA,EACnB,cAAc;AAAA,EAAC;AAAA,EAEvB,OAAO,MAAM;AACX,WAAO,IAAI,SAAQ;AAAA,EACrB;AAAA,EAEA,UAAU,QAAkC;AAC1C,SAAK,KAAK,KAAK,EAAE,SAAS,6DAAuB,GAAG,OAAO,CAAC;AAC5D,WAAO;AAAA,EACT;AAAA,EAEA,UAAU,QAAkC;AAC1C,SAAK,KAAK,KAAK,EAAE,SAAS,6DAAuB,GAAG,OAAO,CAAC;AAC5D,WAAO;AAAA,EACT;AAAA,EAEA,UAAU,QAAkC;AAC1C,SAAK,KAAK,KAAK,EAAE,SAAS,6DAAuB,GAAG,OAAO,CAAC;AAC5D,WAAO;AAAA,EACT;AAAA,EAEA,aAAa,QAAqC;AAChD,SAAK,KAAK,KAAK,EAAE,SAAS,mEAA0B,GAAG,OAAO,CAAC;AAC/D,WAAO;AAAA,EACT;AAAA,EAEA,oBAAoB,QAA4C;AAC9D,SAAK,KAAK,KAAK,EAAE,SAAS,iFAAiC,GAAG,OAAO,CAAC;AACtE,WAAO;AAAA,EACT;AAAA,EAEA,WAAW,QAAmC;AAC5C,SAAK,KAAK,KAAK,EAAE,SAAS,+DAAwB,GAAG,OAAO,CAAC;AAC7D,WAAO;AAAA,EACT;AAAA,EAEA,YAAY,QAAoC;AAC9C,SAAK,KAAK,KAAK,EAAE,SAAS,iEAAyB,GAAG,OAAO,CAAC;AAC9D,WAAO;AAAA,EACT;AAAA,EAEA,aAAa,QAAqC;AAChD,SAAK,KAAK,KAAK,EAAE,SAAS,mEAA0B,GAAG,OAAO,CAAC;AAC/D,WAAO;AAAA,EACT;AAAA,EAEA,KAAK,QAA6B;AAChC,SAAK,KAAK,KAAK,EAAE,SAAS,kDAAiB,GAAG,OAAO,CAAC;AACtD,WAAO;AAAA,EACT;AAAA,EAEA,iBAAiB,QAAyC;AACxD,SAAK,KAAK,KAAK,EAAE,SAAS,2EAA8B,GAAG,OAAO,CAAC;AACnE,WAAO;AAAA,EACT;AACF;","names":["DetailType"]}
1
+ {"version":3,"sources":["../../src/error/detail.ts"],"sourcesContent":["/** reference: https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto */\nimport type { ResolvedErrorReason } from './reason';\n\nexport enum DetailType {\n ERROR_INFO = 'type.googleapis.com/google.rpc.ErrorInfo',\n RETRY_INFO = 'type.googleapis.com/google.rpc.RetryInfo',\n DEBUG_INFO = 'type.googleapis.com/google.rpc.DebugInfo',\n QUOTA_FAILURE = 'type.googleapis.com/google.rpc.QuotaFailure',\n PRECONDITION_FAILURE = 'type.googleapis.com/google.rpc.PreconditionFailure',\n BAD_REQUEST = 'type.googleapis.com/google.rpc.BadRequest',\n REQUEST_INFO = 'type.googleapis.com/google.rpc.RequestInfo',\n RESOURCE_INFO = 'type.googleapis.com/google.rpc.ResourceInfo',\n HELP = 'type.googleapis.com/google.rpc.Help',\n LOCALIZED_MESSAGE = 'type.googleapis.com/google.rpc.LocalizedMessage',\n}\n\nexport interface ErrorInfo {\n '@type': DetailType.ERROR_INFO;\n reason: ResolvedErrorReason;\n domain?: string;\n metadata?: Record<string, string>;\n}\n\nexport interface RetryInfo {\n '@type': DetailType.RETRY_INFO;\n retryDelay: number;\n}\n\nexport interface DebugInfo {\n '@type': DetailType.DEBUG_INFO;\n stackEntries: string[];\n detail: string;\n}\n\nexport interface QuotaFailure {\n '@type': DetailType.QUOTA_FAILURE;\n violations: {\n subject: string;\n description: string;\n apiService: string;\n quoteId: string;\n quoteValue: number;\n quotaMetric: string;\n quotaDimensions: Record<string, string>;\n futureQuotaValue: number;\n }[];\n}\n\nexport interface PreconditionFailure {\n '@type': DetailType.PRECONDITION_FAILURE;\n violations: { type: string; subject: string; description: string }[];\n}\n\nexport interface BadRequest {\n '@type': DetailType.BAD_REQUEST;\n fieldViolations: {\n field: string;\n description: string;\n reason: string;\n localizedMessage: Omit<LocalizedMessage, '@type'>;\n }[];\n}\n\nexport interface RequestInfo {\n '@type': DetailType.REQUEST_INFO;\n requestId: string;\n servingData: string;\n}\n\nexport interface ResourceInfo {\n '@type': DetailType.RESOURCE_INFO;\n resourceType: string;\n resourceName: string;\n owner: string;\n description: string;\n}\n\nexport interface Help {\n '@type': DetailType.HELP;\n links: { url: string; description: string }[];\n}\n\nexport interface LocalizedMessage {\n '@type': DetailType.LOCALIZED_MESSAGE;\n locale: string;\n message: string;\n}\n\nexport type Detail =\n | RetryInfo\n | DebugInfo\n | QuotaFailure\n | ErrorInfo\n | PreconditionFailure\n | BadRequest\n | RequestInfo\n | ResourceInfo\n | Help\n | LocalizedMessage;\n\n/**\n * Example usage:\n * const details = Details.new()\n * .requestInfo({ requestId: '1234567890', servingData: '/v1/tests' })\n * .errorInfo({ reason: 'ACCOUNT_LOCKED' });\n * */\nexport class Details {\n readonly list: Detail[] = [];\n private constructor() {}\n\n static new() {\n return new Details();\n }\n\n errorInfo(detail: Omit<ErrorInfo, '@type'>) {\n this.list.push({ '@type': DetailType.ERROR_INFO, ...detail });\n return this;\n }\n\n retryInfo(detail: Omit<RetryInfo, '@type'>) {\n this.list.push({ '@type': DetailType.RETRY_INFO, ...detail });\n return this;\n }\n\n debugInfo(detail: Omit<DebugInfo, '@type'>) {\n this.list.push({ '@type': DetailType.DEBUG_INFO, ...detail });\n return this;\n }\n\n quotaFailure(detail: Omit<QuotaFailure, '@type'>) {\n this.list.push({ '@type': DetailType.QUOTA_FAILURE, ...detail });\n return this;\n }\n\n preconditionFailure(detail: Omit<PreconditionFailure, '@type'>) {\n this.list.push({ '@type': DetailType.PRECONDITION_FAILURE, ...detail });\n return this;\n }\n\n badRequest(detail: Omit<BadRequest, '@type'>) {\n this.list.push({ '@type': DetailType.BAD_REQUEST, ...detail });\n return this;\n }\n\n requestInfo(detail: Omit<RequestInfo, '@type'>) {\n this.list.push({ '@type': DetailType.REQUEST_INFO, ...detail });\n return this;\n }\n\n resourceInfo(detail: Omit<ResourceInfo, '@type'>) {\n this.list.push({ '@type': DetailType.RESOURCE_INFO, ...detail });\n return this;\n }\n\n help(detail: Omit<Help, '@type'>) {\n this.list.push({ '@type': DetailType.HELP, ...detail });\n return this;\n }\n\n localizedMessage(detail: Omit<LocalizedMessage, '@type'>) {\n this.list.push({ '@type': DetailType.LOCALIZED_MESSAGE, ...detail });\n return this;\n }\n}\n"],"mappings":";AAGO,IAAK,aAAL,kBAAKA,gBAAL;AACL,EAAAA,YAAA,gBAAa;AACb,EAAAA,YAAA,gBAAa;AACb,EAAAA,YAAA,gBAAa;AACb,EAAAA,YAAA,mBAAgB;AAChB,EAAAA,YAAA,0BAAuB;AACvB,EAAAA,YAAA,iBAAc;AACd,EAAAA,YAAA,kBAAe;AACf,EAAAA,YAAA,mBAAgB;AAChB,EAAAA,YAAA,UAAO;AACP,EAAAA,YAAA,uBAAoB;AAVV,SAAAA;AAAA,GAAA;AAuGL,IAAM,UAAN,MAAM,SAAQ;AAAA,EACV,OAAiB,CAAC;AAAA,EACnB,cAAc;AAAA,EAAC;AAAA,EAEvB,OAAO,MAAM;AACX,WAAO,IAAI,SAAQ;AAAA,EACrB;AAAA,EAEA,UAAU,QAAkC;AAC1C,SAAK,KAAK,KAAK,EAAE,SAAS,6DAAuB,GAAG,OAAO,CAAC;AAC5D,WAAO;AAAA,EACT;AAAA,EAEA,UAAU,QAAkC;AAC1C,SAAK,KAAK,KAAK,EAAE,SAAS,6DAAuB,GAAG,OAAO,CAAC;AAC5D,WAAO;AAAA,EACT;AAAA,EAEA,UAAU,QAAkC;AAC1C,SAAK,KAAK,KAAK,EAAE,SAAS,6DAAuB,GAAG,OAAO,CAAC;AAC5D,WAAO;AAAA,EACT;AAAA,EAEA,aAAa,QAAqC;AAChD,SAAK,KAAK,KAAK,EAAE,SAAS,mEAA0B,GAAG,OAAO,CAAC;AAC/D,WAAO;AAAA,EACT;AAAA,EAEA,oBAAoB,QAA4C;AAC9D,SAAK,KAAK,KAAK,EAAE,SAAS,iFAAiC,GAAG,OAAO,CAAC;AACtE,WAAO;AAAA,EACT;AAAA,EAEA,WAAW,QAAmC;AAC5C,SAAK,KAAK,KAAK,EAAE,SAAS,+DAAwB,GAAG,OAAO,CAAC;AAC7D,WAAO;AAAA,EACT;AAAA,EAEA,YAAY,QAAoC;AAC9C,SAAK,KAAK,KAAK,EAAE,SAAS,iEAAyB,GAAG,OAAO,CAAC;AAC9D,WAAO;AAAA,EACT;AAAA,EAEA,aAAa,QAAqC;AAChD,SAAK,KAAK,KAAK,EAAE,SAAS,mEAA0B,GAAG,OAAO,CAAC;AAC/D,WAAO;AAAA,EACT;AAAA,EAEA,KAAK,QAA6B;AAChC,SAAK,KAAK,KAAK,EAAE,SAAS,kDAAiB,GAAG,OAAO,CAAC;AACtD,WAAO;AAAA,EACT;AAAA,EAEA,iBAAiB,QAAyC;AACxD,SAAK,KAAK,KAAK,EAAE,SAAS,2EAA8B,GAAG,OAAO,CAAC;AACnE,WAAO;AAAA,EACT;AACF;","names":["DetailType"]}
@@ -1,8 +1,8 @@
1
- import { NetworkErrorReason, StatusErrorReason, AuthenticationErrorReason, ModerationErrorReason, MultipartErrorReason, AppErrorReason } from './reason.cjs';
2
1
  import { Namespace, DefaultNamespace, TFunction } from 'i18next';
3
2
  import { BadRequest } from './detail.cjs';
3
+ import './reason.cjs';
4
4
 
5
- declare function getErrorReason(data: unknown): (string & {}) | keyof NetworkErrorReason | keyof StatusErrorReason | keyof AuthenticationErrorReason | keyof ModerationErrorReason | keyof MultipartErrorReason | keyof AppErrorReason;
5
+ declare function getErrorReason(data: unknown): string;
6
6
  /**
7
7
  * @example For axios:
8
8
  *
@@ -1,8 +1,8 @@
1
- import { NetworkErrorReason, StatusErrorReason, AuthenticationErrorReason, ModerationErrorReason, MultipartErrorReason, AppErrorReason } from './reason.js';
2
1
  import { Namespace, DefaultNamespace, TFunction } from 'i18next';
3
2
  import { BadRequest } from './detail.js';
3
+ import './reason.js';
4
4
 
5
- declare function getErrorReason(data: unknown): (string & {}) | keyof NetworkErrorReason | keyof StatusErrorReason | keyof AuthenticationErrorReason | keyof ModerationErrorReason | keyof MultipartErrorReason | keyof AppErrorReason;
5
+ declare function getErrorReason(data: unknown): string;
6
6
  /**
7
7
  * @example For axios:
8
8
  *
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/error/reason.ts"],"sourcesContent":["export interface NetworkErrorReason {\n DNS_ERROR: string;\n MISCONFIGURATION: string;\n CONNECTION_ERROR: string;\n}\n\nexport interface StatusErrorReason {\n OK: string;\n CANCELLED: string;\n UNKNOWN: string;\n INVALID_ARGUMENT: string;\n DEADLINE_EXCEEDED: string;\n NOT_FOUND: string;\n ALREADY_EXISTS: string;\n PERMISSION_DENIED: string;\n RESOURCE_EXHAUSTED: string;\n FAILED_PRECONDITION: string;\n ABORTED: string;\n OUT_OF_RANGE: string;\n UNIMPLEMENTED: string;\n INTERNAL: string;\n UNAVAILABLE: string;\n DATA_LOSS: string;\n UNAUTHENTICATED: string;\n // other http status code\n}\n\nexport interface AuthenticationErrorReason {\n ACCOUNT_LOCKED: string;\n ACCOUNT_EXPIRED: string;\n ACCOUNT_INACTIVE: string;\n ACCOUNT_DISABLED: string;\n ACCOUNT_SUSPENDED: string;\n ACCESS_DENIED: string;\n ACCESS_TOKEN_REQUIRED: string;\n PASSWORD_MISMATCH: string;\n USERNAME_ALREADY_EXISTS: string;\n VERIFICATION_CODE_MISMATCH: string;\n VERIFICATION_CODE_SEND_FAILED: string;\n}\n\nexport interface ModerationErrorReason {\n POSSIBLY_SENSITIVE: string;\n ADULT_CONTENT: string;\n NUDITY_CONTENT: string;\n SEXUAL_CONTENT: string;\n BLOODY_CONTENT: string;\n WEAPON_CONTENT: string;\n POLITICS_CONTENT: string;\n VIOLENCE_CONTENT: string;\n ABUSE_CONTENT: string;\n ADVERTISEMENT_CONTENT: string;\n CONTRABAND_CONTENT: string;\n SPAM_CONTENT: string;\n MEANINGLESS_CONTENT: string;\n UNSAFE_TEXT_DETECTED: string;\n}\n\nexport interface MultipartErrorReason {\n MAX_UPLOAD_SIZE_EXCEEDED: string;\n MEDIA_TYPE_NOT_SUPPORTED: string;\n MEDIA_TYPE_NOT_ACCEPTABLE: string;\n}\n\nexport interface AppErrorReason {\n RATE_LIMIT_EXCEEDED: string;\n INSUFFICIENT_CREDITS: string;\n ALREADY_SUBSCRIBED_AT_OTHER_PLATFORM: string;\n}\n\nexport type ErrorReason = NetworkErrorReason &\n StatusErrorReason &\n AuthenticationErrorReason &\n ModerationErrorReason &\n MultipartErrorReason &\n AppErrorReason;\n"],"mappings":";;;;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
1
+ {"version":3,"sources":["../../src/error/reason.ts"],"sourcesContent":["export interface NetworkErrorReason {\n DNS_ERROR: string;\n MISCONFIGURATION: string;\n CONNECTION_ERROR: string;\n}\n\nexport interface StatusErrorReason {\n OK: string;\n CANCELLED: string;\n UNKNOWN: string;\n INVALID_ARGUMENT: string;\n DEADLINE_EXCEEDED: string;\n NOT_FOUND: string;\n ALREADY_EXISTS: string;\n PERMISSION_DENIED: string;\n RESOURCE_EXHAUSTED: string;\n FAILED_PRECONDITION: string;\n ABORTED: string;\n OUT_OF_RANGE: string;\n UNIMPLEMENTED: string;\n INTERNAL: string;\n UNAVAILABLE: string;\n DATA_LOSS: string;\n UNAUTHENTICATED: string;\n // other http status code\n}\n\nexport interface AuthenticationErrorReason {\n ACCOUNT_LOCKED: string;\n ACCOUNT_EXPIRED: string;\n ACCOUNT_INACTIVE: string;\n ACCOUNT_DISABLED: string;\n ACCOUNT_SUSPENDED: string;\n ACCESS_DENIED: string;\n ACCESS_TOKEN_REQUIRED: string;\n PASSWORD_MISMATCH: string;\n USERNAME_ALREADY_EXISTS: string;\n VERIFICATION_CODE_MISMATCH: string;\n VERIFICATION_CODE_SEND_FAILED: string;\n}\n\nexport interface ModerationErrorReason {\n POSSIBLY_SENSITIVE: string;\n ADULT_CONTENT: string;\n NUDITY_CONTENT: string;\n SEXUAL_CONTENT: string;\n BLOODY_CONTENT: string;\n WEAPON_CONTENT: string;\n POLITICS_CONTENT: string;\n VIOLENCE_CONTENT: string;\n ABUSE_CONTENT: string;\n ADVERTISEMENT_CONTENT: string;\n CONTRABAND_CONTENT: string;\n SPAM_CONTENT: string;\n MEANINGLESS_CONTENT: string;\n UNSAFE_TEXT_DETECTED: string;\n}\n\nexport interface MultipartErrorReason {\n MAX_UPLOAD_SIZE_EXCEEDED: string;\n MEDIA_TYPE_NOT_SUPPORTED: string;\n MEDIA_TYPE_NOT_ACCEPTABLE: string;\n}\n\nexport interface AppErrorReason {\n RATE_LIMIT_EXCEEDED: string;\n INSUFFICIENT_CREDITS: string;\n ALREADY_SUBSCRIBED_AT_OTHER_PLATFORM: string;\n}\n\n// oxlint-disable-next-line typescript/no-empty-object-type\nexport interface ErrorReason {}\n\nexport type ResolvedErrorReason = keyof ErrorReason extends never\n ? string\n : keyof ErrorReason;\n"],"mappings":";;;;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
@@ -61,6 +61,8 @@ interface AppErrorReason {
61
61
  INSUFFICIENT_CREDITS: string;
62
62
  ALREADY_SUBSCRIBED_AT_OTHER_PLATFORM: string;
63
63
  }
64
- type ErrorReason = NetworkErrorReason & StatusErrorReason & AuthenticationErrorReason & ModerationErrorReason & MultipartErrorReason & AppErrorReason;
64
+ interface ErrorReason {
65
+ }
66
+ type ResolvedErrorReason = keyof ErrorReason extends never ? string : keyof ErrorReason;
65
67
 
66
- export type { AppErrorReason, AuthenticationErrorReason, ErrorReason, ModerationErrorReason, MultipartErrorReason, NetworkErrorReason, StatusErrorReason };
68
+ export type { AppErrorReason, AuthenticationErrorReason, ErrorReason, ModerationErrorReason, MultipartErrorReason, NetworkErrorReason, ResolvedErrorReason, StatusErrorReason };
@@ -61,6 +61,8 @@ interface AppErrorReason {
61
61
  INSUFFICIENT_CREDITS: string;
62
62
  ALREADY_SUBSCRIBED_AT_OTHER_PLATFORM: string;
63
63
  }
64
- type ErrorReason = NetworkErrorReason & StatusErrorReason & AuthenticationErrorReason & ModerationErrorReason & MultipartErrorReason & AppErrorReason;
64
+ interface ErrorReason {
65
+ }
66
+ type ResolvedErrorReason = keyof ErrorReason extends never ? string : keyof ErrorReason;
65
67
 
66
- export type { AppErrorReason, AuthenticationErrorReason, ErrorReason, ModerationErrorReason, MultipartErrorReason, NetworkErrorReason, StatusErrorReason };
68
+ export type { AppErrorReason, AuthenticationErrorReason, ErrorReason, ModerationErrorReason, MultipartErrorReason, NetworkErrorReason, ResolvedErrorReason, StatusErrorReason };
@@ -39,13 +39,9 @@ function errorHandler(error, c) {
39
39
  (d) => d["@type"] === import_detail.DetailType.BAD_REQUEST
40
40
  );
41
41
  if (badRequest) console.warn(servingData, badRequest);
42
+ console.error(servingData, error.body?.error);
42
43
  return c.json(error.body, error.status);
43
44
  }
44
- if (error instanceof SyntaxError) {
45
- if (/^Cannot convert .* to a BigInt$/.test(error.message)) {
46
- return import_status.Status.invalidArgument(`Invalid number. ${error.message}`).response(details);
47
- }
48
- }
49
45
  if (isAxiosError(error)) {
50
46
  console.error({
51
47
  status: error.status,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/hono/handler.ts"],"sourcesContent":["import type { Context } from 'hono';\nimport type { RequestIdVariables } from 'hono/request-id';\nimport type { Bindings, HTTPResponseError } from 'hono/types';\nimport type { ContentfulStatusCode } from 'hono/utils/http-status';\nimport { DetailType, Details } from '../error/detail';\nimport { Status, StatusError } from '../error/status';\n\nexport type Env = {\n Variables: RequestIdVariables;\n Bindings?: Bindings;\n};\n\ntype AxiosError = {\n code?: string;\n cause?: unknown;\n status?: number;\n message?: string;\n isAxiosError: boolean;\n response?: {\n data: unknown;\n status: number;\n statusText: string;\n headers: Record<string, string>;\n };\n config?: { url?: string; data?: unknown; method?: string; headers?: Record<string, string> };\n};\n\nexport function isAxiosError(payload: unknown): payload is AxiosError {\n return (\n payload !== null &&\n typeof payload === 'object' &&\n 'isAxiosError' in payload &&\n payload.isAxiosError === true\n );\n}\n\nexport function errorHandler<E extends Env = never>(\n error: Error | HTTPResponseError,\n c: Context<E>\n): Response | Promise<Response> {\n const requestId = c.get('requestId');\n const servingData = `${c.req.method}: ${c.req.path}`;\n const details = Details.new().requestInfo({ requestId, servingData });\n\n if (error instanceof StatusError) {\n error.body?.error?.details?.push(...details.list);\n const badRequest = error.body?.error?.details.find(\n (d) => d['@type'] === DetailType.BAD_REQUEST\n );\n if (badRequest) console.warn(servingData, badRequest);\n return c.json(error.body, error.status as ContentfulStatusCode);\n }\n\n if (error instanceof SyntaxError) {\n if (/^Cannot convert .* to a BigInt$/.test(error.message)) {\n return Status.invalidArgument(`Invalid number. ${error.message}`).response(details);\n }\n }\n\n if (isAxiosError(error)) {\n console.error({\n status: error.status,\n message: error.message,\n request: {\n method: error.config?.method,\n url: error.config?.url,\n data: error.config?.data,\n },\n response: { data: error.response?.data },\n });\n return Status.internal('Axios error').response(details);\n }\n\n console.error(`Unknown error: ${servingData}`, error);\n return Status.internal('Unknown error').response(details);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAIA,oBAAoC;AACpC,oBAAoC;AAsB7B,SAAS,aAAa,SAAyC;AACpE,SACE,YAAY,QACZ,OAAO,YAAY,YACnB,kBAAkB,WAClB,QAAQ,iBAAiB;AAE7B;AAEO,SAAS,aACd,OACA,GAC8B;AAC9B,QAAM,YAAY,EAAE,IAAI,WAAW;AACnC,QAAM,cAAc,GAAG,EAAE,IAAI,MAAM,KAAK,EAAE,IAAI,IAAI;AAClD,QAAM,UAAU,sBAAQ,IAAI,EAAE,YAAY,EAAE,WAAW,YAAY,CAAC;AAEpE,MAAI,iBAAiB,2BAAa;AAChC,UAAM,MAAM,OAAO,SAAS,KAAK,GAAG,QAAQ,IAAI;AAChD,UAAM,aAAa,MAAM,MAAM,OAAO,QAAQ;AAAA,MAC5C,CAAC,MAAM,EAAE,OAAO,MAAM,yBAAW;AAAA,IACnC;AACA,QAAI,WAAY,SAAQ,KAAK,aAAa,UAAU;AACpD,WAAO,EAAE,KAAK,MAAM,MAAM,MAAM,MAA8B;AAAA,EAChE;AAEA,MAAI,iBAAiB,aAAa;AAChC,QAAI,kCAAkC,KAAK,MAAM,OAAO,GAAG;AACzD,aAAO,qBAAO,gBAAgB,mBAAmB,MAAM,OAAO,EAAE,EAAE,SAAS,OAAO;AAAA,IACpF;AAAA,EACF;AAEA,MAAI,aAAa,KAAK,GAAG;AACvB,YAAQ,MAAM;AAAA,MACZ,QAAQ,MAAM;AAAA,MACd,SAAS,MAAM;AAAA,MACf,SAAS;AAAA,QACP,QAAQ,MAAM,QAAQ;AAAA,QACtB,KAAK,MAAM,QAAQ;AAAA,QACnB,MAAM,MAAM,QAAQ;AAAA,MACtB;AAAA,MACA,UAAU,EAAE,MAAM,MAAM,UAAU,KAAK;AAAA,IACzC,CAAC;AACD,WAAO,qBAAO,SAAS,aAAa,EAAE,SAAS,OAAO;AAAA,EACxD;AAEA,UAAQ,MAAM,kBAAkB,WAAW,IAAI,KAAK;AACpD,SAAO,qBAAO,SAAS,eAAe,EAAE,SAAS,OAAO;AAC1D;","names":[]}
1
+ {"version":3,"sources":["../../src/hono/handler.ts"],"sourcesContent":["import type { Context } from 'hono';\nimport type { RequestIdVariables } from 'hono/request-id';\nimport type { Bindings, HTTPResponseError } from 'hono/types';\nimport type { ContentfulStatusCode } from 'hono/utils/http-status';\nimport { DetailType, Details } from '../error/detail';\nimport { Status, StatusError } from '../error/status';\n\nexport type Env = {\n Variables: RequestIdVariables;\n Bindings?: Bindings;\n};\n\ntype AxiosError = {\n code?: string;\n cause?: unknown;\n status?: number;\n message?: string;\n isAxiosError: boolean;\n response?: {\n data: unknown;\n status: number;\n statusText: string;\n headers: Record<string, string>;\n };\n config?: { url?: string; data?: unknown; method?: string; headers?: Record<string, string> };\n};\n\nexport function isAxiosError(payload: unknown): payload is AxiosError {\n return (\n payload !== null &&\n typeof payload === 'object' &&\n 'isAxiosError' in payload &&\n payload.isAxiosError === true\n );\n}\n\nexport function errorHandler<E extends Env = never>(\n error: Error | HTTPResponseError,\n c: Context<E>\n): Response | Promise<Response> {\n const requestId = c.get('requestId');\n const servingData = `${c.req.method}: ${c.req.path}`;\n const details = Details.new().requestInfo({ requestId, servingData });\n\n if (error instanceof StatusError) {\n error.body?.error?.details?.push(...details.list);\n const badRequest = error.body?.error?.details.find(\n (d) => d['@type'] === DetailType.BAD_REQUEST\n );\n if (badRequest) console.warn(servingData, badRequest);\n console.error(servingData, error.body?.error);\n return c.json(error.body, error.status as ContentfulStatusCode);\n }\n\n if (isAxiosError(error)) {\n console.error({\n status: error.status,\n message: error.message,\n request: {\n method: error.config?.method,\n url: error.config?.url,\n data: error.config?.data,\n },\n response: { data: error.response?.data },\n });\n return Status.internal('Axios error').response(details);\n }\n\n console.error(`Unknown error: ${servingData}`, error);\n return Status.internal('Unknown error').response(details);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAIA,oBAAoC;AACpC,oBAAoC;AAsB7B,SAAS,aAAa,SAAyC;AACpE,SACE,YAAY,QACZ,OAAO,YAAY,YACnB,kBAAkB,WAClB,QAAQ,iBAAiB;AAE7B;AAEO,SAAS,aACd,OACA,GAC8B;AAC9B,QAAM,YAAY,EAAE,IAAI,WAAW;AACnC,QAAM,cAAc,GAAG,EAAE,IAAI,MAAM,KAAK,EAAE,IAAI,IAAI;AAClD,QAAM,UAAU,sBAAQ,IAAI,EAAE,YAAY,EAAE,WAAW,YAAY,CAAC;AAEpE,MAAI,iBAAiB,2BAAa;AAChC,UAAM,MAAM,OAAO,SAAS,KAAK,GAAG,QAAQ,IAAI;AAChD,UAAM,aAAa,MAAM,MAAM,OAAO,QAAQ;AAAA,MAC5C,CAAC,MAAM,EAAE,OAAO,MAAM,yBAAW;AAAA,IACnC;AACA,QAAI,WAAY,SAAQ,KAAK,aAAa,UAAU;AACpD,YAAQ,MAAM,aAAa,MAAM,MAAM,KAAK;AAC5C,WAAO,EAAE,KAAK,MAAM,MAAM,MAAM,MAA8B;AAAA,EAChE;AAEA,MAAI,aAAa,KAAK,GAAG;AACvB,YAAQ,MAAM;AAAA,MACZ,QAAQ,MAAM;AAAA,MACd,SAAS,MAAM;AAAA,MACf,SAAS;AAAA,QACP,QAAQ,MAAM,QAAQ;AAAA,QACtB,KAAK,MAAM,QAAQ;AAAA,QACnB,MAAM,MAAM,QAAQ;AAAA,MACtB;AAAA,MACA,UAAU,EAAE,MAAM,MAAM,UAAU,KAAK;AAAA,IACzC,CAAC;AACD,WAAO,qBAAO,SAAS,aAAa,EAAE,SAAS,OAAO;AAAA,EACxD;AAEA,UAAQ,MAAM,kBAAkB,WAAW,IAAI,KAAK;AACpD,SAAO,qBAAO,SAAS,eAAe,EAAE,SAAS,OAAO;AAC1D;","names":[]}
@@ -14,13 +14,9 @@ function errorHandler(error, c) {
14
14
  (d) => d["@type"] === DetailType.BAD_REQUEST
15
15
  );
16
16
  if (badRequest) console.warn(servingData, badRequest);
17
+ console.error(servingData, error.body?.error);
17
18
  return c.json(error.body, error.status);
18
19
  }
19
- if (error instanceof SyntaxError) {
20
- if (/^Cannot convert .* to a BigInt$/.test(error.message)) {
21
- return Status.invalidArgument(`Invalid number. ${error.message}`).response(details);
22
- }
23
- }
24
20
  if (isAxiosError(error)) {
25
21
  console.error({
26
22
  status: error.status,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/hono/handler.ts"],"sourcesContent":["import type { Context } from 'hono';\nimport type { RequestIdVariables } from 'hono/request-id';\nimport type { Bindings, HTTPResponseError } from 'hono/types';\nimport type { ContentfulStatusCode } from 'hono/utils/http-status';\nimport { DetailType, Details } from '../error/detail';\nimport { Status, StatusError } from '../error/status';\n\nexport type Env = {\n Variables: RequestIdVariables;\n Bindings?: Bindings;\n};\n\ntype AxiosError = {\n code?: string;\n cause?: unknown;\n status?: number;\n message?: string;\n isAxiosError: boolean;\n response?: {\n data: unknown;\n status: number;\n statusText: string;\n headers: Record<string, string>;\n };\n config?: { url?: string; data?: unknown; method?: string; headers?: Record<string, string> };\n};\n\nexport function isAxiosError(payload: unknown): payload is AxiosError {\n return (\n payload !== null &&\n typeof payload === 'object' &&\n 'isAxiosError' in payload &&\n payload.isAxiosError === true\n );\n}\n\nexport function errorHandler<E extends Env = never>(\n error: Error | HTTPResponseError,\n c: Context<E>\n): Response | Promise<Response> {\n const requestId = c.get('requestId');\n const servingData = `${c.req.method}: ${c.req.path}`;\n const details = Details.new().requestInfo({ requestId, servingData });\n\n if (error instanceof StatusError) {\n error.body?.error?.details?.push(...details.list);\n const badRequest = error.body?.error?.details.find(\n (d) => d['@type'] === DetailType.BAD_REQUEST\n );\n if (badRequest) console.warn(servingData, badRequest);\n return c.json(error.body, error.status as ContentfulStatusCode);\n }\n\n if (error instanceof SyntaxError) {\n if (/^Cannot convert .* to a BigInt$/.test(error.message)) {\n return Status.invalidArgument(`Invalid number. ${error.message}`).response(details);\n }\n }\n\n if (isAxiosError(error)) {\n console.error({\n status: error.status,\n message: error.message,\n request: {\n method: error.config?.method,\n url: error.config?.url,\n data: error.config?.data,\n },\n response: { data: error.response?.data },\n });\n return Status.internal('Axios error').response(details);\n }\n\n console.error(`Unknown error: ${servingData}`, error);\n return Status.internal('Unknown error').response(details);\n}\n"],"mappings":";AAIA,SAAS,YAAY,eAAe;AACpC,SAAS,QAAQ,mBAAmB;AAsB7B,SAAS,aAAa,SAAyC;AACpE,SACE,YAAY,QACZ,OAAO,YAAY,YACnB,kBAAkB,WAClB,QAAQ,iBAAiB;AAE7B;AAEO,SAAS,aACd,OACA,GAC8B;AAC9B,QAAM,YAAY,EAAE,IAAI,WAAW;AACnC,QAAM,cAAc,GAAG,EAAE,IAAI,MAAM,KAAK,EAAE,IAAI,IAAI;AAClD,QAAM,UAAU,QAAQ,IAAI,EAAE,YAAY,EAAE,WAAW,YAAY,CAAC;AAEpE,MAAI,iBAAiB,aAAa;AAChC,UAAM,MAAM,OAAO,SAAS,KAAK,GAAG,QAAQ,IAAI;AAChD,UAAM,aAAa,MAAM,MAAM,OAAO,QAAQ;AAAA,MAC5C,CAAC,MAAM,EAAE,OAAO,MAAM,WAAW;AAAA,IACnC;AACA,QAAI,WAAY,SAAQ,KAAK,aAAa,UAAU;AACpD,WAAO,EAAE,KAAK,MAAM,MAAM,MAAM,MAA8B;AAAA,EAChE;AAEA,MAAI,iBAAiB,aAAa;AAChC,QAAI,kCAAkC,KAAK,MAAM,OAAO,GAAG;AACzD,aAAO,OAAO,gBAAgB,mBAAmB,MAAM,OAAO,EAAE,EAAE,SAAS,OAAO;AAAA,IACpF;AAAA,EACF;AAEA,MAAI,aAAa,KAAK,GAAG;AACvB,YAAQ,MAAM;AAAA,MACZ,QAAQ,MAAM;AAAA,MACd,SAAS,MAAM;AAAA,MACf,SAAS;AAAA,QACP,QAAQ,MAAM,QAAQ;AAAA,QACtB,KAAK,MAAM,QAAQ;AAAA,QACnB,MAAM,MAAM,QAAQ;AAAA,MACtB;AAAA,MACA,UAAU,EAAE,MAAM,MAAM,UAAU,KAAK;AAAA,IACzC,CAAC;AACD,WAAO,OAAO,SAAS,aAAa,EAAE,SAAS,OAAO;AAAA,EACxD;AAEA,UAAQ,MAAM,kBAAkB,WAAW,IAAI,KAAK;AACpD,SAAO,OAAO,SAAS,eAAe,EAAE,SAAS,OAAO;AAC1D;","names":[]}
1
+ {"version":3,"sources":["../../src/hono/handler.ts"],"sourcesContent":["import type { Context } from 'hono';\nimport type { RequestIdVariables } from 'hono/request-id';\nimport type { Bindings, HTTPResponseError } from 'hono/types';\nimport type { ContentfulStatusCode } from 'hono/utils/http-status';\nimport { DetailType, Details } from '../error/detail';\nimport { Status, StatusError } from '../error/status';\n\nexport type Env = {\n Variables: RequestIdVariables;\n Bindings?: Bindings;\n};\n\ntype AxiosError = {\n code?: string;\n cause?: unknown;\n status?: number;\n message?: string;\n isAxiosError: boolean;\n response?: {\n data: unknown;\n status: number;\n statusText: string;\n headers: Record<string, string>;\n };\n config?: { url?: string; data?: unknown; method?: string; headers?: Record<string, string> };\n};\n\nexport function isAxiosError(payload: unknown): payload is AxiosError {\n return (\n payload !== null &&\n typeof payload === 'object' &&\n 'isAxiosError' in payload &&\n payload.isAxiosError === true\n );\n}\n\nexport function errorHandler<E extends Env = never>(\n error: Error | HTTPResponseError,\n c: Context<E>\n): Response | Promise<Response> {\n const requestId = c.get('requestId');\n const servingData = `${c.req.method}: ${c.req.path}`;\n const details = Details.new().requestInfo({ requestId, servingData });\n\n if (error instanceof StatusError) {\n error.body?.error?.details?.push(...details.list);\n const badRequest = error.body?.error?.details.find(\n (d) => d['@type'] === DetailType.BAD_REQUEST\n );\n if (badRequest) console.warn(servingData, badRequest);\n console.error(servingData, error.body?.error);\n return c.json(error.body, error.status as ContentfulStatusCode);\n }\n\n if (isAxiosError(error)) {\n console.error({\n status: error.status,\n message: error.message,\n request: {\n method: error.config?.method,\n url: error.config?.url,\n data: error.config?.data,\n },\n response: { data: error.response?.data },\n });\n return Status.internal('Axios error').response(details);\n }\n\n console.error(`Unknown error: ${servingData}`, error);\n return Status.internal('Unknown error').response(details);\n}\n"],"mappings":";AAIA,SAAS,YAAY,eAAe;AACpC,SAAS,QAAQ,mBAAmB;AAsB7B,SAAS,aAAa,SAAyC;AACpE,SACE,YAAY,QACZ,OAAO,YAAY,YACnB,kBAAkB,WAClB,QAAQ,iBAAiB;AAE7B;AAEO,SAAS,aACd,OACA,GAC8B;AAC9B,QAAM,YAAY,EAAE,IAAI,WAAW;AACnC,QAAM,cAAc,GAAG,EAAE,IAAI,MAAM,KAAK,EAAE,IAAI,IAAI;AAClD,QAAM,UAAU,QAAQ,IAAI,EAAE,YAAY,EAAE,WAAW,YAAY,CAAC;AAEpE,MAAI,iBAAiB,aAAa;AAChC,UAAM,MAAM,OAAO,SAAS,KAAK,GAAG,QAAQ,IAAI;AAChD,UAAM,aAAa,MAAM,MAAM,OAAO,QAAQ;AAAA,MAC5C,CAAC,MAAM,EAAE,OAAO,MAAM,WAAW;AAAA,IACnC;AACA,QAAI,WAAY,SAAQ,KAAK,aAAa,UAAU;AACpD,YAAQ,MAAM,aAAa,MAAM,MAAM,KAAK;AAC5C,WAAO,EAAE,KAAK,MAAM,MAAM,MAAM,MAA8B;AAAA,EAChE;AAEA,MAAI,aAAa,KAAK,GAAG;AACvB,YAAQ,MAAM;AAAA,MACZ,QAAQ,MAAM;AAAA,MACd,SAAS,MAAM;AAAA,MACf,SAAS;AAAA,QACP,QAAQ,MAAM,QAAQ;AAAA,QACtB,KAAK,MAAM,QAAQ;AAAA,QACnB,MAAM,MAAM,QAAQ;AAAA,MACtB;AAAA,MACA,UAAU,EAAE,MAAM,MAAM,UAAU,KAAK;AAAA,IACzC,CAAC;AACD,WAAO,OAAO,SAAS,aAAa,EAAE,SAAS,OAAO;AAAA,EACxD;AAEA,UAAQ,MAAM,kBAAkB,WAAW,IAAI,KAAK;AACpD,SAAO,OAAO,SAAS,eAAe,EAAE,SAAS,OAAO;AAC1D;","names":[]}
@@ -25,6 +25,7 @@ __export(hono_exports, {
25
25
  csrf: () => import_csrf.csrf,
26
26
  errorHandler: () => import_handler.errorHandler,
27
27
  geolocation: () => import_geolocation.geolocation,
28
+ rateLimit: () => import_rate_limit.rateLimit,
28
29
  zValidator: () => import_validator.zValidator
29
30
  });
30
31
  module.exports = __toCommonJS(hono_exports);
@@ -33,6 +34,7 @@ var import_handler = require("./handler.cjs");
33
34
  var import_geolocation = require("./geolocation.cjs");
34
35
  var import_authorizer = require("./authorizer.cjs");
35
36
  var import_csrf = require("./csrf.cjs");
37
+ var import_rate_limit = require("./rate-limit.cjs");
36
38
  // Annotate the CommonJS export names for ESM import in node:
37
39
  0 && (module.exports = {
38
40
  authorizer,
@@ -40,6 +42,7 @@ var import_csrf = require("./csrf.cjs");
40
42
  csrf,
41
43
  errorHandler,
42
44
  geolocation,
45
+ rateLimit,
43
46
  zValidator
44
47
  });
45
48
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/hono/index.ts"],"sourcesContent":["export { zValidator, bigintId } from './validator';\nexport { errorHandler } from './handler';\nexport { geolocation } from './geolocation';\nexport { authorizer, type AuthorizerConfig } from './authorizer';\nexport { csrf, type CSRFConfig, type CSRFIgnoreRule } from './csrf';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,uBAAqC;AACrC,qBAA6B;AAC7B,yBAA4B;AAC5B,wBAAkD;AAClD,kBAA2D;","names":[]}
1
+ {"version":3,"sources":["../../src/hono/index.ts"],"sourcesContent":["export { zValidator, bigintId } from './validator';\nexport { errorHandler } from './handler';\nexport { geolocation } from './geolocation';\nexport { authorizer, type AuthorizerConfig } from './authorizer';\nexport { csrf, type CSRFConfig, type CSRFIgnoreRule } from './csrf';\nexport { rateLimit, type RateLimitOptions } from './rate-limit';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,uBAAqC;AACrC,qBAA6B;AAC7B,yBAA4B;AAC5B,wBAAkD;AAClD,kBAA2D;AAC3D,wBAAiD;","names":[]}
@@ -3,6 +3,7 @@ export { errorHandler } from './handler.cjs';
3
3
  export { geolocation } from './geolocation.cjs';
4
4
  export { AuthorizerConfig, authorizer } from './authorizer.cjs';
5
5
  export { CSRFConfig, CSRFIgnoreRule, csrf } from './csrf.cjs';
6
+ export { RateLimitOptions, rateLimit } from './rate-limit.cjs';
6
7
  import 'zod/mini';
7
8
  import 'hono/utils/http-status';
8
9
  import 'hono/validator';
@@ -3,6 +3,7 @@ export { errorHandler } from './handler.js';
3
3
  export { geolocation } from './geolocation.js';
4
4
  export { AuthorizerConfig, authorizer } from './authorizer.js';
5
5
  export { CSRFConfig, CSRFIgnoreRule, csrf } from './csrf.js';
6
+ export { RateLimitOptions, rateLimit } from './rate-limit.js';
6
7
  import 'zod/mini';
7
8
  import 'hono/utils/http-status';
8
9
  import 'hono/validator';
@@ -4,12 +4,14 @@ import { errorHandler } from "./handler.mjs";
4
4
  import { geolocation } from "./geolocation.mjs";
5
5
  import { authorizer } from "./authorizer.mjs";
6
6
  import { csrf } from "./csrf.mjs";
7
+ import { rateLimit } from "./rate-limit.mjs";
7
8
  export {
8
9
  authorizer,
9
10
  bigintId,
10
11
  csrf,
11
12
  errorHandler,
12
13
  geolocation,
14
+ rateLimit,
13
15
  zValidator
14
16
  };
15
17
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/hono/index.ts"],"sourcesContent":["export { zValidator, bigintId } from './validator';\nexport { errorHandler } from './handler';\nexport { geolocation } from './geolocation';\nexport { authorizer, type AuthorizerConfig } from './authorizer';\nexport { csrf, type CSRFConfig, type CSRFIgnoreRule } from './csrf';\n"],"mappings":";AAAA,SAAS,YAAY,gBAAgB;AACrC,SAAS,oBAAoB;AAC7B,SAAS,mBAAmB;AAC5B,SAAS,kBAAyC;AAClD,SAAS,YAAkD;","names":[]}
1
+ {"version":3,"sources":["../../src/hono/index.ts"],"sourcesContent":["export { zValidator, bigintId } from './validator';\nexport { errorHandler } from './handler';\nexport { geolocation } from './geolocation';\nexport { authorizer, type AuthorizerConfig } from './authorizer';\nexport { csrf, type CSRFConfig, type CSRFIgnoreRule } from './csrf';\nexport { rateLimit, type RateLimitOptions } from './rate-limit';\n"],"mappings":";AAAA,SAAS,YAAY,gBAAgB;AACrC,SAAS,oBAAoB;AAC7B,SAAS,mBAAmB;AAC5B,SAAS,kBAAyC;AAClD,SAAS,YAAkD;AAC3D,SAAS,iBAAwC;","names":[]}
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/hono/rate-limit.ts
21
+ var rate_limit_exports = {};
22
+ __export(rate_limit_exports, {
23
+ checkRateLimit: () => checkRateLimit,
24
+ rateLimit: () => rateLimit
25
+ });
26
+ module.exports = __toCommonJS(rate_limit_exports);
27
+ var import_detail = require("../error/detail.cjs");
28
+ var import_status = require("../error/status.cjs");
29
+ var import_geolocation = require("./geolocation.cjs");
30
+ function calculateTokens(state, capacity, rate, now) {
31
+ if (!state) {
32
+ return { tokens: capacity, lastRefillTime: now };
33
+ }
34
+ const elapsed = (now - state.lastRefillTime) / 1e3;
35
+ const tokensToAdd = elapsed * rate;
36
+ const newTokens = Math.min(capacity, state.tokens + tokensToAdd);
37
+ return {
38
+ tokens: newTokens,
39
+ lastRefillTime: now
40
+ };
41
+ }
42
+ function calculateResetTime(currentTokens, capacity, rate) {
43
+ if (currentTokens >= capacity) return 0;
44
+ return Math.ceil((capacity - currentTokens) / rate);
45
+ }
46
+ function calculateRetryAfter(currentTokens, requested, rate) {
47
+ if (currentTokens >= requested) return 0;
48
+ return Math.ceil((requested - currentTokens) / rate);
49
+ }
50
+ async function checkRateLimit(kv, identifier, options) {
51
+ const {
52
+ rate,
53
+ capacity,
54
+ requested = 1,
55
+ keyPrefix = "rate-limit:",
56
+ expiresIn = Math.max(3600, Math.ceil(capacity / rate * 2))
57
+ } = options;
58
+ const key = `${keyPrefix}${identifier}`;
59
+ const now = Date.now();
60
+ const stateStr = await kv.getItem(key);
61
+ const state = stateStr ? JSON.parse(stateStr) : null;
62
+ const { tokens: currentTokens, lastRefillTime } = calculateTokens(state, capacity, rate, now);
63
+ const allowed = currentTokens >= requested;
64
+ const newTokens = allowed ? currentTokens - requested : currentTokens;
65
+ const newState = { tokens: newTokens, lastRefillTime };
66
+ await kv.setItem(key, JSON.stringify(newState), expiresIn);
67
+ const result = {
68
+ allowed,
69
+ remaining: Math.floor(Math.max(0, newTokens)),
70
+ limit: capacity,
71
+ reset: calculateResetTime(newTokens, capacity, rate)
72
+ };
73
+ if (!allowed) {
74
+ result.retryAfter = calculateRetryAfter(currentTokens, requested, rate);
75
+ }
76
+ return result;
77
+ }
78
+ function rateLimit(options) {
79
+ const {
80
+ kv,
81
+ rate,
82
+ capacity,
83
+ requested = 1,
84
+ keyPrefix = "rate-limit:",
85
+ expiresIn = Math.max(3600, Math.ceil(capacity / rate * 2)),
86
+ getIdentifier,
87
+ onRateLimited,
88
+ skip
89
+ } = options;
90
+ return async (c, next) => {
91
+ if (skip && await skip(c)) {
92
+ return next();
93
+ }
94
+ const identifier = getIdentifier ? getIdentifier(c) : (0, import_geolocation.geolocation)(c).ip_address;
95
+ if (!identifier) return next();
96
+ const result = await checkRateLimit(kv, identifier, {
97
+ rate,
98
+ capacity,
99
+ requested,
100
+ keyPrefix,
101
+ expiresIn
102
+ });
103
+ c.header("X-RateLimit-Limit", String(result.limit));
104
+ c.header("X-RateLimit-Remaining", String(result.remaining));
105
+ c.header("X-RateLimit-Reset", String(result.reset));
106
+ if (!result.allowed) {
107
+ c.header("Retry-After", String(result.retryAfter ?? 1));
108
+ if (onRateLimited) return onRateLimited(c, result.retryAfter ?? 1);
109
+ const details = import_detail.Details.new().errorInfo({ reason: "RATE_LIMIT_EXCEEDED" }).retryInfo({ retryDelay: result.retryAfter ?? 1 });
110
+ const message = "Rate limit exceeded. Please try again later.";
111
+ return import_status.Status.resourceExhausted(message).response(details);
112
+ }
113
+ return next();
114
+ };
115
+ }
116
+ // Annotate the CommonJS export names for ESM import in node:
117
+ 0 && (module.exports = {
118
+ checkRateLimit,
119
+ rateLimit
120
+ });
121
+ //# sourceMappingURL=rate-limit.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/hono/rate-limit.ts"],"sourcesContent":["import type { Context, MiddlewareHandler } from 'hono';\nimport { Details } from '../error/detail';\nimport { Status } from '../error/status';\nimport { geolocation } from './geolocation';\n\nexport interface KV {\n setItem(key: string, value: string, expiresIn?: number): Promise<void>;\n getItem(key: string): Promise<string | null>;\n removeItem(key: string): Promise<void>;\n}\n\ninterface TokenBucketState {\n tokens: number;\n lastRefillTime: number;\n}\n\nexport interface RateLimitOptions {\n kv: KV;\n rate: number;\n capacity: number;\n requested?: number;\n keyPrefix?: string;\n expiresIn?: number;\n getIdentifier?: (c: Context) => string | undefined;\n onRateLimited?: (c: Context, retryAfter: number) => Response | Promise<Response>;\n skip?: (c: Context) => boolean | Promise<boolean>;\n}\n\nexport interface RateLimitResult {\n allowed: boolean;\n remaining: number;\n limit: number;\n reset: number;\n retryAfter?: number;\n}\n\nfunction calculateTokens(\n state: TokenBucketState | null,\n capacity: number,\n rate: number,\n now: number\n): { tokens: number; lastRefillTime: number } {\n if (!state) {\n return { tokens: capacity, lastRefillTime: now };\n }\n\n const elapsed = (now - state.lastRefillTime) / 1000;\n const tokensToAdd = elapsed * rate;\n const newTokens = Math.min(capacity, state.tokens + tokensToAdd);\n\n return {\n tokens: newTokens,\n lastRefillTime: now,\n };\n}\n\nfunction calculateResetTime(currentTokens: number, capacity: number, rate: number): number {\n if (currentTokens >= capacity) return 0;\n return Math.ceil((capacity - currentTokens) / rate);\n}\n\nfunction calculateRetryAfter(currentTokens: number, requested: number, rate: number): number {\n if (currentTokens >= requested) return 0;\n return Math.ceil((requested - currentTokens) / rate);\n}\n\nexport async function checkRateLimit(\n kv: KV,\n identifier: string,\n options: {\n rate: number;\n capacity: number;\n requested?: number;\n keyPrefix?: string;\n expiresIn?: number;\n }\n): Promise<RateLimitResult> {\n const {\n rate,\n capacity,\n requested = 1,\n keyPrefix = 'rate-limit:',\n expiresIn = Math.max(3600, Math.ceil((capacity / rate) * 2)),\n } = options;\n\n const key = `${keyPrefix}${identifier}`;\n const now = Date.now();\n\n const stateStr = await kv.getItem(key);\n const state: TokenBucketState | null = stateStr ? JSON.parse(stateStr) : null;\n\n const { tokens: currentTokens, lastRefillTime } = calculateTokens(state, capacity, rate, now);\n\n const allowed = currentTokens >= requested;\n const newTokens = allowed ? currentTokens - requested : currentTokens;\n\n const newState: TokenBucketState = { tokens: newTokens, lastRefillTime };\n await kv.setItem(key, JSON.stringify(newState), expiresIn);\n\n const result: RateLimitResult = {\n allowed,\n remaining: Math.floor(Math.max(0, newTokens)),\n limit: capacity,\n reset: calculateResetTime(newTokens, capacity, rate),\n };\n\n if (!allowed) {\n result.retryAfter = calculateRetryAfter(currentTokens, requested, rate);\n }\n\n return result;\n}\n\nexport function rateLimit(options: RateLimitOptions): MiddlewareHandler {\n const {\n kv,\n rate,\n capacity,\n requested = 1,\n keyPrefix = 'rate-limit:',\n expiresIn = Math.max(3600, Math.ceil((capacity / rate) * 2)),\n getIdentifier,\n onRateLimited,\n skip,\n } = options;\n\n return async (c, next) => {\n if (skip && (await skip(c))) {\n return next();\n }\n\n const identifier = getIdentifier ? getIdentifier(c) : geolocation(c).ip_address;\n if (!identifier) return next();\n\n const result = await checkRateLimit(kv, identifier, {\n rate,\n capacity,\n requested,\n keyPrefix,\n expiresIn,\n });\n\n c.header('X-RateLimit-Limit', String(result.limit));\n c.header('X-RateLimit-Remaining', String(result.remaining));\n c.header('X-RateLimit-Reset', String(result.reset));\n\n if (!result.allowed) {\n c.header('Retry-After', String(result.retryAfter ?? 1));\n if (onRateLimited) return onRateLimited(c, result.retryAfter ?? 1);\n\n const details = Details.new()\n .errorInfo({ reason: 'RATE_LIMIT_EXCEEDED' })\n .retryInfo({ retryDelay: result.retryAfter ?? 1 });\n\n const message = 'Rate limit exceeded. Please try again later.';\n return Status.resourceExhausted(message).response(details);\n }\n\n return next();\n };\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,35 @@
1
+ import { Context, MiddlewareHandler } from 'hono';
2
+
3
+ interface KV {
4
+ setItem(key: string, value: string, expiresIn?: number): Promise<void>;
5
+ getItem(key: string): Promise<string | null>;
6
+ removeItem(key: string): Promise<void>;
7
+ }
8
+ interface RateLimitOptions {
9
+ kv: KV;
10
+ rate: number;
11
+ capacity: number;
12
+ requested?: number;
13
+ keyPrefix?: string;
14
+ expiresIn?: number;
15
+ getIdentifier?: (c: Context) => string | undefined;
16
+ onRateLimited?: (c: Context, retryAfter: number) => Response | Promise<Response>;
17
+ skip?: (c: Context) => boolean | Promise<boolean>;
18
+ }
19
+ interface RateLimitResult {
20
+ allowed: boolean;
21
+ remaining: number;
22
+ limit: number;
23
+ reset: number;
24
+ retryAfter?: number;
25
+ }
26
+ declare function checkRateLimit(kv: KV, identifier: string, options: {
27
+ rate: number;
28
+ capacity: number;
29
+ requested?: number;
30
+ keyPrefix?: string;
31
+ expiresIn?: number;
32
+ }): Promise<RateLimitResult>;
33
+ declare function rateLimit(options: RateLimitOptions): MiddlewareHandler;
34
+
35
+ export { type KV, type RateLimitOptions, type RateLimitResult, checkRateLimit, rateLimit };
@@ -0,0 +1,35 @@
1
+ import { Context, MiddlewareHandler } from 'hono';
2
+
3
+ interface KV {
4
+ setItem(key: string, value: string, expiresIn?: number): Promise<void>;
5
+ getItem(key: string): Promise<string | null>;
6
+ removeItem(key: string): Promise<void>;
7
+ }
8
+ interface RateLimitOptions {
9
+ kv: KV;
10
+ rate: number;
11
+ capacity: number;
12
+ requested?: number;
13
+ keyPrefix?: string;
14
+ expiresIn?: number;
15
+ getIdentifier?: (c: Context) => string | undefined;
16
+ onRateLimited?: (c: Context, retryAfter: number) => Response | Promise<Response>;
17
+ skip?: (c: Context) => boolean | Promise<boolean>;
18
+ }
19
+ interface RateLimitResult {
20
+ allowed: boolean;
21
+ remaining: number;
22
+ limit: number;
23
+ reset: number;
24
+ retryAfter?: number;
25
+ }
26
+ declare function checkRateLimit(kv: KV, identifier: string, options: {
27
+ rate: number;
28
+ capacity: number;
29
+ requested?: number;
30
+ keyPrefix?: string;
31
+ expiresIn?: number;
32
+ }): Promise<RateLimitResult>;
33
+ declare function rateLimit(options: RateLimitOptions): MiddlewareHandler;
34
+
35
+ export { type KV, type RateLimitOptions, type RateLimitResult, checkRateLimit, rateLimit };
@@ -0,0 +1,95 @@
1
+ // src/hono/rate-limit.ts
2
+ import { Details } from "../error/detail.mjs";
3
+ import { Status } from "../error/status.mjs";
4
+ import { geolocation } from "./geolocation.mjs";
5
+ function calculateTokens(state, capacity, rate, now) {
6
+ if (!state) {
7
+ return { tokens: capacity, lastRefillTime: now };
8
+ }
9
+ const elapsed = (now - state.lastRefillTime) / 1e3;
10
+ const tokensToAdd = elapsed * rate;
11
+ const newTokens = Math.min(capacity, state.tokens + tokensToAdd);
12
+ return {
13
+ tokens: newTokens,
14
+ lastRefillTime: now
15
+ };
16
+ }
17
+ function calculateResetTime(currentTokens, capacity, rate) {
18
+ if (currentTokens >= capacity) return 0;
19
+ return Math.ceil((capacity - currentTokens) / rate);
20
+ }
21
+ function calculateRetryAfter(currentTokens, requested, rate) {
22
+ if (currentTokens >= requested) return 0;
23
+ return Math.ceil((requested - currentTokens) / rate);
24
+ }
25
+ async function checkRateLimit(kv, identifier, options) {
26
+ const {
27
+ rate,
28
+ capacity,
29
+ requested = 1,
30
+ keyPrefix = "rate-limit:",
31
+ expiresIn = Math.max(3600, Math.ceil(capacity / rate * 2))
32
+ } = options;
33
+ const key = `${keyPrefix}${identifier}`;
34
+ const now = Date.now();
35
+ const stateStr = await kv.getItem(key);
36
+ const state = stateStr ? JSON.parse(stateStr) : null;
37
+ const { tokens: currentTokens, lastRefillTime } = calculateTokens(state, capacity, rate, now);
38
+ const allowed = currentTokens >= requested;
39
+ const newTokens = allowed ? currentTokens - requested : currentTokens;
40
+ const newState = { tokens: newTokens, lastRefillTime };
41
+ await kv.setItem(key, JSON.stringify(newState), expiresIn);
42
+ const result = {
43
+ allowed,
44
+ remaining: Math.floor(Math.max(0, newTokens)),
45
+ limit: capacity,
46
+ reset: calculateResetTime(newTokens, capacity, rate)
47
+ };
48
+ if (!allowed) {
49
+ result.retryAfter = calculateRetryAfter(currentTokens, requested, rate);
50
+ }
51
+ return result;
52
+ }
53
+ function rateLimit(options) {
54
+ const {
55
+ kv,
56
+ rate,
57
+ capacity,
58
+ requested = 1,
59
+ keyPrefix = "rate-limit:",
60
+ expiresIn = Math.max(3600, Math.ceil(capacity / rate * 2)),
61
+ getIdentifier,
62
+ onRateLimited,
63
+ skip
64
+ } = options;
65
+ return async (c, next) => {
66
+ if (skip && await skip(c)) {
67
+ return next();
68
+ }
69
+ const identifier = getIdentifier ? getIdentifier(c) : geolocation(c).ip_address;
70
+ if (!identifier) return next();
71
+ const result = await checkRateLimit(kv, identifier, {
72
+ rate,
73
+ capacity,
74
+ requested,
75
+ keyPrefix,
76
+ expiresIn
77
+ });
78
+ c.header("X-RateLimit-Limit", String(result.limit));
79
+ c.header("X-RateLimit-Remaining", String(result.remaining));
80
+ c.header("X-RateLimit-Reset", String(result.reset));
81
+ if (!result.allowed) {
82
+ c.header("Retry-After", String(result.retryAfter ?? 1));
83
+ if (onRateLimited) return onRateLimited(c, result.retryAfter ?? 1);
84
+ const details = Details.new().errorInfo({ reason: "RATE_LIMIT_EXCEEDED" }).retryInfo({ retryDelay: result.retryAfter ?? 1 });
85
+ const message = "Rate limit exceeded. Please try again later.";
86
+ return Status.resourceExhausted(message).response(details);
87
+ }
88
+ return next();
89
+ };
90
+ }
91
+ export {
92
+ checkRateLimit,
93
+ rateLimit
94
+ };
95
+ //# sourceMappingURL=rate-limit.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/hono/rate-limit.ts"],"sourcesContent":["import type { Context, MiddlewareHandler } from 'hono';\nimport { Details } from '../error/detail';\nimport { Status } from '../error/status';\nimport { geolocation } from './geolocation';\n\nexport interface KV {\n setItem(key: string, value: string, expiresIn?: number): Promise<void>;\n getItem(key: string): Promise<string | null>;\n removeItem(key: string): Promise<void>;\n}\n\ninterface TokenBucketState {\n tokens: number;\n lastRefillTime: number;\n}\n\nexport interface RateLimitOptions {\n kv: KV;\n rate: number;\n capacity: number;\n requested?: number;\n keyPrefix?: string;\n expiresIn?: number;\n getIdentifier?: (c: Context) => string | undefined;\n onRateLimited?: (c: Context, retryAfter: number) => Response | Promise<Response>;\n skip?: (c: Context) => boolean | Promise<boolean>;\n}\n\nexport interface RateLimitResult {\n allowed: boolean;\n remaining: number;\n limit: number;\n reset: number;\n retryAfter?: number;\n}\n\nfunction calculateTokens(\n state: TokenBucketState | null,\n capacity: number,\n rate: number,\n now: number\n): { tokens: number; lastRefillTime: number } {\n if (!state) {\n return { tokens: capacity, lastRefillTime: now };\n }\n\n const elapsed = (now - state.lastRefillTime) / 1000;\n const tokensToAdd = elapsed * rate;\n const newTokens = Math.min(capacity, state.tokens + tokensToAdd);\n\n return {\n tokens: newTokens,\n lastRefillTime: now,\n };\n}\n\nfunction calculateResetTime(currentTokens: number, capacity: number, rate: number): number {\n if (currentTokens >= capacity) return 0;\n return Math.ceil((capacity - currentTokens) / rate);\n}\n\nfunction calculateRetryAfter(currentTokens: number, requested: number, rate: number): number {\n if (currentTokens >= requested) return 0;\n return Math.ceil((requested - currentTokens) / rate);\n}\n\nexport async function checkRateLimit(\n kv: KV,\n identifier: string,\n options: {\n rate: number;\n capacity: number;\n requested?: number;\n keyPrefix?: string;\n expiresIn?: number;\n }\n): Promise<RateLimitResult> {\n const {\n rate,\n capacity,\n requested = 1,\n keyPrefix = 'rate-limit:',\n expiresIn = Math.max(3600, Math.ceil((capacity / rate) * 2)),\n } = options;\n\n const key = `${keyPrefix}${identifier}`;\n const now = Date.now();\n\n const stateStr = await kv.getItem(key);\n const state: TokenBucketState | null = stateStr ? JSON.parse(stateStr) : null;\n\n const { tokens: currentTokens, lastRefillTime } = calculateTokens(state, capacity, rate, now);\n\n const allowed = currentTokens >= requested;\n const newTokens = allowed ? currentTokens - requested : currentTokens;\n\n const newState: TokenBucketState = { tokens: newTokens, lastRefillTime };\n await kv.setItem(key, JSON.stringify(newState), expiresIn);\n\n const result: RateLimitResult = {\n allowed,\n remaining: Math.floor(Math.max(0, newTokens)),\n limit: capacity,\n reset: calculateResetTime(newTokens, capacity, rate),\n };\n\n if (!allowed) {\n result.retryAfter = calculateRetryAfter(currentTokens, requested, rate);\n }\n\n return result;\n}\n\nexport function rateLimit(options: RateLimitOptions): MiddlewareHandler {\n const {\n kv,\n rate,\n capacity,\n requested = 1,\n keyPrefix = 'rate-limit:',\n expiresIn = Math.max(3600, Math.ceil((capacity / rate) * 2)),\n getIdentifier,\n onRateLimited,\n skip,\n } = options;\n\n return async (c, next) => {\n if (skip && (await skip(c))) {\n return next();\n }\n\n const identifier = getIdentifier ? getIdentifier(c) : geolocation(c).ip_address;\n if (!identifier) return next();\n\n const result = await checkRateLimit(kv, identifier, {\n rate,\n capacity,\n requested,\n keyPrefix,\n expiresIn,\n });\n\n c.header('X-RateLimit-Limit', String(result.limit));\n c.header('X-RateLimit-Remaining', String(result.remaining));\n c.header('X-RateLimit-Reset', String(result.reset));\n\n if (!result.allowed) {\n c.header('Retry-After', String(result.retryAfter ?? 1));\n if (onRateLimited) return onRateLimited(c, result.retryAfter ?? 1);\n\n const details = Details.new()\n .errorInfo({ reason: 'RATE_LIMIT_EXCEEDED' })\n .retryInfo({ retryDelay: result.retryAfter ?? 1 });\n\n const message = 'Rate limit exceeded. Please try again later.';\n return Status.resourceExhausted(message).response(details);\n }\n\n return next();\n };\n}\n"],"mappings":"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","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * @example\n * import { Details, Status } from '@repo/error';\n *\n * Status.adapter = () => new Error('Error');\n *\n * const details = Details.new()\n * .requestInfo({ requestId: '1234567890', servingData: '/v1/tests' })\n * .errorInfo({ reason: 'ACCOUNT_LOCKED' });\n *\n * throw Status.alreadyExists('xxx').error(details);\n */\n\nexport {\n LoginTimeoutError,\n LoginCanceledError,\n CheckoutCreateError,\n PurchaseError,\n} from './error/index';\nexport type {\n NetworkErrorReason,\n StatusErrorReason,\n AuthenticationErrorReason,\n ModerationErrorReason,\n MultipartErrorReason,\n AppErrorReason,\n ErrorReason,\n} from './error/reason';\nexport {\n DetailType,\n Details,\n type ErrorInfo,\n type RetryInfo,\n type DebugInfo,\n type QuotaFailure,\n type PreconditionFailure,\n type BadRequest,\n type RequestInfo,\n type ResourceInfo,\n type Help,\n type LocalizedMessage,\n type Detail,\n} from './error/detail';\nexport { Status, StatusError, type ErrorBody } from './error/status';\nexport { getErrorReason, getErrorMessage, getFieldViolations } from './error/parse';\nexport {\n Items,\n Pages,\n Cursor,\n pageParamsSchema,\n initialPageParam,\n getPreviousPageParam,\n getNextPageParam,\n} from './response';\nexport type {\n Empty,\n EntityId,\n Entity,\n Response,\n InitParams,\n NextParams,\n PrevParams,\n PageParams,\n ParentPageParams,\n Page,\n InfinitePageData,\n} from './response';\n\nexport { UidGenerator, uid } from './snowflake';\n\nexport * as MAX_LENGTH from './max-length/index';\nexport { timing } from './utils/timing';\nexport { ISO_3601_1, type ISO3166CountryCode } from './iso/iso_3601_1';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAaA,mBAKO;AAUP,oBAcO;AACP,oBAAoD;AACpD,mBAAoE;AACpE,sBAQO;AAeP,uBAAkC;AAElC,iBAA4B;AAC5B,oBAAuB;AACvB,wBAAoD;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * @example\n * import { Details, Status } from '@repo/error';\n *\n * Status.adapter = () => new Error('Error');\n *\n * const details = Details.new()\n * .requestInfo({ requestId: '1234567890', servingData: '/v1/tests' })\n * .errorInfo({ reason: 'ACCOUNT_LOCKED' });\n *\n * throw Status.alreadyExists('xxx').error(details);\n */\n\nexport {\n LoginTimeoutError,\n LoginCanceledError,\n CheckoutCreateError,\n PurchaseError,\n} from './error/index';\nexport type {\n NetworkErrorReason,\n StatusErrorReason,\n AuthenticationErrorReason,\n ModerationErrorReason,\n MultipartErrorReason,\n AppErrorReason,\n ErrorReason,\n ResolvedErrorReason,\n} from './error/reason';\nexport {\n DetailType,\n Details,\n type ErrorInfo,\n type RetryInfo,\n type DebugInfo,\n type QuotaFailure,\n type PreconditionFailure,\n type BadRequest,\n type RequestInfo,\n type ResourceInfo,\n type Help,\n type LocalizedMessage,\n type Detail,\n} from './error/detail';\nexport { Status, StatusError, type ErrorBody } from './error/status';\nexport { getErrorReason, getErrorMessage, getFieldViolations } from './error/parse';\nexport {\n Items,\n Pages,\n Cursor,\n pageParamsSchema,\n initialPageParam,\n getPreviousPageParam,\n getNextPageParam,\n} from './response';\nexport type {\n Empty,\n EntityId,\n Entity,\n Response,\n InitParams,\n NextParams,\n PrevParams,\n PageParams,\n ParentPageParams,\n Page,\n InfinitePageData,\n} from './response';\n\nexport { UidGenerator, uid } from './snowflake';\n\nexport * as MAX_LENGTH from './max-length/index';\nexport { timing } from './utils/timing';\nexport { ISO_3601_1, type ISO3166CountryCode } from './iso/iso_3601_1';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAaA,mBAKO;AAWP,oBAcO;AACP,oBAAoD;AACpD,mBAAoE;AACpE,sBAQO;AAeP,uBAAkC;AAElC,iBAA4B;AAC5B,oBAAuB;AACvB,wBAAoD;","names":[]}
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { CheckoutCreateError, LoginCanceledError, LoginTimeoutError, PurchaseError } from './error/index.cjs';
2
- export { AppErrorReason, AuthenticationErrorReason, ErrorReason, ModerationErrorReason, MultipartErrorReason, NetworkErrorReason, StatusErrorReason } from './error/reason.cjs';
2
+ export { AppErrorReason, AuthenticationErrorReason, ErrorReason, ModerationErrorReason, MultipartErrorReason, NetworkErrorReason, ResolvedErrorReason, StatusErrorReason } from './error/reason.cjs';
3
3
  export { BadRequest, DebugInfo, Detail, DetailType, Details, ErrorInfo, Help, LocalizedMessage, PreconditionFailure, QuotaFailure, RequestInfo, ResourceInfo, RetryInfo } from './error/detail.cjs';
4
4
  export { ErrorBody, Status, StatusError } from './error/status.cjs';
5
5
  export { getErrorMessage, getErrorReason, getFieldViolations } from './error/parse.cjs';
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { CheckoutCreateError, LoginCanceledError, LoginTimeoutError, PurchaseError } from './error/index.js';
2
- export { AppErrorReason, AuthenticationErrorReason, ErrorReason, ModerationErrorReason, MultipartErrorReason, NetworkErrorReason, StatusErrorReason } from './error/reason.js';
2
+ export { AppErrorReason, AuthenticationErrorReason, ErrorReason, ModerationErrorReason, MultipartErrorReason, NetworkErrorReason, ResolvedErrorReason, StatusErrorReason } from './error/reason.js';
3
3
  export { BadRequest, DebugInfo, Detail, DetailType, Details, ErrorInfo, Help, LocalizedMessage, PreconditionFailure, QuotaFailure, RequestInfo, ResourceInfo, RetryInfo } from './error/detail.js';
4
4
  export { ErrorBody, Status, StatusError } from './error/status.js';
5
5
  export { getErrorMessage, getErrorReason, getFieldViolations } from './error/parse.js';
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * @example\n * import { Details, Status } from '@repo/error';\n *\n * Status.adapter = () => new Error('Error');\n *\n * const details = Details.new()\n * .requestInfo({ requestId: '1234567890', servingData: '/v1/tests' })\n * .errorInfo({ reason: 'ACCOUNT_LOCKED' });\n *\n * throw Status.alreadyExists('xxx').error(details);\n */\n\nexport {\n LoginTimeoutError,\n LoginCanceledError,\n CheckoutCreateError,\n PurchaseError,\n} from './error/index';\nexport type {\n NetworkErrorReason,\n StatusErrorReason,\n AuthenticationErrorReason,\n ModerationErrorReason,\n MultipartErrorReason,\n AppErrorReason,\n ErrorReason,\n} from './error/reason';\nexport {\n DetailType,\n Details,\n type ErrorInfo,\n type RetryInfo,\n type DebugInfo,\n type QuotaFailure,\n type PreconditionFailure,\n type BadRequest,\n type RequestInfo,\n type ResourceInfo,\n type Help,\n type LocalizedMessage,\n type Detail,\n} from './error/detail';\nexport { Status, StatusError, type ErrorBody } from './error/status';\nexport { getErrorReason, getErrorMessage, getFieldViolations } from './error/parse';\nexport {\n Items,\n Pages,\n Cursor,\n pageParamsSchema,\n initialPageParam,\n getPreviousPageParam,\n getNextPageParam,\n} from './response';\nexport type {\n Empty,\n EntityId,\n Entity,\n Response,\n InitParams,\n NextParams,\n PrevParams,\n PageParams,\n ParentPageParams,\n Page,\n InfinitePageData,\n} from './response';\n\nexport { UidGenerator, uid } from './snowflake';\n\nexport * as MAX_LENGTH from './max-length/index';\nexport { timing } from './utils/timing';\nexport { ISO_3601_1, type ISO3166CountryCode } from './iso/iso_3601_1';\n"],"mappings":";AAaA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAUP;AAAA,EACE;AAAA,EACA;AAAA,OAYK;AACP,SAAS,QAAQ,mBAAmC;AACpD,SAAS,gBAAgB,iBAAiB,0BAA0B;AACpE;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAeP,SAAS,cAAc,WAAW;AAElC,YAAY,gBAAgB;AAC5B,SAAS,cAAc;AACvB,SAAS,kBAA2C;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * @example\n * import { Details, Status } from '@repo/error';\n *\n * Status.adapter = () => new Error('Error');\n *\n * const details = Details.new()\n * .requestInfo({ requestId: '1234567890', servingData: '/v1/tests' })\n * .errorInfo({ reason: 'ACCOUNT_LOCKED' });\n *\n * throw Status.alreadyExists('xxx').error(details);\n */\n\nexport {\n LoginTimeoutError,\n LoginCanceledError,\n CheckoutCreateError,\n PurchaseError,\n} from './error/index';\nexport type {\n NetworkErrorReason,\n StatusErrorReason,\n AuthenticationErrorReason,\n ModerationErrorReason,\n MultipartErrorReason,\n AppErrorReason,\n ErrorReason,\n ResolvedErrorReason,\n} from './error/reason';\nexport {\n DetailType,\n Details,\n type ErrorInfo,\n type RetryInfo,\n type DebugInfo,\n type QuotaFailure,\n type PreconditionFailure,\n type BadRequest,\n type RequestInfo,\n type ResourceInfo,\n type Help,\n type LocalizedMessage,\n type Detail,\n} from './error/detail';\nexport { Status, StatusError, type ErrorBody } from './error/status';\nexport { getErrorReason, getErrorMessage, getFieldViolations } from './error/parse';\nexport {\n Items,\n Pages,\n Cursor,\n pageParamsSchema,\n initialPageParam,\n getPreviousPageParam,\n getNextPageParam,\n} from './response';\nexport type {\n Empty,\n EntityId,\n Entity,\n Response,\n InitParams,\n NextParams,\n PrevParams,\n PageParams,\n ParentPageParams,\n Page,\n InfinitePageData,\n} from './response';\n\nexport { UidGenerator, uid } from './snowflake';\n\nexport * as MAX_LENGTH from './max-length/index';\nexport { timing } from './utils/timing';\nexport { ISO_3601_1, type ISO3166CountryCode } from './iso/iso_3601_1';\n"],"mappings":";AAaA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAWP;AAAA,EACE;AAAA,EACA;AAAA,OAYK;AACP,SAAS,QAAQ,mBAAmC;AACpD,SAAS,gBAAgB,iBAAiB,0BAA0B;AACpE;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAeP,SAAS,cAAc,WAAW;AAElC,YAAY,gBAAgB;AAC5B,SAAS,cAAc;AACvB,SAAS,kBAA2C;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shware/http",
3
- "version": "2.5.0",
3
+ "version": "2.6.0",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",