@lovrozagar/honey 0.1.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 +51 -0
- package/docs/api-ref/go.md +399 -0
- package/docs/api-ref/python.md +399 -0
- package/docs/api-ref/rust.md +399 -0
- package/docs/api-ref/typescript.md +399 -0
- package/docs/invalidate-spec.md +303 -0
- package/docs/sdk.md +722 -0
- package/package.json +120 -0
- package/src/accepts.ts +78 -0
- package/src/body-limit.ts +100 -0
- package/src/build/index.ts +174 -0
- package/src/cli-go/auth.go +15 -0
- package/src/cli-go/config.go +165 -0
- package/src/cli-go/errors.go +57 -0
- package/src/cli-go/multipart.go +55 -0
- package/src/cli-go/output.go +109 -0
- package/src/cli-go/root.go +30 -0
- package/src/cli-go/stream.go +88 -0
- package/src/cli-go/version.go +5 -0
- package/src/cli.ts +204 -0
- package/src/client/error.ts +27 -0
- package/src/client/http.ts +502 -0
- package/src/client/index.ts +127 -0
- package/src/client/sdk.ts +265 -0
- package/src/client/sse.ts +102 -0
- package/src/client/types.ts +211 -0
- package/src/client/ws.ts +115 -0
- package/src/client-go/errors.go +207 -0
- package/src/client-go/invalidation.go +373 -0
- package/src/client-go/realtime.go +317 -0
- package/src/client-go/result.go +20 -0
- package/src/client-go/runtime.go +413 -0
- package/src/client-go/sse.go +149 -0
- package/src/client-go/transport.go +452 -0
- package/src/client-go/ws.go +104 -0
- package/src/client-mcp/README.md +10 -0
- package/src/client-mcp/server.ts +94 -0
- package/src/client-mcp/types.ts +19 -0
- package/src/client-python/_errors.py +115 -0
- package/src/client-python/_invalidation.py +578 -0
- package/src/client-python/_realtime.py +156 -0
- package/src/client-python/_runtime.py +469 -0
- package/src/client-python/_sse.py +99 -0
- package/src/client-python/_transport.py +335 -0
- package/src/client-python/_ws.py +131 -0
- package/src/client-python/py.typed +0 -0
- package/src/client-rust/errors.rs +200 -0
- package/src/client-rust/invalidation.rs +441 -0
- package/src/client-rust/invalidation_sync.rs +306 -0
- package/src/client-rust/realtime.rs +695 -0
- package/src/client-rust/result.rs +22 -0
- package/src/client-rust/runtime.rs +547 -0
- package/src/client-rust/runtime_sync.rs +452 -0
- package/src/client-rust/sse.rs +167 -0
- package/src/client-rust/ws.rs +143 -0
- package/src/cloudflare/realtime.ts +236 -0
- package/src/codegen-go-cli.ts +1180 -0
- package/src/codegen-go.ts +1010 -0
- package/src/codegen-hash.ts +8 -0
- package/src/codegen-ir.ts +517 -0
- package/src/codegen-loaders.ts +53 -0
- package/src/codegen-mcp.ts +364 -0
- package/src/codegen-python.ts +1028 -0
- package/src/codegen-rust.ts +1652 -0
- package/src/codegen-sanitize.ts +67 -0
- package/src/codegen-schema-naming.ts +146 -0
- package/src/codegen.ts +5015 -0
- package/src/context.ts +210 -0
- package/src/cookie-sign.ts +58 -0
- package/src/cookie.ts +89 -0
- package/src/cors.ts +96 -0
- package/src/crypto.ts +19 -0
- package/src/csrf.ts +54 -0
- package/src/curl-logger.ts +235 -0
- package/src/detect-runtime.ts +19 -0
- package/src/error.ts +52 -0
- package/src/errors.ts +87 -0
- package/src/etag.ts +74 -0
- package/src/feature-load.ts +23 -0
- package/src/go-type-emitter.ts +404 -0
- package/src/honey-response.ts +354 -0
- package/src/i18n-slot.ts +20 -0
- package/src/i18n.ts +234 -0
- package/src/index.ts +3843 -0
- package/src/init.ts +149 -0
- package/src/input.ts +8 -0
- package/src/ip-restrict.ts +192 -0
- package/src/logger.ts +149 -0
- package/src/middleware.ts +199 -0
- package/src/node-request.ts +240 -0
- package/src/node.ts +212 -0
- package/src/openapi/register.ts +19 -0
- package/src/openapi/scalar.ts +33 -0
- package/src/openapi/spec-factory.ts +46 -0
- package/src/openapi/spec.ts +31 -0
- package/src/openapi/swagger.ts +34 -0
- package/src/plugin.ts +771 -0
- package/src/powered-by.ts +17 -0
- package/src/pretty-json.ts +39 -0
- package/src/proxy.ts +155 -0
- package/src/python-type-emitter.ts +144 -0
- package/src/realtime/buffer.ts +101 -0
- package/src/realtime/bus.ts +93 -0
- package/src/realtime/longpoll.ts +148 -0
- package/src/realtime/protocol.ts +128 -0
- package/src/realtime/route.ts +104 -0
- package/src/realtime/runtime.ts +430 -0
- package/src/request-id.ts +23 -0
- package/src/request-to-curl.ts +34 -0
- package/src/response.ts +414 -0
- package/src/rust-type-emitter.ts +412 -0
- package/src/secure-headers.ts +69 -0
- package/src/serve-register.ts +8 -0
- package/src/serve-slot.ts +20 -0
- package/src/serve.ts +141 -0
- package/src/server-timing.ts +66 -0
- package/src/static.ts +60 -0
- package/src/telemetry/otel.ts +139 -0
- package/src/testing.ts +114 -0
- package/src/timeout.ts +36 -0
- package/src/tree.ts +464 -0
- package/src/ts-type-emitter.ts +106 -0
- package/src/type-emitter.ts +524 -0
- package/src/type-extractor.ts +749 -0
- package/src/types.ts +638 -0
- package/src/validation.ts +300 -0
- package/src/ws/bun.ts +89 -0
- package/src/ws/cloudflare.ts +133 -0
- package/src/ws/deno.ts +109 -0
- package/src/ws/node.ts +161 -0
- package/src/yaml.ts +80 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/** OpenAPI cleanup of Zod JSON Schema. Not a package export. */
|
|
2
|
+
|
|
3
|
+
const SCHEMA_VALUE_KEYS = new Set([
|
|
4
|
+
"additionalProperties",
|
|
5
|
+
"contains",
|
|
6
|
+
"else",
|
|
7
|
+
"if",
|
|
8
|
+
"items",
|
|
9
|
+
"not",
|
|
10
|
+
"propertyNames",
|
|
11
|
+
"then",
|
|
12
|
+
"unevaluatedItems",
|
|
13
|
+
"unevaluatedProperties",
|
|
14
|
+
])
|
|
15
|
+
|
|
16
|
+
const SCHEMA_MAP_KEYS = new Set([
|
|
17
|
+
"$defs",
|
|
18
|
+
"definitions",
|
|
19
|
+
"dependentSchemas",
|
|
20
|
+
"patternProperties",
|
|
21
|
+
"properties",
|
|
22
|
+
])
|
|
23
|
+
|
|
24
|
+
const SCHEMA_ARRAY_KEYS = new Set(["allOf", "anyOf", "oneOf", "prefixItems"])
|
|
25
|
+
|
|
26
|
+
function asRecord(value: unknown): value is Record<string, unknown> {
|
|
27
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function sanitizeSchemaValue(value: unknown): unknown {
|
|
31
|
+
if (typeof value === "boolean") return value
|
|
32
|
+
if (asRecord(value)) return sanitizeZodJsonSchema(value)
|
|
33
|
+
return value
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function sanitizeZodJsonSchema(obj: Record<string, unknown>): Record<string, unknown> {
|
|
37
|
+
const out: Record<string, unknown> = {}
|
|
38
|
+
for (const [key, val] of Object.entries(obj)) {
|
|
39
|
+
if (key === "$schema" || key === "~standard") continue
|
|
40
|
+
if (key === "anyOf" && Array.isArray(val)) {
|
|
41
|
+
out.oneOf = val.map((item) => sanitizeSchemaValue(item))
|
|
42
|
+
continue
|
|
43
|
+
}
|
|
44
|
+
if (SCHEMA_MAP_KEYS.has(key) && asRecord(val)) {
|
|
45
|
+
const mapped: Record<string, unknown> = {}
|
|
46
|
+
for (const [childKey, child] of Object.entries(val)) {
|
|
47
|
+
mapped[childKey] = sanitizeSchemaValue(child)
|
|
48
|
+
}
|
|
49
|
+
out[key] = mapped
|
|
50
|
+
continue
|
|
51
|
+
}
|
|
52
|
+
if (SCHEMA_ARRAY_KEYS.has(key) && Array.isArray(val)) {
|
|
53
|
+
out[key] = val.map((item) => sanitizeSchemaValue(item))
|
|
54
|
+
continue
|
|
55
|
+
}
|
|
56
|
+
if (SCHEMA_VALUE_KEYS.has(key)) {
|
|
57
|
+
if (key === "items" && Array.isArray(val)) {
|
|
58
|
+
out[key] = val.map((item) => sanitizeSchemaValue(item))
|
|
59
|
+
} else {
|
|
60
|
+
out[key] = sanitizeSchemaValue(val)
|
|
61
|
+
}
|
|
62
|
+
continue
|
|
63
|
+
}
|
|
64
|
+
out[key] = val
|
|
65
|
+
}
|
|
66
|
+
return out
|
|
67
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { hashString } from "./codegen-hash.ts"
|
|
2
|
+
|
|
3
|
+
export type SchemaRole = "request" | "response"
|
|
4
|
+
|
|
5
|
+
export type SchemaNameContext =
|
|
6
|
+
| { method: string; path: string; role: "request"; fieldPath?: string[] }
|
|
7
|
+
| { method: string; path: string; role: "response"; status: number; fieldPath?: string[] }
|
|
8
|
+
|
|
9
|
+
const KNOWN_ACTIONS = new Set(["accept", "cancel", "refresh", "verify", "confirm", "reset", "revoke", "invite"])
|
|
10
|
+
|
|
11
|
+
/** Returns 6-char lowercase hex derived from content hash. */
|
|
12
|
+
export function shortHash(canonical: string): string {
|
|
13
|
+
return hashString(canonical).slice(0, 6)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function toPascal(segment: string): string {
|
|
17
|
+
return segment
|
|
18
|
+
.replace(/[-_](.)/g, (_, c: string) => c.toUpperCase())
|
|
19
|
+
.replace(/^(.)/, (_, c: string) => c.toUpperCase())
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function stripPathPrefix(path: string): string[] {
|
|
23
|
+
const raw = path.replace(/^\//, "").split("/")
|
|
24
|
+
/* drop leading version segment like v1, v2, v10 */
|
|
25
|
+
if (raw.length > 0 && /^v\d+$/.test(raw[0])) raw.shift()
|
|
26
|
+
return raw
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function isParam(segment: string): boolean {
|
|
30
|
+
return segment.startsWith("{") && segment.endsWith("}")
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/*
|
|
34
|
+
* Drop "variant qualifier": when the last raw segment is a non-param non-action
|
|
35
|
+
* literal that follows DIRECTLY after a plural collection noun (no {param} between
|
|
36
|
+
* them), it's a discriminator (e.g. "github"/"stripe" in /webhooks/github).
|
|
37
|
+
* Dropping it ensures both routes derive the same base name so collision detection
|
|
38
|
+
* kicks in and hash-suffix disambiguates their distinct shapes.
|
|
39
|
+
*/
|
|
40
|
+
function dropVariantQualifier(rawSegments: string[]): string[] {
|
|
41
|
+
if (rawSegments.length < 2) return rawSegments
|
|
42
|
+
const last = rawSegments[rawSegments.length - 1]
|
|
43
|
+
const prev = rawSegments[rawSegments.length - 2]
|
|
44
|
+
if (!isParam(last) && !isParam(prev) && !KNOWN_ACTIONS.has(last.toLowerCase())) {
|
|
45
|
+
const prevPascal = toPascal(prev)
|
|
46
|
+
/* prev must be a collection noun (ends in 's' after PascalCase) */
|
|
47
|
+
if (prevPascal.endsWith("s")) {
|
|
48
|
+
return rawSegments.slice(0, -1)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return rawSegments
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function deriveVerb(method: string, rawSegments: string[]): string {
|
|
55
|
+
const lastRaw = rawSegments[rawSegments.length - 1]
|
|
56
|
+
|
|
57
|
+
if (method === "get") {
|
|
58
|
+
if (lastRaw && isParam(lastRaw)) return "Get"
|
|
59
|
+
return "List"
|
|
60
|
+
}
|
|
61
|
+
if (method === "patch") return "Update"
|
|
62
|
+
if (method === "put") return "Replace"
|
|
63
|
+
if (method === "delete") return "Delete"
|
|
64
|
+
|
|
65
|
+
/* POST */
|
|
66
|
+
if (lastRaw && KNOWN_ACTIONS.has(lastRaw.toLowerCase())) {
|
|
67
|
+
return toPascal(lastRaw)
|
|
68
|
+
}
|
|
69
|
+
return "Create"
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function buildResourcePart(rawSegments: string[], verb: string): string {
|
|
73
|
+
const nonParam = rawSegments.filter((s) => !isParam(s))
|
|
74
|
+
const lastSeg = rawSegments[rawSegments.length - 1]
|
|
75
|
+
|
|
76
|
+
if (lastSeg && KNOWN_ACTIONS.has(lastSeg.toLowerCase()) && verb === toPascal(lastSeg)) {
|
|
77
|
+
/* last segment is the action verb — drop it; it's already the verb prefix */
|
|
78
|
+
return nonParam.slice(0, -1).map(toPascal).join("")
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const parts = nonParam.map(toPascal)
|
|
82
|
+
|
|
83
|
+
/* singularize last noun for non-List verbs */
|
|
84
|
+
if (verb !== "List" && parts.length > 0) {
|
|
85
|
+
const last = parts[parts.length - 1]
|
|
86
|
+
if (last.endsWith("s") && last.length > 2) {
|
|
87
|
+
parts[parts.length - 1] = last.slice(0, -1)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return parts.join("")
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function roleSuffix(ctx: SchemaNameContext): string {
|
|
95
|
+
if (ctx.role === "request") return "Request"
|
|
96
|
+
return `Response${ctx.status}`
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Returns true if schema matches anyrow-style error envelope shape. */
|
|
100
|
+
export function isErrorEnvelope(schema: Record<string, unknown>): boolean {
|
|
101
|
+
if (schema.type !== "object" && !schema.properties) return false
|
|
102
|
+
const props = schema.properties as Record<string, Record<string, unknown>> | undefined
|
|
103
|
+
if (!props) return false
|
|
104
|
+
if (!props.error_key || !props.status || !props.success) return false
|
|
105
|
+
const successDef = props.success
|
|
106
|
+
const isFalseConst = successDef.const === false
|
|
107
|
+
const isFalseEnum = Array.isArray(successDef.enum) && successDef.enum.length === 1 && successDef.enum[0] === false
|
|
108
|
+
return isFalseConst || isFalseEnum
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Derives a canonical name for a shared error envelope (e.g. `Err400InvalidInput`). */
|
|
112
|
+
export function deriveErrorEnvelopeName(schema: Record<string, unknown>, fallbackStatus?: number): string {
|
|
113
|
+
const props = schema.properties as Record<string, Record<string, unknown>>
|
|
114
|
+
const statusProp = props.status
|
|
115
|
+
let status: number | undefined
|
|
116
|
+
if (typeof statusProp.const === "number") status = statusProp.const
|
|
117
|
+
else if (Array.isArray(statusProp.enum) && statusProp.enum.length === 1 && typeof statusProp.enum[0] === "number") status = statusProp.enum[0]
|
|
118
|
+
else if (fallbackStatus !== undefined) status = fallbackStatus
|
|
119
|
+
else throw new Error("deriveErrorEnvelopeName: no status")
|
|
120
|
+
|
|
121
|
+
const keyProp = props.error_key
|
|
122
|
+
const keys: string[] = Array.isArray(keyProp?.enum)
|
|
123
|
+
? [...keyProp.enum].filter((k): k is string => typeof k === "string").sort()
|
|
124
|
+
: []
|
|
125
|
+
|
|
126
|
+
if (keys.length === 0) return `Err${status}`
|
|
127
|
+
const keysPascal = keys
|
|
128
|
+
.map((k) => k.split(/[_-]/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(""))
|
|
129
|
+
.join("")
|
|
130
|
+
return `Err${status}${keysPascal}`
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Derives a human-readable schema component name from operation context. */
|
|
134
|
+
export function deriveSchemaName(ctx: SchemaNameContext): string {
|
|
135
|
+
const rawSegments = dropVariantQualifier(stripPathPrefix(ctx.path))
|
|
136
|
+
const verb = deriveVerb(ctx.method, rawSegments)
|
|
137
|
+
const resource = buildResourcePart(rawSegments, verb)
|
|
138
|
+
const base = `${verb}${resource}`
|
|
139
|
+
|
|
140
|
+
if (ctx.fieldPath && ctx.fieldPath.length > 0) {
|
|
141
|
+
const fieldPart = ctx.fieldPath.map(toPascal).join("")
|
|
142
|
+
return `${base}${fieldPart}`
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return `${base}${roleSuffix(ctx)}`
|
|
146
|
+
}
|