fusee-framework 2.7.0 → 2.7.1

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 (61) hide show
  1. package/README.md +56 -2
  2. package/bin/create-fusee-linux +0 -0
  3. package/bin/create-fusee-mac-arm64 +0 -0
  4. package/bin/create-fusee-mac-intel +0 -0
  5. package/bin/create-fusee-win.exe +0 -0
  6. package/framework/auto-import-preset.js +20 -12
  7. package/framework/auto-imports.d.ts +68 -39
  8. package/framework/core/compiler.js +310 -310
  9. package/framework/core/component-core.js +445 -0
  10. package/framework/core/component.js +11 -440
  11. package/framework/core/composable.js +29 -29
  12. package/framework/core/h.js +588 -557
  13. package/framework/core/jsx-helpers.js +11 -2
  14. package/framework/core/jsx-runtime.js +73 -73
  15. package/framework/core/rust-compiler/pkg/fusee_compiler_bg.wasm +0 -0
  16. package/framework/core/store.js +333 -333
  17. package/framework/globals.js +42 -0
  18. package/framework/index.js +22 -55
  19. package/framework/router/file-router.js +184 -184
  20. package/framework/server/actions.js +183 -183
  21. package/framework/server/generate-manifest.js +3 -3
  22. package/framework/types/component.d.ts +97 -86
  23. package/framework/types/index.d.ts +118 -117
  24. package/package.json +83 -80
  25. package/framework/core/rust-compiler/Cargo.toml +0 -31
  26. package/framework/core/rust-compiler/README.md +0 -69
  27. package/framework/core/rust-compiler/pkg/README.md +0 -69
  28. package/framework/core/rust-compiler/pkg/fusee_compiler.d.ts +0 -6
  29. package/framework/core/rust-compiler/pkg/fusee_compiler.js +0 -9
  30. package/framework/core/rust-compiler/pkg/fusee_compiler_bg.js +0 -137
  31. package/framework/core/rust-compiler/pkg/fusee_compiler_bg.wasm.d.ts +0 -11
  32. package/framework/core/rust-compiler/pkg/package.json +0 -18
  33. package/framework/core/rust-compiler/src/ast.rs +0 -274
  34. package/framework/core/rust-compiler/src/errors.rs +0 -204
  35. package/framework/core/rust-compiler/src/generator.rs +0 -583
  36. package/framework/core/rust-compiler/src/lexer.rs +0 -414
  37. package/framework/core/rust-compiler/src/lib.rs +0 -87
  38. package/framework/core/rust-compiler/src/main_compiler.rs +0 -165
  39. package/framework/core/rust-compiler/src/parser.rs +0 -516
  40. package/framework/core/rust-compiler/src/ssr_generator.rs +0 -688
  41. package/framework/core/rust-compiler/src/transformer.rs +0 -562
  42. package/framework/core/rust-compiler/src/utils.rs +0 -10
  43. package/framework/engine-go/actions.go +0 -112
  44. package/framework/engine-go/cmd/server/main.go +0 -233
  45. package/framework/engine-go/comet/comet.go +0 -41
  46. package/framework/engine-go/comet/fragment.go +0 -51
  47. package/framework/engine-go/dispatcher.go +0 -120
  48. package/framework/engine-go/elements.go +0 -248
  49. package/framework/engine-go/evalexpr/evalexpr.go +0 -733
  50. package/framework/engine-go/go.mod +0 -3
  51. package/framework/engine-go/hydration.go +0 -43
  52. package/framework/engine-go/load-helpers.go +0 -29
  53. package/framework/engine-go/manifest.go +0 -41
  54. package/framework/engine-go/reactive.go +0 -123
  55. package/framework/engine-go/render.go +0 -635
  56. package/framework/engine-go/reqcontext.go +0 -48
  57. package/framework/engine-go/router.go +0 -85
  58. package/framework/engine-go/rustast/rustast.go +0 -317
  59. package/framework/engine-go/ssr.go +0 -103
  60. package/framework/engine-go/static-assets.go +0 -139
  61. package/framework/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
