@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.
Files changed (131) hide show
  1. package/README.md +51 -0
  2. package/docs/api-ref/go.md +399 -0
  3. package/docs/api-ref/python.md +399 -0
  4. package/docs/api-ref/rust.md +399 -0
  5. package/docs/api-ref/typescript.md +399 -0
  6. package/docs/invalidate-spec.md +303 -0
  7. package/docs/sdk.md +722 -0
  8. package/package.json +120 -0
  9. package/src/accepts.ts +78 -0
  10. package/src/body-limit.ts +100 -0
  11. package/src/build/index.ts +174 -0
  12. package/src/cli-go/auth.go +15 -0
  13. package/src/cli-go/config.go +165 -0
  14. package/src/cli-go/errors.go +57 -0
  15. package/src/cli-go/multipart.go +55 -0
  16. package/src/cli-go/output.go +109 -0
  17. package/src/cli-go/root.go +30 -0
  18. package/src/cli-go/stream.go +88 -0
  19. package/src/cli-go/version.go +5 -0
  20. package/src/cli.ts +204 -0
  21. package/src/client/error.ts +27 -0
  22. package/src/client/http.ts +502 -0
  23. package/src/client/index.ts +127 -0
  24. package/src/client/sdk.ts +265 -0
  25. package/src/client/sse.ts +102 -0
  26. package/src/client/types.ts +211 -0
  27. package/src/client/ws.ts +115 -0
  28. package/src/client-go/errors.go +207 -0
  29. package/src/client-go/invalidation.go +373 -0
  30. package/src/client-go/realtime.go +317 -0
  31. package/src/client-go/result.go +20 -0
  32. package/src/client-go/runtime.go +413 -0
  33. package/src/client-go/sse.go +149 -0
  34. package/src/client-go/transport.go +452 -0
  35. package/src/client-go/ws.go +104 -0
  36. package/src/client-mcp/README.md +10 -0
  37. package/src/client-mcp/server.ts +94 -0
  38. package/src/client-mcp/types.ts +19 -0
  39. package/src/client-python/_errors.py +115 -0
  40. package/src/client-python/_invalidation.py +578 -0
  41. package/src/client-python/_realtime.py +156 -0
  42. package/src/client-python/_runtime.py +469 -0
  43. package/src/client-python/_sse.py +99 -0
  44. package/src/client-python/_transport.py +335 -0
  45. package/src/client-python/_ws.py +131 -0
  46. package/src/client-python/py.typed +0 -0
  47. package/src/client-rust/errors.rs +200 -0
  48. package/src/client-rust/invalidation.rs +441 -0
  49. package/src/client-rust/invalidation_sync.rs +306 -0
  50. package/src/client-rust/realtime.rs +695 -0
  51. package/src/client-rust/result.rs +22 -0
  52. package/src/client-rust/runtime.rs +547 -0
  53. package/src/client-rust/runtime_sync.rs +452 -0
  54. package/src/client-rust/sse.rs +167 -0
  55. package/src/client-rust/ws.rs +143 -0
  56. package/src/cloudflare/realtime.ts +236 -0
  57. package/src/codegen-go-cli.ts +1180 -0
  58. package/src/codegen-go.ts +1010 -0
  59. package/src/codegen-hash.ts +8 -0
  60. package/src/codegen-ir.ts +517 -0
  61. package/src/codegen-loaders.ts +53 -0
  62. package/src/codegen-mcp.ts +364 -0
  63. package/src/codegen-python.ts +1028 -0
  64. package/src/codegen-rust.ts +1652 -0
  65. package/src/codegen-sanitize.ts +67 -0
  66. package/src/codegen-schema-naming.ts +146 -0
  67. package/src/codegen.ts +5015 -0
  68. package/src/context.ts +210 -0
  69. package/src/cookie-sign.ts +58 -0
  70. package/src/cookie.ts +89 -0
  71. package/src/cors.ts +96 -0
  72. package/src/crypto.ts +19 -0
  73. package/src/csrf.ts +54 -0
  74. package/src/curl-logger.ts +235 -0
  75. package/src/detect-runtime.ts +19 -0
  76. package/src/error.ts +52 -0
  77. package/src/errors.ts +87 -0
  78. package/src/etag.ts +74 -0
  79. package/src/feature-load.ts +23 -0
  80. package/src/go-type-emitter.ts +404 -0
  81. package/src/honey-response.ts +354 -0
  82. package/src/i18n-slot.ts +20 -0
  83. package/src/i18n.ts +234 -0
  84. package/src/index.ts +3843 -0
  85. package/src/init.ts +149 -0
  86. package/src/input.ts +8 -0
  87. package/src/ip-restrict.ts +192 -0
  88. package/src/logger.ts +149 -0
  89. package/src/middleware.ts +199 -0
  90. package/src/node-request.ts +240 -0
  91. package/src/node.ts +212 -0
  92. package/src/openapi/register.ts +19 -0
  93. package/src/openapi/scalar.ts +33 -0
  94. package/src/openapi/spec-factory.ts +46 -0
  95. package/src/openapi/spec.ts +31 -0
  96. package/src/openapi/swagger.ts +34 -0
  97. package/src/plugin.ts +771 -0
  98. package/src/powered-by.ts +17 -0
  99. package/src/pretty-json.ts +39 -0
  100. package/src/proxy.ts +155 -0
  101. package/src/python-type-emitter.ts +144 -0
  102. package/src/realtime/buffer.ts +101 -0
  103. package/src/realtime/bus.ts +93 -0
  104. package/src/realtime/longpoll.ts +148 -0
  105. package/src/realtime/protocol.ts +128 -0
  106. package/src/realtime/route.ts +104 -0
  107. package/src/realtime/runtime.ts +430 -0
  108. package/src/request-id.ts +23 -0
  109. package/src/request-to-curl.ts +34 -0
  110. package/src/response.ts +414 -0
  111. package/src/rust-type-emitter.ts +412 -0
  112. package/src/secure-headers.ts +69 -0
  113. package/src/serve-register.ts +8 -0
  114. package/src/serve-slot.ts +20 -0
  115. package/src/serve.ts +141 -0
  116. package/src/server-timing.ts +66 -0
  117. package/src/static.ts +60 -0
  118. package/src/telemetry/otel.ts +139 -0
  119. package/src/testing.ts +114 -0
  120. package/src/timeout.ts +36 -0
  121. package/src/tree.ts +464 -0
  122. package/src/ts-type-emitter.ts +106 -0
  123. package/src/type-emitter.ts +524 -0
  124. package/src/type-extractor.ts +749 -0
  125. package/src/types.ts +638 -0
  126. package/src/validation.ts +300 -0
  127. package/src/ws/bun.ts +89 -0
  128. package/src/ws/cloudflare.ts +133 -0
  129. package/src/ws/deno.ts +109 -0
  130. package/src/ws/node.ts +161 -0
  131. package/src/yaml.ts +80 -0
