@lovrozagar/honey 0.1.0 → 0.2.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 +1596 -6
- package/dist/accepts.d.ts +6 -0
- package/dist/accepts.d.ts.map +1 -0
- package/dist/body-limit.d.ts +22 -0
- package/dist/body-limit.d.ts.map +1 -0
- package/dist/build/index.d.ts +42 -0
- package/dist/build/index.d.ts.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/client/error.d.ts +14 -0
- package/dist/client/error.d.ts.map +1 -0
- package/dist/client/http.d.ts +88 -0
- package/dist/client/http.d.ts.map +1 -0
- package/dist/client/index.d.ts +22 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/sdk.d.ts +34 -0
- package/dist/client/sdk.d.ts.map +1 -0
- package/dist/client/sse.d.ts +10 -0
- package/dist/client/sse.d.ts.map +1 -0
- package/dist/client/types.d.ts +124 -0
- package/dist/client/types.d.ts.map +1 -0
- package/dist/client/ws.d.ts +15 -0
- package/dist/client/ws.d.ts.map +1 -0
- package/dist/client-mcp/server.d.ts +10 -0
- package/dist/client-mcp/server.d.ts.map +1 -0
- package/dist/client-mcp/types.d.ts +16 -0
- package/dist/client-mcp/types.d.ts.map +1 -0
- package/dist/cloudflare/realtime.d.ts +30 -0
- package/dist/cloudflare/realtime.d.ts.map +1 -0
- package/dist/codegen-go-cli.d.ts +22 -0
- package/dist/codegen-go-cli.d.ts.map +1 -0
- package/dist/codegen-go.d.ts +29 -0
- package/dist/codegen-go.d.ts.map +1 -0
- package/dist/codegen-hash.d.ts +3 -0
- package/dist/codegen-hash.d.ts.map +1 -0
- package/dist/codegen-ir.d.ts +123 -0
- package/dist/codegen-ir.d.ts.map +1 -0
- package/dist/codegen-loaders.d.ts +12 -0
- package/dist/codegen-loaders.d.ts.map +1 -0
- package/dist/codegen-mcp.d.ts +13 -0
- package/dist/codegen-mcp.d.ts.map +1 -0
- package/dist/codegen-python.d.ts +21 -0
- package/dist/codegen-python.d.ts.map +1 -0
- package/dist/codegen-rust.d.ts +18 -0
- package/dist/codegen-rust.d.ts.map +1 -0
- package/dist/codegen-sanitize.d.ts +3 -0
- package/dist/codegen-sanitize.d.ts.map +1 -0
- package/dist/codegen-schema-naming.d.ts +22 -0
- package/dist/codegen-schema-naming.d.ts.map +1 -0
- package/dist/codegen.d.ts +178 -0
- package/dist/codegen.d.ts.map +1 -0
- package/dist/context.d.ts +56 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/cookie-sign.d.ts +3 -0
- package/dist/cookie-sign.d.ts.map +1 -0
- package/dist/cookie.d.ts +12 -0
- package/dist/cookie.d.ts.map +1 -0
- package/dist/cors.d.ts +13 -0
- package/dist/cors.d.ts.map +1 -0
- package/dist/crypto.d.ts +2 -0
- package/dist/crypto.d.ts.map +1 -0
- package/dist/csrf.d.ts +9 -0
- package/dist/csrf.d.ts.map +1 -0
- package/dist/curl-logger.d.ts +33 -0
- package/dist/curl-logger.d.ts.map +1 -0
- package/dist/detect-runtime.d.ts +16 -0
- package/dist/detect-runtime.d.ts.map +1 -0
- package/dist/error.d.ts +21 -0
- package/dist/error.d.ts.map +1 -0
- package/dist/errors.d.ts +40 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/etag.d.ts +9 -0
- package/dist/etag.d.ts.map +1 -0
- package/dist/feature-load.d.ts +3 -0
- package/dist/feature-load.d.ts.map +1 -0
- package/dist/go-type-emitter.d.ts +33 -0
- package/dist/go-type-emitter.d.ts.map +1 -0
- package/dist/honey-response.d.ts +82 -0
- package/dist/honey-response.d.ts.map +1 -0
- package/dist/i18n-slot.d.ts +7 -0
- package/dist/i18n-slot.d.ts.map +1 -0
- package/dist/i18n.d.ts +26 -0
- package/dist/i18n.d.ts.map +1 -0
- package/dist/index.d.ts +470 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/init.d.ts +7 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/input.d.ts +6 -0
- package/dist/input.d.ts.map +1 -0
- package/dist/ip-restrict.d.ts +12 -0
- package/dist/ip-restrict.d.ts.map +1 -0
- package/dist/logger.d.ts +45 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/meta-spec.d.ts +81 -0
- package/dist/meta-spec.d.ts.map +1 -0
- package/dist/middleware.d.ts +60 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/node-request.d.ts +59 -0
- package/dist/node-request.d.ts.map +1 -0
- package/dist/node.d.ts +13 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/openapi/register.d.ts +3 -0
- package/dist/openapi/register.d.ts.map +1 -0
- package/dist/openapi/scalar.d.ts +12 -0
- package/dist/openapi/scalar.d.ts.map +1 -0
- package/dist/openapi/spec-factory.d.ts +31 -0
- package/dist/openapi/spec-factory.d.ts.map +1 -0
- package/dist/openapi/spec.d.ts +19 -0
- package/dist/openapi/spec.d.ts.map +1 -0
- package/dist/openapi/swagger.d.ts +12 -0
- package/dist/openapi/swagger.d.ts.map +1 -0
- package/dist/plugin.d.ts +167 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/powered-by.d.ts +7 -0
- package/dist/powered-by.d.ts.map +1 -0
- package/dist/pretty-json.d.ts +10 -0
- package/dist/pretty-json.d.ts.map +1 -0
- package/dist/proxy.d.ts +51 -0
- package/dist/proxy.d.ts.map +1 -0
- package/dist/python-type-emitter.d.ts +11 -0
- package/dist/python-type-emitter.d.ts.map +1 -0
- package/dist/realtime/buffer.d.ts +18 -0
- package/dist/realtime/buffer.d.ts.map +1 -0
- package/dist/realtime/bus.d.ts +11 -0
- package/dist/realtime/bus.d.ts.map +1 -0
- package/dist/realtime/longpoll.d.ts +23 -0
- package/dist/realtime/longpoll.d.ts.map +1 -0
- package/dist/realtime/protocol.d.ts +50 -0
- package/dist/realtime/protocol.d.ts.map +1 -0
- package/dist/realtime/route.d.ts +42 -0
- package/dist/realtime/route.d.ts.map +1 -0
- package/dist/realtime/runtime.d.ts +74 -0
- package/dist/realtime/runtime.d.ts.map +1 -0
- package/dist/request-id.d.ts +12 -0
- package/dist/request-id.d.ts.map +1 -0
- package/dist/request-to-curl.d.ts +9 -0
- package/dist/request-to-curl.d.ts.map +1 -0
- package/dist/response.d.ts +57 -0
- package/dist/response.d.ts.map +1 -0
- package/dist/rust-type-emitter.d.ts +32 -0
- package/dist/rust-type-emitter.d.ts.map +1 -0
- package/dist/secure-headers.d.ts +16 -0
- package/dist/secure-headers.d.ts.map +1 -0
- package/dist/serve-register.d.ts +2 -0
- package/dist/serve-register.d.ts.map +1 -0
- package/dist/serve-slot.d.ts +6 -0
- package/dist/serve-slot.d.ts.map +1 -0
- package/dist/serve.d.ts +21 -0
- package/dist/serve.d.ts.map +1 -0
- package/dist/server-timing.d.ts +9 -0
- package/dist/server-timing.d.ts.map +1 -0
- package/dist/static.d.ts +12 -0
- package/dist/static.d.ts.map +1 -0
- package/dist/telemetry/otel.d.ts +59 -0
- package/dist/telemetry/otel.d.ts.map +1 -0
- package/dist/testing.d.ts +24 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/timeout.d.ts +7 -0
- package/dist/timeout.d.ts.map +1 -0
- package/dist/tree.d.ts +90 -0
- package/dist/tree.d.ts.map +1 -0
- package/dist/ts-type-emitter.d.ts +4 -0
- package/dist/ts-type-emitter.d.ts.map +1 -0
- package/dist/type-emitter.d.ts +8 -0
- package/dist/type-emitter.d.ts.map +1 -0
- package/dist/type-extractor.d.ts +39 -0
- package/dist/type-extractor.d.ts.map +1 -0
- package/dist/types.d.ts +451 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/validation.d.ts +18 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/ws/bun.d.ts +28 -0
- package/dist/ws/bun.d.ts.map +1 -0
- package/dist/ws/cloudflare.d.ts +55 -0
- package/dist/ws/cloudflare.d.ts.map +1 -0
- package/dist/ws/deno.d.ts +13 -0
- package/dist/ws/deno.d.ts.map +1 -0
- package/dist/ws/node.d.ts +10 -0
- package/dist/ws/node.d.ts.map +1 -0
- package/dist/yaml.d.ts +4 -0
- package/dist/yaml.d.ts.map +1 -0
- package/docs/api-ref/go.md +82 -82
- package/docs/api-ref/python.md +82 -82
- package/docs/api-ref/rust.md +82 -82
- package/docs/api-ref/typescript.md +82 -82
- package/docs/invalidate-spec.md +13 -13
- package/docs/meta-spec.md +513 -0
- package/docs/packaging.md +85 -0
- package/docs/sdk.md +87 -73
- package/package.json +195 -51
- package/src/body-limit.ts +2 -1
- package/src/build/index.ts +2 -9
- package/src/cli.ts +2 -3
- package/src/client/http.ts +204 -223
- package/src/client/index.ts +6 -14
- package/src/client/sdk.ts +24 -17
- package/src/client/types.ts +4 -9
- package/src/client/ws.ts +3 -5
- package/src/client-mcp/server.ts +1 -4
- package/src/cloudflare/realtime.ts +8 -10
- package/src/codegen-go-cli.ts +74 -67
- package/src/codegen-go.ts +71 -51
- package/src/codegen-ir.ts +16 -27
- package/src/codegen-loaders.ts +5 -8
- package/src/codegen-mcp.ts +9 -27
- package/src/codegen-python.ts +47 -27
- package/src/codegen-rust.ts +110 -66
- package/src/codegen-sanitize.ts +1 -7
- package/src/codegen-schema-naming.ts +8 -2
- package/src/codegen.ts +299 -196
- package/src/context.ts +91 -95
- package/src/cookie-sign.ts +5 -13
- package/src/cookie.ts +1 -2
- package/src/cors.ts +4 -6
- package/src/crypto.ts +1 -4
- package/src/csrf.ts +3 -3
- package/src/curl-logger.ts +14 -32
- package/src/detect-runtime.ts +1 -3
- package/src/errors.ts +1 -3
- package/src/etag.ts +3 -4
- package/src/go-type-emitter.ts +45 -40
- package/src/honey-response.ts +1 -3
- package/src/i18n.ts +3 -12
- package/src/index.ts +1168 -1412
- package/src/ip-restrict.ts +2 -1
- package/src/logger.ts +13 -13
- package/src/meta-spec.ts +0 -0
- package/src/middleware.ts +111 -79
- package/src/node-request.ts +2 -1
- package/src/node.ts +9 -10
- package/src/openapi/register.ts +1 -2
- package/src/openapi/scalar.ts +9 -9
- package/src/openapi/spec-factory.ts +2 -2
- package/src/openapi/spec.ts +26 -19
- package/src/openapi/swagger.ts +9 -9
- package/src/plugin.ts +226 -256
- package/src/powered-by.ts +2 -1
- package/src/pretty-json.ts +2 -1
- package/src/proxy.ts +1 -3
- package/src/python-type-emitter.ts +37 -12
- package/src/realtime/route.ts +1 -1
- package/src/realtime/runtime.ts +9 -12
- package/src/request-id.ts +3 -4
- package/src/request-to-curl.ts +1 -4
- package/src/response.ts +150 -199
- package/src/rust-type-emitter.ts +66 -25
- package/src/secure-headers.ts +2 -1
- package/src/serve.ts +3 -12
- package/src/server-timing.ts +2 -1
- package/src/static.ts +3 -4
- package/src/telemetry/otel.ts +1 -6
- package/src/testing.ts +2 -11
- package/src/timeout.ts +2 -1
- package/src/tree.ts +12 -27
- package/src/ts-type-emitter.ts +71 -15
- package/src/type-emitter.ts +226 -237
- package/src/type-extractor.ts +260 -334
- package/src/types.ts +168 -68
- package/src/validation.ts +7 -30
- package/src/ws/bun.ts +2 -9
- package/src/ws/cloudflare.ts +13 -16
- package/src/ws/deno.ts +31 -40
- package/src/ws/node.ts +4 -19
package/src/codegen.ts
CHANGED
|
@@ -1,20 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
deriveErrorEnvelopeName,
|
|
3
|
-
deriveSchemaName,
|
|
4
|
-
isErrorEnvelope,
|
|
5
|
-
shortHash,
|
|
6
|
-
} from "./codegen-schema-naming.ts"
|
|
1
|
+
import { deriveErrorEnvelopeName, deriveSchemaName, isErrorEnvelope, shortHash } from "./codegen-schema-naming.ts"
|
|
7
2
|
import type { SchemaNameContext } from "./codegen-schema-naming.ts"
|
|
8
|
-
import {
|
|
9
|
-
effectJsonSchemaFn,
|
|
10
|
-
loadEffectJsonSchema,
|
|
11
|
-
loadToJSONSchema,
|
|
12
|
-
toJSONSchemaFn,
|
|
13
|
-
} from "./codegen-loaders.ts"
|
|
3
|
+
import { effectJsonSchemaFn, loadEffectJsonSchema, loadToJSONSchema, toJSONSchemaFn } from "./codegen-loaders.ts"
|
|
14
4
|
import { sanitizeZodJsonSchema } from "./codegen-sanitize.ts"
|
|
15
5
|
import { methodsOf, namespacesOf, schemaToIR, toIR } from "./codegen-ir.ts"
|
|
16
6
|
import type { IRNamespace } from "./codegen-ir.ts"
|
|
17
7
|
import type { HoneyError } from "./error.ts"
|
|
8
|
+
import {
|
|
9
|
+
applyMetaSpec,
|
|
10
|
+
compileMetaSpec,
|
|
11
|
+
type CompiledMetaSpec,
|
|
12
|
+
MetaSpecCollector,
|
|
13
|
+
resolveProfile,
|
|
14
|
+
type SchemaMetaHit,
|
|
15
|
+
type SchemaMetaLookup,
|
|
16
|
+
} from "./meta-spec.ts"
|
|
18
17
|
import { ERROR_META } from "./errors.ts"
|
|
19
18
|
import type { ErrorMetaEntry } from "./errors.ts"
|
|
20
19
|
import type { Honey } from "./index.ts"
|
|
@@ -24,6 +23,8 @@ import { emitSchemaType } from "./type-emitter.ts"
|
|
|
24
23
|
import type {
|
|
25
24
|
InputSchemaEntry,
|
|
26
25
|
InputSchemasDef,
|
|
26
|
+
MetaSpecConfig,
|
|
27
|
+
MetaSpecSchemaSource,
|
|
27
28
|
OutputSchemaDef,
|
|
28
29
|
StandardSchemaLike,
|
|
29
30
|
} from "./types.ts"
|
|
@@ -314,12 +315,7 @@ function isMetaInternal(handler: RouteHandler): boolean {
|
|
|
314
315
|
return (handler.mt as { internal?: boolean } | null)?.internal === true
|
|
315
316
|
}
|
|
316
317
|
|
|
317
|
-
function walkTree(
|
|
318
|
-
node: TreeNode,
|
|
319
|
-
currentPath: string,
|
|
320
|
-
routes: CollectedRoute[],
|
|
321
|
-
includeSkipped?: boolean,
|
|
322
|
-
): void {
|
|
318
|
+
function walkTree(node: TreeNode, currentPath: string, routes: CollectedRoute[], includeSkipped?: boolean): void {
|
|
323
319
|
if (node.m !== null) {
|
|
324
320
|
for (const [method, handler] of Object.entries(node.m)) {
|
|
325
321
|
if (handler._skip && !includeSkipped) continue
|
|
@@ -362,10 +358,7 @@ function walkWSRoutes(node: TreeNode, currentPath: string, routes: CollectedWSRo
|
|
|
362
358
|
|
|
363
359
|
type ErrorInfo = { errorKey: string; status: number; statusKey: string }
|
|
364
360
|
|
|
365
|
-
function resolveErrorInfo(
|
|
366
|
-
errorKey: string,
|
|
367
|
-
factory: Record<string, () => HoneyError> | null,
|
|
368
|
-
): ErrorInfo {
|
|
361
|
+
function resolveErrorInfo(errorKey: string, factory: Record<string, () => HoneyError> | null): ErrorInfo {
|
|
369
362
|
if (factory?.[errorKey]) {
|
|
370
363
|
try {
|
|
371
364
|
const err = factory[errorKey]()
|
|
@@ -380,8 +373,7 @@ function resolveErrorInfo(
|
|
|
380
373
|
function getErrorFactory<TEnv, TCtx>(
|
|
381
374
|
app: Honey<TEnv, TCtx, unknown, unknown, unknown, string, string>,
|
|
382
375
|
): Record<string, () => HoneyError> | null {
|
|
383
|
-
return (app as unknown as { _errorFactory: Record<string, () => HoneyError> | null })
|
|
384
|
-
._errorFactory
|
|
376
|
+
return (app as unknown as { _errorFactory: Record<string, () => HoneyError> | null })._errorFactory
|
|
385
377
|
}
|
|
386
378
|
|
|
387
379
|
export const DEFAULT_ERROR_JSON_SCHEMA: Record<string, unknown> = {
|
|
@@ -423,9 +415,7 @@ function getCustomErrorSchema<TEnv, TCtx>(
|
|
|
423
415
|
return (app as unknown as { _customErrorSchema: StandardSchemaLike | null })._customErrorSchema
|
|
424
416
|
}
|
|
425
417
|
|
|
426
|
-
function getErrorMeta(
|
|
427
|
-
factory: Record<string, () => HoneyError> | null,
|
|
428
|
-
): Record<string, ErrorMetaEntry> | null {
|
|
418
|
+
function getErrorMeta(factory: Record<string, () => HoneyError> | null): Record<string, ErrorMetaEntry> | null {
|
|
429
419
|
if (!factory) return null
|
|
430
420
|
return (factory as Record<symbol, Record<string, ErrorMetaEntry>>)[ERROR_META] ?? null
|
|
431
421
|
}
|
|
@@ -446,25 +436,7 @@ function introspectSchema(schema: StandardSchemaLike): unknown {
|
|
|
446
436
|
}
|
|
447
437
|
}
|
|
448
438
|
|
|
449
|
-
|
|
450
|
-
export function normalizeSecurity(raw: unknown): Record<string, string[]>[] {
|
|
451
|
-
if (typeof raw === "string") return [{ [raw]: [] }]
|
|
452
|
-
if (!Array.isArray(raw)) return []
|
|
453
|
-
if (raw.length === 0) return []
|
|
454
|
-
if (raw.every((entry) => typeof entry === "string")) {
|
|
455
|
-
return raw.map((entry) => ({ [entry]: [] }))
|
|
456
|
-
}
|
|
457
|
-
if (raw.every((entry) => Array.isArray(entry))) {
|
|
458
|
-
return (raw as string[][]).map((group) =>
|
|
459
|
-
Object.fromEntries(group.map((s) => [s, []])),
|
|
460
|
-
)
|
|
461
|
-
}
|
|
462
|
-
if (raw.every((entry) => entry !== null && typeof entry === "object" && !Array.isArray(entry))) {
|
|
463
|
-
return raw as Record<string, string[]>[]
|
|
464
|
-
}
|
|
465
|
-
/* mixed shapes — refuse to guess */
|
|
466
|
-
return []
|
|
467
|
-
}
|
|
439
|
+
export { normalizeSecurity } from "./meta-spec.ts"
|
|
468
440
|
|
|
469
441
|
/* Strip properties marked with x-internal from an object JSON schema */
|
|
470
442
|
function stripInternalProps(schema: Record<string, unknown>): Record<string, unknown> {
|
|
@@ -486,9 +458,7 @@ function stripInternalProps(schema: Record<string, unknown>): Record<string, unk
|
|
|
486
458
|
export function canonicalizeSchema(schema: Record<string, unknown>): string {
|
|
487
459
|
return JSON.stringify(schema, (_, value) => {
|
|
488
460
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
489
|
-
return Object.fromEntries(
|
|
490
|
-
Object.entries(value as Record<string, unknown>).sort(([a], [b]) => a.localeCompare(b)),
|
|
491
|
-
)
|
|
461
|
+
return Object.fromEntries(Object.entries(value as Record<string, unknown>).sort(([a], [b]) => a.localeCompare(b)))
|
|
492
462
|
}
|
|
493
463
|
return value
|
|
494
464
|
})
|
|
@@ -509,9 +479,7 @@ function isObjectSchema(schema: unknown): schema is Record<string, unknown> {
|
|
|
509
479
|
return s.type === "object" || (typeof s.properties === "object" && s.properties !== null)
|
|
510
480
|
}
|
|
511
481
|
|
|
512
|
-
function collectSchemaSlots(
|
|
513
|
-
paths: Record<string, Record<string, Record<string, unknown>>>,
|
|
514
|
-
): SchemaSlot[] {
|
|
482
|
+
function collectSchemaSlots(paths: Record<string, Record<string, Record<string, unknown>>>): SchemaSlot[] {
|
|
515
483
|
const slots: SchemaSlot[] = []
|
|
516
484
|
|
|
517
485
|
for (const [pathKey, methods] of Object.entries(paths)) {
|
|
@@ -615,8 +583,7 @@ function collectHoistedSchemas(
|
|
|
615
583
|
const slotName = slotNames.get(slot)
|
|
616
584
|
/* Mark slots whose schema is an error envelope — their nested objects (e.g. `fields`)
|
|
617
585
|
* are env-specific and must not be named after the parent envelope's owner slot. */
|
|
618
|
-
if (slotName)
|
|
619
|
-
walkNestedObjects(slot.schema, slotName, [], nestedMap, isErrorEnvelope(slot.schema))
|
|
586
|
+
if (slotName) walkNestedObjects(slot.schema, slotName, [], nestedMap, isErrorEnvelope(slot.schema))
|
|
620
587
|
}
|
|
621
588
|
|
|
622
589
|
const schemas: Record<string, Record<string, unknown>> = {}
|
|
@@ -825,8 +792,7 @@ export function deduplicateSchemas(spec: OpenApiSpec): OpenApiSpec {
|
|
|
825
792
|
if (groupSlots.length === 1) {
|
|
826
793
|
name = deriveSchemaName(firstSlot.context)
|
|
827
794
|
} else if (isErrorEnvelope(firstSlot.schema)) {
|
|
828
|
-
const fallbackStatus =
|
|
829
|
-
firstSlot.context.role === "response" ? firstSlot.context.status : undefined
|
|
795
|
+
const fallbackStatus = firstSlot.context.role === "response" ? firstSlot.context.status : undefined
|
|
830
796
|
name = deriveErrorEnvelopeName(firstSlot.schema, fallbackStatus)
|
|
831
797
|
} else {
|
|
832
798
|
name = deriveSchemaName(firstSlot.context)
|
|
@@ -852,8 +818,11 @@ export function deduplicateSchemas(spec: OpenApiSpec): OpenApiSpec {
|
|
|
852
818
|
* Error envelope slots (any tier) mark their nested schemas as envelope-owned so
|
|
853
819
|
* envelope-exclusive nesting (e.g. `fields`) is not hoisted under a per-spec name. */
|
|
854
820
|
const allTopLevelNames = new Set(slotNames.values())
|
|
855
|
-
const { schemas: hoistedSchemas, canonicalToName: hoistedCanonicalToName } =
|
|
856
|
-
|
|
821
|
+
const { schemas: hoistedSchemas, canonicalToName: hoistedCanonicalToName } = collectHoistedSchemas(
|
|
822
|
+
slots,
|
|
823
|
+
slotNames,
|
|
824
|
+
allTopLevelNames,
|
|
825
|
+
)
|
|
857
826
|
|
|
858
827
|
/* Build components map, rewriting nested fields → $refs where hoisted */
|
|
859
828
|
const extractedSchemas: Record<string, Record<string, unknown>> = {}
|
|
@@ -863,9 +832,7 @@ export function deduplicateSchemas(spec: OpenApiSpec): OpenApiSpec {
|
|
|
863
832
|
if (name && !seenNames.has(name)) {
|
|
864
833
|
seenNames.add(name)
|
|
865
834
|
extractedSchemas[name] =
|
|
866
|
-
hoistedCanonicalToName.size > 0
|
|
867
|
-
? rewriteNestedRefs(slot.schema, hoistedCanonicalToName)
|
|
868
|
-
: slot.schema
|
|
835
|
+
hoistedCanonicalToName.size > 0 ? rewriteNestedRefs(slot.schema, hoistedCanonicalToName) : slot.schema
|
|
869
836
|
}
|
|
870
837
|
}
|
|
871
838
|
Object.assign(extractedSchemas, hoistedSchemas)
|
|
@@ -894,10 +861,7 @@ export type OpenApiSanitizeOptions = {
|
|
|
894
861
|
stripXExtensions?: boolean | string[]
|
|
895
862
|
}
|
|
896
863
|
|
|
897
|
-
export function sanitizeOpenApiSpec(
|
|
898
|
-
spec: OpenApiSpec,
|
|
899
|
-
options: OpenApiSanitizeOptions,
|
|
900
|
-
): OpenApiSpec {
|
|
864
|
+
export function sanitizeOpenApiSpec(spec: OpenApiSpec, options: OpenApiSanitizeOptions): OpenApiSpec {
|
|
901
865
|
const result = cloneJson(spec)
|
|
902
866
|
|
|
903
867
|
if (options.stripSecuritySchemes?.length && result.components?.securitySchemes) {
|
|
@@ -989,8 +953,16 @@ function resolveSchemaDeep(
|
|
|
989
953
|
}
|
|
990
954
|
}
|
|
991
955
|
/* resolve additionalProperties */
|
|
992
|
-
if (
|
|
993
|
-
schema.additionalProperties
|
|
956
|
+
if (
|
|
957
|
+
schema.additionalProperties &&
|
|
958
|
+
typeof schema.additionalProperties === "object" &&
|
|
959
|
+
!Array.isArray(schema.additionalProperties)
|
|
960
|
+
) {
|
|
961
|
+
schema.additionalProperties = resolveSchema(
|
|
962
|
+
schema.additionalProperties as Record<string, unknown>,
|
|
963
|
+
schemas,
|
|
964
|
+
visited,
|
|
965
|
+
)
|
|
994
966
|
}
|
|
995
967
|
return schema
|
|
996
968
|
}
|
|
@@ -1088,7 +1060,11 @@ function applyZodBag(schema: unknown, json: Record<string, unknown>): Record<str
|
|
|
1088
1060
|
}
|
|
1089
1061
|
|
|
1090
1062
|
function zodDefOf(schema: unknown): Record<string, unknown> | undefined {
|
|
1091
|
-
const s = schema as {
|
|
1063
|
+
const s = schema as {
|
|
1064
|
+
_def?: Record<string, unknown>
|
|
1065
|
+
def?: Record<string, unknown>
|
|
1066
|
+
_zod?: { def?: Record<string, unknown> }
|
|
1067
|
+
}
|
|
1092
1068
|
return s._def ?? s.def ?? s._zod?.def
|
|
1093
1069
|
}
|
|
1094
1070
|
|
|
@@ -1242,8 +1218,7 @@ function schemaToJsonSchema(schema: StandardSchemaLike, io: "input" | "output" =
|
|
|
1242
1218
|
}
|
|
1243
1219
|
} catch (err) {
|
|
1244
1220
|
const msg = err instanceof Error ? err.message : String(err)
|
|
1245
|
-
const defType =
|
|
1246
|
-
(schema as { _zod?: { def?: { type?: string } } })?._zod?.def?.type ?? "unknown"
|
|
1221
|
+
const defType = (schema as { _zod?: { def?: { type?: string } } })?._zod?.def?.type ?? "unknown"
|
|
1247
1222
|
console.warn(
|
|
1248
1223
|
`[honey:codegen] schemaToJsonSchema failed for zod/${defType} (io=${io}): ${msg}. Falling back to metadata-only introspection.`,
|
|
1249
1224
|
)
|
|
@@ -1350,15 +1325,137 @@ export type OpenApiRouteInfo<TMeta = unknown> = {
|
|
|
1350
1325
|
path: string
|
|
1351
1326
|
}
|
|
1352
1327
|
|
|
1328
|
+
function getMetaSpecConfig(app: unknown): MetaSpecConfig | null {
|
|
1329
|
+
return (app as { _metaSpec?: MetaSpecConfig | null })._metaSpec ?? null
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
/** Lowest declared 2xx JSON output schema — the "output" schema-meta source */
|
|
1333
|
+
function primaryOutputSchema(handler: RouteHandler): StandardSchemaLike | null {
|
|
1334
|
+
const json = handler.os?.["application/json"] as Record<string, unknown> | undefined
|
|
1335
|
+
if (!json) return null
|
|
1336
|
+
let best: { code: number; schema: StandardSchemaLike } | null = null
|
|
1337
|
+
for (const [statusKey, schema] of Object.entries(json)) {
|
|
1338
|
+
if (schema === undefined) continue
|
|
1339
|
+
const code = statusKeyToCode[statusKey as keyof typeof statusKeyToCode]
|
|
1340
|
+
if (!code || code < 200 || code > 299) continue
|
|
1341
|
+
if (!best || code < best.code) best = { code, schema: schema as StandardSchemaLike }
|
|
1342
|
+
}
|
|
1343
|
+
return best?.schema ?? null
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
/**
|
|
1347
|
+
* Reads app keys off the *root* of a route schema's JSON Schema. Vendor-neutral: Zod's
|
|
1348
|
+
* `.meta({ … })` lands on the JSON Schema root, and so does every other validator honey
|
|
1349
|
+
* supports that carries schema metadata.
|
|
1350
|
+
*/
|
|
1351
|
+
/** Child schema nodes a deep search descends into, in visit order */
|
|
1352
|
+
function schemaChildren(node: Record<string, unknown>): Record<string, unknown>[] {
|
|
1353
|
+
const out: Record<string, unknown>[] = []
|
|
1354
|
+
const push = (value: unknown): void => {
|
|
1355
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
1356
|
+
out.push(value as Record<string, unknown>)
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
const props = node.properties
|
|
1360
|
+
if (props !== null && typeof props === "object" && !Array.isArray(props)) {
|
|
1361
|
+
for (const child of Object.values(props as Record<string, unknown>)) push(child)
|
|
1362
|
+
}
|
|
1363
|
+
push(node.items)
|
|
1364
|
+
for (const key of ["allOf", "anyOf", "oneOf", "prefixItems"]) {
|
|
1365
|
+
const members = node[key]
|
|
1366
|
+
if (Array.isArray(members)) {
|
|
1367
|
+
for (const member of members) push(member)
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
return out
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
/** Bounded BFS — shallowest match wins; several distinct values at that depth is ambiguous */
|
|
1374
|
+
function searchSchemaKey(root: Record<string, unknown>, key: string, maxDepth: number): SchemaMetaHit {
|
|
1375
|
+
let level = [root]
|
|
1376
|
+
for (let depth = 0; depth <= maxDepth && level.length > 0; depth++) {
|
|
1377
|
+
const matches: unknown[] = []
|
|
1378
|
+
const seen = new Set<string>()
|
|
1379
|
+
for (const node of level) {
|
|
1380
|
+
if (!Object.hasOwn(node, key) || node[key] === undefined) continue
|
|
1381
|
+
const fingerprint = JSON.stringify(node[key]) ?? "undefined"
|
|
1382
|
+
if (seen.has(fingerprint)) continue
|
|
1383
|
+
seen.add(fingerprint)
|
|
1384
|
+
matches.push(node[key])
|
|
1385
|
+
}
|
|
1386
|
+
if (matches.length === 1) return { found: true, value: matches[0] }
|
|
1387
|
+
if (matches.length > 1) return { found: "ambiguous", values: matches }
|
|
1388
|
+
const next: Record<string, unknown>[] = []
|
|
1389
|
+
for (const node of level) next.push(...schemaChildren(node))
|
|
1390
|
+
level = next
|
|
1391
|
+
}
|
|
1392
|
+
return { found: false }
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
const DEEP_SEARCH_MAX_DEPTH = 6
|
|
1396
|
+
|
|
1397
|
+
/**
|
|
1398
|
+
* Resolves one schema-metadata key against one schema source. `"root"` reads the schema root,
|
|
1399
|
+
* seeing through one level of `items` so a bare `array<Entity>` list output still finds the
|
|
1400
|
+
* descriptor stamped on the item. `"deep"` walks the schema for envelope shapes.
|
|
1401
|
+
*/
|
|
1402
|
+
function makeSchemaMetaLookup(handler: RouteHandler): SchemaMetaLookup {
|
|
1403
|
+
const cache = new Map<string, Record<string, unknown> | undefined>()
|
|
1404
|
+
|
|
1405
|
+
const rootOf = (source: MetaSpecSchemaSource): Record<string, unknown> | undefined => {
|
|
1406
|
+
if (cache.has(source)) return cache.get(source)
|
|
1407
|
+
let schema: StandardSchemaLike | null = null
|
|
1408
|
+
let io: "input" | "output" = "output"
|
|
1409
|
+
if (source === "output") {
|
|
1410
|
+
schema = primaryOutputSchema(handler)
|
|
1411
|
+
} else {
|
|
1412
|
+
io = "input"
|
|
1413
|
+
const key = source.slice("input.".length) as keyof InputSchemasDef
|
|
1414
|
+
const entry = handler.iv?.[key]
|
|
1415
|
+
schema = entry ? unwrapEntry(entry as InputSchemaEntry) : null
|
|
1416
|
+
}
|
|
1417
|
+
let root: Record<string, unknown> | undefined
|
|
1418
|
+
if (schema) {
|
|
1419
|
+
const converted = asJsonSchema(schema, io)
|
|
1420
|
+
root = converted && typeof converted === "object" ? converted : undefined
|
|
1421
|
+
}
|
|
1422
|
+
cache.set(source, root)
|
|
1423
|
+
return root
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
return (source, key, search) => {
|
|
1427
|
+
const root = rootOf(source)
|
|
1428
|
+
if (!root) return { found: false }
|
|
1429
|
+
if (search === "deep") return searchSchemaKey(root, key, DEEP_SEARCH_MAX_DEPTH)
|
|
1430
|
+
if (Object.hasOwn(root, key) && root[key] !== undefined) return { found: true, value: root[key] }
|
|
1431
|
+
/* a list output is `array<Entity>` — the descriptor sits on the item */
|
|
1432
|
+
if (root.type === "array") {
|
|
1433
|
+
const items = root.items
|
|
1434
|
+
if (items !== null && typeof items === "object" && !Array.isArray(items)) {
|
|
1435
|
+
const itemRoot = items as Record<string, unknown>
|
|
1436
|
+
if (Object.hasOwn(itemRoot, key) && itemRoot[key] !== undefined) {
|
|
1437
|
+
return { found: true, value: itemRoot[key] }
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
return { found: false }
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1353
1445
|
export async function generateOpenApi<TEnv, TCtx, TMeta = unknown>(
|
|
1354
1446
|
app: Honey<TEnv, TCtx, unknown, TMeta, unknown, string, string>,
|
|
1355
1447
|
options: {
|
|
1356
1448
|
filterRoutes?: (route: OpenApiRouteInfo<TMeta>) => boolean
|
|
1357
1449
|
info: OpenApiInfo
|
|
1450
|
+
/** Named metaSpec profile — selects which emitted keys this document carries */
|
|
1451
|
+
profile?: string
|
|
1358
1452
|
securitySchemes?: Record<string, unknown>
|
|
1359
1453
|
},
|
|
1360
1454
|
): Promise<OpenApiSpec> {
|
|
1361
1455
|
await Promise.all([loadToJSONSchema(), loadEffectJsonSchema()])
|
|
1456
|
+
const collector = new MetaSpecCollector()
|
|
1457
|
+
const metaSpec: CompiledMetaSpec = compileMetaSpec(getMetaSpecConfig(app), collector)
|
|
1458
|
+
const { filter: profileFilter, name: profileName } = resolveProfile(metaSpec, options.profile, collector)
|
|
1362
1459
|
const factory = getErrorFactory(app)
|
|
1363
1460
|
const errorMeta = getErrorMeta(factory)
|
|
1364
1461
|
|
|
@@ -1396,17 +1493,19 @@ export async function generateOpenApi<TEnv, TCtx, TMeta = unknown>(
|
|
|
1396
1493
|
const responses: Record<string, unknown> = {}
|
|
1397
1494
|
const parameters: Array<Record<string, unknown>> = []
|
|
1398
1495
|
|
|
1399
|
-
/* meta → openApi operation fields
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1496
|
+
/* meta + schema meta → openApi operation fields, per the declared policy */
|
|
1497
|
+
applyMetaSpec({
|
|
1498
|
+
collector,
|
|
1499
|
+
filter: profileFilter,
|
|
1500
|
+
kind: "http",
|
|
1501
|
+
meta: handler.mt as Record<string, unknown> | null,
|
|
1502
|
+
method,
|
|
1503
|
+
operation,
|
|
1504
|
+
path,
|
|
1505
|
+
profile: profileName,
|
|
1506
|
+
schemaMeta: metaSpec.needsSchemas ? makeSchemaMetaLookup(handler) : undefined,
|
|
1507
|
+
spec: metaSpec,
|
|
1508
|
+
})
|
|
1410
1509
|
|
|
1411
1510
|
/* path params */
|
|
1412
1511
|
const params = extractParams(path)
|
|
@@ -1425,9 +1524,7 @@ export async function generateOpenApi<TEnv, TCtx, TMeta = unknown>(
|
|
|
1425
1524
|
for (const [source, entry] of Object.entries(handler.iv)) {
|
|
1426
1525
|
if (entry === undefined) continue
|
|
1427
1526
|
const unwrapped =
|
|
1428
|
-
"~standard" in (entry as object)
|
|
1429
|
-
? unwrapEntry(entry as InputSchemaEntry)
|
|
1430
|
-
: (entry as Record<string, unknown>)
|
|
1527
|
+
"~standard" in (entry as object) ? unwrapEntry(entry as InputSchemaEntry) : (entry as Record<string, unknown>)
|
|
1431
1528
|
const jsonSchema = asJsonSchema(unwrapped, "input")
|
|
1432
1529
|
|
|
1433
1530
|
if (source === "json" || source === "form") {
|
|
@@ -1540,9 +1637,7 @@ export async function generateOpenApi<TEnv, TCtx, TMeta = unknown>(
|
|
|
1540
1637
|
for (const [status, entries] of byStatus) {
|
|
1541
1638
|
if (responses[String(status)] === undefined) {
|
|
1542
1639
|
const standardKeys = entries.filter((e) => !e.schema).map((e) => e.key)
|
|
1543
|
-
const customSchemas = entries
|
|
1544
|
-
.filter((e) => e.schema)
|
|
1545
|
-
.map((e) => e.schema as Record<string, unknown>)
|
|
1640
|
+
const customSchemas = entries.filter((e) => e.schema).map((e) => e.schema as Record<string, unknown>)
|
|
1546
1641
|
|
|
1547
1642
|
let schema: Record<string, unknown>
|
|
1548
1643
|
|
|
@@ -1611,11 +1706,18 @@ export async function generateOpenApi<TEnv, TCtx, TMeta = unknown>(
|
|
|
1611
1706
|
const operation: Record<string, unknown> = { "x-websocket": true }
|
|
1612
1707
|
const parameters: Array<Record<string, unknown>> = []
|
|
1613
1708
|
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1709
|
+
applyMetaSpec({
|
|
1710
|
+
collector,
|
|
1711
|
+
filter: profileFilter,
|
|
1712
|
+
kind: "ws",
|
|
1713
|
+
meta: handler.mt as Record<string, unknown> | null,
|
|
1714
|
+
method: "WS",
|
|
1715
|
+
operation,
|
|
1716
|
+
path,
|
|
1717
|
+
profile: profileName,
|
|
1718
|
+
schemaMeta: metaSpec.needsSchemas ? makeSchemaMetaLookup(handler as unknown as RouteHandler) : undefined,
|
|
1719
|
+
spec: metaSpec,
|
|
1720
|
+
})
|
|
1619
1721
|
|
|
1620
1722
|
/* path params */
|
|
1621
1723
|
const wsParams = extractParams(path)
|
|
@@ -1635,17 +1737,10 @@ export async function generateOpenApi<TEnv, TCtx, TMeta = unknown>(
|
|
|
1635
1737
|
"_tag" in searchEntry
|
|
1636
1738
|
? (searchEntry as { schema: StandardSchemaLike }).schema
|
|
1637
1739
|
: (searchEntry as StandardSchemaLike)
|
|
1638
|
-
const jsonSchema = asJsonSchema(
|
|
1639
|
-
searchSchema as StandardSchemaLike | Record<string, unknown>,
|
|
1640
|
-
"input",
|
|
1641
|
-
)
|
|
1740
|
+
const jsonSchema = asJsonSchema(searchSchema as StandardSchemaLike | Record<string, unknown>, "input")
|
|
1642
1741
|
if (jsonSchema && typeof jsonSchema === "object") {
|
|
1643
|
-
const props = (jsonSchema as Record<string, unknown>).properties as
|
|
1644
|
-
|
|
1645
|
-
| undefined
|
|
1646
|
-
const required = new Set(
|
|
1647
|
-
((jsonSchema as Record<string, unknown>).required ?? []) as string[],
|
|
1648
|
-
)
|
|
1742
|
+
const props = (jsonSchema as Record<string, unknown>).properties as Record<string, unknown> | undefined
|
|
1743
|
+
const required = new Set(((jsonSchema as Record<string, unknown>).required ?? []) as string[])
|
|
1649
1744
|
if (props) {
|
|
1650
1745
|
for (const [name, schema] of Object.entries(props)) {
|
|
1651
1746
|
parameters.push({
|
|
@@ -1665,6 +1760,8 @@ export async function generateOpenApi<TEnv, TCtx, TMeta = unknown>(
|
|
|
1665
1760
|
paths[oaPath].get = operation
|
|
1666
1761
|
}
|
|
1667
1762
|
|
|
1763
|
+
collector.flush()
|
|
1764
|
+
|
|
1668
1765
|
const result: OpenApiSpec = {
|
|
1669
1766
|
info: options.info,
|
|
1670
1767
|
openapi: "3.1.0",
|
|
@@ -1738,9 +1835,7 @@ type RouteConfig = {
|
|
|
1738
1835
|
path: string
|
|
1739
1836
|
}
|
|
1740
1837
|
|
|
1741
|
-
function serializeInputSchemas(
|
|
1742
|
-
iv: InputSchemasDef | null,
|
|
1743
|
-
): Record<string, Record<string, unknown>> | null {
|
|
1838
|
+
function serializeInputSchemas(iv: InputSchemasDef | null): Record<string, Record<string, unknown>> | null {
|
|
1744
1839
|
if (!iv) return null
|
|
1745
1840
|
const result: Record<string, Record<string, unknown>> = {}
|
|
1746
1841
|
for (const [source, entry] of Object.entries(iv)) {
|
|
@@ -1751,9 +1846,7 @@ function serializeInputSchemas(
|
|
|
1751
1846
|
return Object.keys(result).length > 0 ? result : null
|
|
1752
1847
|
}
|
|
1753
1848
|
|
|
1754
|
-
function serializeOutputSchemas(
|
|
1755
|
-
os: OutputSchemaDef | null,
|
|
1756
|
-
): Record<string, Record<string, unknown>> | null {
|
|
1849
|
+
function serializeOutputSchemas(os: OutputSchemaDef | null): Record<string, Record<string, unknown>> | null {
|
|
1757
1850
|
if (!os) return null
|
|
1758
1851
|
const result: Record<string, Record<string, unknown>> = {}
|
|
1759
1852
|
for (const [contentType, schemas] of Object.entries(os)) {
|
|
@@ -1840,9 +1933,7 @@ function serializeNode(node: TreeBuild): string {
|
|
|
1840
1933
|
: "null"
|
|
1841
1934
|
|
|
1842
1935
|
const mExpr =
|
|
1843
|
-
node.handlers.length > 0
|
|
1844
|
-
? `{ ${node.handlers.map((h) => `${h.method}: ${h.handler}`).join(", ")} }`
|
|
1845
|
-
: "null"
|
|
1936
|
+
node.handlers.length > 0 ? `{ ${node.handlers.map((h) => `${h.method}: ${h.handler}`).join(", ")} }` : "null"
|
|
1846
1937
|
|
|
1847
1938
|
return `{ s: ${sExpr}, d: ${dExpr}, w: ${wExpr}, m: ${mExpr}, ws: null }`
|
|
1848
1939
|
}
|
|
@@ -1874,19 +1965,13 @@ export function generateRouteTree(routes: RouteConfig[]): string {
|
|
|
1874
1965
|
/* emit handler configs with pre-built ek and mt */
|
|
1875
1966
|
for (const { index, route } of handlerEntries) {
|
|
1876
1967
|
const mwList = "[]"
|
|
1877
|
-
const bekExpr =
|
|
1878
|
-
|
|
1879
|
-
const ekSet =
|
|
1880
|
-
route.errorKeys.length > 0 ? `new Set(${JSON.stringify(route.errorKeys)})` : "new Set()"
|
|
1968
|
+
const bekExpr = route.boundaryErrorKey !== null ? JSON.stringify(route.boundaryErrorKey) : "null"
|
|
1969
|
+
const ekSet = route.errorKeys.length > 0 ? `new Set(${JSON.stringify(route.errorKeys)})` : "new Set()"
|
|
1881
1970
|
const mtExpr = route.meta !== null ? JSON.stringify(route.meta) : "null"
|
|
1882
1971
|
const ivExpr =
|
|
1883
|
-
route.inputSchemas !== null
|
|
1884
|
-
? `${JSON.stringify(route.inputSchemas)} as unknown as RouteHandler["iv"]`
|
|
1885
|
-
: "null"
|
|
1972
|
+
route.inputSchemas !== null ? `${JSON.stringify(route.inputSchemas)} as unknown as RouteHandler["iv"]` : "null"
|
|
1886
1973
|
const osExpr =
|
|
1887
|
-
route.outputSchemas !== null
|
|
1888
|
-
? `${JSON.stringify(route.outputSchemas)} as unknown as RouteHandler["os"]`
|
|
1889
|
-
: "null"
|
|
1974
|
+
route.outputSchemas !== null ? `${JSON.stringify(route.outputSchemas)} as unknown as RouteHandler["os"]` : "null"
|
|
1890
1975
|
lines.push(
|
|
1891
1976
|
`const H${index}: RouteHandler = { bek: ${bekExpr}, ef: null, fn: null as unknown as RouteHandler["fn"], mw: ${mwList}, ek: ${ekSet}, iv: ${ivExpr}, os: ${osExpr}, mt: ${mtExpr}, ov: null, rp: "" }`,
|
|
1892
1977
|
)
|
|
@@ -1952,9 +2037,7 @@ export function generateRouteTree(routes: RouteConfig[]): string {
|
|
|
1952
2037
|
const handlerMapEntries = handlerEntries.map(
|
|
1953
2038
|
({ index, route }) => `\t${JSON.stringify(`${route.method} ${route.path}`)}: H${index}`,
|
|
1954
2039
|
)
|
|
1955
|
-
lines.push(
|
|
1956
|
-
`export const handlers: Record<string, RouteHandler> = {\n${handlerMapEntries.join(",\n")}\n}`,
|
|
1957
|
-
)
|
|
2040
|
+
lines.push(`export const handlers: Record<string, RouteHandler> = {\n${handlerMapEntries.join(",\n")}\n}`)
|
|
1958
2041
|
lines.push("")
|
|
1959
2042
|
|
|
1960
2043
|
return lines.join("\n")
|
|
@@ -2020,8 +2103,7 @@ function generateFromTreeRoot(root: TreeNode): string {
|
|
|
2020
2103
|
}
|
|
2021
2104
|
|
|
2022
2105
|
if (metaEntries.length > 0) {
|
|
2023
|
-
const metaType =
|
|
2024
|
-
allKeys.size > 0 ? "Record<string, MetaShape>" : "Record<string, Record<string, unknown>>"
|
|
2106
|
+
const metaType = allKeys.size > 0 ? "Record<string, MetaShape>" : "Record<string, Record<string, unknown>>"
|
|
2025
2107
|
code += `export const meta: ${metaType} = {\n${metaEntries.join(",\n")}\n}\n`
|
|
2026
2108
|
}
|
|
2027
2109
|
|
|
@@ -2135,10 +2217,7 @@ function emitErrorType(handler: RouteHandler): string {
|
|
|
2135
2217
|
.join(" | ")
|
|
2136
2218
|
}
|
|
2137
2219
|
|
|
2138
|
-
function emitErrorShapes(
|
|
2139
|
-
handler: RouteHandler,
|
|
2140
|
-
meta: Record<string, ErrorMetaEntry> | null,
|
|
2141
|
-
): string | null {
|
|
2220
|
+
function emitErrorShapes(handler: RouteHandler, meta: Record<string, ErrorMetaEntry> | null): string | null {
|
|
2142
2221
|
if (handler.ek.size === 0) return null
|
|
2143
2222
|
const entries: string[] = []
|
|
2144
2223
|
for (const key of Array.from(handler.ek).sort()) {
|
|
@@ -2252,12 +2331,8 @@ export function generateTypes<TEnv, TCtx>(
|
|
|
2252
2331
|
lines.push(`type TapMap = ${options.inlineTapsType}`)
|
|
2253
2332
|
lines.push("")
|
|
2254
2333
|
}
|
|
2255
|
-
const ctxBase = options.inlineTapsType
|
|
2256
|
-
|
|
2257
|
-
: `HoneyContext<${envType}>`
|
|
2258
|
-
const tapPart = options.inlineTapsType
|
|
2259
|
-
? " & { tap<K extends keyof TapMap>(key: K, payload: TapMap[K]): void }"
|
|
2260
|
-
: ""
|
|
2334
|
+
const ctxBase = options.inlineTapsType ? `Omit<HoneyContext<${envType}>, "tap">` : `HoneyContext<${envType}>`
|
|
2335
|
+
const tapPart = options.inlineTapsType ? " & { tap<K extends keyof TapMap>(key: K, payload: TapMap[K]): void }" : ""
|
|
2261
2336
|
lines.push(`export type ${baseCtxName} = ${ctxBase}${mwPart}${tapPart}`)
|
|
2262
2337
|
lines.push("")
|
|
2263
2338
|
|
|
@@ -2372,8 +2447,7 @@ export function generateTypes<TEnv, TCtx>(
|
|
|
2372
2447
|
}
|
|
2373
2448
|
const errorsCtx = emitErrorsCtxType(handler)
|
|
2374
2449
|
if (errorsCtx) additions.push(errorsCtx)
|
|
2375
|
-
const ctxType =
|
|
2376
|
-
additions.length === 0 ? basePart : `${basePart} & { ${additions.join("; ")} }`
|
|
2450
|
+
const ctxType = additions.length === 0 ? basePart : `${basePart} & { ${additions.join("; ")} }`
|
|
2377
2451
|
const errorType = emitErrorType(handler)
|
|
2378
2452
|
const metaType = emitMetaType(handler)
|
|
2379
2453
|
const outputType = emitOutputType(handler)
|
|
@@ -2620,10 +2694,7 @@ export function jsonSchemaToTS(schema: Record<string, unknown> | undefined, dept
|
|
|
2620
2694
|
}
|
|
2621
2695
|
|
|
2622
2696
|
/* extract input type for an operation */
|
|
2623
|
-
function emitSDKInputType(
|
|
2624
|
-
op: Record<string, unknown>,
|
|
2625
|
-
path: string,
|
|
2626
|
-
): { hasMandatory: boolean; type: string } {
|
|
2697
|
+
function emitSDKInputType(op: Record<string, unknown>, path: string): { hasMandatory: boolean; type: string } {
|
|
2627
2698
|
const mandatoryParts: string[] = []
|
|
2628
2699
|
const optionalParts: string[] = []
|
|
2629
2700
|
|
|
@@ -2657,7 +2728,7 @@ function emitSDKInputType(
|
|
|
2657
2728
|
const schema = p.schema as Record<string, unknown> | undefined
|
|
2658
2729
|
const tsType = jsonSchemaToTS(schema)
|
|
2659
2730
|
const safeQueryName = sdkSafeName(name)
|
|
2660
|
-
|
|
2731
|
+
return `${safeQueryName}${required ? "" : "?"}: ${tsType}`
|
|
2661
2732
|
})
|
|
2662
2733
|
const searchEntry = `search: { ${entries.join("; ")} }`
|
|
2663
2734
|
if (hasRequired) {
|
|
@@ -2693,8 +2764,7 @@ function emitSDKInputType(
|
|
|
2693
2764
|
optionalParts.push(entry)
|
|
2694
2765
|
}
|
|
2695
2766
|
}
|
|
2696
|
-
const formContent =
|
|
2697
|
-
content["multipart/form-data"] ?? content["application/x-www-form-urlencoded"]
|
|
2767
|
+
const formContent = content["multipart/form-data"] ?? content["application/x-www-form-urlencoded"]
|
|
2698
2768
|
if (formContent?.schema) {
|
|
2699
2769
|
const entry = `form: ${jsonSchemaToTS(formContent.schema as Record<string, unknown>)}`
|
|
2700
2770
|
if (required) {
|
|
@@ -2738,9 +2808,7 @@ function emitSDKResponseType(op: Record<string, unknown>): string {
|
|
|
2738
2808
|
continue
|
|
2739
2809
|
}
|
|
2740
2810
|
if (content["application/json"]?.schema) {
|
|
2741
|
-
successTypes.push(
|
|
2742
|
-
jsonSchemaToTS(content["application/json"].schema as Record<string, unknown>),
|
|
2743
|
-
)
|
|
2811
|
+
successTypes.push(jsonSchemaToTS(content["application/json"].schema as Record<string, unknown>))
|
|
2744
2812
|
} else if ("application/octet-stream" in content) {
|
|
2745
2813
|
successTypes.push("ArrayBuffer")
|
|
2746
2814
|
} else {
|
|
@@ -2760,9 +2828,7 @@ function emitSDKResponseType(op: Record<string, unknown>): string {
|
|
|
2760
2828
|
}
|
|
2761
2829
|
|
|
2762
2830
|
/* detect whether a JSON schema matches the standard error envelope shape */
|
|
2763
|
-
export function isStandardErrEnvelope(
|
|
2764
|
-
schema: Record<string, unknown>,
|
|
2765
|
-
): { keys: string[]; status: number } | null {
|
|
2831
|
+
export function isStandardErrEnvelope(schema: Record<string, unknown>): { keys: string[]; status: number } | null {
|
|
2766
2832
|
const props = schema.properties as Record<string, Record<string, unknown>> | undefined
|
|
2767
2833
|
if (!props) return null
|
|
2768
2834
|
|
|
@@ -2862,8 +2928,7 @@ function buildMethodSig(
|
|
|
2862
2928
|
else if (m.sse) returnType = m.responseType
|
|
2863
2929
|
else if (resolvedErr)
|
|
2864
2930
|
returnType = `TThrow extends true ? Promise<${resolvedRes}> : Promise<SDKResult<${resolvedRes}, ${resolvedErr}>>`
|
|
2865
|
-
else
|
|
2866
|
-
returnType = `TThrow extends true ? Promise<${resolvedRes}> : Promise<SDKResult<${resolvedRes}>>`
|
|
2931
|
+
else returnType = `TThrow extends true ? Promise<${resolvedRes}> : Promise<SDKResult<${resolvedRes}>>`
|
|
2867
2932
|
return { inputArg, returnType }
|
|
2868
2933
|
}
|
|
2869
2934
|
|
|
@@ -2889,16 +2954,75 @@ function buildTypeAliases(
|
|
|
2889
2954
|
|
|
2890
2955
|
/* TS reserved words that must be quoted when used as object/interface property keys */
|
|
2891
2956
|
const SDK_TS_RESERVED = new Set([
|
|
2892
|
-
"break",
|
|
2893
|
-
"
|
|
2894
|
-
"
|
|
2895
|
-
"
|
|
2896
|
-
"
|
|
2897
|
-
"
|
|
2898
|
-
"
|
|
2899
|
-
"
|
|
2900
|
-
"
|
|
2901
|
-
"
|
|
2957
|
+
"break",
|
|
2958
|
+
"case",
|
|
2959
|
+
"catch",
|
|
2960
|
+
"class",
|
|
2961
|
+
"const",
|
|
2962
|
+
"continue",
|
|
2963
|
+
"debugger",
|
|
2964
|
+
"default",
|
|
2965
|
+
"delete",
|
|
2966
|
+
"do",
|
|
2967
|
+
"else",
|
|
2968
|
+
"enum",
|
|
2969
|
+
"export",
|
|
2970
|
+
"extends",
|
|
2971
|
+
"false",
|
|
2972
|
+
"finally",
|
|
2973
|
+
"for",
|
|
2974
|
+
"function",
|
|
2975
|
+
"if",
|
|
2976
|
+
"import",
|
|
2977
|
+
"in",
|
|
2978
|
+
"instanceof",
|
|
2979
|
+
"new",
|
|
2980
|
+
"null",
|
|
2981
|
+
"return",
|
|
2982
|
+
"super",
|
|
2983
|
+
"switch",
|
|
2984
|
+
"this",
|
|
2985
|
+
"throw",
|
|
2986
|
+
"true",
|
|
2987
|
+
"try",
|
|
2988
|
+
"typeof",
|
|
2989
|
+
"var",
|
|
2990
|
+
"void",
|
|
2991
|
+
"while",
|
|
2992
|
+
"with",
|
|
2993
|
+
"yield",
|
|
2994
|
+
"let",
|
|
2995
|
+
"static",
|
|
2996
|
+
"implements",
|
|
2997
|
+
"interface",
|
|
2998
|
+
"package",
|
|
2999
|
+
"private",
|
|
3000
|
+
"protected",
|
|
3001
|
+
"public",
|
|
3002
|
+
"abstract",
|
|
3003
|
+
"as",
|
|
3004
|
+
"async",
|
|
3005
|
+
"await",
|
|
3006
|
+
"constructor",
|
|
3007
|
+
"declare",
|
|
3008
|
+
"from",
|
|
3009
|
+
"get",
|
|
3010
|
+
"infer",
|
|
3011
|
+
"is",
|
|
3012
|
+
"keyof",
|
|
3013
|
+
"module",
|
|
3014
|
+
"namespace",
|
|
3015
|
+
"never",
|
|
3016
|
+
"of",
|
|
3017
|
+
"readonly",
|
|
3018
|
+
"require",
|
|
3019
|
+
"set",
|
|
3020
|
+
"satisfies",
|
|
3021
|
+
"symbol",
|
|
3022
|
+
"type",
|
|
3023
|
+
"unique",
|
|
3024
|
+
"unknown",
|
|
3025
|
+
"override",
|
|
2902
3026
|
])
|
|
2903
3027
|
|
|
2904
3028
|
function sdkSafeName(name: string): string {
|
|
@@ -2947,16 +3071,12 @@ function buildSDKTypes(
|
|
|
2947
3071
|
* single literal is long enough (>200 chars) to dominate the line.
|
|
2948
3072
|
*/
|
|
2949
3073
|
const { aliases: resAliases, lines: resLines } = buildTypeAliases(
|
|
2950
|
-
sdkMethods
|
|
2951
|
-
.filter((m) => !m.ws && !m.sse && isHoistableResponseType(m.responseType))
|
|
2952
|
-
.map((m) => m.responseType),
|
|
3074
|
+
sdkMethods.filter((m) => !m.ws && !m.sse && isHoistableResponseType(m.responseType)).map((m) => m.responseType),
|
|
2953
3075
|
"_Res",
|
|
2954
3076
|
2,
|
|
2955
3077
|
)
|
|
2956
3078
|
const { aliases: inpAliases, lines: inpLines } = buildTypeAliases(
|
|
2957
|
-
sdkMethods
|
|
2958
|
-
.filter((m) => m.inputType !== "{}" && isHoistableResponseType(m.inputType))
|
|
2959
|
-
.map((m) => m.inputType),
|
|
3079
|
+
sdkMethods.filter((m) => m.inputType !== "{}" && isHoistableResponseType(m.inputType)).map((m) => m.inputType),
|
|
2960
3080
|
"_Inp",
|
|
2961
3081
|
2,
|
|
2962
3082
|
)
|
|
@@ -2991,9 +3111,7 @@ function buildSDKTypes(
|
|
|
2991
3111
|
/* TypedWebSocket */
|
|
2992
3112
|
l.push("export type TypedWebSocket = {")
|
|
2993
3113
|
l.push("\tclose(code?: number, reason?: string): void")
|
|
2994
|
-
l.push(
|
|
2995
|
-
'\toff(event: "close" | "error" | "message" | "open", handler: (...args: never[]) => void): void',
|
|
2996
|
-
)
|
|
3114
|
+
l.push('\toff(event: "close" | "error" | "message" | "open", handler: (...args: never[]) => void): void')
|
|
2997
3115
|
l.push('\ton(event: "close", handler: (code: number, reason: string) => void): void')
|
|
2998
3116
|
l.push('\ton(event: "error", handler: (error: unknown) => void): void')
|
|
2999
3117
|
l.push('\ton(event: "message", handler: (data: string) => void): void')
|
|
@@ -3012,9 +3130,7 @@ function buildSDKTypes(
|
|
|
3012
3130
|
l.push(
|
|
3013
3131
|
`\theaders?: Record<string, string> | ((ctx: { method: string; path: string }) => Record<string, string | undefined> | Promise<Record<string, string | undefined>>)`,
|
|
3014
3132
|
)
|
|
3015
|
-
l.push(
|
|
3016
|
-
"\tinvalidation?: { maxSourcesPerTarget?: number; staleMaxEntries?: number; staleTime: number }",
|
|
3017
|
-
)
|
|
3133
|
+
l.push("\tinvalidation?: { maxSourcesPerTarget?: number; staleMaxEntries?: number; staleTime: number }")
|
|
3018
3134
|
l.push("\tmaxErrorMessageChars?: number")
|
|
3019
3135
|
l.push("\tmode?: RequestMode")
|
|
3020
3136
|
l.push(
|
|
@@ -3091,10 +3207,7 @@ function buildSDKTypes(
|
|
|
3091
3207
|
function emitNestedMapNode(node: NestedServiceNode, indent: string): string[] {
|
|
3092
3208
|
if (node.kind === "leaf") {
|
|
3093
3209
|
const entry = node.entry
|
|
3094
|
-
const parts = [
|
|
3095
|
-
`method: ${JSON.stringify(entry.method)}`,
|
|
3096
|
-
`path: ${JSON.stringify(entry.path)}`,
|
|
3097
|
-
]
|
|
3210
|
+
const parts = [`method: ${JSON.stringify(entry.method)}`, `path: ${JSON.stringify(entry.path)}`]
|
|
3098
3211
|
if (entry.params) parts.push(`params: ${JSON.stringify(entry.params)}`)
|
|
3099
3212
|
if (entry.sse) parts.push("sse: true")
|
|
3100
3213
|
if (entry.ws) parts.push("ws: true")
|
|
@@ -3840,14 +3953,11 @@ type _RequestMeta = {
|
|
|
3840
3953
|
}
|
|
3841
3954
|
|
|
3842
3955
|
function sdkClientClientError(): string {
|
|
3843
|
-
const subclassDecls = STATUS_ERROR_CLASSES
|
|
3844
|
-
|
|
3956
|
+
const subclassDecls = STATUS_ERROR_CLASSES.map(
|
|
3957
|
+
({ name }) =>
|
|
3845
3958
|
`class _${name} extends _ClientError { constructor(init: ConstructorParameters<typeof _ClientError>[0]) { super(init); this.name = "${name}" } }`,
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
const mapEntries = STATUS_ERROR_CLASSES
|
|
3849
|
-
.map(({ name, status }) => `\t${status}: _${name},`)
|
|
3850
|
-
.join("\n")
|
|
3959
|
+
).join("\n")
|
|
3960
|
+
const mapEntries = STATUS_ERROR_CLASSES.map(({ name, status }) => `\t${status}: _${name},`).join("\n")
|
|
3851
3961
|
return `
|
|
3852
3962
|
class _ClientError extends Error {
|
|
3853
3963
|
\treadonly body: unknown
|
|
@@ -4891,7 +5001,7 @@ export function collectSDKMethods(spec: OpenApiSpecInput): {
|
|
|
4891
5001
|
|
|
4892
5002
|
const segments = operationId.split(".")
|
|
4893
5003
|
const isTopLevel = segments.length === 1
|
|
4894
|
-
const resource = isTopLevel ? operationId : segments[0] ?? operationId
|
|
5004
|
+
const resource = isTopLevel ? operationId : (segments[0] ?? operationId)
|
|
4895
5005
|
const action = isTopLevel ? "_call" : segments.slice(1).join(".")
|
|
4896
5006
|
|
|
4897
5007
|
if (serviceMap[resource] === undefined) {
|
|
@@ -4934,11 +5044,7 @@ export function collectSDKMethods(spec: OpenApiSpecInput): {
|
|
|
4934
5044
|
return { methods, nestedMap, serviceMap }
|
|
4935
5045
|
}
|
|
4936
5046
|
|
|
4937
|
-
function buildServiceEntryForOp(
|
|
4938
|
-
op: Record<string, unknown>,
|
|
4939
|
-
path: string,
|
|
4940
|
-
method: string,
|
|
4941
|
-
): ServiceEntry {
|
|
5047
|
+
function buildServiceEntryForOp(op: Record<string, unknown>, path: string, method: string): ServiceEntry {
|
|
4942
5048
|
const params = extractOpenApiPathParams(path)
|
|
4943
5049
|
const entry: ServiceEntry = { method: method.toUpperCase(), path }
|
|
4944
5050
|
if (params.length > 0) entry.params = params
|
|
@@ -4985,10 +5091,7 @@ function buildNestedServiceMap(
|
|
|
4985
5091
|
return walkNs(ns)
|
|
4986
5092
|
}
|
|
4987
5093
|
|
|
4988
|
-
export function generateSDK(
|
|
4989
|
-
spec: OpenApiSpecInput,
|
|
4990
|
-
options?: { name?: string; stem?: string },
|
|
4991
|
-
): GeneratedSDK {
|
|
5094
|
+
export function generateSDK(spec: OpenApiSpecInput, options?: { name?: string; stem?: string }): GeneratedSDK {
|
|
4992
5095
|
const sdkName = options?.name ?? "SDK"
|
|
4993
5096
|
const stem = options?.stem ?? "sdk"
|
|
4994
5097
|
const { methods: sdkMethods, nestedMap, serviceMap } = collectSDKMethods(spec)
|