1use std::fmt::Write as _;
14
15use bynk_syntax::ast::*;
16
17pub fn collect_boundary_types(
23 types: &std::collections::HashMap<String, TypeDecl>,
24 services: &std::collections::HashMap<String, ServiceDecl>,
25 agents: &std::collections::HashMap<String, AgentDecl>,
30) -> Vec<String> {
31 let mut seen: std::collections::HashSet<String> = std::collections::HashSet::new();
32 let mut out: Vec<String> = Vec::new();
33 let mut stack: Vec<String> = Vec::new();
34
35 let mut svc_names: Vec<&String> = services.keys().collect();
36 svc_names.sort();
37 for name in svc_names {
38 let service = &services[name];
39 for h in &service.handlers {
40 for p in &h.params {
41 collect_type_names(&p.type_ref, &mut stack);
42 }
43 collect_type_names(&h.return_type, &mut stack);
44 }
45 }
46
47 let mut agent_names: Vec<&String> = agents.keys().collect();
48 agent_names.sort();
49 for name in agent_names {
50 for f in &agents[name].store_fields {
51 for arg in &f.kind.args {
52 collect_type_names(arg, &mut stack);
53 }
54 }
55 }
56
57 while let Some(name) = stack.pop() {
58 if !seen.insert(name.clone()) {
59 continue;
60 }
61 out.push(name.clone());
62 let Some(decl) = types.get(&name) else {
63 continue;
64 };
65 match &decl.body {
66 TypeBody::Record(r) => {
67 for f in &r.fields {
68 collect_type_names(&f.type_ref, &mut stack);
69 }
70 }
71 TypeBody::Sum(s) => {
72 for v in &s.variants {
73 for p in &v.payload {
74 collect_type_names(&p.type_ref, &mut stack);
75 }
76 }
77 }
78 TypeBody::Refined { .. } | TypeBody::Opaque { .. } => {}
79 }
80 }
81
82 out.sort();
83 out
84}
85
86fn collect_type_names(t: &TypeRef, stack: &mut Vec<String>) {
87 match t {
88 TypeRef::Named(id) => stack.push(id.name.clone()),
89 TypeRef::Query(..) | TypeRef::Stream(..) | TypeRef::Connection(..) => {}
92 TypeRef::Fn(..) => {}
95 TypeRef::Result(a, b, _) => {
96 collect_type_names(a, stack);
97 collect_type_names(b, stack);
98 }
99 TypeRef::Option(a, _) => collect_type_names(a, stack),
100 TypeRef::Effect(a, _) => collect_type_names(a, stack),
101 TypeRef::HttpResult(a, _) => collect_type_names(a, stack),
102 TypeRef::List(a, _) => collect_type_names(a, stack),
105 TypeRef::Map(k, v, _) => {
106 collect_type_names(k, stack);
107 collect_type_names(v, stack);
108 }
109 TypeRef::Base(_, _)
110 | TypeRef::QueueResult(_)
111 | TypeRef::ValidationError(_)
112 | TypeRef::JsonError(_)
113 | TypeRef::Unit(_) => {}
114 }
115}
116
117pub fn emit_helpers_for_owner(
122 out: &mut String,
123 type_names: &[String],
124 types: &std::collections::HashMap<String, TypeDecl>,
125 _owner_qualified: &str,
126) {
127 let mut emitted_any = false;
131 for name in type_names {
132 let Some(decl) = types.get(name) else {
133 continue;
134 };
135 emitted_any = true;
136 emit_one(out, name, decl);
137 }
138 if emitted_any {
139 writeln!(out).unwrap();
140 }
141}
142
143fn emit_one(out: &mut String, name: &str, decl: &TypeDecl) {
144 match &decl.body {
145 TypeBody::Refined { base, .. } => emit_refined(out, name, *base, decl),
146 TypeBody::Opaque { base, .. } => emit_refined(out, name, *base, decl),
147 TypeBody::Record(r) => emit_record(out, name, r),
148 TypeBody::Sum(s) => emit_sum(out, name, s),
149 }
150}
151
152fn ts_base_for_serialisation(b: BaseType) -> &'static str {
153 match b {
154 BaseType::Int => "number",
155 BaseType::String => "string",
156 BaseType::Bool => "boolean",
157 BaseType::Float => "number",
158 BaseType::Duration | BaseType::Instant => "number",
159 BaseType::Bytes => "string",
161 }
162}
163
164fn emit_bytes_named_codec(out: &mut String, name: &str) {
171 writeln!(
172 out,
173 "export function serialise_{name}(value: {name}): JsonValue {{"
174 )
175 .unwrap();
176 writeln!(
177 out,
178 " return __bynkBytesToBase64(value as unknown as Uint8Array);"
179 )
180 .unwrap();
181 writeln!(out, "}}").unwrap();
182 writeln!(out).unwrap();
183
184 writeln!(
185 out,
186 "export function deserialise_{name}(json: JsonValue, path: string = \"$\"): Result<{name}, BoundaryError> {{"
187 )
188 .unwrap();
189 writeln!(out, " if (typeof json !== \"string\") {{").unwrap();
190 writeln!(
191 out,
192 " return Err({{ kind: \"StructuralMismatch\", path, expected: \"base64 string\", actual: typeof json }});"
193 )
194 .unwrap();
195 writeln!(out, " }}").unwrap();
196 writeln!(out, " const __b = __bynkBytesFromBase64(json);").unwrap();
197 writeln!(out, " if (__b.tag === \"None\") {{").unwrap();
198 writeln!(
199 out,
200 " return Err({{ kind: \"StructuralMismatch\", path, expected: \"base64 string\", actual: \"invalid base64\" }});"
201 )
202 .unwrap();
203 writeln!(out, " }}").unwrap();
204 writeln!(out, " return Ok(__b.value as unknown as {name});").unwrap();
205 writeln!(out, "}}").unwrap();
206 writeln!(out).unwrap();
207}
208
209fn emit_refined(out: &mut String, name: &str, base: BaseType, _decl: &TypeDecl) {
210 if base == BaseType::Bytes {
212 emit_bytes_named_codec(out, name);
213 return;
214 }
215 let prim = ts_base_for_serialisation(base);
216 let typeof_str = match base {
217 BaseType::Int => "number",
218 BaseType::String => "string",
219 BaseType::Bool => "boolean",
220 BaseType::Float => "number",
221 BaseType::Duration | BaseType::Instant => "number",
222 BaseType::Bytes => "string",
224 };
225 writeln!(
226 out,
227 "export function serialise_{name}(value: {name}): JsonValue {{"
228 )
229 .unwrap();
230 writeln!(out, " return value as unknown as {prim};").unwrap();
231 writeln!(out, "}}").unwrap();
232 writeln!(out).unwrap();
233
234 writeln!(
235 out,
236 "export function deserialise_{name}(json: JsonValue, path: string = \"$\"): Result<{name}, BoundaryError> {{"
237 )
238 .unwrap();
239 writeln!(out, " if (typeof json !== \"{typeof_str}\") {{").unwrap();
240 writeln!(
241 out,
242 " return Err({{ kind: \"StructuralMismatch\", path, expected: \"{typeof_str}\", actual: typeof json }});"
243 )
244 .unwrap();
245 writeln!(out, " }}").unwrap();
246 writeln!(
250 out,
251 " const validated = (typeof ({name} as any).of === \"function\")"
252 )
253 .unwrap();
254 writeln!(out, " ? ({name} as any).of(json)").unwrap();
255 writeln!(out, " : Ok(json as unknown as {name});").unwrap();
256 writeln!(out, " if (validated.tag === \"Err\") {{").unwrap();
257 writeln!(
258 out,
259 " return Err({{ kind: \"RefinementViolation\", path, violation: validated.error }});"
260 )
261 .unwrap();
262 writeln!(out, " }}").unwrap();
263 writeln!(out, " return Ok(validated.value as {name});").unwrap();
264 writeln!(out, "}}").unwrap();
265 writeln!(out).unwrap();
266}
267
268fn emit_record(out: &mut String, name: &str, body: &RecordBody) {
269 writeln!(
271 out,
272 "export function serialise_{name}(value: {name}): JsonValue {{"
273 )
274 .unwrap();
275 writeln!(out, " return {{").unwrap();
276 for f in &body.fields {
277 let fname = &f.name.name;
278 let expr = serialise_field_expr(&f.type_ref, &format!("value.{fname}"));
279 writeln!(out, " {fname}: {expr},").unwrap();
280 }
281 writeln!(out, " }};").unwrap();
282 writeln!(out, "}}").unwrap();
283 writeln!(out).unwrap();
284
285 writeln!(
287 out,
288 "export function deserialise_{name}(json: JsonValue, path: string = \"$\"): Result<{name}, BoundaryError> {{"
289 )
290 .unwrap();
291 writeln!(
292 out,
293 " if (typeof json !== \"object\" || json === null || Array.isArray(json)) {{"
294 )
295 .unwrap();
296 writeln!(
297 out,
298 " return Err({{ kind: \"StructuralMismatch\", path, expected: \"object\", actual: typeof json }});"
299 )
300 .unwrap();
301 writeln!(out, " }}").unwrap();
302 writeln!(out, " const obj = json as {{ [k: string]: JsonValue }};").unwrap();
303 for f in &body.fields {
304 let fname = &f.name.name;
305 let access = format!("obj[\"{fname}\"]");
306 let sub_path = format!("`${{path}}.{fname}`");
307 emit_field_deserialise(out, fname, &f.type_ref, &access, &sub_path);
308 }
309 write!(out, " return Ok({{ ").unwrap();
310 let parts: Vec<String> = body
311 .fields
312 .iter()
313 .map(|f| format!("{}: __{}", f.name.name, f.name.name))
314 .collect();
315 write!(out, "{}", parts.join(", ")).unwrap();
316 writeln!(out, " }} as {name});").unwrap();
317 writeln!(out, "}}").unwrap();
318 writeln!(out).unwrap();
319}
320
321fn emit_sum(out: &mut String, name: &str, body: &SumBody) {
322 writeln!(
323 out,
324 "export function serialise_{name}(value: {name}): JsonValue {{"
325 )
326 .unwrap();
327 writeln!(out, " switch (value.tag) {{").unwrap();
328 for v in &body.variants {
329 let vname = &v.name.name;
330 if v.payload.is_empty() {
331 writeln!(out, " case \"{vname}\":").unwrap();
332 writeln!(out, " return {{ kind: \"{vname}\" }};").unwrap();
333 } else {
334 writeln!(out, " case \"{vname}\": {{").unwrap();
335 write!(out, " return {{ kind: \"{vname}\"").unwrap();
336 for f in &v.payload {
337 let fname = &f.name.name;
338 let expr = serialise_field_expr(&f.type_ref, &format!("(value as any).{fname}"));
339 write!(out, ", {fname}: {expr}").unwrap();
340 }
341 writeln!(out, " }};").unwrap();
342 writeln!(out, " }}").unwrap();
343 }
344 }
345 writeln!(out, " }}").unwrap();
346 writeln!(out, "}}").unwrap();
347 writeln!(out).unwrap();
348
349 writeln!(
350 out,
351 "export function deserialise_{name}(json: JsonValue, path: string = \"$\"): Result<{name}, BoundaryError> {{"
352 )
353 .unwrap();
354 writeln!(
355 out,
356 " if (typeof json !== \"object\" || json === null || Array.isArray(json)) {{"
357 )
358 .unwrap();
359 writeln!(
360 out,
361 " return Err({{ kind: \"StructuralMismatch\", path, expected: \"object\", actual: typeof json }});"
362 )
363 .unwrap();
364 writeln!(out, " }}").unwrap();
365 writeln!(out, " const obj = json as {{ [k: string]: JsonValue }};").unwrap();
366 writeln!(out, " const kind = obj[\"kind\"];").unwrap();
367 writeln!(out, " switch (kind) {{").unwrap();
368 for v in &body.variants {
369 let vname = &v.name.name;
370 if v.payload.is_empty() {
371 writeln!(out, " case \"{vname}\":").unwrap();
372 writeln!(out, " return Ok({{ tag: \"{vname}\" }} as {name});").unwrap();
373 } else {
374 writeln!(out, " case \"{vname}\": {{").unwrap();
375 for f in &v.payload {
376 let fname = &f.name.name;
377 let access = format!("obj[\"{fname}\"]");
378 let sub_path = format!("`${{path}}.{fname}`");
379 emit_field_deserialise(out, fname, &f.type_ref, &access, &sub_path);
380 }
381 write!(out, " return Ok({{ tag: \"{vname}\"").unwrap();
382 for f in &v.payload {
383 let fname = &f.name.name;
384 write!(out, ", {fname}: __{fname}").unwrap();
385 }
386 writeln!(out, " }} as {name});").unwrap();
387 writeln!(out, " }}").unwrap();
388 }
389 }
390 writeln!(out, " default:").unwrap();
391 writeln!(
392 out,
393 " return Err({{ kind: \"StructuralMismatch\", path, expected: \"sum variant kind\", actual: String(kind) }});"
394 )
395 .unwrap();
396 writeln!(out, " }}").unwrap();
397 writeln!(out, "}}").unwrap();
398 writeln!(out).unwrap();
399}
400
401fn emit_field_deserialise(out: &mut String, name: &str, t: &TypeRef, json: &str, path_expr: &str) {
404 match t {
405 TypeRef::Fn(..) | TypeRef::Query(..) | TypeRef::Stream(..) | TypeRef::Connection(..) => {
409 unreachable!("function/query/stream types are rejected at boundaries")
410 }
411 TypeRef::Base(BaseType::Bytes, _) => {
416 writeln!(out, " if (typeof {json} !== \"string\") {{").unwrap();
417 writeln!(
418 out,
419 " return Err({{ kind: \"StructuralMismatch\", path: {path_expr}, expected: \"base64 string\", actual: typeof {json} }});"
420 )
421 .unwrap();
422 writeln!(out, " }}").unwrap();
423 writeln!(out, " const __b_{name} = __bynkBytesFromBase64({json});").unwrap();
424 writeln!(out, " if (__b_{name}.tag === \"None\") {{").unwrap();
425 writeln!(
426 out,
427 " return Err({{ kind: \"StructuralMismatch\", path: {path_expr}, expected: \"base64 string\", actual: \"invalid base64\" }});"
428 )
429 .unwrap();
430 writeln!(out, " }}").unwrap();
431 writeln!(out, " const __{name} = __b_{name}.value;").unwrap();
432 }
433 TypeRef::Base(b, _) => {
434 let typeof_str = match b {
435 BaseType::Int => "number",
436 BaseType::String => "string",
437 BaseType::Bool => "boolean",
438 BaseType::Float => "number",
439 BaseType::Duration | BaseType::Instant => "number",
440 BaseType::Bytes => "string",
442 };
443 writeln!(out, " if (typeof {json} !== \"{typeof_str}\") {{").unwrap();
444 writeln!(
445 out,
446 " return Err({{ kind: \"StructuralMismatch\", path: {path_expr}, expected: \"{typeof_str}\", actual: typeof {json} }});"
447 )
448 .unwrap();
449 writeln!(out, " }}").unwrap();
450 if *b == BaseType::Int || *b == BaseType::Instant {
455 writeln!(out, " if (!Number.isInteger({json})) {{").unwrap();
456 writeln!(
457 out,
458 " return Err({{ kind: \"StructuralMismatch\", path: {path_expr}, expected: \"integer\", actual: String({json}) }});"
459 )
460 .unwrap();
461 writeln!(out, " }}").unwrap();
462 }
463 if *b == BaseType::Float {
467 writeln!(out, " if (!Number.isFinite({json})) {{").unwrap();
468 writeln!(
469 out,
470 " return Err({{ kind: \"StructuralMismatch\", path: {path_expr}, expected: \"finite number\", actual: String({json}) }});"
471 )
472 .unwrap();
473 writeln!(out, " }}").unwrap();
474 }
475 writeln!(out, " const __{name} = {json};").unwrap();
476 }
477 TypeRef::Named(id) => {
478 writeln!(
481 out,
482 " const __r_{name} = deserialise_{}({json}, {path_expr});",
483 id.name
484 )
485 .unwrap();
486 writeln!(out, " if (__r_{name}.tag === \"Err\") return __r_{name};").unwrap();
487 writeln!(out, " const __{name} = __r_{name}.value;").unwrap();
488 }
489 TypeRef::Result(a, b, _) => {
490 let ts_a = inner_ts_name(a);
491 let ts_b = inner_ts_name(b);
492 writeln!(
493 out,
494 " const __r_{name} = deserialise_Result_{ts_a}_{ts_b}({json}, {path_expr});",
495 )
496 .unwrap();
497 writeln!(out, " if (__r_{name}.tag === \"Err\") return __r_{name};").unwrap();
498 writeln!(out, " const __{name} = __r_{name}.value;").unwrap();
499 }
500 TypeRef::Option(a, _) => {
501 let ts_a = inner_ts_name(a);
502 writeln!(
503 out,
504 " const __r_{name} = deserialise_Option_{ts_a}({json}, {path_expr});",
505 )
506 .unwrap();
507 writeln!(out, " if (__r_{name}.tag === \"Err\") return __r_{name};").unwrap();
508 writeln!(out, " const __{name} = __r_{name}.value;").unwrap();
509 }
510 TypeRef::List(a, _) => {
513 let ts_a = inner_ts_name(a);
514 writeln!(
515 out,
516 " const __r_{name} = deserialise_List_{ts_a}({json}, {path_expr});",
517 )
518 .unwrap();
519 writeln!(out, " if (__r_{name}.tag === \"Err\") return __r_{name};").unwrap();
520 writeln!(out, " const __{name} = __r_{name}.value;").unwrap();
521 }
522 TypeRef::Map(k, v, _) => {
523 let ts_k = inner_ts_name(k);
524 let ts_v = inner_ts_name(v);
525 writeln!(
526 out,
527 " const __r_{name} = deserialise_Map_{ts_k}_{ts_v}({json}, {path_expr});",
528 )
529 .unwrap();
530 writeln!(out, " if (__r_{name}.tag === \"Err\") return __r_{name};").unwrap();
531 writeln!(out, " const __{name} = __r_{name}.value;").unwrap();
532 }
533 TypeRef::Effect(_, _)
534 | TypeRef::ValidationError(_)
535 | TypeRef::JsonError(_)
536 | TypeRef::HttpResult(_, _)
537 | TypeRef::QueueResult(_) => {
538 writeln!(out, " const __{name} = {json} as any;").unwrap();
539 }
540 TypeRef::Unit(_) => {
541 writeln!(out, " const __{name} = undefined;").unwrap();
542 }
543 }
544}
545
546fn serialise_field_expr(t: &TypeRef, value: &str) -> String {
547 match t {
548 TypeRef::Fn(..) | TypeRef::Query(..) | TypeRef::Stream(..) | TypeRef::Connection(..) => {
552 unreachable!("function/query/stream types are rejected at boundaries")
553 }
554 TypeRef::Base(BaseType::Float, _) => format!(
558 "((v: number) => {{ if (!Number.isFinite(v)) throw new Error(\"non-finite Float at boundary\"); return v as JsonValue; }})({value})"
559 ),
560 TypeRef::Base(BaseType::Bytes, _) => {
563 format!("__bynkBytesToBase64({value}) as JsonValue")
564 }
565 TypeRef::Base(_, _) => format!("{value} as JsonValue"),
566 TypeRef::Named(id) => format!("serialise_{}({value})", id.name),
567 TypeRef::Result(a, b, _) => format!(
568 "serialise_Result_{}_{}({value})",
569 inner_ts_name(a),
570 inner_ts_name(b)
571 ),
572 TypeRef::Option(a, _) => format!("serialise_Option_{}({value})", inner_ts_name(a)),
573 TypeRef::List(a, _) => format!("serialise_List_{}({value})", inner_ts_name(a)),
574 TypeRef::Map(k, v, _) => format!(
575 "serialise_Map_{}_{}({value})",
576 inner_ts_name(k),
577 inner_ts_name(v)
578 ),
579 TypeRef::Effect(_, _)
580 | TypeRef::ValidationError(_)
581 | TypeRef::JsonError(_)
582 | TypeRef::HttpResult(_, _)
583 | TypeRef::QueueResult(_) => {
584 format!("{value} as JsonValue")
585 }
586 TypeRef::Unit(_) => "null".to_string(),
587 }
588}
589
590fn inner_ts_name(t: &TypeRef) -> String {
591 match t {
592 TypeRef::Base(b, _) => b.name().to_string(),
593 TypeRef::Fn(..) | TypeRef::Query(..) | TypeRef::Stream(..) | TypeRef::Connection(..) => {
597 unreachable!("function/query/stream types are rejected at boundaries")
598 }
599 TypeRef::Named(id) => id.name.clone(),
600 TypeRef::Result(a, b, _) => format!("Result_{}_{}", inner_ts_name(a), inner_ts_name(b)),
601 TypeRef::Option(a, _) => format!("Option_{}", inner_ts_name(a)),
602 TypeRef::Effect(a, _) => format!("Effect_{}", inner_ts_name(a)),
603 TypeRef::HttpResult(a, _) => format!("HttpResult_{}", inner_ts_name(a)),
604 TypeRef::List(a, _) => format!("List_{}", inner_ts_name(a)),
605 TypeRef::Map(k, v, _) => format!("Map_{}_{}", inner_ts_name(k), inner_ts_name(v)),
606 TypeRef::QueueResult(_) => "QueueResult".to_string(),
607 TypeRef::ValidationError(_) => "ValidationError".to_string(),
608 TypeRef::JsonError(_) => "JsonError".to_string(),
609 TypeRef::Unit(_) => "Unit".to_string(),
610 }
611}
612
613pub fn collect_codec_closure(
619 roots: &[TypeRef],
620 types: &std::collections::HashMap<String, TypeDecl>,
621) -> (Vec<String>, Vec<GenericInst>) {
622 let mut seen: std::collections::HashSet<String> = std::collections::HashSet::new();
623 let mut names: Vec<String> = Vec::new();
624 let mut stack: Vec<String> = Vec::new();
625 for r in roots {
626 collect_type_names(r, &mut stack);
627 }
628 while let Some(name) = stack.pop() {
629 if !seen.insert(name.clone()) {
630 continue;
631 }
632 names.push(name.clone());
633 let Some(decl) = types.get(&name) else {
634 continue;
635 };
636 match &decl.body {
637 TypeBody::Record(r) => {
638 for f in &r.fields {
639 collect_type_names(&f.type_ref, &mut stack);
640 }
641 }
642 TypeBody::Sum(s) => {
643 for v in &s.variants {
644 for p in &v.payload {
645 collect_type_names(&p.type_ref, &mut stack);
646 }
647 }
648 }
649 TypeBody::Refined { .. } | TypeBody::Opaque { .. } => {}
650 }
651 }
652 names.sort();
653
654 let mut insts: Vec<GenericInst> = Vec::new();
655 let mut inst_seen: std::collections::HashSet<String> = std::collections::HashSet::new();
656 for r in roots {
657 walk_generic_inst(r, &mut insts, &mut inst_seen);
658 }
659 for name in &names {
660 let Some(decl) = types.get(name) else {
661 continue;
662 };
663 match &decl.body {
664 TypeBody::Record(r) => {
665 for f in &r.fields {
666 walk_generic_inst(&f.type_ref, &mut insts, &mut inst_seen);
667 }
668 }
669 TypeBody::Sum(s) => {
670 for v in &s.variants {
671 for p in &v.payload {
672 walk_generic_inst(&p.type_ref, &mut insts, &mut inst_seen);
673 }
674 }
675 }
676 TypeBody::Refined { .. } | TypeBody::Opaque { .. } => {}
677 }
678 }
679 (names, insts)
680}
681
682pub fn serialise_expr(t: &TypeRef, value: &str) -> String {
685 serialise_field_expr(t, value)
686}
687
688pub fn deserialise_expr(t: &TypeRef, json: &str, path: &str) -> String {
692 match t {
693 TypeRef::Named(id) => format!("deserialise_{}({json}, \"{path}\")", id.name),
694 TypeRef::Result(..) | TypeRef::Option(..) | TypeRef::List(..) | TypeRef::Map(..) => {
695 format!("deserialise_{}({json}, \"{path}\")", inner_ts_name(t))
696 }
697 TypeRef::Base(BaseType::Bytes, _) => {
700 format!(
701 "((__v) => typeof __v === \"string\" ? ((__b) => __b.tag === \"Some\" ? Ok(__b.value) : Err({{ kind: \"StructuralMismatch\", path: \"{path}\", expected: \"base64 string\", actual: \"invalid base64\" }} as BoundaryError))(__bynkBytesFromBase64(__v)) : Err({{ kind: \"StructuralMismatch\", path: \"{path}\", expected: \"base64 string\", actual: typeof __v }} as BoundaryError))({json})"
702 )
703 }
704 TypeRef::Base(b, _) => {
705 let typeof_str = match b {
706 BaseType::Int => "number",
707 BaseType::String => "string",
708 BaseType::Bool => "boolean",
709 BaseType::Float => "number",
710 BaseType::Duration | BaseType::Instant => "number",
711 BaseType::Bytes => "string",
713 };
714 let extra = match b {
715 BaseType::Float => " && Number.isFinite(__v)",
716 BaseType::Int | BaseType::Duration | BaseType::Instant => {
719 " && Number.isInteger(__v)"
720 }
721 _ => "",
722 };
723 format!(
724 "((__v) => typeof __v === \"{typeof_str}\"{extra} ? Ok(__v) : Err({{ kind: \"StructuralMismatch\", path: \"{path}\", expected: \"{typeof_str}\", actual: typeof __v }} as BoundaryError))({json})"
725 )
726 }
727 _ => unreachable!("non-codable type reached the Json codec lowering"),
729 }
730}
731
732pub fn collect_generic_instantiations(
739 services: &std::collections::HashMap<String, ServiceDecl>,
740 agents: &std::collections::HashMap<String, AgentDecl>,
744 boundary_type_names: &[String],
745 types: &std::collections::HashMap<String, TypeDecl>,
746) -> Vec<GenericInst> {
747 let mut out: Vec<GenericInst> = Vec::new();
748 let mut seen: std::collections::HashSet<String> = std::collections::HashSet::new();
749 let mut svc_names: Vec<&String> = services.keys().collect();
755 svc_names.sort();
756 for name in svc_names {
757 let s = &services[name];
758 for h in &s.handlers {
759 for p in &h.params {
760 walk_generic_inst(&p.type_ref, &mut out, &mut seen);
761 }
762 walk_generic_inst(&h.return_type, &mut out, &mut seen);
763 }
764 }
765 let mut agent_names: Vec<&String> = agents.keys().collect();
766 agent_names.sort();
767 for name in agent_names {
768 for f in &agents[name].store_fields {
769 for arg in &f.kind.args {
770 walk_generic_inst(arg, &mut out, &mut seen);
771 }
772 }
773 }
774 for name in boundary_type_names {
775 let Some(decl) = types.get(name) else {
776 continue;
777 };
778 match &decl.body {
779 TypeBody::Record(r) => {
780 for f in &r.fields {
781 walk_generic_inst(&f.type_ref, &mut out, &mut seen);
782 }
783 }
784 TypeBody::Sum(s) => {
785 for v in &s.variants {
786 for p in &v.payload {
787 walk_generic_inst(&p.type_ref, &mut out, &mut seen);
788 }
789 }
790 }
791 TypeBody::Refined { .. } | TypeBody::Opaque { .. } => {}
792 }
793 }
794 out
795}
796
797#[derive(Debug, Clone)]
798pub enum GenericInst {
799 ResultInst {
800 ok: TypeRef,
801 err: TypeRef,
802 },
803 OptionInst {
804 inner: TypeRef,
805 },
806 ListInst {
808 elem: TypeRef,
809 },
810 MapInst {
813 key: TypeRef,
814 val: TypeRef,
815 },
816}
817
818impl GenericInst {
819 pub fn ts_name(&self) -> String {
820 match self {
821 GenericInst::ResultInst { ok, err } => {
822 format!("Result_{}_{}", inner_ts_name(ok), inner_ts_name(err))
823 }
824 GenericInst::OptionInst { inner } => {
825 format!("Option_{}", inner_ts_name(inner))
826 }
827 GenericInst::ListInst { elem } => format!("List_{}", inner_ts_name(elem)),
828 GenericInst::MapInst { key, val } => {
829 format!("Map_{}_{}", inner_ts_name(key), inner_ts_name(val))
830 }
831 }
832 }
833}
834
835fn walk_generic_inst(
836 t: &TypeRef,
837 out: &mut Vec<GenericInst>,
838 seen: &mut std::collections::HashSet<String>,
839) {
840 match t {
841 TypeRef::Result(a, b, _) => {
842 let inst = GenericInst::ResultInst {
843 ok: (**a).clone(),
844 err: (**b).clone(),
845 };
846 let key = inst.ts_name();
847 if seen.insert(key) {
848 out.push(inst);
849 }
850 walk_generic_inst(a, out, seen);
851 walk_generic_inst(b, out, seen);
852 }
853 TypeRef::Option(a, _) => {
854 let inst = GenericInst::OptionInst {
855 inner: (**a).clone(),
856 };
857 let key = inst.ts_name();
858 if seen.insert(key) {
859 out.push(inst);
860 }
861 walk_generic_inst(a, out, seen);
862 }
863 TypeRef::Effect(a, _) => walk_generic_inst(a, out, seen),
864 TypeRef::HttpResult(a, _) => walk_generic_inst(a, out, seen),
865 TypeRef::List(a, _) => {
866 let inst = GenericInst::ListInst {
867 elem: (**a).clone(),
868 };
869 let key = inst.ts_name();
870 if seen.insert(key) {
871 out.push(inst);
872 }
873 walk_generic_inst(a, out, seen);
874 }
875 TypeRef::Map(k, v, _) => {
876 let inst = GenericInst::MapInst {
877 key: (**k).clone(),
878 val: (**v).clone(),
879 };
880 let key = inst.ts_name();
881 if seen.insert(key) {
882 out.push(inst);
883 }
884 walk_generic_inst(k, out, seen);
885 walk_generic_inst(v, out, seen);
886 }
887 _ => {}
888 }
889}
890
891pub fn emit_generic_helpers(out: &mut String, insts: &[GenericInst]) {
894 for inst in insts {
895 match inst {
896 GenericInst::ResultInst { ok, err } => {
897 let ok_ts = inner_ts_name(ok);
898 let err_ts = inner_ts_name(err);
899 let ok_inner = ts_inner_type(ok);
900 let err_inner = ts_inner_type(err);
901 let serialise_ok = serialise_field_expr(ok, "value.value");
902 let serialise_err = serialise_field_expr(err, "value.error");
903 writeln!(
904 out,
905 "export function serialise_Result_{ok_ts}_{err_ts}(value: Result<{ok_inner}, {err_inner}>): JsonValue {{"
906 )
907 .unwrap();
908 writeln!(
909 out,
910 " if (value.tag === \"Ok\") return {{ kind: \"Ok\", value: {serialise_ok} }};"
911 )
912 .unwrap();
913 writeln!(out, " return {{ kind: \"Err\", error: {serialise_err} }};").unwrap();
914 writeln!(out, "}}").unwrap();
915 writeln!(out).unwrap();
916
917 writeln!(
918 out,
919 "export function deserialise_Result_{ok_ts}_{err_ts}(json: JsonValue, path: string = \"$\"): Result<Result<{ok_inner}, {err_inner}>, BoundaryError> {{"
920 )
921 .unwrap();
922 writeln!(
923 out,
924 " if (typeof json !== \"object\" || json === null || Array.isArray(json)) {{"
925 )
926 .unwrap();
927 writeln!(
928 out,
929 " return Err({{ kind: \"StructuralMismatch\", path, expected: \"object\", actual: typeof json }});"
930 )
931 .unwrap();
932 writeln!(out, " }}").unwrap();
933 writeln!(out, " const obj = json as {{ [k: string]: JsonValue }};").unwrap();
934 writeln!(out, " if (obj[\"kind\"] === \"Ok\") {{").unwrap();
935 emit_field_deserialise(out, "v", ok, "obj[\"value\"]", "`${path}.value`");
936 writeln!(
937 out,
938 " return Ok(Ok(__v) as Result<{ok_inner}, {err_inner}>);"
939 )
940 .unwrap();
941 writeln!(out, " }} else if (obj[\"kind\"] === \"Err\") {{").unwrap();
942 emit_field_deserialise(out, "e", err, "obj[\"error\"]", "`${path}.error`");
943 writeln!(
944 out,
945 " return Ok(Err(__e) as Result<{ok_inner}, {err_inner}>);"
946 )
947 .unwrap();
948 writeln!(out, " }}").unwrap();
949 writeln!(out, " return Err({{ kind: \"StructuralMismatch\", path, expected: \"Ok | Err\", actual: String(obj[\"kind\"]) }});").unwrap();
950 writeln!(out, "}}").unwrap();
951 writeln!(out).unwrap();
952 }
953 GenericInst::OptionInst { inner } => {
954 let inner_ts = inner_ts_name(inner);
955 let inner_ty = ts_inner_type(inner);
956 let serialise_inner = serialise_field_expr(inner, "value.value");
957 writeln!(
958 out,
959 "export function serialise_Option_{inner_ts}(value: Option<{inner_ty}>): JsonValue {{"
960 )
961 .unwrap();
962 writeln!(out, " if (value.tag === \"Some\") return {{ kind: \"Some\", value: {serialise_inner} }};").unwrap();
963 writeln!(out, " return {{ kind: \"None\" }};").unwrap();
964 writeln!(out, "}}").unwrap();
965 writeln!(out).unwrap();
966
967 writeln!(
968 out,
969 "export function deserialise_Option_{inner_ts}(json: JsonValue, path: string = \"$\"): Result<Option<{inner_ty}>, BoundaryError> {{"
970 )
971 .unwrap();
972 writeln!(
973 out,
974 " if (typeof json !== \"object\" || json === null || Array.isArray(json)) {{"
975 )
976 .unwrap();
977 writeln!(
978 out,
979 " return Err({{ kind: \"StructuralMismatch\", path, expected: \"object\", actual: typeof json }});"
980 )
981 .unwrap();
982 writeln!(out, " }}").unwrap();
983 writeln!(out, " const obj = json as {{ [k: string]: JsonValue }};").unwrap();
984 writeln!(out, " if (obj[\"kind\"] === \"Some\") {{").unwrap();
985 emit_field_deserialise(out, "v", inner, "obj[\"value\"]", "`${path}.value`");
986 writeln!(out, " return Ok(Some(__v) as Option<{inner_ty}>);").unwrap();
987 writeln!(out, " }} else if (obj[\"kind\"] === \"None\") {{").unwrap();
988 writeln!(out, " return Ok(None as Option<{inner_ty}>);").unwrap();
989 writeln!(out, " }}").unwrap();
990 writeln!(out, " return Err({{ kind: \"StructuralMismatch\", path, expected: \"Some | None\", actual: String(obj[\"kind\"]) }});").unwrap();
991 writeln!(out, "}}").unwrap();
992 writeln!(out).unwrap();
993 }
994 GenericInst::ListInst { elem } => {
996 let elem_ts = inner_ts_name(elem);
997 let elem_ty = ts_inner_type(elem);
998 let serialise_elem = serialise_field_expr(elem, "v");
999 writeln!(
1000 out,
1001 "export function serialise_List_{elem_ts}(value: readonly {elem_ty}[]): JsonValue {{"
1002 )
1003 .unwrap();
1004 writeln!(out, " return value.map((v) => {serialise_elem});").unwrap();
1005 writeln!(out, "}}").unwrap();
1006 writeln!(out).unwrap();
1007
1008 writeln!(
1009 out,
1010 "export function deserialise_List_{elem_ts}(json: JsonValue, path: string = \"$\"): Result<readonly {elem_ty}[], BoundaryError> {{"
1011 )
1012 .unwrap();
1013 writeln!(out, " if (!Array.isArray(json)) {{").unwrap();
1014 writeln!(
1015 out,
1016 " return Err({{ kind: \"StructuralMismatch\", path, expected: \"array\", actual: typeof json }});"
1017 )
1018 .unwrap();
1019 writeln!(out, " }}").unwrap();
1020 writeln!(out, " const out: {elem_ty}[] = [];").unwrap();
1021 writeln!(out, " for (let i = 0; i < json.length; i++) {{").unwrap();
1022 writeln!(out, " const item = json[i];").unwrap();
1025 emit_field_deserialise(out, "el", elem, "item", "`${path}[${i}]`");
1026 writeln!(out, " out.push(__el);").unwrap();
1027 writeln!(out, " }}").unwrap();
1028 writeln!(out, " return Ok(out);").unwrap();
1029 writeln!(out, "}}").unwrap();
1030 writeln!(out).unwrap();
1031 }
1032 GenericInst::MapInst { key, val } => {
1036 let key_ts = inner_ts_name(key);
1037 let val_ts = inner_ts_name(val);
1038 let key_ty = ts_inner_type(key);
1039 let val_ty = ts_inner_type(val);
1040 let serialise_key = serialise_field_expr(key, "k");
1041 let serialise_val = serialise_field_expr(val, "v");
1042 writeln!(
1043 out,
1044 "export function serialise_Map_{key_ts}_{val_ts}(value: ReadonlyMap<{key_ty}, {val_ty}>): JsonValue {{"
1045 )
1046 .unwrap();
1047 writeln!(out, " const entries: JsonValue[] = [];").unwrap();
1048 writeln!(out, " for (const [k, v] of value) {{").unwrap();
1049 writeln!(out, " entries.push([{serialise_key}, {serialise_val}]);").unwrap();
1050 writeln!(out, " }}").unwrap();
1051 writeln!(out, " return entries;").unwrap();
1052 writeln!(out, "}}").unwrap();
1053 writeln!(out).unwrap();
1054
1055 writeln!(
1056 out,
1057 "export function deserialise_Map_{key_ts}_{val_ts}(json: JsonValue, path: string = \"$\"): Result<ReadonlyMap<{key_ty}, {val_ty}>, BoundaryError> {{"
1058 )
1059 .unwrap();
1060 writeln!(out, " if (!Array.isArray(json)) {{").unwrap();
1061 writeln!(
1062 out,
1063 " return Err({{ kind: \"StructuralMismatch\", path, expected: \"array\", actual: typeof json }});"
1064 )
1065 .unwrap();
1066 writeln!(out, " }}").unwrap();
1067 writeln!(out, " const out = new Map<{key_ty}, {val_ty}>();").unwrap();
1068 writeln!(out, " for (let i = 0; i < json.length; i++) {{").unwrap();
1069 writeln!(out, " const entry = json[i];").unwrap();
1070 writeln!(out, " if (!Array.isArray(entry) || entry.length !== 2) {{").unwrap();
1071 writeln!(
1072 out,
1073 " return Err({{ kind: \"StructuralMismatch\", path: `${{path}}[${{i}}]`, expected: \"[key, value] entry\", actual: typeof entry }});"
1074 )
1075 .unwrap();
1076 writeln!(out, " }}").unwrap();
1077 writeln!(out, " const entryK = entry[0];").unwrap();
1078 writeln!(out, " const entryV = entry[1];").unwrap();
1079 emit_field_deserialise(out, "k", key, "entryK", "`${path}[${i}][0]`");
1080 emit_field_deserialise(out, "v", val, "entryV", "`${path}[${i}][1]`");
1081 writeln!(out, " out.set(__k, __v);").unwrap();
1082 writeln!(out, " }}").unwrap();
1083 writeln!(out, " return Ok(out);").unwrap();
1084 writeln!(out, "}}").unwrap();
1085 writeln!(out).unwrap();
1086 }
1087 }
1088 }
1089}
1090
1091fn ts_inner_type(t: &TypeRef) -> String {
1092 match t {
1093 TypeRef::Fn(..) | TypeRef::Query(..) | TypeRef::Stream(..) | TypeRef::Connection(..) => {
1097 unreachable!("function/query/stream types are rejected at boundaries")
1098 }
1099 TypeRef::Base(b, _) => match b {
1100 BaseType::Int => "number".to_string(),
1101 BaseType::String => "string".to_string(),
1102 BaseType::Bool => "boolean".to_string(),
1103 BaseType::Float => "number".to_string(),
1104 BaseType::Duration | BaseType::Instant => "number".to_string(),
1105 BaseType::Bytes => "Uint8Array".to_string(),
1107 },
1108 TypeRef::Named(id) => id.name.clone(),
1109 TypeRef::Result(a, b, _) => format!("Result<{}, {}>", ts_inner_type(a), ts_inner_type(b)),
1110 TypeRef::Option(a, _) => format!("Option<{}>", ts_inner_type(a)),
1111 TypeRef::Effect(a, _) => format!("Promise<{}>", ts_inner_type(a)),
1112 TypeRef::HttpResult(a, _) => format!("HttpResult<{}>", ts_inner_type(a)),
1113 TypeRef::List(a, _) => format!("readonly {}[]", ts_inner_type(a)),
1114 TypeRef::Map(k, v, _) => {
1115 format!("ReadonlyMap<{}, {}>", ts_inner_type(k), ts_inner_type(v))
1116 }
1117 TypeRef::QueueResult(_) => "QueueResult".to_string(),
1118 TypeRef::ValidationError(_) => "ValidationError".to_string(),
1119 TypeRef::JsonError(_) => "JsonError".to_string(),
1120 TypeRef::Unit(_) => "void".to_string(),
1121 }
1122}