@browsersync/bslive 0.0.7 → 0.0.9

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 (136) hide show
  1. package/Cargo.toml +30 -2
  2. package/bin.js +3 -2
  3. package/bslive/Cargo.toml +16 -1
  4. package/bslive/build.rs +1 -1
  5. package/bslive/src/lib.rs +120 -4
  6. package/bsnext/Cargo.toml +21 -0
  7. package/bsnext/src/main.rs +110 -0
  8. package/crates/bsnext_client/Cargo.toml +8 -0
  9. package/crates/bsnext_client/build.rs +53 -0
  10. package/crates/bsnext_client/dist/index.js +6493 -0
  11. package/crates/bsnext_client/generated/dto.ts +116 -0
  12. package/crates/bsnext_client/generated/schema.ts +187 -0
  13. package/crates/bsnext_client/index.html +14 -0
  14. package/crates/bsnext_client/package-lock.json +2059 -0
  15. package/crates/bsnext_client/package.json +25 -0
  16. package/crates/bsnext_client/src/lib.rs +1 -0
  17. package/crates/bsnext_client/style.css +3 -0
  18. package/crates/bsnext_client/ts/console.ts +25 -0
  19. package/crates/bsnext_client/ts/index.ts +73 -0
  20. package/crates/bsnext_client/tsconfig.json +16 -0
  21. package/crates/bsnext_core/Cargo.toml +43 -0
  22. package/crates/bsnext_core/src/dir_loader.rs +230 -0
  23. package/crates/bsnext_core/src/dto.rs +281 -0
  24. package/crates/bsnext_core/src/handlers/mod.rs +1 -0
  25. package/crates/bsnext_core/src/handlers/proxy.rs +95 -0
  26. package/crates/bsnext_core/src/lib.rs +11 -0
  27. package/crates/bsnext_core/src/meta/mod.rs +5 -0
  28. package/crates/bsnext_core/src/not_found/mod.rs +2 -0
  29. package/crates/bsnext_core/src/not_found/not_found.html +20 -0
  30. package/crates/bsnext_core/src/not_found/not_found_service.rs +41 -0
  31. package/crates/bsnext_core/src/not_found/route_list.rs +49 -0
  32. package/crates/bsnext_core/src/panic_handler.rs +32 -0
  33. package/crates/bsnext_core/src/raw_loader.rs +226 -0
  34. package/crates/bsnext_core/src/server/actor.rs +92 -0
  35. package/crates/bsnext_core/src/server/error.rs +41 -0
  36. package/crates/bsnext_core/src/server/handler_change.rs +85 -0
  37. package/crates/bsnext_core/src/server/handler_listen.rs +157 -0
  38. package/crates/bsnext_core/src/server/handler_patch.rs +42 -0
  39. package/crates/bsnext_core/src/server/handler_routes_updated.rs +27 -0
  40. package/crates/bsnext_core/src/server/handler_stop.rs +38 -0
  41. package/crates/bsnext_core/src/server/mod.rs +10 -0
  42. package/crates/bsnext_core/src/server/router/mod.rs +112 -0
  43. package/crates/bsnext_core/src/server/router/tests.rs +204 -0
  44. package/crates/bsnext_core/src/server/signals.rs +11 -0
  45. package/crates/bsnext_core/src/server/state.rs +19 -0
  46. package/crates/bsnext_core/src/servers_supervisor/actor.rs +199 -0
  47. package/crates/bsnext_core/src/servers_supervisor/file_changed_handler.rs +41 -0
  48. package/crates/bsnext_core/src/servers_supervisor/get_servers_handler.rs +23 -0
  49. package/crates/bsnext_core/src/servers_supervisor/input_changed_handler.rs +21 -0
  50. package/crates/bsnext_core/src/servers_supervisor/mod.rs +6 -0
  51. package/crates/bsnext_core/src/servers_supervisor/start_handler.rs +82 -0
  52. package/crates/bsnext_core/src/servers_supervisor/stop_handler.rs +26 -0
  53. package/crates/bsnext_core/src/ws/mod.rs +164 -0
  54. package/crates/bsnext_example/Cargo.toml +10 -0
  55. package/crates/bsnext_example/src/basic.rs +51 -0
  56. package/crates/bsnext_example/src/lib.rs +26 -0
  57. package/crates/bsnext_example/src/lit.rs +37 -0
  58. package/crates/bsnext_example/src/md.rs +18 -0
  59. package/crates/bsnext_fs/Cargo.toml +22 -0
  60. package/crates/bsnext_fs/src/actor.rs +122 -0
  61. package/crates/bsnext_fs/src/buffered_debounce.rs +166 -0
  62. package/crates/bsnext_fs/src/filter.rs +30 -0
  63. package/crates/bsnext_fs/src/inner_fs_event_handler.rs +94 -0
  64. package/crates/bsnext_fs/src/lib.rs +141 -0
  65. package/crates/bsnext_fs/src/remove_path_handler.rs +46 -0
  66. package/crates/bsnext_fs/src/stop_handler.rs +15 -0
  67. package/crates/bsnext_fs/src/stream.rs +167 -0
  68. package/crates/bsnext_fs/src/test/mod.rs +213 -0
  69. package/crates/bsnext_fs/src/watch_path_handler.rs +67 -0
  70. package/crates/bsnext_fs/src/watcher.rs +348 -0
  71. package/crates/bsnext_input/Cargo.toml +22 -0
  72. package/crates/bsnext_input/src/input_test/mod.rs +151 -0
  73. package/crates/bsnext_input/src/lib.rs +153 -0
  74. package/crates/bsnext_input/src/md.rs +541 -0
  75. package/crates/bsnext_input/src/paths.rs +64 -0
  76. package/crates/bsnext_input/src/route.rs +185 -0
  77. package/crates/bsnext_input/src/route_manifest.rs +186 -0
  78. package/crates/bsnext_input/src/server_config.rs +88 -0
  79. package/crates/bsnext_input/src/target.rs +7 -0
  80. package/crates/bsnext_input/src/watch_opt_test/mod.rs +68 -0
  81. package/crates/bsnext_input/src/yml.rs +1 -0
  82. package/crates/bsnext_output/Cargo.toml +16 -0
  83. package/crates/bsnext_output/src/json.rs +11 -0
  84. package/crates/bsnext_output/src/lib.rs +25 -0
  85. package/crates/bsnext_output/src/pretty.rs +147 -0
  86. package/crates/bsnext_resp/Cargo.toml +13 -0
  87. package/crates/bsnext_resp/src/js/snippet.html +1 -0
  88. package/crates/bsnext_resp/src/js/ws.js +1 -0
  89. package/crates/bsnext_resp/src/lib.rs +79 -0
  90. package/crates/bsnext_system/Cargo.toml +29 -0
  91. package/crates/bsnext_system/src/args.rs +43 -0
  92. package/crates/bsnext_system/src/lib.rs +227 -0
  93. package/crates/bsnext_system/src/monitor.rs +241 -0
  94. package/crates/bsnext_system/src/monitor_any_watchables.rs +127 -0
  95. package/crates/bsnext_system/src/start_kind/snapshots/bsnext_system__start_kind__start_from_paths__test__test-2.snap +11 -0
  96. package/crates/bsnext_system/src/start_kind/snapshots/bsnext_system__start_kind__start_from_paths__test__test.snap +29 -0
  97. package/crates/bsnext_system/src/start_kind/start_from_example.rs +49 -0
  98. package/crates/bsnext_system/src/start_kind/start_from_inputs.rs +67 -0
  99. package/crates/bsnext_system/src/start_kind/start_from_paths.rs +51 -0
  100. package/crates/bsnext_system/src/start_kind.rs +56 -0
  101. package/crates/bsnext_system/src/startup.rs +51 -0
  102. package/crates/bsnext_tracing/Cargo.toml +10 -0
  103. package/crates/bsnext_tracing/src/lib.rs +89 -0
  104. package/examples/basic/input.yml +5 -0
  105. package/examples/basic/public/index.html +15 -0
  106. package/examples/basic/public/reset.css +52 -0
  107. package/examples/basic/public/script.js +1 -0
  108. package/examples/basic/public/styles.css +3 -0
  109. package/examples/kitchen-sink/a-file.md +1 -0
  110. package/examples/kitchen-sink/api/1.json +1 -0
  111. package/examples/kitchen-sink/api/2.json +3 -0
  112. package/examples/kitchen-sink/app.js +1 -0
  113. package/examples/kitchen-sink/input.html +185 -0
  114. package/examples/kitchen-sink/input.yml +133 -0
  115. package/examples/kitchen-sink/styles-2.css +3 -0
  116. package/examples/lit/index.html +21 -0
  117. package/examples/lit/input.yml +5 -0
  118. package/examples/lit/lit.js +82 -0
  119. package/examples/lit/package-lock.json +62 -0
  120. package/examples/lit/package.json +15 -0
  121. package/examples/md-single/frontmatter.md +35 -0
  122. package/examples/md-single/md-single.md +35 -0
  123. package/examples/openai/.nvm +1 -0
  124. package/examples/openai/build.mjs +21 -0
  125. package/examples/openai/index.html +13 -0
  126. package/examples/openai/input.yml +59 -0
  127. package/examples/openai/package-lock.json +720 -0
  128. package/examples/openai/package.json +21 -0
  129. package/examples/openai/src/index.js +21 -0
  130. package/examples/openai/sse/01.txt +8 -0
  131. package/examples/proxy-simple/input.yml +9 -0
  132. package/examples/single/input.yaml +26 -0
  133. package/index.d.ts +2 -0
  134. package/index.js +2 -1
  135. package/package.json +17 -17
  136. package/run.sh +6 -0
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "bsnext_client",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "tsc": "tsc",
8
+ "build": "esbuild ts/index.ts --bundle --outdir=dist --format=esm",
9
+ "test": "echo \"Error: no test specified\" && exit 1",
10
+ "schema": "ts-to-zod generated/dto.ts generated/schema.ts"
11
+ },
12
+ "keywords": [],
13
+ "author": "",
14
+ "license": "ISC",
15
+ "type": "module",
16
+ "dependencies": {
17
+ "esbuild": "^0.20.2",
18
+ "livereload-js": "^4.0.2",
19
+ "rxjs": "^7.8.1",
20
+ "typescript": "^5.4.5",
21
+ "@types/node": "18.19.25",
22
+ "ts-to-zod": "^3.8.2",
23
+ "zod": "^3.22.4"
24
+ }
25
+ }
@@ -0,0 +1 @@
1
+ pub const DIR: &str = "dist";
@@ -0,0 +1,3 @@
1
+ body {
2
+ background: green;
3
+ }
@@ -0,0 +1,25 @@
1
+ import {Observable, Subject} from "rxjs";
2
+
3
+ export enum Level {
4
+ Trace = "Trace",
5
+ Debug = "Debug",
6
+ Info = "Info",
7
+ Error = "Error",
8
+ }
9
+
10
+ export interface ConsoleEvent {
11
+ level: Level,
12
+ text: string
13
+ }
14
+
15
+ export function createLRConsoleObserver(): [Subject<ConsoleEvent>, Pick<typeof console, "log">] {
16
+ const subject = new Subject<ConsoleEvent>;
17
+ return [subject, {
18
+ log: function (...data: any[]): void {
19
+ subject.next({
20
+ level: Level.Debug,
21
+ text: data.join('\n')
22
+ });
23
+ },
24
+ }]
25
+ }
@@ -0,0 +1,73 @@
1
+ // @ts-ignore
2
+ import {Reloader} from "livereload-js/src/reloader.js";
3
+ // @ts-ignore
4
+ import {Timer} from "livereload-js/src/timer.js";
5
+
6
+ import {webSocket} from "rxjs/webSocket";
7
+ import {retry} from "rxjs";
8
+ import {clientEventSchema} from "../generated/schema.js";
9
+ import {ChangeDTO, ClientEvent} from "../generated/dto";
10
+ import {createLRConsoleObserver, Level} from "./console";
11
+
12
+ const [consoleSubject, consoleApi] = createLRConsoleObserver();
13
+
14
+ const r = new Reloader(window, consoleApi, Timer);
15
+ const url = new URL(window.location.href);
16
+ url.protocol = url.protocol === 'http:' ? 'ws' : 'wss';
17
+ url.pathname = '/__bs_ws'
18
+ const socket = webSocket<ClientEvent>(url.href);
19
+
20
+ socket
21
+ .pipe(retry({delay: 5000}))
22
+ .subscribe(m => {
23
+ console.log(JSON.stringify(m, null, 2))
24
+ const parsed = clientEventSchema.parse(m);
25
+ switch (parsed.kind) {
26
+ case "Change": {
27
+ changedPath(parsed.payload);
28
+ break;
29
+ }
30
+ default: {
31
+ console.warn("unhandled client event")
32
+ }
33
+ }
34
+ })
35
+
36
+ function changedPath(change: ChangeDTO) {
37
+ switch (change.kind) {
38
+ case "FsMany": {
39
+ // todo: if this collection of events contains
40
+ // anything that will cause a refresh, just do it immediately
41
+ for (let changeDTO of change.payload) {
42
+ changedPath(changeDTO);
43
+ }
44
+ break
45
+ }
46
+ case "Fs": {
47
+ let path = change.payload.path;
48
+ r.reload(path, {
49
+ liveCSS: true,
50
+ liveImg: true,
51
+ reloadMissingCSS: false,
52
+ originalPath: '',
53
+ overrideURL: '',
54
+ serverURL: ``,
55
+ })
56
+ }
57
+ }
58
+ }
59
+
60
+ consoleSubject.subscribe(evt => {
61
+ switch (evt.level) {
62
+ case Level.Trace:
63
+ break;
64
+ case Level.Debug:
65
+ console.log('[debug]', evt.text)
66
+ break;
67
+ case Level.Info:
68
+ break;
69
+ case Level.Error:
70
+ break;
71
+ }
72
+ })
73
+
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES6",
4
+ "module": "commonjs",
5
+ "allowJs": true,
6
+ "checkJs": true,
7
+ "noEmit": true,
8
+ "esModuleInterop": true,
9
+ "forceConsistentCasingInFileNames": true,
10
+ "strict": true,
11
+ "skipLibCheck": true
12
+ },
13
+ "include": [
14
+ "ts"
15
+ ]
16
+ }
@@ -0,0 +1,43 @@
1
+ [package]
2
+ name = "bsnext_core"
3
+ version = "0.1.0"
4
+ edition = "2021"
5
+
6
+ # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7
+
8
+ [dependencies]
9
+ bsnext_input = { path = "../bsnext_input" }
10
+ bsnext_fs = { path = "../bsnext_fs" }
11
+ bsnext_resp = { path = "../bsnext_resp" }
12
+ bsnext_client = { path = "../bsnext_client" }
13
+
14
+ axum-server = { version = "0.6.0", features = ["tls-rustls"] }
15
+ axum-extra = { version = "0.9.3", features = ["typed-header"] }
16
+ hyper = { version = "1.0.0", features = ["full"] }
17
+ hyper-tls = { version = "0.6.0" }
18
+ hyper-util = { version = "0.1.1", features = ["client-legacy"] }
19
+ matchit = { version = "0.7.3" }
20
+ htmlescape = { version = "0.3.1" }
21
+
22
+ mime_guess = { workspace = true }
23
+ axum = { workspace = true }
24
+ clap = { workspace = true }
25
+ http-body-util = { workspace = true }
26
+ tokio = { workspace = true }
27
+ tokio-stream = { workspace = true }
28
+ tower = { workspace = true }
29
+ tower-http = { workspace = true }
30
+ tracing = { workspace = true }
31
+ actix = { workspace = true }
32
+ actix-rt = { workspace = true }
33
+ anyhow = { workspace = true }
34
+ futures = { workspace = true }
35
+ futures-util = { workspace = true }
36
+ serde = { workspace = true }
37
+ serde_yaml = { workspace = true }
38
+ thiserror = { workspace = true }
39
+ tracing-subscriber = { workspace = true }
40
+ serde_json = { workspace = true }
41
+ bytes = { workspace = true }
42
+ http = { workspace = true }
43
+ typeshare = { workspace = true }
@@ -0,0 +1,230 @@
1
+ #![allow(clippy::redundant_pattern_matching)]
2
+ #![allow(clippy::single_match)]
3
+ use crate::handlers::proxy::ProxyConfig;
4
+ use crate::meta::MetaData;
5
+ use crate::raw_loader::add_route_layers;
6
+ use axum::body::Body;
7
+ use axum::extract::{Request, State};
8
+
9
+ use axum::middleware::Next;
10
+ use axum::response::{IntoResponse, Response};
11
+ use axum::{middleware, Extension, Router};
12
+ use bytes::Bytes;
13
+ use futures::channel::mpsc::unbounded;
14
+ use futures::SinkExt;
15
+
16
+ use crate::handlers::proxy;
17
+ use crate::server::state::ServerState;
18
+ use axum::routing::any;
19
+ use bsnext_input::route::{DirRoute, ProxyRoute, Route, RouteKind};
20
+ use bsnext_resp::{response_modifications_layer, RespMod};
21
+ use http::{HeaderValue, StatusCode};
22
+ use http_body_util::BodyExt;
23
+ use std::collections::HashMap;
24
+ use std::convert::Infallible;
25
+ use std::sync::Arc;
26
+ use std::time::Duration;
27
+ use tokio::fs::File;
28
+ use tokio::io::{AsyncWriteExt, BufWriter};
29
+ use tokio_stream::StreamExt;
30
+ use tower::{ServiceBuilder, ServiceExt};
31
+ use tower_http::decompression::DecompressionLayer;
32
+ use tower_http::services::ServeDir;
33
+ use tracing::{span, Instrument, Level};
34
+
35
+ pub async fn serve_dir_loader(
36
+ State(state): State<Arc<ServerState>>,
37
+ req: Request,
38
+ _next: Next,
39
+ ) -> Response {
40
+ let _path = req.uri().path().to_owned();
41
+ let span = span!(parent: None, Level::INFO, "serve_dir_loader", path = req.uri().path());
42
+ let _guard = span.enter();
43
+
44
+ let accepts_html = RespMod::accepts_html(&req);
45
+ let routes = state.routes.read().instrument(span.clone()).await;
46
+ let mut app = Router::new();
47
+ let route_map = routes
48
+ .iter()
49
+ .filter(|r| matches!(&r.kind, RouteKind::Proxy(_) | RouteKind::Dir(_)))
50
+ .fold(HashMap::<&str, Vec<&Route>>::new(), |mut acc, route| {
51
+ acc.entry(route.path.as_str())
52
+ .and_modify(|acc| acc.push(route))
53
+ .or_insert(vec![route]);
54
+ acc
55
+ });
56
+
57
+ for (_, routes) in route_map.iter() {
58
+ if routes.len() > 1 {
59
+ tracing::error!(
60
+ "cannot register duplicate dir or proxy routes, only the first will take effect"
61
+ );
62
+ }
63
+
64
+ let route = routes.first().expect("guarded");
65
+ let mut router = match &route.kind {
66
+ RouteKind::Proxy(ProxyRoute { proxy }) => {
67
+ let response_handling = if accepts_html {
68
+ ResponseHandling::Modify
69
+ } else {
70
+ ResponseHandling::None
71
+ };
72
+ service_for_proxy(response_handling, route).layer(Extension(ProxyConfig {
73
+ target: proxy.to_owned(),
74
+ path: route.path.clone(),
75
+ }))
76
+ }
77
+ RouteKind::Dir(DirRoute { dir }) => service_for_dir(route, dir),
78
+ _ => unreachable!("{:?}", route.kind),
79
+ };
80
+ router = add_route_layers(router, route);
81
+ app = app.merge(router);
82
+ }
83
+
84
+ drop(routes);
85
+
86
+ let r = app.oneshot(req).await;
87
+
88
+ r.into_response()
89
+ }
90
+
91
+ fn service_for_dir(route: &Route, dir: &str) -> Router {
92
+ Router::new()
93
+ .nest_service(route.path.as_str(), ServeDir::new(dir))
94
+ .layer(middleware::from_fn(tag_file))
95
+ .layer(middleware::from_fn(response_modifications_layer))
96
+ }
97
+
98
+ async fn tag_file(req: Request, next: Next) -> Result<impl IntoResponse, (StatusCode, String)> {
99
+ let (mut parts, body) = next.run(req).await.into_parts();
100
+ if parts.status.as_u16() == 200 {
101
+ parts.extensions.insert(MetaData::ServedFile);
102
+ }
103
+ Ok(Response::from_parts(parts, body))
104
+ }
105
+
106
+ enum ResponseHandling {
107
+ Modify,
108
+ None,
109
+ }
110
+
111
+ fn service_for_proxy(handling: ResponseHandling, route: &Route) -> Router {
112
+ match handling {
113
+ ResponseHandling::Modify => Router::new()
114
+ .nest_service(route.path.as_str(), any(proxy::proxy_handler))
115
+ .layer(
116
+ ServiceBuilder::new()
117
+ .layer(middleware::from_fn(response_modifications_layer))
118
+ .layer(DecompressionLayer::new()),
119
+ ),
120
+ ResponseHandling::None => Router::new()
121
+ .nest_service(route.path.as_str(), any(proxy::proxy_handler))
122
+ .layer(middleware::from_fn(response_modifications_layer)),
123
+ }
124
+ }
125
+
126
+ #[allow(dead_code)]
127
+ async fn print_request_response(
128
+ req: Request,
129
+ next: Next,
130
+ ) -> Result<impl IntoResponse, (StatusCode, String)> {
131
+ let (parts, body) = req.into_parts();
132
+ let bytes = buffer_and_print("request", body).await?;
133
+ let req = Request::from_parts(parts, Body::from(bytes));
134
+
135
+ let res = next.run(req).await;
136
+ let (parts, body) = res.into_parts();
137
+
138
+ let (mut sender, rec) = unbounded::<Bytes>();
139
+ let is_event_stream = parts.headers.get("content-type")
140
+ == Some(&HeaderValue::from_str("text/event-stream").unwrap());
141
+
142
+ tokio::spawn(async move {
143
+ let mut stream = body.into_data_stream();
144
+ let mut chunks: Vec<Bytes> = vec![];
145
+ while let Some(b) = stream.next().await {
146
+ tracing::info!("CHUNK");
147
+ match b {
148
+ Ok(bytes) => {
149
+ if is_event_stream {
150
+ // let _written = file.write(&bytes).await;
151
+ chunks.push(bytes.to_owned());
152
+ match sender.send(bytes.to_owned()).await {
153
+ Ok(_) => tracing::trace!("stripped chunk sent"),
154
+ Err(_) => tracing::error!("stripped chunk not sent"),
155
+ };
156
+ } else {
157
+ match sender.send(bytes.to_owned()).await {
158
+ Ok(_) => tracing::trace!("chunk sent"),
159
+ Err(_) => tracing::error!("not sent"),
160
+ };
161
+ }
162
+ }
163
+ Err(e) => {
164
+ tracing::error!(?e, "error")
165
+ }
166
+ }
167
+ }
168
+ if is_event_stream {
169
+ let path = std::path::Path::new("record").join("out4.yml");
170
+ let mut file = BufWriter::new(File::create(path).await.expect("file"));
171
+ let to_str = chunks
172
+ .into_iter()
173
+ .map(|x| std::str::from_utf8(&x).expect("bytes").to_owned())
174
+ .collect::<Vec<_>>();
175
+ let yml = serde_yaml::to_string(&to_str).expect("to yaml");
176
+ println!("yml={}", yml);
177
+ let _r = file.write(yml.as_bytes());
178
+
179
+ match file.flush().await {
180
+ Ok(_) => {}
181
+ Err(_) => {}
182
+ }
183
+ }
184
+ });
185
+
186
+ // let res = Response::from_parts(parts, body);
187
+ // while let Some(b) = res.body().next().await {
188
+ // dbg!(b);
189
+ // }
190
+
191
+ // let (parts, body) = res.into_parts();
192
+ // let bytes = buffer_and_print("response", body).await?;
193
+ // let l = lines
194
+ // .lines()
195
+ // .map(|l| l.to_owned())
196
+ // .map(|l| l.strip_prefix("data:").unwrap_or(&l).to_owned())
197
+ // .filter(|l| !l.trim().is_empty())
198
+ // .collect::<Vec<_>>();
199
+
200
+ let stream = rec
201
+ .throttle(Duration::from_millis(500))
202
+ .map(Ok::<_, Infallible>);
203
+
204
+ let res = Response::from_parts(parts, Body::from_stream(stream));
205
+
206
+ Ok(res)
207
+ }
208
+
209
+ #[allow(dead_code)]
210
+ async fn buffer_and_print<B>(direction: &str, body: B) -> Result<Bytes, (StatusCode, String)>
211
+ where
212
+ B: axum::body::HttpBody<Data = Bytes>,
213
+ B::Error: std::fmt::Display,
214
+ {
215
+ let bytes = match body.collect().await {
216
+ Ok(collected) => collected.to_bytes(),
217
+ Err(err) => {
218
+ return Err((
219
+ StatusCode::BAD_REQUEST,
220
+ format!("failed to read {direction} body: {err}"),
221
+ ));
222
+ }
223
+ };
224
+
225
+ if let Ok(body) = std::str::from_utf8(&bytes) {
226
+ tracing::debug!("{direction} body = {body:?}");
227
+ }
228
+
229
+ Ok(bytes)
230
+ }
@@ -0,0 +1,281 @@
1
+ use actix::MessageResponse;
2
+ use bsnext_input::server_config::Identity;
3
+ use bsnext_input::InputError;
4
+ use std::fmt::{Display, Formatter};
5
+ use std::path::Path;
6
+
7
+ use crate::server::handler_change::{Change, ChangeKind};
8
+ use bsnext_fs::Debounce;
9
+ use typeshare::typeshare;
10
+
11
+ #[typeshare]
12
+ #[derive(Debug, serde::Serialize)]
13
+ pub struct ServersStarted {
14
+ pub servers_resp: GetServersMessageResponse,
15
+ pub changeset: ServerChangeSet,
16
+ }
17
+
18
+ #[typeshare]
19
+ #[derive(Debug, serde::Serialize)]
20
+ pub enum EventLevel {
21
+ #[serde(rename = "BSLIVE_EXTERNAL")]
22
+ External,
23
+ }
24
+
25
+ #[typeshare]
26
+ #[derive(Debug, serde::Serialize)]
27
+ pub struct ExternalEvent {
28
+ pub level: EventLevel,
29
+ pub fields: ExternalEvents,
30
+ }
31
+
32
+ #[typeshare]
33
+ #[derive(Debug, serde::Serialize)]
34
+ #[serde(tag = "kind", content = "payload")]
35
+ pub enum ExternalEvents {
36
+ ServersStarted(ServersStarted),
37
+ Watching(Watching),
38
+ WatchingStopped(StoppedWatching),
39
+ FileChanged(FileChanged),
40
+ FilesChanged(FilesChangedDTO),
41
+ InputFileChanged(FileChanged),
42
+ InputAccepted(InputAccepted),
43
+ StartupFailed(InputErrorDTO),
44
+ }
45
+
46
+ #[typeshare]
47
+ #[derive(Debug, serde::Serialize)]
48
+ pub struct InputAccepted {
49
+ pub path: String,
50
+ }
51
+
52
+ #[typeshare]
53
+ #[derive(Debug, serde::Serialize)]
54
+ pub struct FileChanged {
55
+ pub path: String,
56
+ }
57
+
58
+ #[typeshare]
59
+ #[derive(Debug, serde::Serialize)]
60
+ pub struct FilesChangedDTO {
61
+ pub paths: Vec<String>,
62
+ }
63
+
64
+ #[typeshare]
65
+ #[derive(Debug, serde::Serialize)]
66
+ pub struct Watching {
67
+ pub paths: Vec<String>,
68
+ pub debounce: DebounceDTO,
69
+ }
70
+
71
+ #[typeshare]
72
+ #[derive(Debug, serde::Serialize)]
73
+ pub struct StoppedWatching {
74
+ pub paths: Vec<String>,
75
+ }
76
+
77
+ impl StoppedWatching {
78
+ pub fn from_path_buf(p: &Path) -> Self {
79
+ Self {
80
+ paths: vec![p.to_string_lossy().to_string()],
81
+ }
82
+ }
83
+ }
84
+
85
+ impl Display for Watching {
86
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
87
+ write!(f, "yo")
88
+ }
89
+ }
90
+
91
+ #[typeshare]
92
+ #[derive(Debug, serde::Serialize)]
93
+ pub struct DebounceDTO {
94
+ kind: String,
95
+ ms: String,
96
+ }
97
+
98
+ impl Display for DebounceDTO {
99
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
100
+ write!(f, "{}:{}ms", self.kind, self.ms)
101
+ }
102
+ }
103
+
104
+ impl From<Debounce> for DebounceDTO {
105
+ fn from(value: Debounce) -> Self {
106
+ match value {
107
+ Debounce::Trailing { duration } => Self {
108
+ kind: "trailing".to_string(),
109
+ ms: duration.as_millis().to_string(),
110
+ },
111
+ Debounce::Buffered { duration } => Self {
112
+ kind: "buffered".to_string(),
113
+ ms: duration.as_millis().to_string(),
114
+ },
115
+ }
116
+ }
117
+ }
118
+
119
+ impl FileChanged {
120
+ pub fn from_path_buf(p: &Path) -> Self {
121
+ Self {
122
+ path: p.to_string_lossy().to_string(),
123
+ }
124
+ }
125
+ }
126
+
127
+ impl Watching {
128
+ pub fn from_path_buf(p: &Path, debounce: Debounce) -> Self {
129
+ Self {
130
+ paths: vec![p.to_string_lossy().to_string()],
131
+ debounce: debounce.into(),
132
+ }
133
+ }
134
+ }
135
+
136
+ #[typeshare]
137
+ #[derive(Debug, Clone, serde::Serialize)]
138
+ #[serde(tag = "kind", content = "payload")]
139
+ pub enum ServerChange {
140
+ Stopped { bind_address: String },
141
+ Started,
142
+ Patched,
143
+ }
144
+
145
+ #[typeshare]
146
+ #[derive(Debug, serde::Serialize)]
147
+ pub struct ServerChangeSetItem {
148
+ pub identity: IdentityDTO,
149
+ pub change: ServerChange,
150
+ }
151
+ #[typeshare]
152
+ #[derive(Debug, serde::Serialize)]
153
+ pub struct ServerChangeSet {
154
+ pub items: Vec<ServerChangeSetItem>,
155
+ }
156
+
157
+ #[typeshare::typeshare]
158
+ #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, MessageResponse)]
159
+ pub struct GetServersMessageResponse {
160
+ pub servers: Vec<ServersDTO>,
161
+ }
162
+
163
+ #[typeshare::typeshare]
164
+ #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
165
+ pub struct ServersDTO {
166
+ pub identity: IdentityDTO,
167
+ pub socket_addr: String,
168
+ }
169
+
170
+ #[typeshare::typeshare]
171
+ #[derive(Debug, PartialEq, Hash, Eq, Clone, serde::Deserialize, serde::Serialize)]
172
+ #[serde(tag = "kind", content = "payload")]
173
+ pub enum IdentityDTO {
174
+ Both { name: String, bind_address: String },
175
+ Address { bind_address: String },
176
+ Named { name: String },
177
+ }
178
+
179
+ impl From<&Identity> for IdentityDTO {
180
+ fn from(value: &Identity) -> Self {
181
+ match value {
182
+ Identity::Both { name, bind_address } => IdentityDTO::Both {
183
+ name: name.to_owned(),
184
+ bind_address: bind_address.to_owned(),
185
+ },
186
+ Identity::Address { bind_address } => IdentityDTO::Address {
187
+ bind_address: bind_address.to_owned(),
188
+ },
189
+ Identity::Named { name } => IdentityDTO::Named {
190
+ name: name.to_owned(),
191
+ },
192
+ }
193
+ }
194
+ }
195
+
196
+ #[typeshare::typeshare]
197
+ #[derive(Debug, PartialEq, Hash, Eq, Clone, serde::Deserialize, serde::Serialize)]
198
+ #[serde(tag = "kind", content = "payload")]
199
+ pub enum InputErrorDTO {
200
+ MissingInputs(String),
201
+ InvalidInput(String),
202
+ NotFound(String),
203
+ InputWriteError(String),
204
+ PathError(String),
205
+ PortError(String),
206
+ DirError(String),
207
+ }
208
+
209
+ impl From<&InputError> for InputErrorDTO {
210
+ fn from(value: &InputError) -> Self {
211
+ match value {
212
+ e @ InputError::MissingInputs => InputErrorDTO::MissingInputs(e.to_string()),
213
+ e @ InputError::InvalidInput(_) => InputErrorDTO::InvalidInput(e.to_string()),
214
+ e @ InputError::NotFound(_) => InputErrorDTO::NotFound(e.to_string()),
215
+ e @ InputError::InputWriteError(_) => InputErrorDTO::InputWriteError(e.to_string()),
216
+ e @ InputError::PathError(_) => InputErrorDTO::PathError(e.to_string()),
217
+ e @ InputError::PortError(_) => InputErrorDTO::PortError(e.to_string()),
218
+ e @ InputError::DirError(_) => InputErrorDTO::DirError(e.to_string()),
219
+ }
220
+ }
221
+ }
222
+
223
+ #[typeshare::typeshare]
224
+ #[derive(Debug, Clone, serde::Serialize)]
225
+ #[serde(tag = "kind", content = "payload")]
226
+ pub enum ClientEvent {
227
+ Change(ChangeDTO),
228
+ }
229
+
230
+ #[typeshare::typeshare]
231
+ #[derive(Debug, Clone, serde::Serialize)]
232
+ #[serde(tag = "kind", content = "payload")]
233
+ pub enum ChangeDTO {
234
+ Fs {
235
+ path: String,
236
+ change_kind: ChangeKind,
237
+ },
238
+ FsMany(Vec<ChangeDTO>),
239
+ }
240
+
241
+ impl From<&Change> for ChangeDTO {
242
+ fn from(value: &Change) -> Self {
243
+ match value {
244
+ Change::Fs { path, change_kind } => Self::Fs {
245
+ path: path.to_string_lossy().to_string(),
246
+ change_kind: change_kind.clone(),
247
+ },
248
+ Change::FsMany(changes) => Self::FsMany(
249
+ changes
250
+ .iter()
251
+ .map(|change| match change {
252
+ Change::Fs { path, change_kind } => Self::Fs {
253
+ path: path.to_string_lossy().to_string(),
254
+ change_kind: change_kind.clone(),
255
+ },
256
+ Change::FsMany(_) => unreachable!("recursive not supported"),
257
+ })
258
+ .collect(),
259
+ ),
260
+ }
261
+ }
262
+ }
263
+
264
+ #[cfg(test)]
265
+ mod test {
266
+ use super::*;
267
+ #[test]
268
+ fn test_serialize() -> anyhow::Result<()> {
269
+ let fs = Change::fs("./a.js");
270
+ let evt = ClientEvent::Change((&fs).into());
271
+ let _json = serde_json::to_string(&evt).unwrap();
272
+ Ok(())
273
+ }
274
+ #[test]
275
+ fn test_serialize_server_start() -> anyhow::Result<()> {
276
+ let fs = Change::fs("./a.js");
277
+ let evt = ClientEvent::Change((&fs).into());
278
+ let _json = serde_json::to_string(&evt).unwrap();
279
+ Ok(())
280
+ }
281
+ }
@@ -0,0 +1 @@
1
+ pub mod proxy;