@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,143 @@
1
+ /* Code generated by honey. DO NOT EDIT. */
2
+
3
+ use crate::errors::Error;
4
+ use serde::Serialize;
5
+ use std::collections::HashMap;
6
+ use std::sync::atomic::{AtomicBool, Ordering};
7
+ use std::sync::Mutex;
8
+ use tokio_tungstenite::tungstenite::Message;
9
+ use tokio_tungstenite::WebSocketStream;
10
+
11
+ /// WS_STATUS_NORMAL_CLOSURE is the normal WebSocket close code (1000).
12
+ pub const WS_STATUS_NORMAL_CLOSURE: u16 = 1000;
13
+
14
+ /// WS_STATUS_GOING_AWAY is the going-away WebSocket close code (1001).
15
+ pub const WS_STATUS_GOING_AWAY: u16 = 1001;
16
+
17
+ /// WS_STATUS_PROTOCOL_ERROR is the protocol error WebSocket close code (1002).
18
+ pub const WS_STATUS_PROTOCOL_ERROR: u16 = 1002;
19
+
20
+ type WsStream = WebSocketStream<tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>>;
21
+
22
+ type WsHandler = Box<dyn Fn(&[u8]) + Send + Sync>;
23
+ type HandlerMap = Mutex<HashMap<String, Vec<WsHandler>>>;
24
+
25
+ /// TypedWebSocket wraps a tokio-tungstenite connection with a buffered pre-OPEN
26
+ /// send queue (flushed once on first read via AtomicBool) and an event fanout
27
+ /// system that mirrors the TS client's .on() escape hatch.
28
+ pub struct TypedWebSocket {
29
+ conn: WsStream,
30
+ send_buffer: Option<Vec<u8>>,
31
+ once: AtomicBool,
32
+ handlers: HandlerMap,
33
+ }
34
+
35
+ impl TypedWebSocket {
36
+ /// new constructs a TypedWebSocket from an already-dialled stream.
37
+ pub fn new(conn: WsStream) -> Self {
38
+ TypedWebSocket {
39
+ conn,
40
+ send_buffer: None,
41
+ once: AtomicBool::new(false),
42
+ handlers: Mutex::new(HashMap::new()),
43
+ }
44
+ }
45
+
46
+ /// send serialises data to JSON and writes it to the WebSocket connection.
47
+ /// Accepts any serde_json::Value or JSON-serializable payload pre-encoded as Value.
48
+ pub async fn send(&mut self, data: &serde_json::Value) -> Result<(), Error> {
49
+ use futures_util::SinkExt;
50
+ let encoded = serde_json::to_vec(data).map_err(Error::Json)?;
51
+ self.conn
52
+ .send(Message::Text(String::from_utf8_lossy(&encoded).into_owned()))
53
+ .await
54
+ .map_err(|e| Error::Other(e.to_string()))
55
+ }
56
+
57
+ /// send_typed serialises any Serialize value to JSON and writes it to the WebSocket connection.
58
+ pub async fn send_typed<T: Serialize + ?Sized>(&mut self, data: &T) -> Result<(), Error> {
59
+ use futures_util::SinkExt;
60
+ let encoded = serde_json::to_vec(data).map_err(Error::Json)?;
61
+ self.conn
62
+ .send(Message::Text(String::from_utf8_lossy(&encoded).into_owned()))
63
+ .await
64
+ .map_err(|e| Error::Other(e.to_string()))
65
+ }
66
+
67
+ /// close sends a WebSocket close frame with the given status code and reason.
68
+ pub async fn close(&mut self, code: u16, reason: &str) -> Result<(), Error> {
69
+ use futures_util::SinkExt;
70
+ use tokio_tungstenite::tungstenite::protocol::frame::coding::CloseCode;
71
+ use tokio_tungstenite::tungstenite::protocol::CloseFrame;
72
+ let frame = CloseFrame {
73
+ code: CloseCode::from(code),
74
+ reason: reason.to_string().into(),
75
+ };
76
+ self.conn
77
+ .send(Message::Close(Some(frame)))
78
+ .await
79
+ .map_err(|e| Error::Other(e.to_string()))
80
+ }
81
+
82
+ /// read reads the next message from the connection.
83
+ /// On the first call, any pre-OPEN buffered send is flushed via AtomicBool compare_exchange.
84
+ pub async fn read(&mut self) -> Result<Vec<u8>, Error> {
85
+ use futures_util::{SinkExt, StreamExt};
86
+
87
+ /* flush send_buffer exactly once on first read — mirrors Go sync.Once */
88
+ if self
89
+ .once
90
+ .compare_exchange(false, true, Ordering::AcqRel, Ordering::Relaxed)
91
+ .is_ok()
92
+ {
93
+ if let Some(buf) = self.send_buffer.take() {
94
+ let _ = self
95
+ .conn
96
+ .send(Message::Text(String::from_utf8_lossy(&buf).into_owned()))
97
+ .await;
98
+ }
99
+ }
100
+
101
+ loop {
102
+ match self.conn.next().await {
103
+ None => return Err(Error::Closed { code: 1006, reason: String::new() }),
104
+ Some(Err(e)) => return Err(Error::Other(e.to_string())),
105
+ Some(Ok(msg)) => {
106
+ let data = match msg {
107
+ Message::Text(t) => t.into_bytes(),
108
+ Message::Binary(b) => b,
109
+ Message::Close(frame) => {
110
+ let (code, reason) = match frame {
111
+ Some(f) => (u16::from(f.code), f.reason.into_owned()),
112
+ None => (1005, String::new()),
113
+ };
114
+ return Err(Error::Closed { code, reason })
115
+ }
116
+ _ => continue,
117
+ };
118
+ self.fanout("message", &data);
119
+ return Ok(data);
120
+ }
121
+ }
122
+ }
123
+ }
124
+
125
+ /// on registers an event handler for a named event.
126
+ /// Supported events: "message", "close", "error", "open".
127
+ pub fn on(&mut self, event: &str, handler: WsHandler) {
128
+ let mut handlers = self.handlers.lock().unwrap();
129
+ handlers
130
+ .entry(event.to_string())
131
+ .or_default()
132
+ .push(handler);
133
+ }
134
+
135
+ fn fanout(&self, event: &str, data: &[u8]) {
136
+ let handlers = self.handlers.lock().unwrap();
137
+ if let Some(hs) = handlers.get(event) {
138
+ for h in hs {
139
+ h(data);
140
+ }
141
+ }
142
+ }
143
+ }
@@ -0,0 +1,236 @@
1
+ import { createBus } from "../realtime/bus.ts"
2
+ import { ReconnectBuffer } from "../realtime/buffer.ts"
3
+ import { decodeClientFrame, encodeServerFrame } from "../realtime/protocol.ts"
4
+ import type { ServerFrame } from "../realtime/protocol.ts"
5
+ import { createConnContext } from "../realtime/route.ts"
6
+ import type { ConnContext } from "../realtime/route.ts"
7
+
8
+ export type DurableObjectContext = {
9
+ id: { toString(): string }
10
+ storage: {
11
+ getAlarm(): Promise<number | null>
12
+ setAlarm(time: number): Promise<void>
13
+ deleteAlarm(): Promise<void>
14
+ }
15
+ acceptWebSocket(ws: WebSocket): void
16
+ getWebSockets(): WebSocket[]
17
+ getTags(ws: WebSocket): string[]
18
+ }
19
+
20
+ export type RealtimeHandlerConfig<TEnv = unknown> = {
21
+ ctx: DurableObjectContext
22
+ env: TEnv
23
+ onAuth?: (req: Request) => string | null | Promise<string | null>
24
+ reconnectBuffer?: number
25
+ routes?: Record<string, (c: unknown, conn: ConnContext) => void | Promise<void>>
26
+ }
27
+
28
+ export type RealtimeHandlers = {
29
+ fetch(req: Request): Promise<Response>
30
+ message(ws: WebSocket, msg: ArrayBuffer | string): void | Promise<void>
31
+ close(ws: WebSocket, code: number, reason: string, wasClean: boolean): void | Promise<void>
32
+ error(ws: WebSocket, err: unknown): void | Promise<void>
33
+ alarm(): void | Promise<void>
34
+ }
35
+
36
+ type Attachment = {
37
+ connId: string
38
+ reconnectToken: string
39
+ userId: string | null
40
+ }
41
+
42
+ const DEFAULT_BUFFER_SIZE = 100
43
+ const BUFFER_TTL_MS = 30_000
44
+
45
+ export function createRealtimeHandlers<TEnv = unknown>(
46
+ config: RealtimeHandlerConfig<TEnv>,
47
+ ): RealtimeHandlers {
48
+ const bufferSize = config.reconnectBuffer ?? DEFAULT_BUFFER_SIZE
49
+ const buffer = new ReconnectBuffer({ size: bufferSize })
50
+ const bus = createBus()
51
+
52
+ /* Track registered tokens so we can lazily register on hibernation wake */
53
+ const knownTokens = new Set<string>()
54
+
55
+ function ensureToken(token: string): void {
56
+ if (knownTokens.has(token)) return
57
+ /* Token was created before hibernation — re-register with buffer */
58
+ buffer.create(token)
59
+ knownTokens.add(token)
60
+ }
61
+
62
+ function getAttachment(ws: WebSocket): Attachment | null {
63
+ const raw = (ws as unknown as { deserializeAttachment(): string | null }).deserializeAttachment()
64
+ if (!raw) return null
65
+ try {
66
+ return JSON.parse(raw) as Attachment
67
+ } catch {
68
+ return null
69
+ }
70
+ }
71
+
72
+ function sendFrame(ws: WebSocket, frame: ServerFrame): void {
73
+ (ws as unknown as { send(data: string): void }).send(encodeServerFrame(frame))
74
+ }
75
+
76
+ function getRouteHandler(): ((c: unknown, conn: ConnContext) => void | Promise<void>) | null {
77
+ if (!config.routes) return null
78
+ if (config.routes["/"]) return config.routes["/"]
79
+ const keys = Object.keys(config.routes)
80
+ if (keys.length > 0) return config.routes[keys[0]]
81
+ return null
82
+ }
83
+
84
+ function buildConnContext(attachment: Attachment, ws: WebSocket): ConnContext {
85
+ return createConnContext({
86
+ bus,
87
+ closeFn: (reason) => {
88
+ (ws as unknown as { close(code?: number, reason?: string): void }).close(1000, reason ?? "")
89
+ },
90
+ id: attachment.connId,
91
+ sendFn: (payload) => {
92
+ ensureToken(attachment.reconnectToken)
93
+ const id = buffer.push(attachment.reconnectToken, payload)
94
+ sendFrame(ws, { data: payload, id, t: "msg" })
95
+ },
96
+ transport: "ws",
97
+ userId: attachment.userId,
98
+ })
99
+ }
100
+
101
+ async function handleFetch(req: Request): Promise<Response> {
102
+ const upgradeHeader = req.headers.get("upgrade")
103
+ if (!upgradeHeader || upgradeHeader.toLowerCase() !== "websocket") {
104
+ return new Response("Upgrade Required", { status: 426 })
105
+ }
106
+
107
+ let userId: string | null = null
108
+ if (config.onAuth) {
109
+ userId = await config.onAuth(req)
110
+ }
111
+
112
+ const pair = new (globalThis as unknown as { WebSocketPair: new () => { 0: WebSocket; 1: WebSocket } }).WebSocketPair()
113
+ const client = pair[0]
114
+ const server = pair[1]
115
+
116
+ config.ctx.acceptWebSocket(server)
117
+
118
+ const connId = crypto.randomUUID()
119
+ const reconnectToken = buffer.create()
120
+ knownTokens.add(reconnectToken)
121
+
122
+ const attachment: Attachment = { connId, reconnectToken, userId }
123
+ ;(server as unknown as { serializeAttachment(data: string): void }).serializeAttachment(
124
+ JSON.stringify(attachment),
125
+ )
126
+
127
+ sendFrame(server, { reconnectToken, t: "ready" })
128
+
129
+ /*
130
+ * CF Workers returns a special Response with status 101 and the webSocket property.
131
+ * Standard Response rejects 101, so we construct the object manually.
132
+ */
133
+ const response = new Response(null, { status: 200 })
134
+ Object.defineProperty(response, "status", { value: 101 })
135
+ Object.defineProperty(response, "webSocket", { value: client })
136
+ return response
137
+ }
138
+
139
+ async function handleMessage(ws: WebSocket, msg: ArrayBuffer | string): Promise<void> {
140
+ if (typeof msg !== "string") return
141
+
142
+ const attachment = getAttachment(ws)
143
+ if (!attachment) return
144
+
145
+ if (!msg) return
146
+
147
+ const frame = decodeClientFrame(msg)
148
+ if (!frame) return
149
+
150
+ if (frame.t === "ping") {
151
+ sendFrame(ws, { t: "pong" })
152
+ return
153
+ }
154
+
155
+ if (frame.t === "msg") {
156
+ /* Ensure token is registered (survives hibernation wake) */
157
+ ensureToken(attachment.reconnectToken)
158
+ buffer.push(attachment.reconnectToken, frame.data)
159
+
160
+ const routeHandler = getRouteHandler()
161
+ if (routeHandler) {
162
+ const conn = buildConnContext(attachment, ws)
163
+ await routeHandler(config.env, conn)
164
+ if (conn._handlers.message) {
165
+ conn._handlers.message(frame.data)
166
+ }
167
+ }
168
+ return
169
+ }
170
+
171
+ if (frame.t === "resume") {
172
+ /* Ensure the connection's own token is known */
173
+ ensureToken(attachment.reconnectToken)
174
+
175
+ const replayed = buffer.replay(frame.reconnectToken, frame.lastId)
176
+ if (!replayed) {
177
+ sendFrame(ws, { reason: "server_restart", t: "bye" })
178
+ return
179
+ }
180
+
181
+ for (const entry of replayed) {
182
+ sendFrame(ws, { data: entry.data, id: entry.id, t: "msg" })
183
+ }
184
+ sendFrame(ws, { reconnectToken: attachment.reconnectToken, t: "ready" })
185
+ return
186
+ }
187
+ }
188
+
189
+ async function handleClose(ws: WebSocket, _code: number, reason: string, _wasClean: boolean): Promise<void> {
190
+ const attachment = getAttachment(ws)
191
+ if (!attachment) return
192
+
193
+ /* Run route handler to get close callback */
194
+ const routeHandler = getRouteHandler()
195
+ if (routeHandler) {
196
+ const conn = buildConnContext(attachment, ws)
197
+ await routeHandler(config.env, conn)
198
+ if (conn._handlers.close) {
199
+ conn._handlers.close(reason)
200
+ }
201
+ }
202
+
203
+ bus.unsubscribeAll(attachment.connId)
204
+ bus.removeHandler(attachment.connId)
205
+
206
+ ensureToken(attachment.reconnectToken)
207
+ buffer.disconnect(attachment.reconnectToken)
208
+
209
+ /* Schedule alarm to clean up expired buffers */
210
+ const existingAlarm = await config.ctx.storage.getAlarm()
211
+ if (!existingAlarm) {
212
+ await config.ctx.storage.setAlarm(Date.now() + BUFFER_TTL_MS)
213
+ }
214
+ }
215
+
216
+ function handleError(ws: WebSocket, _err: unknown): Promise<void> {
217
+ try {
218
+ (ws as unknown as { close(code?: number, reason?: string): void }).close(1011, "internal error")
219
+ } catch {
220
+ /* swallow — ws may already be closed */
221
+ }
222
+ return Promise.resolve()
223
+ }
224
+
225
+ async function handleAlarm(): Promise<void> {
226
+ await config.ctx.storage.deleteAlarm()
227
+ }
228
+
229
+ return {
230
+ alarm: handleAlarm,
231
+ close: handleClose,
232
+ error: handleError,
233
+ fetch: handleFetch,
234
+ message: handleMessage,
235
+ }
236
+ }