@mpen/routekit 0.1.0 → 0.1.2

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 (133) hide show
  1. package/dist/bin.d.mts +4 -0
  2. package/dist/client/react.d.mts +178 -0
  3. package/dist/client/react.mjs +142 -0
  4. package/dist/client.d.mts +433 -0
  5. package/dist/client.mjs +264 -0
  6. package/dist/content-BuDOmhH_.mjs +102 -0
  7. package/dist/core-CzUCxvGk.d.mts +140 -0
  8. package/dist/core-DbmQauwS.mjs +81 -0
  9. package/dist/handlers.d.mts +72 -0
  10. package/dist/handlers.mjs +153 -0
  11. package/dist/index.d.mts +3 -0
  12. package/dist/index.mjs +1152 -0
  13. package/dist/middleware.d.mts +388 -0
  14. package/dist/middleware.mjs +1222 -0
  15. package/dist/request-Dn0zc-xm.mjs +1025 -0
  16. package/dist/response/content.d.mts +79 -0
  17. package/dist/response/content.mjs +2 -0
  18. package/dist/response/json-rpc.d.mts +1 -0
  19. package/dist/response/json-rpc.mjs +1 -0
  20. package/dist/response/problem/valibot.d.mts +230 -0
  21. package/dist/response/problem/valibot.mjs +258 -0
  22. package/dist/response/problem.d.mts +415 -0
  23. package/dist/response/problem.mjs +183 -0
  24. package/dist/response/status.d.mts +45 -0
  25. package/dist/response/status.mjs +2 -0
  26. package/dist/responses-B379Ep9Y.d.mts +296 -0
  27. package/dist/responses-BpVrgeYi.mjs +101 -0
  28. package/dist/router-Cwb7ak0J.d.mts +1819 -0
  29. package/dist/routes.d.mts +282 -0
  30. package/dist/routes.mjs +311 -0
  31. package/dist/status-C-8mw-FB.mjs +59 -0
  32. package/dist/valibot-D7liFYyB.d.mts +290 -0
  33. package/dist/valibot-Du97X-TS.mjs +326 -0
  34. package/package.json +8 -2
  35. package/src/bin/gen-api-client.test.ts +0 -70
  36. package/src/bin/gen-api-client.ts +0 -986
  37. package/src/client/headers.ts +0 -31
  38. package/src/client/index.ts +0 -8
  39. package/src/client/promise.ts +0 -11
  40. package/src/client/react/index.test.tsx +0 -266
  41. package/src/client/react/index.ts +0 -431
  42. package/src/client/responses.test.ts +0 -151
  43. package/src/client/responses.ts +0 -278
  44. package/src/client/transport.ts +0 -74
  45. package/src/client/transports/body-codec.ts +0 -61
  46. package/src/client/transports/fetch.ts +0 -113
  47. package/src/client/tsconfig.json +0 -9
  48. package/src/client/types.ts +0 -15
  49. package/src/client/url.ts +0 -31
  50. package/src/index.ts +0 -63
  51. package/src/router/fetch-types.ts +0 -13
  52. package/src/router/handlers/index.ts +0 -2
  53. package/src/router/handlers/openapi/index.ts +0 -2
  54. package/src/router/handlers/openapi/openapi.ts +0 -293
  55. package/src/router/integration/zod-openapi.test.ts +0 -74
  56. package/src/router/lib/charset.test.ts +0 -22
  57. package/src/router/lib/charset.ts +0 -133
  58. package/src/router/lib/collections.ts +0 -3
  59. package/src/router/lib/format.test.ts +0 -67
  60. package/src/router/lib/format.ts +0 -35
  61. package/src/router/lib/host.ts +0 -4
  62. package/src/router/lib/json-schema.ts +0 -6
  63. package/src/router/lib/media-type.test.ts +0 -122
  64. package/src/router/lib/media-type.ts +0 -289
  65. package/src/router/lib/pathname.test.ts +0 -18
  66. package/src/router/lib/pathname.ts +0 -19
  67. package/src/router/lib/route-names.ts +0 -70
  68. package/src/router/lib/route-normalize.test.ts +0 -36
  69. package/src/router/lib/route-normalize.ts +0 -67
  70. package/src/router/lib/schema-merge.ts +0 -56
  71. package/src/router/middleware/accept-ctx.test.ts +0 -33
  72. package/src/router/middleware/accept-ctx.ts +0 -12
  73. package/src/router/middleware/body-limit.test.ts +0 -112
  74. package/src/router/middleware/body-limit.ts +0 -121
  75. package/src/router/middleware/content-type-context.ts +0 -0
  76. package/src/router/middleware/cors.test.ts +0 -269
  77. package/src/router/middleware/cors.ts +0 -490
  78. package/src/router/middleware/csrf.test.ts +0 -106
  79. package/src/router/middleware/csrf.ts +0 -192
  80. package/src/router/middleware/define.ts +0 -249
  81. package/src/router/middleware/index.ts +0 -34
  82. package/src/router/middleware/jsxhtml-response.ts +0 -0
  83. package/src/router/middleware/oas-swagger.ts +0 -0
  84. package/src/router/middleware/rate-limit.test.ts +0 -886
  85. package/src/router/middleware/rate-limit.ts +0 -920
  86. package/src/router/middleware/request-id-ctx.test.ts +0 -183
  87. package/src/router/middleware/request-id-ctx.ts +0 -135
  88. package/src/router/middleware/request-logger-format.test.ts +0 -16
  89. package/src/router/middleware/request-logger-format.ts +0 -269
  90. package/src/router/middleware/request-logger.test.ts +0 -267
  91. package/src/router/middleware/request-logger.ts +0 -131
  92. package/src/router/middleware/start-time-ctx.ts +0 -5
  93. package/src/router/request.ts +0 -611
  94. package/src/router/response/core.ts +0 -181
  95. package/src/router/response/directives.ts +0 -233
  96. package/src/router/response/formats/content/bodyless.ts +0 -54
  97. package/src/router/response/formats/content/content.ts +0 -79
  98. package/src/router/response/formats/content/index.ts +0 -2
  99. package/src/router/response/formats/json-rpc/index.ts +0 -2
  100. package/src/router/response/formats/problem/badRequest.ts +0 -90
  101. package/src/router/response/formats/problem/conflict.ts +0 -90
  102. package/src/router/response/formats/problem/created.ts +0 -40
  103. package/src/router/response/formats/problem/index.ts +0 -27
  104. package/src/router/response/formats/problem/notFound.ts +0 -90
  105. package/src/router/response/formats/problem/permissionDenied.ts +0 -90
  106. package/src/router/response/formats/problem/problem.test.ts +0 -888
  107. package/src/router/response/formats/problem/rateLimited.ts +0 -90
  108. package/src/router/response/formats/problem/responses.ts +0 -219
  109. package/src/router/response/formats/problem/root-errors.ts +0 -48
  110. package/src/router/response/formats/problem/sessionExpired.ts +0 -90
  111. package/src/router/response/formats/problem/types.ts +0 -170
  112. package/src/router/response/formats/problem/unauthenticated.ts +0 -90
  113. package/src/router/response/formats/problem/valibot.ts +0 -410
  114. package/src/router/response/formats/status/index.ts +0 -1
  115. package/src/router/response/formats/status/responses.ts +0 -59
  116. package/src/router/response/formats/status/status.test.ts +0 -21
  117. package/src/router/response/framers.ts +0 -85
  118. package/src/router/response/index.ts +0 -28
  119. package/src/router/response/openapi.test.ts +0 -96
  120. package/src/router/response/openapi.ts +0 -1
  121. package/src/router/response/serializers.ts +0 -66
  122. package/src/router/response/stream.ts +0 -35
  123. package/src/router/router.test.ts +0 -1571
  124. package/src/router/router.ts +0 -1965
  125. package/src/router/routes/index.ts +0 -46
  126. package/src/router/routes/valibot/index.ts +0 -18
  127. package/src/router/routes/valibot/valibot.ts +0 -1393
  128. package/src/router/routes/valibot.test.ts +0 -286
  129. package/src/router/routes/zod/index.ts +0 -18
  130. package/src/router/routes/zod/zod.ts +0 -1318
  131. package/src/router/routes/zod.test.ts +0 -280
  132. package/src/router/server-interface.ts +0 -31
  133. package/src/router/types.ts +0 -657