@@ -1,562 +0,0 @@
1
- use crate::args;
2
- use crate::ast::*;
3
- use crate::errors::{CompileWarning, ErrorCode};
4
- use once_cell::sync::Lazy;
5
- use regex::Regex;
6
- use std::collections::HashSet;
7
-
8
- pub struct TransformOptions {
9
- pub components: HashSet<String>,
10
- pub source: String,
11
- pub scope: HashSet<String>,
12
- }
13
-
14
- pub struct TransformCtx {
15
- pub components: HashSet<String>,
16
- pub source: String,
17
- pub hoisted: Vec<NodeId>,
18
- pub warnings: Vec<CompileWarning>,
19
- pub scope: HashSet<String>,
20
- }
21
-
22
- pub fn transform(mut ast: Node, options: TransformOptions) -> (Node, Vec<CompileWarning>) {
23
- let mut ctx = TransformCtx {
24
- components: options.components,
25
- source: options.source,
26
- hoisted: Vec::new(),
27
- warnings: Vec::new(),
28
- scope: options.scope,
29
- };
30
-
31
- mark_static_pass(&mut ast, &mut ctx);
32
- walk_and_chain(&mut ast, &mut ctx);
33
-
34
- walk_all(&ast, &mut ctx, &mut |n, c| {
35
- validate_for(n, c);
36
- validate_model(n, c);
37
- validate_components(n, c);
38
- });
39
-
40
- if !ctx.scope.is_empty() {
41
- let scope = ctx.scope.clone();
42
- analyse_scope(&ast, &mut ctx, &scope);
43
- }
44
-
45
- if let Node::Root(r) = &mut ast {
46
- r.hoisted = ctx.hoisted.clone();
47
- }
48
-
49
- (ast, ctx.warnings)
50
- }
51
-
52
- fn mark_static_pass(node: &mut Node, ctx: &mut TransformCtx) -> bool {
53
- match node {
54
- Node::Text(t) => {
55
- t.is_static = true;
56
- true
57
- }
58
-
59
- Node::Interpolation(i) => {
60
- let is_static = is_literal_expression(&i.expression.content);
61
- i.expression.is_static = is_static;
62
- i.is_static = is_static;
63
- is_static
64
- }
65
-
66
- Node::Element(e) => {
67
- let has_once = e
68
- .props
69
- .iter()
70
- .any(|p| matches!(p, Prop::Directive(d) if d.name == "once"));
71
- if has_once {
72
- e.is_static = true;
73
- e.hoisted = true;
74
- ctx.hoisted.push(e.id);
75
- return true;
76
- }
77
-
78
- let has_dynamic = e.props.iter().any(|p| {
79
- matches!(p, Prop::Directive(_) | Prop::Binding(_) | Prop::Event(_))
80
- });
81
-
82
- if has_dynamic {
83
- e.is_static = false;
84
- for c in e.children.iter_mut() {
85
- mark_static_pass(c, ctx);
86
- }
87
- return false;
88
- }
89
-
90
- let has_interpolated_attr = e.props.iter().any(|p| {
91
- matches!(p, Prop::Attribute(a) if a.value.as_deref().map(|v| v.contains("{{")).unwrap_or(false))
92
- });
93
- if has_interpolated_attr {
94
- e.is_static = false;
95
- for c in e.children.iter_mut() {
96
- mark_static_pass(c, ctx);
97
- }
98
- return false;
99
- }
100
-
101
- let mut children_static = true;
102
- for c in e.children.iter_mut() {
103
- if !mark_static_pass(c, ctx) {
104
- children_static = false;
105
- break;
106
- }
107
- }
108
-
109
- e.is_static = children_static;
110
- if e.is_static && e.children.len() >= 2 {
111
- e.hoisted = true;
112
- ctx.hoisted.push(e.id);
113
- }
114
- e.is_static
115
- }
116
-
117
- Node::Component(c) => {
118
- for (_, slot_children) in c.slots.iter_mut() {
119
- for child in slot_children.iter_mut() {
120
- mark_static_pass(child, ctx);
121
- }
122
- }
123
- false
124
- }
125
-
126
- Node::Root(r) => {
127
- for c in r.children.iter_mut() {
128
- mark_static_pass(c, ctx);
129
- }
130
- false
131
- }
132
-
133
- _ => false,
134
- }
135
- }
136
-
137
- fn walk_and_chain(node: &mut Node, ctx: &mut TransformCtx) {
138
- match node {
139
- Node::Root(r) => {
140
- chain_conditionals_in_children(&mut r.children, ctx);
141
- for c in r.children.iter_mut() {
142
- walk_and_chain(c, ctx);
143
- }
144
- }
145
- Node::Element(e) => {
146
- chain_conditionals_in_children(&mut e.children, ctx);
147
- for c in e.children.iter_mut() {
148
- walk_and_chain(c, ctx);
149
- }
150
- }
151
- Node::Component(c) => {
152
- for (_, children) in c.slots.iter_mut() {
153
- for child in children.iter_mut() {
154
- walk_and_chain(child, ctx);
155
- }
156
- }
157
- }
158
- Node::If(i) => {
159
- for b in i.branches.iter_mut() {
160
- walk_and_chain(&mut b.node, ctx);
161
- }
162
- }
163
- _ => {}
164
- }
165
- }
166
-
167
- fn is_whitespace_text(node: &Node) -> bool {
168
- matches!(node, Node::Text(t) if t.content.trim().is_empty())
169
- }
170
-
171
- fn chain_conditionals_in_children(children: &mut Vec<Node>, ctx: &mut TransformCtx) {
172
- let mut i = 0;
173
- while i < children.len() {
174
- if !children[i].is_element_or_component() {
175
- i += 1;
176
- continue;
177
- }
178
-
179
- let if_dir = children[i].find_directive("if").cloned();
180
- if if_dir.is_none() {
181
- let else_dir = children[i]
182
- .find_directive("else")
183
- .or_else(|| children[i].find_directive("else-if"))
184
- .cloned();
185
- if let Some(d) = else_dir {
186
- ctx.warnings.push(CompileWarning::new(
187
- ErrorCode::VElseNoIf,
188
- d.loc.clone(),
189
- &ctx.source,
190
- args!(),
191
- ));
192
- }
193
- i += 1;
194
- continue;
195
- }
196
- let if_dir = if_dir.unwrap();
197
-
198
- let mut branches = vec![Branch {
199
- condition: if_dir.expression.clone(),
200
- node: Box::new(children[i].clone()),
201
- }];
202
- let mut j = i + 1;
203
- let mut end = i + 1;
204
-
205
- while j < children.len() {
206
- if !children[j].is_element_or_component() {
207
- if is_whitespace_text(&children[j]) {
208
- j += 1;
209
- continue;
210
- }
211
- break;
212
- }
213
-
214
- let else_if_dir = children[j].find_directive("else-if").cloned();
215
- let else_dir = children[j].find_directive("else").cloned();
216
-
217
- if let Some(d) = else_if_dir {
218
- branches.push(Branch {
219
- condition: d.expression.clone(),
220
- node: Box::new(children[j].clone()),
221
- });
222
- j += 1;
223
- end = j;
224
- } else if else_dir.is_some() {
225
- branches.push(Branch {
226
- condition: None,
227
- node: Box::new(children[j].clone()),
228
- });
229
- j += 1;
230
- end = j;
231
- break;
232
- } else {
233
- break;
234
- }
235
- }
236
-
237
- let loc = children[i].loc().clone();
238
- let if_node = Node::If(IfNode { branches, loc });
239
-
240
- children.splice(i..end, std::iter::once(if_node));
241
- i += 1;
242
- }
243
- }
244
-
245
- fn validate_for(node: &Node, ctx: &mut TransformCtx) {
246
- if !node.is_element_or_component() {
247
- return;
248
- }
249
- let for_dir = match node.find_directive("for") {
250
- Some(d) => d,
251
- None => return,
252
- };
253
-
254
- let has_key = node
255
- .props()
256
- .map(|props| {
257
- props
258
- .iter()
259
- .any(|p| matches!(p, Prop::Binding(b) if b.name == "key"))
260
- })
261
- .unwrap_or(false);
262
-
263
- if !has_key {
264
- ctx.warnings.push(CompileWarning::new(
265
- ErrorCode::ForMissingKey,
266
- for_dir.loc.clone(),
267
- &ctx.source,
268
- args!(node.tag_or_name().to_string()),
269
- ));
270
- }
271
- }
272
-
273
- fn validate_model(node: &Node, ctx: &mut TransformCtx) {
274
- let el = match node {
275
- Node::Element(e) => e,
276
- _ => return,
277
- };
278
- let model_dir = match node.find_directive("model") {
279
- Some(d) => d,
280
- None => return,
281
- };
282
- let allowed = ["input", "textarea", "select"];
283
- if !allowed.contains(&el.tag.to_lowercase().as_str()) {
284
- ctx.warnings.push(CompileWarning::new(
285
- ErrorCode::ModelOnNonInput,
286
- model_dir.loc.clone(),
287
- &ctx.source,
288
- args!(el.tag.clone()),
289
- ));
290
- }
291
- }
292
-
293
- fn validate_components(node: &Node, ctx: &mut TransformCtx) {
294
- let comp = match node {
295
- Node::Component(c) => c,
296
- _ => return,
297
- };
298
- if !ctx.components.contains(&comp.name) {
299
- ctx.warnings.push(CompileWarning::new(
300
- ErrorCode::ComponentNotRegistered,
301
- comp.loc.clone(),
302
- &ctx.source,
303
- args!(comp.name.clone()),
304
- ));
305
- }
306
- }
307
-
308
- fn analyse_scope(node: &Node, ctx: &mut TransformCtx, local_scope: &HashSet<String>) {
309
- match node {
310
- Node::Root(r) => {
311
- for child in &r.children {
312
- analyse_scope(child, ctx, local_scope);
313
- }
314
- }
315
-
316
- Node::Element(e) => {
317
- let for_dir = node.find_directive("for");
318
- let child_scope = match for_dir {
319
- Some(d) => extend_scope_from_for(local_scope, d.arg.as_ref()),
320
- None => local_scope.clone(),
321
- };
322
-
323
- for prop in &e.props {
324
- validate_prop(prop, ctx, &child_scope);
325
- }
326
- for child in &e.children {
327
- analyse_scope(child, ctx, &child_scope);
328
- }
329
- }
330
-
331
- Node::Component(c) => {
332
- let for_dir = node.find_directive("for");
333
- let child_scope = match for_dir {
334
- Some(d) => extend_scope_from_for(local_scope, d.arg.as_ref()),
335
- None => local_scope.clone(),
336
- };
337
-
338
- for prop in &c.props {
339
- validate_prop(prop, ctx, &child_scope);
340
- }
341
- for (_, slot_children) in &c.slots {
342
- for child in slot_children {
343
- analyse_scope(child, ctx, &child_scope);
344
- }
345
- }
346
- }
347
-
348
- Node::Interpolation(i) => {
349
- validate_expression(&i.expression, ctx, local_scope, false);
350
- }
351
-
352
- Node::If(ifn) => {
353
- for branch in &ifn.branches {
354
- if let Some(cond) = &branch.condition {
355
- validate_expression(cond, ctx, local_scope, false);
356
- }
357
- analyse_scope(&branch.node, ctx, local_scope);
358
- }
359
- }
360
-
361
- Node::Text(_) | Node::SlotOutlet(_) | Node::SlotContent(_) => {}
362
- }
363
- }
364
-
365
- fn validate_prop(prop: &Prop, ctx: &mut TransformCtx, scope: &HashSet<String>) {
366
- match prop {
367
- Prop::Binding(b) => validate_expression(&b.expression, ctx, scope, false),
368
- Prop::Event(e) => validate_expression(&e.expression, ctx, scope, true),
369
- Prop::Directive(d) => {
370
- if d.name == "for" {
371
- return;
372
- }
373
- if let Some(expr) = &d.expression {
374
- validate_expression(expr, ctx, scope, false);
375
- }
376
- }
377
- Prop::Attribute(_) => {}
378
- }
379
- }
380
-
381
- fn validate_expression(expr_node: &Expression, ctx: &mut TransformCtx, scope: &HashSet<String>, is_handler: bool) {
382
- if expr_node.is_static {
383
- return;
384
- }
385
-
386
- let raw = expr_node.content.trim();
387
- if raw.is_empty() {
388
- return;
389
- }
390
-
391
- let identifiers = extract_identifiers(raw);
392
-
393
- for id in &identifiers {
394
- if JS_GLOBALS.contains(id.as_str()) {
395
- continue;
396
- }
397
- if is_handler && id == "$event" {
398
- continue;
399
- }
400
- if !scope.contains(id) {
401
- ctx.warnings.push(CompileWarning::new(
402
- ErrorCode::UnknownIdentifier,
403
- expr_node.loc.clone(),
404
- &ctx.source,
405
- args!(id.clone(), raw.to_string()),
406
- ));
407
- }
408
- }
409
-
410
- if expr_node.is_for_key && is_literal_expression(raw) {
411
- ctx.warnings.push(CompileWarning::new(
412
- ErrorCode::StaticForKey,
413
- expr_node.loc.clone(),
414
- &ctx.source,
415
- args!(raw.to_string()),
416
- ));
417
- }
418
- }
419
-
420
- fn strip_string_literals(expr: &str) -> String {
421
- static SINGLE: Lazy<Regex> = Lazy::new(|| Regex::new(r#"'[^'\\]*(?:\\.[^'\\]*)*'"#).unwrap());
422
- static DOUBLE: Lazy<Regex> = Lazy::new(|| Regex::new(r#""[^"\\]*(?:\\.[^"\\]*)*""#).unwrap());
423
- static BACKTICK: Lazy<Regex> = Lazy::new(|| Regex::new(r#"`[^`\\]*(?:\\.[^`\\]*)*`"#).unwrap());
424
-
425
- let s = SINGLE.replace_all(expr, "\"\"");
426
- let s = DOUBLE.replace_all(&s, "\"\"");
427
- let s = BACKTICK.replace_all(&s, "\"\"");
428
- s.into_owned()
429
- }
430
-
431
- fn extract_identifiers(expr: &str) -> HashSet<String> {
432
- let stripped = strip_string_literals(expr);
433
- let chars: Vec<char> = stripped.chars().collect();
434
- let mut found = HashSet::new();
435
- let mut i = 0;
436
- let mut skip_next = false;
437
-
438
- while i < chars.len() {
439
- let c = chars[i];
440
- if c.is_ascii_alphabetic() || c == '_' || c == '$' {
441
- let start = i;
442
- let mut j = i;
443
- while j < chars.len()
444
- && (chars[j].is_ascii_alphanumeric() || chars[j] == '_' || chars[j] == '$')
445
- {
446
- j += 1;
447
- }
448
- let id: String = chars[start..j].iter().collect();
449
-
450
- if skip_next {
451
- skip_next = false;
452
- i = j;
453
- continue;
454
- }
455
-
456
- let preceded_by_word = start > 0
457
- && (chars[start - 1].is_ascii_alphanumeric()
458
- || chars[start - 1] == '_'
459
- || chars[start - 1] == '$');
460
- let preceded_by_dot = start > 0 && chars[start - 1] == '.';
461
-
462
- if !JS_KEYWORDS.contains(id.as_str()) && !preceded_by_word && !preceded_by_dot {
463
- found.insert(id.clone());
464
- if id == "as" || id == "satisfies" {
465
- skip_next = true;
466
- }
467
- }
468
-
469
- let mut k = j;
470
- while k < chars.len() && chars[k].is_whitespace() {
471
- k += 1;
472
- }
473
- if k < chars.len() && chars[k] == ':' {
474
- skip_next = true;
475
- }
476
-
477
- i = j;
478
- } else {
479
- i += 1;
480
- }
481
- }
482
-
483
- found
484
- }
485
-
486
- fn extend_scope_from_for(parent: &HashSet<String>, for_arg: Option<&ForArg>) -> HashSet<String> {
487
- let for_arg = match for_arg {
488
- Some(a) => a,
489
- None => return parent.clone(),
490
- };
491
- let mut extended = parent.clone();
492
- if !for_arg.item.is_empty() {
493
- extended.insert(for_arg.item.clone());
494
- }
495
- if let Some(idx) = &for_arg.index {
496
- extended.insert(idx.clone());
497
- }
498
- extended
499
- }
500
-
501
- static JS_GLOBALS: Lazy<HashSet<&'static str>> = Lazy::new(|| {
502
- [
503
- "Math", "Object", "Array", "String", "Number", "Boolean", "Symbol", "Date", "RegExp",
504
- "Map", "Set", "WeakMap", "WeakSet", "WeakRef", "Promise", "Proxy", "Reflect", "JSON",
505
- "Error", "TypeError", "RangeError", "parseInt", "parseFloat", "isNaN", "isFinite",
506
- "encodeURIComponent", "decodeURIComponent", "encodeURI", "decodeURI", "console",
507
- "window", "document", "globalThis", "undefined", "null", "Infinity", "NaN", "setTimeout",
508
- "clearTimeout", "setInterval", "clearInterval", "queueMicrotask", "fetch", "true", "false",
509
- ]
510
- .into_iter()
511
- .collect()
512
- });
513
-
514
- static JS_KEYWORDS: Lazy<HashSet<&'static str>> = Lazy::new(|| {
515
- [
516
- "if", "else", "for", "while", "do", "switch", "case", "break", "continue", "return",
517
- "throw", "try", "catch", "finally", "new", "delete", "typeof", "instanceof", "in", "of",
518
- "void", "const", "let", "var", "function", "class", "import", "export", "default",
519
- "extends", "super", "this", "yield", "await", "async", "true", "false", "null",
520
- "undefined", "as", "satisfies",
521
- ]
522
- .into_iter()
523
- .collect()
524
- });
525
-
526
- static LITERAL_RE: Lazy<Regex> = Lazy::new(|| {
527
- Regex::new(r#"^(?:true|false|null|undefined|-?\d[\d._]*(?:n)?|'[^']*'|"[^"]*"|`[^`]*`)$"#)
528
- .unwrap()
529
- });
530
-
531
- fn is_literal_expression(expr: &str) -> bool {
532
- LITERAL_RE.is_match(expr.trim())
533
- }
534
-
535
- fn walk_all<F: FnMut(&Node, &mut TransformCtx)>(node: &Node, ctx: &mut TransformCtx, visitor: &mut F) {
536
- visitor(node, ctx);
537
- match node {
538
- Node::Root(r) => {
539
- for c in &r.children {
540
- walk_all(c, ctx, visitor);
541
- }
542
- }
543
- Node::Element(e) => {
544
- for c in &e.children {
545
- walk_all(c, ctx, visitor);
546
- }
547
- }
548
- Node::If(i) => {
549
- for b in &i.branches {
550
- walk_all(&b.node, ctx, visitor);
551
- }
552
- }
553
- Node::Component(c) => {
554
- for (_, children) in &c.slots {
555
- for child in children {
556
- walk_all(child, ctx, visitor);
557
- }
558
- }
559
- }
560
- _ => {}
561
- }
562
- }
@@ -1,10 +0,0 @@
1
- pub fn set_panic_hook() {
2
- // When the `console_error_panic_hook` feature is enabled, we can call the
3
- // `set_panic_hook` function at least once during initialization, and then
4
- // we will get better error messages if our code ever panics.
5
- //
6
- // For more details see
7
- // https://github.com/rustwasm/console_error_panic_hook#readme
8
- #[cfg(feature = "console_error_panic_hook")]
9
- console_error_panic_hook::set_once();
10
- }
@@ -1,112 +0,0 @@
1
- package engine
2
-
3
- import (
4
- "encoding/json"
5
- "errors"
6
- "fmt"
7
- "io"
8
- "net/http"
9
- "sync"
10
- )
11
-
12
- type ActionFunc func(args []any) (any, error)
13
-
14
- type StatusError struct {
15
- Status int
16
- Message string
17
- Code string
18
- }
19
-
20
- func (e *StatusError) Error() string {
21
- return e.Message
22
- }
23
-
24
- type ActionRegistry struct {
25
- mu sync.RWMutex
26
- actions map[string]ActionFunc
27
- }
28
-
29
- func NewActionRegistry() *ActionRegistry {
30
- return &ActionRegistry{actions: make(map[string]ActionFunc)}
31
- }
32
-
33
- func (r *ActionRegistry) DefineAction(name string, fn ActionFunc) {
34
- r.mu.Lock()
35
- defer r.mu.Unlock()
36
- r.actions[name] = fn
37
- }
38
-
39
- func (r *ActionRegistry) lookup(name string) (ActionFunc, bool) {
40
- r.mu.RLock()
41
- defer r.mu.RUnlock()
42
- fn, ok := r.actions[name]
43
- return fn, ok
44
- }
45
-
46
- func (r *ActionRegistry) RegisteredNames() []string {
47
- r.mu.RLock()
48
- defer r.mu.RUnlock()
49
- names := make([]string, 0, len(r.actions))
50
- for name := range r.actions {
51
- names = append(names, name)
52
- }
53
- return names
54
- }
55
-
56
- type requestBody struct {
57
- Args []any `json:"args"`
58
- }
59
-
60
- type responseBody struct {
61
- Data any `json:"data,omitempty"`
62
- Error string `json:"error,omitempty"`
63
- Code string `json:"code,omitempty"`
64
- }
65
-
66
- func (r *ActionRegistry) HandleRequest(w http.ResponseWriter, req *http.Request, name string) {
67
- fn, ok := r.lookup(name)
68
- if !ok {
69
- sendJSON(w, http.StatusNotFound, responseBody{Error: fmt.Sprintf("Action %q not found", name)})
70
- return
71
- }
72
-
73
- raw, err := io.ReadAll(req.Body)
74
- if err != nil {
75
- sendJSON(w, http.StatusBadRequest, responseBody{Error: "Invalid request body"})
76
- return
77
- }
78
-
79
- var body requestBody
80
- if len(raw) > 0 {
81
- if err := json.Unmarshal(raw, &body); err != nil {
82
- sendJSON(w, http.StatusBadRequest, responseBody{Error: "Invalid JSON body"})
83
- return
84
- }
85
- }
86
-
87
- data, err := fn(body.Args)
88
- if err != nil {
89
- var se *StatusError
90
- if errors.As(err, &se) {
91
- sendJSON(w, se.Status, responseBody{Error: se.Message, Code: se.Code})
92
- return
93
- }
94
-
95
- fmt.Printf("[fusee] action %q returned an error: %v\n", name, err)
96
- sendJSON(w, http.StatusInternalServerError, responseBody{Error: "Internal server error"})
97
- return
98
- }
99
-
100
- sendJSON(w, http.StatusOK, responseBody{Data: data})
101
- }
102
-
103
- func sendJSON(w http.ResponseWriter, status int, body responseBody) {
104
- raw, err := json.Marshal(body)
105
- if err != nil {
106
- w.WriteHeader(http.StatusInternalServerError)
107
- return
108
- }
109
- w.Header().Set("Content-Type", "application/json; charset=utf-8")
110
- w.WriteHeader(status)
111
- w.Write(raw)
112
- }