1use std::fmt::Write as _;
10
11use crate::emitter::http_handler_method_name;
12use crate::project::UnitTable;
13use bynk_syntax::ast::*;
14
15pub fn emit_worker_entry(context: &str, table: &UnitTable) -> String {
16 let mut out = String::new();
17 let _ = writeln!(out, "// Generated by bynkc — do not edit by hand.");
18 let _ = writeln!(out, "// Worker entry point for context `{context}`.");
19 writeln!(out).unwrap();
20
21 let ctx_uses_send = table.services.values().any(|s| {
25 s.handlers
26 .iter()
27 .any(|h| crate::emitter::block_uses_send(&h.body))
28 });
29 let exec_param = if ctx_uses_send {
30 ", ctx: { waitUntil(promise: Promise<unknown>): void }"
31 } else {
32 ""
33 };
34 let compose_call = if ctx_uses_send {
35 "compose(env, ctx)"
36 } else {
37 "compose(env)"
38 };
39
40 let mut http_routes: Vec<HttpRoute> = Vec::new();
43 let mut service_names: Vec<&String> = table.services.keys().collect();
44 service_names.sort();
45 for sname in &service_names {
46 let service = table.services.get(*sname).unwrap();
47 for h in &service.handlers {
48 if let HandlerKind::Http { method, path } = &h.kind {
49 http_routes.push(HttpRoute {
50 service: (*sname).clone(),
51 method: *method,
52 path: path.clone(),
53 handler: h.clone(),
54 bearer: bynk_check::actors::bearer_seam_for(h, &table.actors).is_some(),
57 signature: bynk_check::actors::signature_seam_for(h, &table.actors),
58 sum: bynk_check::actors::sum_members_for(h, &table.actors).is_some(),
62 });
63 }
64 }
65 }
66 http_routes.sort_by(|a, b| {
67 param_count(&a.path)
71 .cmp(¶m_count(&b.path))
72 .then_with(|| a.method.as_str().cmp(b.method.as_str()))
73 .then_with(|| a.path.cmp(&b.path))
74 });
75
76 let mut cron_routes: Vec<CronRoute> = Vec::new();
80 for sname in &service_names {
81 let service = table.services.get(*sname).unwrap();
82 let mut cron_idx = 0usize;
83 for h in &service.handlers {
84 if let HandlerKind::Cron { expr } = &h.kind {
85 cron_routes.push(CronRoute {
86 service: (*sname).clone(),
87 index: cron_idx,
88 expr: expr.clone(),
89 has_param: !h.params.is_empty(),
90 });
91 cron_idx += 1;
92 }
93 }
94 }
95 cron_routes.sort_by(|a, b| a.expr.cmp(&b.expr));
96
97 let mut queue_routes: Vec<QueueRoute> = Vec::new();
100 for sname in &service_names {
101 let service = table.services.get(*sname).unwrap();
102 let mut queue_idx = 0usize;
103 for h in &service.handlers {
104 if let HandlerKind::Message = &h.kind {
105 let ServiceProtocol::Queue { name } = &service.protocol else {
108 continue;
109 };
110 let msg_type = h.params.first().map(|p| p.type_ref.clone());
111 queue_routes.push(QueueRoute {
112 service: (*sname).clone(),
113 index: queue_idx,
114 name: name.clone(),
115 msg_type,
116 });
117 queue_idx += 1;
118 }
119 }
120 }
121 queue_routes.sort_by(|a, b| a.name.cmp(&b.name));
122
123 let mut ws_open_routes: Vec<(&String, &Handler)> = Vec::new();
128 for sname in &service_names {
129 let service = table.services.get(*sname).unwrap();
130 for h in &service.handlers {
131 if matches!(h.kind, HandlerKind::Open) {
132 ws_open_routes.push((*sname, h));
133 }
134 }
135 }
136
137 let has_http = !http_routes.is_empty();
138
139 let mut imports: Vec<&str> = vec![
140 "Ok",
141 "Err",
142 "type Result",
143 "type JsonValue",
144 "type BoundaryError",
145 "boundaryError",
146 ];
147 if has_http {
148 imports.push("matchPath");
149 imports.push("httpResultToResponse");
150 }
151 if http_routes.iter().any(|r| r.signature.is_some()) {
153 imports.push("verifySignatureHmacSha256");
154 }
155 let _ = writeln!(
156 out,
157 "import {{ {} }} from \"../../runtime.js\";",
158 imports.join(", ")
159 );
160 let _ = writeln!(out, "import {{ compose, type Env }} from \"./compose.js\";");
161 let _ = writeln!(out, "import * as handlers from \"./handlers.js\";");
162 writeln!(out).unwrap();
163
164 let mut agent_names: Vec<&String> = table.agents.keys().collect();
169 agent_names.sort();
170 if !agent_names.is_empty() {
171 let joined = agent_names
172 .iter()
173 .map(|n| n.as_str())
174 .collect::<Vec<_>>()
175 .join(", ");
176 let _ = writeln!(out, "export {{ {joined} }} from \"./handlers.js\";");
177 writeln!(out).unwrap();
178 }
179
180 let _ = writeln!(out, "export default {{");
181 let _ = writeln!(
182 out,
183 " async fetch(request: Request, env: Env{exec_param}): Promise<Response> {{"
184 );
185 let _ = writeln!(out, " const url = new URL(request.url);");
186 let _ = writeln!(out, " const path = url.pathname;");
187 let _ = writeln!(out, " const method = request.method;");
188 let _ = writeln!(out, " const surface = {compose_call};");
189 let _ = writeln!(out, " try {{");
190
191 let _ = writeln!(out, " if (path.startsWith(\"/_bynk/call/\")) {{");
193 let _ = writeln!(
194 out,
195 " const servicePath = path.slice(\"/_bynk/call/\".length);"
196 );
197 let _ = writeln!(out, " switch (servicePath) {{");
198 for sname in &service_names {
199 let service = table.services.get(*sname).unwrap();
200 let Some(h) = service
201 .handlers
202 .iter()
203 .find(|h| matches!(h.kind, HandlerKind::Call))
204 else {
205 continue;
206 };
207
208 let _ = writeln!(out, " case \"{sname}\": {{");
209 let _ = writeln!(
210 out,
211 " const args = await request.json() as JsonValue;"
212 );
213 emit_call_handler_dispatch(&mut out, sname, h, &table.actors);
214 let _ = writeln!(out, " }}");
215 }
216 let _ = writeln!(out, " default:");
217 let _ = writeln!(
218 out,
219 " return new Response(\"Not found\", {{ status: 404 }});"
220 );
221 let _ = writeln!(out, " }}");
222 let _ = writeln!(out, " }}");
223 writeln!(out).unwrap();
224
225 if !ws_open_routes.is_empty() {
231 let _ = writeln!(
232 out,
233 " if (request.headers.get(\"Upgrade\") === \"websocket\") {{"
234 );
235 for (sname, h) in &ws_open_routes {
236 let mut args: Vec<String> = vec!["request".to_string()];
237 for p in &h.params {
238 let pn = &p.name.name;
239 let _ = writeln!(
240 out,
241 " const __ws_{pn} = url.searchParams.get(\"{pn}\");"
242 );
243 let _ = writeln!(
244 out,
245 " if (__ws_{pn} === null) return new Response(\"Missing parameter: {pn}\", {{ status: 400 }});"
246 );
247 args.push(format!("__ws_{pn}"));
248 }
249 let _ = writeln!(
250 out,
251 " return surface.ws_{sname}_open({});",
252 args.join(", ")
253 );
254 }
255 let _ = writeln!(out, " }}");
256 writeln!(out).unwrap();
257 }
258
259 for route in &http_routes {
261 emit_http_route_dispatch(&mut out, route);
262 }
263
264 let _ = writeln!(
265 out,
266 " return new Response(\"Not Found\", {{ status: 404 }});"
267 );
268 let _ = writeln!(out, " }} catch {{");
269 let _ = writeln!(
270 out,
271 " return new Response(\"Internal Server Error\", {{ status: 500 }});"
272 );
273 let _ = writeln!(out, " }}");
274 let _ = writeln!(out, " }},");
275
276 if !cron_routes.is_empty() {
280 emit_scheduled_handler(&mut out, &cron_routes, exec_param, compose_call);
281 }
282
283 if !queue_routes.is_empty() {
287 emit_queue_handler(&mut out, &queue_routes, exec_param, compose_call);
288 }
289
290 let _ = writeln!(out, "}};");
291
292 out
293}
294
295fn emit_scheduled_handler(
300 out: &mut String,
301 cron_routes: &[CronRoute],
302 exec_param: &str,
303 compose_call: &str,
304) {
305 let _ = writeln!(
306 out,
307 " async scheduled(event: {{ readonly cron: string; readonly scheduledTime: number }}, env: Env{exec_param}): Promise<void> {{"
308 );
309 let _ = writeln!(out, " const surface = {compose_call};");
310 let _ = writeln!(out, " switch (event.cron) {{");
311 for route in cron_routes {
312 let method_key = crate::emitter::cron_handler_method_name(&route.service, route.index);
313 let expr_lit = route.expr.replace('\\', "\\\\").replace('"', "\\\"");
314 let arg = if route.has_param {
316 "event.scheduledTime"
317 } else {
318 ""
319 };
320 let _ = writeln!(out, " case \"{expr_lit}\": {{");
321 let _ = writeln!(
322 out,
323 " const result = await surface.{method_key}({arg});"
324 );
325 let _ = writeln!(
326 out,
327 " if (result.tag === \"Err\") console.error(\"cron {expr_lit} failed\", result.error);"
328 );
329 let _ = writeln!(out, " return;");
330 let _ = writeln!(out, " }}");
331 }
332 let _ = writeln!(out, " default:");
333 let _ = writeln!(out, " return;");
334 let _ = writeln!(out, " }}");
335 let _ = writeln!(out, " }},");
336}
337
338fn emit_queue_handler(
344 out: &mut String,
345 queue_routes: &[QueueRoute],
346 exec_param: &str,
347 compose_call: &str,
348) {
349 let _ = writeln!(
350 out,
351 " async queue(batch: {{ readonly queue: string; readonly messages: ReadonlyArray<{{ readonly body: unknown; ack(): void; retry(): void }}> }}, env: Env{exec_param}): Promise<void> {{"
352 );
353 let _ = writeln!(out, " const surface = {compose_call};");
354 let _ = writeln!(out, " switch (batch.queue) {{");
355 for route in queue_routes {
356 let method_key = crate::emitter::queue_handler_method_name(&route.service, route.index);
357 let name_lit = route.name.replace('\\', "\\\\").replace('"', "\\\"");
358 let dser = match &route.msg_type {
359 Some(t) => deserialise_call(t, "(msg.body as JsonValue)", "$"),
360 None => "Ok(msg.body as any) as Result<any, BoundaryError>".to_string(),
361 };
362 let _ = writeln!(out, " case \"{name_lit}\": {{");
363 let _ = writeln!(out, " for (const msg of batch.messages) {{");
364 let _ = writeln!(out, " try {{");
365 let _ = writeln!(out, " const __r = {dser};");
366 let _ = writeln!(
367 out,
368 " if (__r.tag === \"Err\") {{ console.error(\"queue {name_lit} deserialise failed\", __r.error); msg.retry(); continue; }}"
369 );
370 let _ = writeln!(
371 out,
372 " const result = await surface.{method_key}(__r.value);"
373 );
374 let _ = writeln!(out, " if (result.tag === \"Ack\") msg.ack();");
375 let _ = writeln!(
376 out,
377 " else {{ console.error(\"queue {name_lit} retry\", result.reason); msg.retry(); }}"
378 );
379 let _ = writeln!(out, " }} catch (e) {{");
380 let _ = writeln!(
381 out,
382 " console.error(\"queue {name_lit} threw\", e); msg.retry();"
383 );
384 let _ = writeln!(out, " }}");
385 let _ = writeln!(out, " }}");
386 let _ = writeln!(out, " return;");
387 let _ = writeln!(out, " }}");
388 }
389 let _ = writeln!(out, " default:");
390 let _ = writeln!(out, " return;");
391 let _ = writeln!(out, " }}");
392 let _ = writeln!(out, " }},");
393}
394
395fn param_count(path: &str) -> usize {
397 path.split('/')
398 .filter(|s| s.starts_with(':') && s.len() > 1)
399 .count()
400}
401
402#[derive(Debug, Clone)]
403struct HttpRoute {
404 service: String,
405 method: HttpMethod,
406 path: String,
407 handler: Handler,
408 bearer: bool,
412 signature: Option<bynk_check::actors::SignatureSeam>,
416 sum: bool,
420}
421
422#[derive(Debug, Clone)]
425struct CronRoute {
426 service: String,
427 index: usize,
428 expr: String,
429 has_param: bool,
431}
432
433#[derive(Debug, Clone)]
437struct QueueRoute {
438 service: String,
439 index: usize,
440 name: String,
441 msg_type: Option<TypeRef>,
442}
443
444fn emit_http_route_dispatch(out: &mut String, route: &HttpRoute) {
449 let h = &route.handler;
450 let method_key = http_handler_method_name(route.method, &route.path);
451 let has_path_params = param_count(&route.path) > 0;
452 let path_lit = route.path.replace('"', "\\\"");
453 let _ = writeln!(out, " {{");
454 if has_path_params {
455 let _ = writeln!(out, " const __m = matchPath(\"{path_lit}\", path);");
456 let _ = writeln!(
457 out,
458 " if (method === \"{}\" && __m) {{",
459 route.method.as_str()
460 );
461 } else {
462 let _ = writeln!(
463 out,
464 " if (method === \"{}\" && path === \"{path_lit}\") {{",
465 route.method.as_str()
466 );
467 }
468 let mut call_args: Vec<String> = Vec::new();
470 for p in &h.params {
471 let pname = &p.name.name;
472 if pname == "body" {
473 continue;
475 }
476 let _ = writeln!(
478 out,
479 " const __raw_{pname} = __m.params[\"{pname}\"];"
480 );
481 emit_path_param_construction(out, pname, &p.type_ref);
482 call_args.push(pname.clone());
483 }
484 if !route.sum
488 && let Some(body_param) = h.params.iter().find(|p| p.name.name == "body")
489 {
490 let _ = writeln!(out, " let __body_json: JsonValue;");
491 if let Some(seam) = &route.signature {
492 let secret = seam.secret.replace('\\', "\\\\").replace('"', "\\\"");
496 let header = seam.header.replace('\\', "\\\\").replace('"', "\\\"");
497 let _ = writeln!(out, " let __raw: string;");
498 let _ = writeln!(out, " try {{");
499 let _ = writeln!(out, " __raw = await request.text();");
500 let _ = writeln!(out, " }} catch {{");
501 let _ = writeln!(
502 out,
503 " return new Response(JSON.stringify({{ kind: \"MalformedJson\", details: \"Invalid request body\" }}), {{ status: 400, headers: {{ \"content-type\": \"application/json\" }} }});"
504 );
505 let _ = writeln!(out, " }}");
506 let _ = writeln!(
507 out,
508 " const __secret = (env as Record<string, unknown>)[\"{secret}\"] ?? (globalThis as {{ process?: {{ env?: Record<string, unknown> }} }}).process?.env?.[\"{secret}\"];"
509 );
510 let _ = writeln!(
511 out,
512 " if (typeof __secret !== \"string\") return new Response(null, {{ status: 401 }});"
513 );
514 let ts_expr = match &seam.timestamp_header {
516 Some(th) => {
517 let th = th.replace('\\', "\\\\").replace('"', "\\\"");
518 let _ = writeln!(out, " const __ts = request.headers.get(\"{th}\");");
519 let _ = writeln!(
520 out,
521 " if (__ts === null) return new Response(null, {{ status: 401 }});"
522 );
523 "__ts"
524 }
525 None => "null",
526 };
527 let tol = match seam.tolerance_secs {
528 Some(n) => n.to_string(),
529 None => "null".to_string(),
530 };
531 let _ = writeln!(
532 out,
533 " const __ok = await verifySignatureHmacSha256(__raw, __secret, request.headers.get(\"{header}\"), {ts_expr}, {tol});"
534 );
535 let _ = writeln!(
536 out,
537 " if (!__ok) return new Response(null, {{ status: 401 }});"
538 );
539 let _ = writeln!(out, " try {{");
540 let _ = writeln!(
541 out,
542 " __body_json = JSON.parse(__raw) as JsonValue;"
543 );
544 let _ = writeln!(out, " }} catch {{");
545 let _ = writeln!(
546 out,
547 " return new Response(JSON.stringify({{ kind: \"MalformedJson\", details: \"Invalid request body\" }}), {{ status: 400, headers: {{ \"content-type\": \"application/json\" }} }});"
548 );
549 let _ = writeln!(out, " }}");
550 } else {
551 let _ = writeln!(out, " try {{");
552 let _ = writeln!(
553 out,
554 " __body_json = (await request.json()) as JsonValue;"
555 );
556 let _ = writeln!(out, " }} catch {{");
557 let _ = writeln!(
558 out,
559 " return new Response(JSON.stringify({{ kind: \"MalformedJson\", details: \"Invalid request body\" }}), {{ status: 400, headers: {{ \"content-type\": \"application/json\" }} }});"
560 );
561 let _ = writeln!(out, " }}");
562 }
563 let dser = deserialise_call(&body_param.type_ref, "__body_json", "$");
564 let _ = writeln!(out, " const __r_body = {dser};");
565 let _ = writeln!(
566 out,
567 " if (__r_body.tag === \"Err\") return new Response(JSON.stringify(__r_body.error), {{ status: 400, headers: {{ \"content-type\": \"application/json\" }} }});"
568 );
569 let _ = writeln!(out, " const body = __r_body.value;");
570 call_args.push("body".to_string());
571 }
572 let surface_args = if route.bearer || route.sum {
576 let mut a = vec!["request".to_string()];
580 a.extend(call_args.iter().cloned());
581 a.join(", ")
582 } else {
583 call_args.join(", ")
584 };
585 let _ = writeln!(
586 out,
587 " const result = await surface.{method_key}({surface_args});",
588 );
589 let _ = route.service.as_str();
590 let inner = http_result_inner(&h.return_type);
591 let ser_fn = http_value_serialiser(&inner);
592 let _ = writeln!(
593 out,
594 " return httpResultToResponse(result, {ser_fn});"
595 );
596 let _ = writeln!(out, " }}");
597 let _ = writeln!(out, " }}");
598}
599
600fn emit_call_handler_dispatch(
604 out: &mut String,
605 sname: &str,
606 h: &Handler,
607 actors: &std::collections::HashMap<String, bynk_syntax::ast::ActorDecl>,
608) {
609 let caller_args = if bynk_check::actors::caller_binder_for(h, actors).is_some() {
614 let _ = writeln!(
615 out,
616 " const __caller = request.headers.get(\"X-Bynk-Caller\");"
617 );
618 let _ = writeln!(
619 out,
620 " if (__caller === null || __caller === \"\") return new Response(JSON.stringify({{ kind: \"Unauthorized\", details: \"missing caller identity\" }}), {{ status: 401, headers: {{ \"content-type\": \"application/json\" }} }});"
621 );
622 "__caller, "
623 } else {
624 ""
625 };
626 if h.params.len() == 1 {
627 let p = &h.params[0];
628 let pname = &p.name.name;
629 let dser_call = deserialise_call(&p.type_ref, "args", "$");
630 let _ = writeln!(out, " const __r_{pname} = {dser_call};");
631 let _ = writeln!(
632 out,
633 " if (__r_{pname}.tag === \"Err\") return new Response(JSON.stringify(__r_{pname}.error), {{ status: 400, headers: {{ \"content-type\": \"application/json\" }} }});"
634 );
635 let _ = writeln!(out, " const {pname} = __r_{pname}.value;");
636 let _ = writeln!(
637 out,
638 " const result = await surface.{sname}({caller_args}{pname});"
639 );
640 } else {
641 let _ = writeln!(
642 out,
643 " if (typeof args !== \"object\" || args === null || Array.isArray(args)) return new Response(JSON.stringify({{ kind: \"StructuralMismatch\", path: \"$\", expected: \"object\", actual: typeof args }}), {{ status: 400, headers: {{ \"content-type\": \"application/json\" }} }});"
644 );
645 let _ = writeln!(
646 out,
647 " const argsObj = args as {{ [k: string]: JsonValue }};"
648 );
649 let mut names = Vec::new();
650 for p in &h.params {
651 let pname = &p.name.name;
652 let dser = deserialise_call(
653 &p.type_ref,
654 &format!("argsObj[\"{pname}\"]"),
655 &format!("$.{pname}"),
656 );
657 let _ = writeln!(out, " const __r_{pname} = {dser};");
658 let _ = writeln!(
659 out,
660 " if (__r_{pname}.tag === \"Err\") return new Response(JSON.stringify(__r_{pname}.error), {{ status: 400, headers: {{ \"content-type\": \"application/json\" }} }});"
661 );
662 let _ = writeln!(out, " const {pname} = __r_{pname}.value;");
663 names.push(pname.clone());
664 }
665 let mut call_args: Vec<&str> = Vec::new();
668 if !caller_args.is_empty() {
669 call_args.push("__caller");
670 }
671 call_args.extend(names.iter().map(String::as_str));
672 let _ = writeln!(
673 out,
674 " const result = await surface.{sname}({});",
675 call_args.join(", ")
676 );
677 }
678 let ser_expr = serialise_call(&h.return_type, "result");
679 let _ = writeln!(out, " const body = {ser_expr};");
680 let _ = writeln!(
681 out,
682 " return new Response(JSON.stringify(body), {{ status: 200, headers: {{ \"content-type\": \"application/json\" }} }});"
683 );
684}
685
686fn emit_path_param_construction(out: &mut String, pname: &str, t: &TypeRef) {
691 match t {
692 TypeRef::Base(BaseType::String, _) => {
693 let _ = writeln!(out, " const {pname} = __raw_{pname};");
694 }
695 TypeRef::Named(id) => {
696 let _ = writeln!(
697 out,
698 " const __r_{pname} = handlers.{}.of(__raw_{pname});",
699 id.name
700 );
701 let _ = writeln!(
702 out,
703 " if (__r_{pname}.tag === \"Err\") return new Response(JSON.stringify({{ kind: \"RefinementViolation\", path: \"path.{pname}\", violation: __r_{pname}.error }}), {{ status: 400, headers: {{ \"content-type\": \"application/json\" }} }});"
704 );
705 let _ = writeln!(out, " const {pname} = __r_{pname}.value;");
706 }
707 _ => {
708 let _ = writeln!(out, " const {pname} = __raw_{pname} as any;");
710 }
711 }
712}
713
714fn http_result_inner(t: &TypeRef) -> TypeRef {
718 let inner = match t {
719 TypeRef::Effect(t, _) => t.as_ref(),
720 other => other,
721 };
722 match inner {
723 TypeRef::HttpResult(payload, _) => (**payload).clone(),
724 other => other.clone(),
725 }
726}
727
728fn http_value_serialiser(t: &TypeRef) -> String {
732 match t {
733 TypeRef::Base(_, _) => "(v: any) => v as JsonValue".to_string(),
734 TypeRef::Fn(..) | TypeRef::Query(..) | TypeRef::Stream(..) | TypeRef::Connection(..) => {
738 unreachable!("function/query/stream types are rejected at boundaries")
739 }
740 TypeRef::Unit(_) => "(_v: any) => null".to_string(),
741 TypeRef::Named(id) => format!("handlers.serialise_{}", id.name),
742 TypeRef::Result(_, _, _)
743 | TypeRef::Option(_, _)
744 | TypeRef::List(_, _)
745 | TypeRef::Map(_, _, _) => {
746 let inst_name = inner_ts_name(t);
747 format!("handlers.serialise_{inst_name}")
748 }
749 TypeRef::Effect(inner, _) => http_value_serialiser(inner),
750 TypeRef::HttpResult(_, _)
751 | TypeRef::QueueResult(_)
752 | TypeRef::ValidationError(_)
753 | TypeRef::JsonError(_) => "(v: any) => v as JsonValue".to_string(),
754 }
755}
756
757pub(crate) fn deserialise_call(t: &TypeRef, json_expr: &str, path: &str) -> String {
758 match t {
759 TypeRef::Fn(..) | TypeRef::Query(..) | TypeRef::Stream(..) | TypeRef::Connection(..) => {
763 unreachable!("function/query/stream types are rejected at boundaries")
764 }
765 TypeRef::Base(BaseType::Bytes, _) => {
771 format!(
772 "(typeof {json_expr} === \"string\" ? ((__b) => __b.tag === \"Some\" ? Ok(__b.value) : Err({{ kind: \"StructuralMismatch\", path: \"{path}\", expected: \"base64 string\", actual: \"invalid base64\" }})) (__bynkBytesFromBase64({json_expr})) : Err({{ kind: \"StructuralMismatch\", path: \"{path}\", expected: \"base64 string\", actual: typeof {json_expr} }})) as Result<any, BoundaryError>"
773 )
774 }
775 TypeRef::Base(b, _) => {
776 let typeof_str = match b {
777 BaseType::Int => "number",
778 BaseType::String => "string",
779 BaseType::Bool => "boolean",
780 BaseType::Float => "number",
781 BaseType::Duration | BaseType::Instant => "number",
782 BaseType::Bytes => "string",
784 };
785 if *b == BaseType::Int || *b == BaseType::Duration || *b == BaseType::Instant {
788 return format!(
789 "(typeof {json_expr} === \"number\" && Number.isInteger({json_expr}) ? Ok({json_expr}) : Err({{ kind: \"StructuralMismatch\", path: \"{path}\", expected: \"integer\", actual: String({json_expr}) }}) as Result<any, BoundaryError>)"
790 );
791 }
792 if *b == BaseType::Float {
794 return format!(
795 "(typeof {json_expr} === \"number\" && Number.isFinite({json_expr}) ? Ok({json_expr}) : Err({{ kind: \"StructuralMismatch\", path: \"{path}\", expected: \"finite number\", actual: String({json_expr}) }}) as Result<any, BoundaryError>)"
796 );
797 }
798 format!(
799 "(typeof {json_expr} === \"{typeof_str}\" ? Ok({json_expr}) : Err({{ kind: \"StructuralMismatch\", path: \"{path}\", expected: \"{typeof_str}\", actual: typeof {json_expr} }}) as Result<any, BoundaryError>)"
800 )
801 }
802 TypeRef::Named(id) => {
803 format!("handlers.deserialise_{}({json_expr}, \"{path}\")", id.name)
804 }
805 TypeRef::Result(_, _, _)
806 | TypeRef::Option(_, _)
807 | TypeRef::List(_, _)
808 | TypeRef::Map(_, _, _) => {
809 let inst_name = inner_ts_name(t);
810 format!("handlers.deserialise_{inst_name}({json_expr}, \"{path}\")")
811 }
812 TypeRef::Effect(inner, _) => deserialise_call(inner, json_expr, path),
813 TypeRef::HttpResult(_, _)
814 | TypeRef::QueueResult(_)
815 | TypeRef::ValidationError(_)
816 | TypeRef::JsonError(_)
817 | TypeRef::Unit(_) => {
818 format!("Ok({json_expr} as any) as Result<any, BoundaryError>")
819 }
820 }
821}
822
823fn serialise_call(t: &TypeRef, value: &str) -> String {
824 match t {
825 TypeRef::Base(BaseType::Float, _) => format!(
828 "((v: number) => {{ if (!Number.isFinite(v)) throw new Error(\"non-finite Float at boundary\"); return v as JsonValue; }})({value})"
829 ),
830 TypeRef::Base(_, _) => format!("{value} as JsonValue"),
831 TypeRef::Fn(..) | TypeRef::Query(..) | TypeRef::Stream(..) | TypeRef::Connection(..) => {
835 unreachable!("function/query/stream types are rejected at boundaries")
836 }
837 TypeRef::Named(id) => format!("handlers.serialise_{}({value})", id.name),
838 TypeRef::Result(_, _, _)
839 | TypeRef::Option(_, _)
840 | TypeRef::List(_, _)
841 | TypeRef::Map(_, _, _) => {
842 let inst_name = inner_ts_name(t);
843 format!("handlers.serialise_{inst_name}({value})")
844 }
845 TypeRef::Effect(inner, _) => serialise_call(inner, value),
846 TypeRef::Unit(_) => "null".to_string(),
849 TypeRef::HttpResult(_, _)
850 | TypeRef::QueueResult(_)
851 | TypeRef::ValidationError(_)
852 | TypeRef::JsonError(_) => {
853 format!("{value} as JsonValue")
854 }
855 }
856}
857
858fn inner_ts_name(t: &TypeRef) -> String {
859 match t {
860 TypeRef::Base(b, _) => b.name().to_string(),
861 TypeRef::Fn(..) | TypeRef::Query(..) | TypeRef::Stream(..) | TypeRef::Connection(..) => {
865 unreachable!("function/query/stream types are rejected at boundaries")
866 }
867 TypeRef::Named(id) => id.name.clone(),
868 TypeRef::Result(a, b, _) => format!("Result_{}_{}", inner_ts_name(a), inner_ts_name(b)),
869 TypeRef::Option(a, _) => format!("Option_{}", inner_ts_name(a)),
870 TypeRef::Effect(a, _) => format!("Effect_{}", inner_ts_name(a)),
871 TypeRef::HttpResult(a, _) => format!("HttpResult_{}", inner_ts_name(a)),
872 TypeRef::List(a, _) => format!("List_{}", inner_ts_name(a)),
873 TypeRef::Map(k, v, _) => format!("Map_{}_{}", inner_ts_name(k), inner_ts_name(v)),
874 TypeRef::QueueResult(_) => "QueueResult".to_string(),
875 TypeRef::ValidationError(_) => "ValidationError".to_string(),
876 TypeRef::JsonError(_) => "JsonError".to_string(),
877 TypeRef::Unit(_) => "Unit".to_string(),
878 }
879}