@@ -1,293 +0,0 @@
1
- import { HttpMethod, StatusText } from '@mpen/http'
2
- import type { HttpStatus } from '@mpen/http'
3
- import type { Router } from '../../router'
4
- import type { Handler, JsonObjectSchema, JsonSchema, NormalizedRoute, RouteMeta } from '../../types'
5
- import { ok } from '../../response/formats/status'
6
-
7
- /**
8
- * OpenAPI document `info` section.
9
- */
10
- export type OpenApiInfo = {
11
- title: string
12
- version: string
13
- description?: string
14
- termsOfService?: string
15
- contact?: Record<string, unknown>
16
- license?: Record<string, unknown>
17
- }
18
-
19
- /**
20
- * OpenAPI server definition.
21
- */
22
- export type OpenApiServer = {
23
- url: string
24
- description?: string
25
- variables?: Record<string, unknown>
26
- }
27
-
28
- /**
29
- * OpenAPI operation object.
30
- */
31
- export type OpenApiOperation = Record<string, unknown>
32
-
33
- /**
34
- * OpenAPI paths dictionary keyed by pathname then method.
35
- */
36
- export type OpenApiPaths = Record<string, Record<string, OpenApiOperation>>
37
-
38
- /**
39
- * OpenAPI document returned by the `openapi` plugin handler.
40
- */
41
- export type OpenApiDocument = {
42
- openapi: string
43
- info: OpenApiInfo
44
- servers?: OpenApiServer[]
45
- paths: OpenApiPaths
46
- components?: Record<string, unknown>
47
- security?: Array<Record<string, string[]>>
48
- }
49
-
50
- /**
51
- * Options used to build an OpenAPI document from registered routes.
52
- */
53
- export type OpenApiOptions = {
54
- info: OpenApiInfo
55
- servers?: OpenApiServer[]
56
- components?: Record<string, unknown>
57
- security?: Array<Record<string, string[]>>
58
- openapi?: string
59
- }
60
-
61
- type OpenApiParameter = {
62
- name: string
63
- in: 'path' | 'query'
64
- required?: boolean
65
- schema: JsonSchema
66
- }
67
-
68
- type OpenApiRequestBody = {
69
- required?: boolean
70
- content: Record<string, { schema: JsonSchema }>
71
- }
72
-
73
- type OpenApiResponse = {
74
- description: string
75
- content?: Record<string, { schema: JsonSchema }>
76
- }
77
-
78
- const DEFAULT_OPENAPI_VERSION = '3.0.3'
79
- const DEFAULT_METHODS: HttpMethod[] = [
80
- HttpMethod.GET,
81
- HttpMethod.PUT,
82
- HttpMethod.POST,
83
- HttpMethod.DELETE,
84
- HttpMethod.OPTIONS,
85
- HttpMethod.HEAD,
86
- HttpMethod.PATCH,
87
- HttpMethod.TRACE,
88
- ]
89
-
90
- function routePathToOpenApi(pathname: string): string {
91
- return pathname.replace(/:([A-Za-z0-9_]+)/g, '{$1}')
92
- }
93
-
94
- function normalizeOpenApiMethods(route: NormalizedRoute<any>): string[] {
95
- const rawMethods = route.method
96
- ? Array.isArray(route.method)
97
- ? route.method
98
- : [route.method]
99
- : DEFAULT_METHODS
100
- const normalized = new Set<string>()
101
- for (const method of rawMethods) {
102
- if (method === HttpMethod.CONNECT) continue
103
- normalized.add(method.toLowerCase())
104
- }
105
- return [...normalized]
106
- }
107
-
108
- function buildParameterEntries(
109
- schema: JsonObjectSchema,
110
- location: 'path' | 'query',
111
- ): OpenApiParameter[] {
112
- const properties = schema.properties
113
- const requiredList = Array.isArray(schema.required)
114
- ? schema.required.filter((value): value is string => typeof value === 'string')
115
- : []
116
- if (properties && typeof properties === 'object') {
117
- return Object.entries(properties).map(([name, propSchema]) => ({
118
- name,
119
- in: location,
120
- required: location === 'path' ? true : requiredList.includes(name),
121
- schema: (propSchema as JsonSchema) ?? {},
122
- }))
123
- }
124
- return [
125
- {
126
- name: location,
127
- in: location,
128
- required: location === 'path',
129
- schema,
130
- },
131
- ]
132
- }
133
-
134
- function openApiRequestContentTypes(route: NormalizedRoute<any>): string[] {
135
- if (!route.accept || route.accept.length === 0) return ['application/json']
136
- const normalized = new Set<string>()
137
- for (const accept of route.accept) {
138
- normalized.add(accept.type)
139
- }
140
- return [...normalized]
141
- }
142
-
143
- function openApiResponseContentTypes(): string[] {
144
- return ['application/json']
145
- }
146
-
147
- function defaultResponseDescription(status: string): string {
148
- const numericStatus = Number(status)
149
- if (Number.isInteger(numericStatus)) {
150
- return StatusText[numericStatus as HttpStatus] ?? String(status)
151
- }
152
- return status
153
- }
154
-
155
- function buildOperationFromSchema(route: NormalizedRoute<any>): OpenApiOperation {
156
- const schema = route.schema
157
- const operation: OpenApiOperation = {}
158
- if (!schema) {
159
- operation.responses = { 200: { description: 'OK' } }
160
- return operation
161
- }
162
-
163
- const parameters: OpenApiParameter[] = []
164
- if (schema.request?.query) {
165
- parameters.push(...buildParameterEntries(schema.request.query, 'query'))
166
- }
167
- if (schema.request?.path) {
168
- parameters.push(...buildParameterEntries(schema.request.path, 'path'))
169
- }
170
- if (parameters.length > 0) {
171
- operation.parameters = parameters
172
- }
173
-
174
- if (schema.request?.body !== undefined) {
175
- const content = Object.fromEntries(
176
- openApiRequestContentTypes(route).map((contentType) => [
177
- contentType,
178
- { schema: schema.request!.body! },
179
- ]),
180
- )
181
- operation.requestBody = {
182
- required: true,
183
- content,
184
- } satisfies OpenApiRequestBody
185
- }
186
-
187
- if (schema.response?.body && Object.keys(schema.response.body).length > 0) {
188
- const contentTypes = openApiResponseContentTypes()
189
- operation.responses = Object.fromEntries(
190
- Object.entries(schema.response.body).flatMap(([status, responseSchema]) => {
191
- if (responseSchema === undefined) return []
192
- const response: OpenApiResponse = {
193
- description: defaultResponseDescription(status),
194
- }
195
- response.content = Object.fromEntries(
196
- contentTypes.map((contentType) => [contentType, { schema: responseSchema }]),
197
- )
198
- return [[status, response]]
199
- }),
200
- )
201
- } else {
202
- operation.responses = { 200: { description: 'OK' } }
203
- }
204
-
205
- return operation
206
- }
207
-
208
- function mergeOpenApiOperations(
209
- generated: OpenApiOperation,
210
- custom?: RouteMeta['openapi'],
211
- ): OpenApiOperation {
212
- if (!custom) return generated
213
-
214
- const merged: OpenApiOperation = { ...generated, ...custom }
215
- const generatedParameters = Array.isArray(generated.parameters) ? generated.parameters : []
216
- const customParameters = Array.isArray(custom.parameters) ? custom.parameters : []
217
- if (generatedParameters.length > 0 || customParameters.length > 0) {
218
- merged.parameters = [...customParameters, ...generatedParameters]
219
- }
220
-
221
- if (generated.requestBody && custom.requestBody) {
222
- const generatedRequestBody = generated.requestBody as OpenApiRequestBody
223
- const customRequestBody = custom.requestBody as OpenApiRequestBody
224
- merged.requestBody = {
225
- ...generatedRequestBody,
226
- ...customRequestBody,
227
- content: {
228
- ...(generatedRequestBody.content ?? {}),
229
- ...(customRequestBody.content ?? {}),
230
- },
231
- }
232
- }
233
-
234
- if (generated.responses && custom.responses) {
235
- merged.responses = {
236
- ...(generated.responses as Record<string, unknown>),
237
- ...(custom.responses as Record<string, unknown>),
238
- }
239
- }
240
-
241
- return merged
242
- }
243
-
244
- function buildOperation(route: NormalizedRoute<any>): OpenApiOperation {
245
- return mergeOpenApiOperations(buildOperationFromSchema(route), route.meta?.openapi)
246
- }
247
-
248
- /**
249
- * Create an OpenAPI response handler that reflects the active router.
250
- *
251
- * @example
252
- * ```ts
253
- * router.add({
254
- * path: '/swagger.json',
255
- * method: HttpMethod.GET,
256
- * handler: openapi({
257
- * info: {title: 'Example API', version: '1.0.0'},
258
- * servers: [{url: 'https://api.example.com'}],
259
- * }),
260
- * })
261
- * ```
262
- *
263
- * @param options - OpenAPI document options for info, servers, and optional components/security.
264
- * @returns A route handler that returns the generated OpenAPI JSON document.
265
- */
266
- export function openapi(options: OpenApiOptions): Handler<OpenApiDocument> {
267
- return function openapiHandler(this: Router<any>) {
268
- const routes = this.getRoutes()
269
- const paths: OpenApiPaths = {}
270
-
271
- for (const route of routes) {
272
- const pathPattern = routePathToOpenApi(route.path.pathname)
273
- const methods = normalizeOpenApiMethods(route)
274
- if (methods.length === 0) continue
275
-
276
- const pathItem = paths[pathPattern] ?? (paths[pathPattern] = {})
277
- for (const method of methods) {
278
- pathItem[method] = buildOperation(route)
279
- }
280
- }
281
-
282
- const document: OpenApiDocument = {
283
- openapi: options.openapi ?? DEFAULT_OPENAPI_VERSION,
284
- info: options.info,
285
- paths,
286
- ...(options.servers ? { servers: options.servers } : {}),
287
- ...(options.components ? { components: options.components } : {}),
288
- ...(options.security ? { security: options.security } : {}),
289
- }
290
-
291
- return ok(document)
292
- }
293
- }
@@ -1,74 +0,0 @@
1
- #!/usr/bin/env -S bun test
2
- import { describe, expect, it } from 'bun:test'
3
- import { HttpMethod } from '@mpen/http'
4
- import { Router } from '../router'
5
- import { openapi } from '../handlers/openapi'
6
- import type { OpenApiDocument } from '../handlers/openapi'
7
- import { createZodRouteBuilder } from '../routes/zod'
8
- import { z } from 'zod'
9
-
10
- type JsonSchemaObject = {
11
- type?: string
12
- properties: Record<string, JsonSchemaObject>
13
- }
14
-
15
- type OperationWithSchemas = {
16
- requestBody: {
17
- content: Record<string, { schema: JsonSchemaObject }>
18
- }
19
- responses: Record<string, { content: Record<string, { schema: JsonSchemaObject }> }>
20
- parameters: unknown[]
21
- }
22
-
23
- describe('openapi', () => {
24
- it('consumes route schemas generated by createZodRouteBuilder', async () => {
25
- const route = createZodRouteBuilder()
26
- const router = new Router()
27
- router.add(
28
- route({
29
- path: '/users/:id',
30
- method: HttpMethod.POST,
31
- schema: {
32
- request: {
33
- path: z.object({ id: z.string() }),
34
- query: z.object({ include: z.string() }),
35
- body: z.object({ name: z.string() }),
36
- },
37
- response: {
38
- body: {
39
- 200: z.object({ ok: z.boolean() }),
40
- },
41
- },
42
- },
43
- validateResponse: false,
44
- handler: () => new Response('ok'),
45
- }),
46
- )
47
-
48
- router.add({
49
- path: '/swagger.json',
50
- method: HttpMethod.GET,
51
- handler: openapi({
52
- info: { title: 'Example API', version: '1.0.0' },
53
- }),
54
- })
55
-
56
- const response = await router.fetch(new Request('https://example.com/swagger.json'))
57
- const document = (await response.json()) as OpenApiDocument
58
- const operation = document.paths['/users/{id}'].post as OperationWithSchemas
59
-
60
- expect(operation.requestBody.content['application/json'].schema.type).toBe('object')
61
- expect(operation.requestBody.content['application/json'].schema.properties.name.type).toBe(
62
- 'string',
63
- )
64
- expect(
65
- operation.responses['200'].content['application/json'].schema.properties.ok.type,
66
- ).toBe('boolean')
67
- expect(operation.parameters).toEqual(
68
- expect.arrayContaining([
69
- expect.objectContaining({ name: 'id', in: 'path' }),
70
- expect.objectContaining({ name: 'include', in: 'query' }),
71
- ]),
72
- )
73
- })
74
- })
@@ -1,22 +0,0 @@
1
- #!/usr/bin/env -S bun test
2
- import { describe, expect, it } from 'bun:test'
3
- import { normalizeCharsetName } from './charset'
4
-
5
- describe(normalizeCharsetName.name, () => {
6
- it('returns preferred MIME names for common aliases', () => {
7
- expect(normalizeCharsetName('UTF8')).toBe('utf-8')
8
- expect(normalizeCharsetName('latin1')).toBe('iso-8859-1')
9
- expect(normalizeCharsetName('ANSI_X3.4-1968')).toBe('us-ascii')
10
- })
11
-
12
- it('ignores punctuation and whitespace differences', () => {
13
- expect(normalizeCharsetName(' utf_16 ')).toBe('utf-16')
14
- expect(normalizeCharsetName('utf 8')).toBe('utf-8')
15
- expect(normalizeCharsetName('Shift-JIS')).toBe('shift_jis')
16
- })
17
-
18
- it('returns an empty string for missing input', () => {
19
- expect(normalizeCharsetName('')).toBe('')
20
- expect(normalizeCharsetName(' ')).toBe('')
21
- })
22
- })
@@ -1,133 +0,0 @@
1
- /**
2
- * Normalize a charset label to a lowercase "Preferred MIME Name" where possible.
3
- *
4
- * - Charset comparison is case-insensitive (IANA).
5
- * - Uses Preferred MIME Names when mapped.
6
- * - Unknown charsets return a cleaned lowercase form.
7
- */
8
- export function normalizeCharsetName(input: string): string {
9
- if (!input?.length) return ''
10
-
11
- const raw = input.trim()
12
- if (raw.length === 0) return ''
13
-
14
- const keyA = normalizeKey(raw)
15
- const hitA = ALIAS_TO_PREFERRED_LOWER.get(keyA)
16
- if (hitA) return hitA
17
-
18
- // Secondary fuzzy match: ignore punctuation differences (utf8 vs utf-8)
19
- const keyB = stripKey(keyA)
20
- const hitB = STRIPPED_ALIAS_TO_PREFERRED_LOWER.get(keyB)
21
- if (hitB) return hitB
22
-
23
- // Fallback: normalized lowercase token
24
- return keyA
25
- }
26
-
27
- function normalizeKey(s: string): string {
28
- // IANA says comparison is case-insensitive; we normalize to lowercase. :contentReference[oaicite:4]{index=4}
29
- return s
30
- .trim()
31
- .toLowerCase()
32
- .replace(/\s+/g, '') // drop internal spaces
33
- .replace(/_/g, '-') // common alias form
34
- }
35
-
36
- function stripKey(s: string): string {
37
- // Keep only alnum for fuzzy matching across punctuation variants.
38
- return s.replace(/[^a-z0-9]+/g, '')
39
- }
40
-
41
- /**
42
- * Minimal, practical alias set.
43
- * Extend this map as needed (ideally generated from IANA CSV for full coverage). :contentReference[oaicite:5]{index=5}
44
- */
45
- const ALIAS_TO_PREFERRED_LOWER = new Map<string, string>([
46
- // Unicode
47
- ['utf-8', 'utf-8'],
48
- ['utf8', 'utf-8'],
49
- ['unicode-1-1-utf-8', 'utf-8'],
50
-
51
- ['utf-16', 'utf-16'],
52
- ['utf16', 'utf-16'],
53
- ['utf-16le', 'utf-16le'],
54
- ['utf-16be', 'utf-16be'],
55
-
56
- // ASCII
57
- ['us-ascii', 'us-ascii'],
58
- ['ascii', 'us-ascii'],
59
- ['ansi_x3.4-1968', 'us-ascii'],
60
- ['ansi_x3.4-1986', 'us-ascii'],
61
- ['iso646-us', 'us-ascii'],
62
- ['cp367', 'us-ascii'],
63
- ['ibm367', 'us-ascii'],
64
-
65
- // ISO-8859 (Latin / Cyrillic / etc.)
66
- ['iso-8859-1', 'iso-8859-1'],
67
- ['iso_8859-1:1987', 'iso-8859-1'],
68
- ['iso_8859-1', 'iso-8859-1'],
69
- ['latin1', 'iso-8859-1'],
70
- ['l1', 'iso-8859-1'],
71
- ['cp819', 'iso-8859-1'],
72
- ['ibm819', 'iso-8859-1'],
73
-
74
- ['iso-8859-2', 'iso-8859-2'],
75
- ['latin2', 'iso-8859-2'],
76
- ['l2', 'iso-8859-2'],
77
-
78
- ['iso-8859-3', 'iso-8859-3'],
79
- ['latin3', 'iso-8859-3'],
80
- ['l3', 'iso-8859-3'],
81
-
82
- ['iso-8859-4', 'iso-8859-4'],
83
- ['latin4', 'iso-8859-4'],
84
- ['l4', 'iso-8859-4'],
85
-
86
- ['iso-8859-5', 'iso-8859-5'],
87
- ['cyrillic', 'iso-8859-5'],
88
-
89
- ['iso-8859-6', 'iso-8859-6'],
90
- ['arabic', 'iso-8859-6'],
91
-
92
- ['iso-8859-7', 'iso-8859-7'],
93
- ['greek', 'iso-8859-7'],
94
- ['greek8', 'iso-8859-7'],
95
-
96
- ['iso-8859-8', 'iso-8859-8'],
97
- ['hebrew', 'iso-8859-8'],
98
-
99
- ['iso-8859-9', 'iso-8859-9'],
100
- ['latin5', 'iso-8859-9'],
101
- ['l5', 'iso-8859-9'],
102
-
103
- // Common “windows” encodings
104
- ['windows-1252', 'windows-1252'],
105
- ['cp1252', 'windows-1252'],
106
-
107
- ['windows-1251', 'windows-1251'],
108
- ['cp1251', 'windows-1251'],
109
-
110
- // East Asian (common on the web)
111
- ['shift_jis', 'shift_jis'],
112
- ['shift-jis', 'shift_jis'],
113
- ['sjis', 'shift_jis'],
114
- ['ms_kanji', 'shift_jis'],
115
-
116
- ['euc-jp', 'euc-jp'],
117
- ['eucjp', 'euc-jp'],
118
-
119
- ['euc-kr', 'euc-kr'],
120
- ['euckr', 'euc-kr'],
121
-
122
- ['iso-2022-jp', 'iso-2022-jp'],
123
- ['iso-2022-kr', 'iso-2022-kr'],
124
-
125
- ['big5', 'big5'],
126
-
127
- ['gbk', 'gbk'],
128
- ['gb18030', 'gb18030'],
129
- ])
130
-
131
- const STRIPPED_ALIAS_TO_PREFERRED_LOWER = new Map<string, string>(
132
- Array.from(ALIAS_TO_PREFERRED_LOWER.entries(), ([k, v]) => [stripKey(k), v]),
133
- )
@@ -1,3 +0,0 @@
1
- import type { OneOrMany } from '../types'
2
-
3
- export const toArray = <T>(v: OneOrMany<T>): T[] => (Array.isArray(v) ? v : [v])
@@ -1,67 +0,0 @@
1
- #!/usr/bin/env -S bun test
2
- import { describe, expect, it } from 'bun:test'
3
- import { fullWide } from './format'
4
-
5
- describe(fullWide.name, () => {
6
- it('formats finite numbers without grouping or exponential notation', () => {
7
- expect(fullWide(1234567)).toBe('1234567')
8
- expect(fullWide(1e21)).toBe('1000000000000000000000')
9
- expect(fullWide(1234.5)).toBe('1234.5')
10
- expect(fullWide(0.0000001)).toBe('0.0000001')
11
- })
12
-
13
- it('formats bigint values as base-10 strings', () => {
14
- expect(fullWide(123_456_789_012_345_678_901_234_567_890n)).toBe(
15
- '123456789012345678901234567890',
16
- )
17
- })
18
-
19
- it('trims leading zeroes', () => {
20
- expect(fullWide('000123')).toBe('123')
21
- expect(fullWide('+000123')).toBe('123')
22
- expect(fullWide('-000123')).toBe('-123')
23
- })
24
-
25
- it('trims trailing zeros zeroes', () => {
26
- expect(fullWide('1230')).toBe('1230')
27
- expect(fullWide('1230.')).toBe('1230')
28
- expect(fullWide('1230.0')).toBe('1230')
29
- expect(fullWide('1230.01')).toBe('1230.01')
30
- expect(fullWide('1230.010')).toBe('1230.01')
31
- expect(fullWide('1230.010200')).toBe('1230.0102')
32
-
33
- expect(fullWide('+1230')).toBe('1230')
34
- expect(fullWide('+1230.')).toBe('1230')
35
- expect(fullWide('+1230.0')).toBe('1230')
36
- expect(fullWide('+1230.01')).toBe('1230.01')
37
- expect(fullWide('+1230.010')).toBe('1230.01')
38
-
39
- expect(fullWide('-1230')).toBe('-1230')
40
- expect(fullWide('-1230.')).toBe('-1230')
41
- expect(fullWide('-1230.0')).toBe('-1230')
42
- expect(fullWide('-1230.01')).toBe('-1230.01')
43
- expect(fullWide('-1230.010')).toBe('-1230.01')
44
- })
45
-
46
- it('formats decimal strings without losing precision', () => {
47
- expect(fullWide('0.000000000000000001')).toBe('0.000000000000000001')
48
- expect(fullWide('123456789012345678901234567890.125')).toBe(
49
- '123456789012345678901234567890.125',
50
- )
51
- })
52
-
53
- it('returns safe integer bounds for infinite numeric values', () => {
54
- expect(fullWide(Number.POSITIVE_INFINITY)).toBe(String(Number.MAX_SAFE_INTEGER))
55
- expect(fullWide(Number.NEGATIVE_INFINITY)).toBe(String(Number.MIN_SAFE_INTEGER))
56
- })
57
-
58
- it('returns zero for NaN numeric values', () => {
59
- expect(fullWide(Number.NaN)).toBe('0')
60
- })
61
-
62
- it('returns safe integer bounds for infinite string values', () => {
63
- expect(fullWide('Infinity')).toBe(String(Number.MAX_SAFE_INTEGER))
64
- expect(fullWide('+Infinity')).toBe(String(Number.MAX_SAFE_INTEGER))
65
- expect(fullWide('-Infinity')).toBe(String(Number.MIN_SAFE_INTEGER))
66
- })
67
- })
@@ -1,35 +0,0 @@
1
- export type FormattableNumber = Parameters<Intl.NumberFormat['format']>[0]
2
-
3
- const FULL_WIDE_FORMAT = new Intl.NumberFormat('en-US', {
4
- useGrouping: false,
5
- maximumFractionDigits: 20,
6
- })
7
-
8
- const DECIMAL_STRING = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/
9
- const MAX_SAFE_INTEGER_STRING = String(Number.MAX_SAFE_INTEGER)
10
- const MIN_SAFE_INTEGER_STRING = String(Number.MIN_SAFE_INTEGER)
11
-
12
- /**
13
- * Formats a number with full decimal places.
14
- *
15
- * e.g. `1e21` formats as "1000000000000000000000" instead of "1e+21"
16
- *
17
- * @param n The number to format.
18
- */
19
- export function fullWide(n: FormattableNumber): string {
20
- if (typeof n === 'bigint') return n.toString()
21
-
22
- if (typeof n === 'string') {
23
- if (n === 'Infinity' || n === '+Infinity') return MAX_SAFE_INTEGER_STRING
24
- if (n === '-Infinity') return MIN_SAFE_INTEGER_STRING
25
- if (!DECIMAL_STRING.test(n)) return '0'
26
-
27
- return FULL_WIDE_FORMAT.format(n)
28
- }
29
-
30
- if (n === Number.POSITIVE_INFINITY) return MAX_SAFE_INTEGER_STRING
31
- if (n === Number.NEGATIVE_INFINITY) return MIN_SAFE_INTEGER_STRING
32
- if (!Number.isFinite(n)) return '0'
33
-
34
- return FULL_WIDE_FORMAT.format(n)
35
- }
@@ -1,4 +0,0 @@
1
- export function isLocalhost(hostname: string): boolean {
2
- const lower = hostname.toLowerCase()
3
- return lower === 'localhost' || lower === '127.0.0.1' || lower === '::1' || lower === '0.0.0.0'
4
- }
@@ -1,6 +0,0 @@
1
- export const enum JsonSchemaTarget {
2
- DRAFT_04 = 'draft-04',
3
- DRAFT_07 = 'draft-07',
4
- DRAFT_2020_12 = 'draft-2020-12',
5
- OPENAPI_3_0 = 'openapi-3.0',
6
- }