@@ -0,0 +1,404 @@
1
+ import { schemaToIR } from "./codegen-ir.ts"
2
+ import type { IRSchema } from "./codegen-ir.ts"
3
+
4
+ export const GO_KEYWORDS = new Set([
5
+ "break", "case", "chan", "const", "continue", "default", "defer",
6
+ "else", "fallthrough", "for", "func", "go", "goto", "if", "import",
7
+ "interface", "map", "package", "range", "return", "select", "struct",
8
+ "switch", "type", "var",
9
+ ])
10
+
11
+ /** Converts a string to PascalCase from kebab-case, snake_case, dotted, or camelCase. */
12
+ export function goPascal(name: string): string {
13
+ return name
14
+ .replace(/[-_.](.)/g, (_, c: string) => c.toUpperCase())
15
+ .replace(/^(.)/, (_, c: string) => c.toUpperCase())
16
+ }
17
+
18
+ /**
19
+ * Returns a safe Go identifier. PascalCase makes all exported identifiers safe
20
+ * without keyword conflicts (Go keywords are all lowercase; Title casing avoids clashes).
21
+ * For unexported uses the keyword check adds `_` suffix.
22
+ */
23
+ export function goIdent(name: string, exported = true): string {
24
+ const pascal = goPascal(name)
25
+ if (exported) return pascal
26
+ const lower = name.toLowerCase()
27
+ if (GO_KEYWORDS.has(lower)) return `${lower}_`
28
+ return name
29
+ }
30
+
31
+ export function goTag(jsonKey: string, omitempty: boolean): string {
32
+ return omitempty ? `\`json:"${jsonKey},omitempty"\`` : `\`json:"${jsonKey}"\``
33
+ }
34
+
35
+ export type RenderUseCtx = {
36
+ parentName: string
37
+ fieldName: string
38
+ decls: Map<string, string>
39
+ circularRefs?: Set<string>
40
+ depth?: number
41
+ }
42
+
43
+ export function isNullable(schema: Record<string, unknown>): boolean {
44
+ if (schema.nullable === true) return true
45
+ const t = schema.type
46
+ if (Array.isArray(t) && t.includes("null")) return true
47
+ const variants = (schema.anyOf ?? schema.oneOf) as Record<string, unknown>[] | undefined
48
+ if (variants && variants.some((v) => v.type === "null")) return true
49
+ return false
50
+ }
51
+
52
+ export function isStringEnum(schema: Record<string, unknown>): boolean {
53
+ const e = schema.enum as unknown[] | undefined
54
+ if (!e || e.length === 0) return false
55
+ const t = schema.type
56
+ if (t === "string") return true
57
+ if (!t && e.every((v) => typeof v === "string")) return true
58
+ return false
59
+ }
60
+
61
+ export function isIntEnum(schema: Record<string, unknown>): boolean {
62
+ const e = schema.enum as unknown[] | undefined
63
+ if (!e || e.length === 0) return false
64
+ const t = schema.type
65
+ if (t === "integer") return true
66
+ if (!t && e.every((v) => typeof v === "number" && Number.isInteger(v))) return true
67
+ return false
68
+ }
69
+
70
+ export function isLiteralConst(schema: Record<string, unknown>): boolean {
71
+ return schema.const !== undefined
72
+ }
73
+
74
+ export function hoistEnumName(parentName: string, fieldName: string): string {
75
+ return `${goPascal(parentName)}${goPascal(fieldName)}`
76
+ }
77
+
78
+ function renderHoistedStringEnum(typeName: string, enumVals: unknown[]): string {
79
+ const l: string[] = []
80
+ l.push(`type ${typeName} string`)
81
+ l.push(`const (`)
82
+ for (const v of enumVals) {
83
+ const label = typeof v === "string" ? goPascal(v) : String(v)
84
+ const val = typeof v === "string" ? JSON.stringify(v) : String(v)
85
+ l.push(`\t${typeName}${label} ${typeName} = ${val}`)
86
+ }
87
+ l.push(`)`)
88
+ return l.join("\n")
89
+ }
90
+
91
+ function renderHoistedIntEnum(typeName: string, enumVals: unknown[]): string {
92
+ const l: string[] = []
93
+ l.push(`type ${typeName} int`)
94
+ l.push(`const (`)
95
+ for (const v of enumVals) {
96
+ l.push(`\t${typeName}${String(v)} ${typeName} = ${String(v)}`)
97
+ }
98
+ l.push(`)`)
99
+ return l.join("\n")
100
+ }
101
+
102
+ function primitiveFor(t: string | undefined): string | null {
103
+ switch (t) {
104
+ case "string": return "string"
105
+ case "integer": return "int64"
106
+ case "number": return "float64"
107
+ case "boolean": return "bool"
108
+ default: return null
109
+ }
110
+ }
111
+
112
+ function constBaseType(val: unknown): string {
113
+ if (typeof val === "boolean") return "bool"
114
+ if (typeof val === "number") return Number.isInteger(val) ? "int64" : "float64"
115
+ return "string"
116
+ }
117
+
118
+ /** Mutates `ctx.decls` when hoisting enums. */
119
+ export function irRenderUse(ir: IRSchema, ctx: RenderUseCtx, depth = 0): string {
120
+ if (depth > 12) return "json.RawMessage"
121
+
122
+ switch (ir.kind) {
123
+ case "ref": {
124
+ const goName = goPascal(ir.name)
125
+ if (ctx.circularRefs?.has(goName)) return `*${goName}`
126
+ return goName
127
+ }
128
+
129
+ case "allOf": {
130
+ const firstRef = ir.parts.find((p) => p.kind === "ref")
131
+ if (firstRef && firstRef.kind === "ref") return goPascal(firstRef.name)
132
+ return "json.RawMessage"
133
+ }
134
+
135
+ case "nullable": {
136
+ const innerStr = irRenderUse(ir.inner, ctx, depth + 1)
137
+ /* already-nilable forms need no pointer prefix */
138
+ if (
139
+ innerStr === "json.RawMessage" ||
140
+ innerStr.startsWith("*") ||
141
+ innerStr.startsWith("[]") ||
142
+ innerStr.startsWith("map[")
143
+ ) return innerStr
144
+ return `*${innerStr}`
145
+ }
146
+
147
+ case "union": {
148
+ /* union at use-position → always json.RawMessage (Go has no native union type) */
149
+ return "json.RawMessage"
150
+ }
151
+
152
+ case "scalar": {
153
+ if (ir.enum) {
154
+ const name = hoistEnumName(ctx.parentName, ctx.fieldName)
155
+ if (!ctx.decls.has(name)) {
156
+ if (ir.type === "string") {
157
+ ctx.decls.set(name, renderHoistedStringEnum(name, ir.enum as unknown[]))
158
+ } else {
159
+ ctx.decls.set(name, renderHoistedIntEnum(name, ir.enum as unknown[]))
160
+ }
161
+ }
162
+ return name
163
+ }
164
+ const prim = primitiveFor(ir.type)
165
+ if (prim) return prim
166
+ return "json.RawMessage"
167
+ }
168
+
169
+ case "const": {
170
+ return constBaseType(ir.value)
171
+ }
172
+
173
+ case "array": {
174
+ const el = irRenderUse(ir.items, ctx, depth + 1)
175
+ return `[]${el}`
176
+ }
177
+
178
+ case "tuple": {
179
+ return `[${ir.items.length}]interface{}`
180
+ }
181
+
182
+ case "object": {
183
+ const { fields, additional } = ir
184
+
185
+ if (fields.length === 0) {
186
+ if (additional) {
187
+ const valType = irRenderUse(additional, ctx, depth + 1)
188
+ return `map[string]${valType}`
189
+ }
190
+ if (additional === false) return "struct{}"
191
+ return "map[string]interface{}"
192
+ }
193
+
194
+ return irRenderAnonStruct(ir, ctx, depth)
195
+ }
196
+
197
+ case "binary": {
198
+ /*
199
+ * dominant case: {type:"string", format:"binary"} → IR binary → "string".
200
+ * bare {format:"binary"} no-type is a 1-case deliberate divergence from raw
201
+ * (raw returns "json.RawMessage", IR returns "string"). Documented in spec §binary.
202
+ */
203
+ return "string"
204
+ }
205
+
206
+ case "unknown": {
207
+ return "json.RawMessage"
208
+ }
209
+ }
210
+ }
211
+
212
+ function irRenderAnonStruct(
213
+ ir: Extract<IRSchema, { kind: "object" }>,
214
+ ctx: RenderUseCtx,
215
+ depth: number,
216
+ ): string {
217
+ const sorted = ir.fields.slice().sort((a, b) => a.name.localeCompare(b.name))
218
+ const lines: string[] = []
219
+ lines.push(`struct {`)
220
+ for (const field of sorted) {
221
+ const fieldGoName = goPascal(field.name)
222
+ const childParent = `${ctx.parentName}${goPascal(ctx.fieldName)}`
223
+ const childCtx: RenderUseCtx = {
224
+ circularRefs: ctx.circularRefs,
225
+ decls: ctx.decls,
226
+ depth: depth + 1,
227
+ fieldName: field.name,
228
+ parentName: childParent,
229
+ }
230
+ const fieldType = irRenderUse(field.schema, childCtx, depth + 1)
231
+ const alreadyNilable =
232
+ fieldType.startsWith("*") ||
233
+ fieldType.startsWith("[]") ||
234
+ fieldType.startsWith("map[")
235
+ const finalType = !field.required && !alreadyNilable ? `*${fieldType}` : fieldType
236
+ const tag = goTag(field.name, !field.required)
237
+ lines.push(`\t${fieldGoName} ${finalType} ${tag}`)
238
+ }
239
+ lines.push(`}`)
240
+ return lines.join("\n")
241
+ }
242
+
243
+ /**
244
+ * `raw` 4th arg is for the const-with-declared-type branch only: IR `const` kind
245
+ * strips the type field; `raw.type` provides the override.
246
+ */
247
+ export function irRenderTopLevel(
248
+ name: string,
249
+ ir: IRSchema,
250
+ decls: Map<string, string>,
251
+ raw?: Record<string, unknown>,
252
+ ): string {
253
+ const typeName = goPascal(name)
254
+ const circularRefs = new Set([typeName])
255
+
256
+ if (ir.kind === "scalar" && ir.enum) {
257
+ if (ir.type === "string") {
258
+ return renderHoistedStringEnum(typeName, ir.enum as unknown[])
259
+ }
260
+ return renderHoistedIntEnum(typeName, ir.enum as unknown[])
261
+ }
262
+
263
+ if (ir.kind === "const") {
264
+ const rawType = raw?.type as string | undefined
265
+ const base = rawType ? (primitiveFor(rawType) ?? "string") : constBaseType(ir.value)
266
+ const val = typeof ir.value === "string" ? JSON.stringify(ir.value) : String(ir.value)
267
+ return [
268
+ `type ${typeName} ${base}`,
269
+ `const ${typeName}Value ${typeName} = ${val}`,
270
+ ].join("\n")
271
+ }
272
+
273
+ if (ir.kind === "allOf") {
274
+ const l: string[] = []
275
+ l.push(`type ${typeName} struct {`)
276
+ for (const part of ir.parts) {
277
+ if (part.kind === "ref") {
278
+ l.push(`\t${goPascal(part.name)}`)
279
+ continue
280
+ }
281
+ if (part.kind !== "object") continue
282
+ const sorted = part.fields.slice().sort((a, b) => a.name.localeCompare(b.name))
283
+ for (const field of sorted) {
284
+ const fieldGoName = goPascal(field.name)
285
+ const ft = irRenderUse(field.schema, {
286
+ circularRefs,
287
+ decls,
288
+ fieldName: field.name,
289
+ parentName: typeName,
290
+ })
291
+ const alreadyNilable =
292
+ ft.startsWith("*") || ft.startsWith("[]") || ft.startsWith("map[")
293
+ const finalType = !field.required && !alreadyNilable ? `*${ft}` : ft
294
+ l.push(`\t${fieldGoName} ${finalType} ${goTag(field.name, !field.required)}`)
295
+ }
296
+ }
297
+ l.push(`}`)
298
+ return l.join("\n")
299
+ }
300
+
301
+ if (ir.kind === "union" && ir.discriminator) {
302
+ const discProp = ir.discriminator.propertyName
303
+ return [
304
+ `type ${typeName} struct {`,
305
+ `\tType string ${goTag(discProp, false)}`,
306
+ `\tPayload json.RawMessage ${goTag("payload", false)}`,
307
+ `}`,
308
+ ].join("\n")
309
+ }
310
+
311
+ if (ir.kind === "object") {
312
+ const { fields, additional } = ir
313
+
314
+ if (fields.length === 0 && additional) {
315
+ const valType = irRenderUse(additional, {
316
+ circularRefs,
317
+ decls,
318
+ fieldName: "Value",
319
+ parentName: typeName,
320
+ })
321
+ return `type ${typeName} map[string]${valType}`
322
+ }
323
+
324
+ if (fields.length === 0) {
325
+ if (additional === false) return `type ${typeName} struct{}`
326
+ return `type ${typeName} map[string]interface{}`
327
+ }
328
+
329
+ const sorted = fields.slice().sort((a, b) => a.name.localeCompare(b.name))
330
+ const l: string[] = []
331
+ l.push(`type ${typeName} struct {`)
332
+ for (const field of sorted) {
333
+ const fieldGoName = goPascal(field.name)
334
+ const ft = irRenderUse(field.schema, {
335
+ circularRefs,
336
+ decls,
337
+ fieldName: field.name,
338
+ parentName: typeName,
339
+ })
340
+ const alreadyNilable =
341
+ ft.startsWith("*") || ft.startsWith("[]") || ft.startsWith("map[")
342
+ let finalType = !field.required && !alreadyNilable ? `*${ft}` : ft
343
+ /* self-ref → pointer (circular) */
344
+ if (circularRefs.has(ft.replace(/^\*/, "")) && !finalType.startsWith("*")) {
345
+ finalType = `*${ft}`
346
+ }
347
+ l.push(`\t${fieldGoName} ${finalType} ${goTag(field.name, !field.required)}`)
348
+ }
349
+
350
+ if (additional) {
351
+ const valType = irRenderUse(additional, {
352
+ circularRefs,
353
+ decls,
354
+ fieldName: "Extra",
355
+ parentName: typeName,
356
+ })
357
+ l.push(`\tExtra map[string]${valType} \`json:"-"\``)
358
+ l.push(`}`)
359
+ l.push(``)
360
+ l.push(`func (s *${typeName}) UnmarshalJSON(data []byte) error {`)
361
+ l.push(`\ttype plain ${typeName}`)
362
+ l.push(`\tvar base plain`)
363
+ l.push(`\tif err := json.Unmarshal(data, &base); err != nil {`)
364
+ l.push(`\t\treturn err`)
365
+ l.push(`\t}`)
366
+ l.push(`\t*s = ${typeName}(base)`)
367
+ l.push(`\treturn nil`)
368
+ l.push(`}`)
369
+ l.push(``)
370
+ l.push(`func (s ${typeName}) MarshalJSON() ([]byte, error) {`)
371
+ l.push(`\ttype plain ${typeName}`)
372
+ l.push(`\treturn json.Marshal(plain(s))`)
373
+ l.push(`}`)
374
+ return l.join("\n")
375
+ }
376
+
377
+ l.push(`}`)
378
+ return l.join("\n")
379
+ }
380
+
381
+ const aliased = irRenderUse(ir, {
382
+ circularRefs,
383
+ decls,
384
+ fieldName: "Value",
385
+ parentName: typeName,
386
+ })
387
+ return `type ${typeName} = ${aliased}`
388
+ }
389
+
390
+ export function renderUse(
391
+ schema: Record<string, unknown> | undefined,
392
+ ctx: RenderUseCtx,
393
+ ): string {
394
+ return irRenderUse(schemaToIR(schema), ctx, ctx.depth ?? 0)
395
+ }
396
+
397
+ export function renderTopLevel(
398
+ name: string,
399
+ schema: Record<string, unknown>,
400
+ decls: Map<string, string>,
401
+ ): string {
402
+ return irRenderTopLevel(name, schemaToIR(schema), decls, schema)
403
+ }
404
+