titanpl 6.0.0 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -6
- package/packages/cli/index.js +25 -11
- package/packages/cli/package.json +4 -4
- package/packages/cli/src/commands/build-ext.js +157 -0
- package/packages/cli/src/commands/build.js +12 -0
- package/packages/cli/src/commands/create.js +160 -0
- package/packages/cli/src/commands/init.js +5 -11
- package/packages/cli/src/commands/run-ext.js +104 -0
- package/{titanpl-sdk → packages/core-source}/LICENSE +1 -1
- package/packages/core-source/README.md +128 -0
- package/packages/core-source/V8_SERIALIZATION.md +125 -0
- package/packages/core-source/configure.js +50 -0
- package/packages/core-source/globals.d.ts +2238 -0
- package/packages/core-source/index.d.ts +515 -0
- package/packages/core-source/index.js +639 -0
- package/packages/core-source/jsconfig.json +12 -0
- package/packages/core-source/mkctx.config.json +7 -0
- package/packages/core-source/native/Cargo.lock +1559 -0
- package/packages/core-source/native/Cargo.toml +30 -0
- package/packages/core-source/native/src/crypto_impl.rs +139 -0
- package/packages/core-source/native/src/lib.rs +702 -0
- package/packages/core-source/native/src/storage_impl.rs +73 -0
- package/packages/core-source/native/src/v8_impl.rs +93 -0
- package/packages/core-source/package-lock.json +1464 -0
- package/packages/core-source/package.json +53 -0
- package/packages/core-source/tests/buffer.test.js +78 -0
- package/packages/core-source/tests/cookies.test.js +117 -0
- package/packages/core-source/tests/crypto.test.js +142 -0
- package/packages/core-source/tests/fs.test.js +176 -0
- package/packages/core-source/tests/ls.test.js +149 -0
- package/packages/core-source/tests/net.test.js +84 -0
- package/packages/core-source/tests/os.test.js +81 -0
- package/packages/core-source/tests/path.test.js +102 -0
- package/packages/core-source/tests/response.test.js +146 -0
- package/packages/core-source/tests/session.test.js +110 -0
- package/packages/core-source/tests/setup.js +325 -0
- package/packages/core-source/tests/time.test.js +57 -0
- package/packages/core-source/tests/url.test.js +82 -0
- package/packages/core-source/titan-ext.d.ts +2 -0
- package/packages/core-source/titan.json +9 -0
- package/packages/core-source/vitest.config.js +8 -0
- package/packages/engine-darwin-arm64/README.md +0 -2
- package/packages/engine-darwin-arm64/package.json +1 -1
- package/packages/engine-linux-x64/README.md +0 -2
- package/packages/engine-linux-x64/package.json +1 -1
- package/packages/engine-win32-x64/README.md +0 -1
- package/packages/engine-win32-x64/bin/titan-server.exe +0 -0
- package/packages/engine-win32-x64/package.json +1 -1
- package/packages/native/README.md +0 -1
- package/packages/native/index.d.ts +25 -4
- package/packages/native/index.js +7 -0
- package/packages/native/package.json +2 -2
- package/packages/native/t.native.d.ts +167 -2
- package/packages/packet/index.js +103 -94
- package/packages/packet/package.json +1 -1
- package/packages/route/package.json +1 -1
- package/packages/sdk/index.js +2 -0
- package/packages/sdk/package.json +18 -0
- package/packages/sdk/test/index.js +120 -0
- package/templates/common/_tanfig.json +19 -13
- package/templates/extension/index.d.ts +26 -22
- package/templates/extension/index.js +15 -15
- package/templates/extension/native/Cargo.toml +5 -3
- package/templates/extension/native/src/lib.rs +2 -3
- package/templates/extension/package.json +10 -20
- package/templates/extension/titan.json +5 -16
- package/templates/extension/utils/registerExtension.js +44 -0
- package/templates/js/package.json +8 -8
- package/templates/rust-js/package.json +5 -5
- package/templates/rust-ts/package.json +5 -5
- package/templates/ts/package.json +8 -8
- package/packages/packet/node_modules/typescript/LICENSE.txt +0 -55
- package/packages/packet/node_modules/typescript/README.md +0 -50
- package/packages/packet/node_modules/typescript/SECURITY.md +0 -41
- package/packages/packet/node_modules/typescript/ThirdPartyNoticeText.txt +0 -193
- package/packages/packet/node_modules/typescript/bin/tsc +0 -2
- package/packages/packet/node_modules/typescript/bin/tsserver +0 -2
- package/packages/packet/node_modules/typescript/lib/_tsc.js +0 -133818
- package/packages/packet/node_modules/typescript/lib/_tsserver.js +0 -659
- package/packages/packet/node_modules/typescript/lib/_typingsInstaller.js +0 -222
- package/packages/packet/node_modules/typescript/lib/cs/diagnosticMessages.generated.json +0 -2122
- package/packages/packet/node_modules/typescript/lib/de/diagnosticMessages.generated.json +0 -2122
- package/packages/packet/node_modules/typescript/lib/es/diagnosticMessages.generated.json +0 -2122
- package/packages/packet/node_modules/typescript/lib/fr/diagnosticMessages.generated.json +0 -2122
- package/packages/packet/node_modules/typescript/lib/it/diagnosticMessages.generated.json +0 -2122
- package/packages/packet/node_modules/typescript/lib/ja/diagnosticMessages.generated.json +0 -2122
- package/packages/packet/node_modules/typescript/lib/ko/diagnosticMessages.generated.json +0 -2122
- package/packages/packet/node_modules/typescript/lib/lib.d.ts +0 -22
- package/packages/packet/node_modules/typescript/lib/lib.decorators.d.ts +0 -384
- package/packages/packet/node_modules/typescript/lib/lib.decorators.legacy.d.ts +0 -22
- package/packages/packet/node_modules/typescript/lib/lib.dom.asynciterable.d.ts +0 -41
- package/packages/packet/node_modules/typescript/lib/lib.dom.d.ts +0 -39429
- package/packages/packet/node_modules/typescript/lib/lib.dom.iterable.d.ts +0 -571
- package/packages/packet/node_modules/typescript/lib/lib.es2015.collection.d.ts +0 -147
- package/packages/packet/node_modules/typescript/lib/lib.es2015.core.d.ts +0 -597
- package/packages/packet/node_modules/typescript/lib/lib.es2015.d.ts +0 -28
- package/packages/packet/node_modules/typescript/lib/lib.es2015.generator.d.ts +0 -77
- package/packages/packet/node_modules/typescript/lib/lib.es2015.iterable.d.ts +0 -605
- package/packages/packet/node_modules/typescript/lib/lib.es2015.promise.d.ts +0 -81
- package/packages/packet/node_modules/typescript/lib/lib.es2015.proxy.d.ts +0 -128
- package/packages/packet/node_modules/typescript/lib/lib.es2015.reflect.d.ts +0 -144
- package/packages/packet/node_modules/typescript/lib/lib.es2015.symbol.d.ts +0 -46
- package/packages/packet/node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts +0 -326
- package/packages/packet/node_modules/typescript/lib/lib.es2016.array.include.d.ts +0 -116
- package/packages/packet/node_modules/typescript/lib/lib.es2016.d.ts +0 -21
- package/packages/packet/node_modules/typescript/lib/lib.es2016.full.d.ts +0 -23
- package/packages/packet/node_modules/typescript/lib/lib.es2016.intl.d.ts +0 -31
- package/packages/packet/node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts +0 -21
- package/packages/packet/node_modules/typescript/lib/lib.es2017.d.ts +0 -26
- package/packages/packet/node_modules/typescript/lib/lib.es2017.date.d.ts +0 -31
- package/packages/packet/node_modules/typescript/lib/lib.es2017.full.d.ts +0 -23
- package/packages/packet/node_modules/typescript/lib/lib.es2017.intl.d.ts +0 -44
- package/packages/packet/node_modules/typescript/lib/lib.es2017.object.d.ts +0 -49
- package/packages/packet/node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts +0 -135
- package/packages/packet/node_modules/typescript/lib/lib.es2017.string.d.ts +0 -45
- package/packages/packet/node_modules/typescript/lib/lib.es2017.typedarrays.d.ts +0 -53
- package/packages/packet/node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts +0 -77
- package/packages/packet/node_modules/typescript/lib/lib.es2018.asynciterable.d.ts +0 -53
- package/packages/packet/node_modules/typescript/lib/lib.es2018.d.ts +0 -24
- package/packages/packet/node_modules/typescript/lib/lib.es2018.full.d.ts +0 -24
- package/packages/packet/node_modules/typescript/lib/lib.es2018.intl.d.ts +0 -83
- package/packages/packet/node_modules/typescript/lib/lib.es2018.promise.d.ts +0 -30
- package/packages/packet/node_modules/typescript/lib/lib.es2018.regexp.d.ts +0 -37
- package/packages/packet/node_modules/typescript/lib/lib.es2019.array.d.ts +0 -79
- package/packages/packet/node_modules/typescript/lib/lib.es2019.d.ts +0 -24
- package/packages/packet/node_modules/typescript/lib/lib.es2019.full.d.ts +0 -24
- package/packages/packet/node_modules/typescript/lib/lib.es2019.intl.d.ts +0 -23
- package/packages/packet/node_modules/typescript/lib/lib.es2019.object.d.ts +0 -33
- package/packages/packet/node_modules/typescript/lib/lib.es2019.string.d.ts +0 -37
- package/packages/packet/node_modules/typescript/lib/lib.es2019.symbol.d.ts +0 -24
- package/packages/packet/node_modules/typescript/lib/lib.es2020.bigint.d.ts +0 -765
- package/packages/packet/node_modules/typescript/lib/lib.es2020.d.ts +0 -27
- package/packages/packet/node_modules/typescript/lib/lib.es2020.date.d.ts +0 -42
- package/packages/packet/node_modules/typescript/lib/lib.es2020.full.d.ts +0 -24
- package/packages/packet/node_modules/typescript/lib/lib.es2020.intl.d.ts +0 -474
- package/packages/packet/node_modules/typescript/lib/lib.es2020.number.d.ts +0 -28
- package/packages/packet/node_modules/typescript/lib/lib.es2020.promise.d.ts +0 -47
- package/packages/packet/node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts +0 -99
- package/packages/packet/node_modules/typescript/lib/lib.es2020.string.d.ts +0 -44
- package/packages/packet/node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts +0 -41
- package/packages/packet/node_modules/typescript/lib/lib.es2021.d.ts +0 -23
- package/packages/packet/node_modules/typescript/lib/lib.es2021.full.d.ts +0 -24
- package/packages/packet/node_modules/typescript/lib/lib.es2021.intl.d.ts +0 -166
- package/packages/packet/node_modules/typescript/lib/lib.es2021.promise.d.ts +0 -48
- package/packages/packet/node_modules/typescript/lib/lib.es2021.string.d.ts +0 -33
- package/packages/packet/node_modules/typescript/lib/lib.es2021.weakref.d.ts +0 -78
- package/packages/packet/node_modules/typescript/lib/lib.es2022.array.d.ts +0 -121
- package/packages/packet/node_modules/typescript/lib/lib.es2022.d.ts +0 -25
- package/packages/packet/node_modules/typescript/lib/lib.es2022.error.d.ts +0 -75
- package/packages/packet/node_modules/typescript/lib/lib.es2022.full.d.ts +0 -24
- package/packages/packet/node_modules/typescript/lib/lib.es2022.intl.d.ts +0 -145
- package/packages/packet/node_modules/typescript/lib/lib.es2022.object.d.ts +0 -26
- package/packages/packet/node_modules/typescript/lib/lib.es2022.regexp.d.ts +0 -39
- package/packages/packet/node_modules/typescript/lib/lib.es2022.string.d.ts +0 -25
- package/packages/packet/node_modules/typescript/lib/lib.es2023.array.d.ts +0 -924
- package/packages/packet/node_modules/typescript/lib/lib.es2023.collection.d.ts +0 -21
- package/packages/packet/node_modules/typescript/lib/lib.es2023.d.ts +0 -22
- package/packages/packet/node_modules/typescript/lib/lib.es2023.full.d.ts +0 -24
- package/packages/packet/node_modules/typescript/lib/lib.es2023.intl.d.ts +0 -56
- package/packages/packet/node_modules/typescript/lib/lib.es2024.arraybuffer.d.ts +0 -65
- package/packages/packet/node_modules/typescript/lib/lib.es2024.collection.d.ts +0 -29
- package/packages/packet/node_modules/typescript/lib/lib.es2024.d.ts +0 -26
- package/packages/packet/node_modules/typescript/lib/lib.es2024.full.d.ts +0 -24
- package/packages/packet/node_modules/typescript/lib/lib.es2024.object.d.ts +0 -29
- package/packages/packet/node_modules/typescript/lib/lib.es2024.promise.d.ts +0 -35
- package/packages/packet/node_modules/typescript/lib/lib.es2024.regexp.d.ts +0 -25
- package/packages/packet/node_modules/typescript/lib/lib.es2024.sharedmemory.d.ts +0 -68
- package/packages/packet/node_modules/typescript/lib/lib.es2024.string.d.ts +0 -29
- package/packages/packet/node_modules/typescript/lib/lib.es5.d.ts +0 -4601
- package/packages/packet/node_modules/typescript/lib/lib.es6.d.ts +0 -23
- package/packages/packet/node_modules/typescript/lib/lib.esnext.array.d.ts +0 -35
- package/packages/packet/node_modules/typescript/lib/lib.esnext.collection.d.ts +0 -96
- package/packages/packet/node_modules/typescript/lib/lib.esnext.d.ts +0 -29
- package/packages/packet/node_modules/typescript/lib/lib.esnext.decorators.d.ts +0 -28
- package/packages/packet/node_modules/typescript/lib/lib.esnext.disposable.d.ts +0 -193
- package/packages/packet/node_modules/typescript/lib/lib.esnext.error.d.ts +0 -24
- package/packages/packet/node_modules/typescript/lib/lib.esnext.float16.d.ts +0 -445
- package/packages/packet/node_modules/typescript/lib/lib.esnext.full.d.ts +0 -24
- package/packages/packet/node_modules/typescript/lib/lib.esnext.intl.d.ts +0 -21
- package/packages/packet/node_modules/typescript/lib/lib.esnext.iterator.d.ts +0 -148
- package/packages/packet/node_modules/typescript/lib/lib.esnext.promise.d.ts +0 -34
- package/packages/packet/node_modules/typescript/lib/lib.esnext.sharedmemory.d.ts +0 -25
- package/packages/packet/node_modules/typescript/lib/lib.scripthost.d.ts +0 -322
- package/packages/packet/node_modules/typescript/lib/lib.webworker.asynciterable.d.ts +0 -41
- package/packages/packet/node_modules/typescript/lib/lib.webworker.d.ts +0 -13150
- package/packages/packet/node_modules/typescript/lib/lib.webworker.importscripts.d.ts +0 -23
- package/packages/packet/node_modules/typescript/lib/lib.webworker.iterable.d.ts +0 -340
- package/packages/packet/node_modules/typescript/lib/pl/diagnosticMessages.generated.json +0 -2122
- package/packages/packet/node_modules/typescript/lib/pt-br/diagnosticMessages.generated.json +0 -2122
- package/packages/packet/node_modules/typescript/lib/ru/diagnosticMessages.generated.json +0 -2122
- package/packages/packet/node_modules/typescript/lib/tr/diagnosticMessages.generated.json +0 -2122
- package/packages/packet/node_modules/typescript/lib/tsc.js +0 -8
- package/packages/packet/node_modules/typescript/lib/tsserver.js +0 -8
- package/packages/packet/node_modules/typescript/lib/tsserverlibrary.d.ts +0 -17
- package/packages/packet/node_modules/typescript/lib/tsserverlibrary.js +0 -21
- package/packages/packet/node_modules/typescript/lib/typesMap.json +0 -497
- package/packages/packet/node_modules/typescript/lib/typescript.d.ts +0 -11437
- package/packages/packet/node_modules/typescript/lib/typescript.js +0 -200276
- package/packages/packet/node_modules/typescript/lib/typingsInstaller.js +0 -8
- package/packages/packet/node_modules/typescript/lib/watchGuard.js +0 -53
- package/packages/packet/node_modules/typescript/lib/zh-cn/diagnosticMessages.generated.json +0 -2122
- package/packages/packet/node_modules/typescript/lib/zh-tw/diagnosticMessages.generated.json +0 -2122
- package/packages/packet/node_modules/typescript/package.json +0 -120
- package/titanpl-sdk/README.md +0 -111
- package/titanpl-sdk/assets/titanpl-sdk.png +0 -0
- package/titanpl-sdk/bin/run.js +0 -274
- package/titanpl-sdk/index.js +0 -5
- package/titanpl-sdk/package-lock.json +0 -28
- package/titanpl-sdk/package.json +0 -40
- package/titanpl-sdk/templates/app/actions/hello.js +0 -5
- package/titanpl-sdk/templates/app/app.js +0 -7
- package/titanpl-sdk/templates/jsconfig.json +0 -19
- package/titanpl-sdk/templates/server/Cargo.toml +0 -52
- package/titanpl-sdk/templates/server/src/action_management.rs +0 -175
- package/titanpl-sdk/templates/server/src/errors.rs +0 -12
- package/titanpl-sdk/templates/server/src/extensions/builtin.rs +0 -1060
- package/titanpl-sdk/templates/server/src/extensions/external.rs +0 -338
- package/titanpl-sdk/templates/server/src/extensions/mod.rs +0 -580
- package/titanpl-sdk/templates/server/src/extensions/titan_core.js +0 -249
- package/titanpl-sdk/templates/server/src/fast_path.rs +0 -719
- package/titanpl-sdk/templates/server/src/main.rs +0 -607
- package/titanpl-sdk/templates/server/src/runtime.rs +0 -284
- package/titanpl-sdk/templates/server/src/utils.rs +0 -33
- package/titanpl-sdk/templates/titan/bundle.js +0 -259
- package/titanpl-sdk/templates/titan/dev.js +0 -390
- package/titanpl-sdk/templates/titan/error-box.js +0 -277
- package/titanpl-sdk/templates/titan/titan.js +0 -129
|
@@ -1,607 +0,0 @@
|
|
|
1
|
-
//! Titan HTTP Server (Performance Optimized)
|
|
2
|
-
//!
|
|
3
|
-
//! Key Features:
|
|
4
|
-
//! 1. Fast-path integration: static actions bypass V8 entirely.
|
|
5
|
-
//! 2. Pre-computed route responses: reply routes serve cached bytes.
|
|
6
|
-
//! 3. Benchmark mode: `TITAN_BENCHMARK=1` disables per-request logging & timings.
|
|
7
|
-
//! 4. Early fast-path check BEFORE body/header parsing.
|
|
8
|
-
//! 5. Mimalloc global allocator for faster allocations.
|
|
9
|
-
//! 6. Optimized response construction.
|
|
10
|
-
|
|
11
|
-
use anyhow::Result;
|
|
12
|
-
use axum::{
|
|
13
|
-
Router,
|
|
14
|
-
body::{Body, to_bytes},
|
|
15
|
-
extract::State,
|
|
16
|
-
http::{Request, StatusCode},
|
|
17
|
-
response::{IntoResponse, Json},
|
|
18
|
-
routing::any,
|
|
19
|
-
};
|
|
20
|
-
use serde_json::Value;
|
|
21
|
-
use smallvec::SmallVec;
|
|
22
|
-
use std::time::Instant;
|
|
23
|
-
use std::{collections::HashMap, fs, path::PathBuf, sync::Arc};
|
|
24
|
-
use tokio::net::TcpListener;
|
|
25
|
-
|
|
26
|
-
mod action_management;
|
|
27
|
-
mod extensions;
|
|
28
|
-
mod fast_path;
|
|
29
|
-
mod runtime;
|
|
30
|
-
mod utils;
|
|
31
|
-
|
|
32
|
-
use action_management::{DynamicRoute, RouteVal, match_dynamic_route};
|
|
33
|
-
use fast_path::{FastPathRegistry, PrecomputedRoute};
|
|
34
|
-
use runtime::RuntimeManager;
|
|
35
|
-
use utils::{blue, gray, green, red, white, yellow};
|
|
36
|
-
|
|
37
|
-
/// Global allocator: mimalloc for ~5-15% better allocation throughput.
|
|
38
|
-
#[global_allocator]
|
|
39
|
-
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
|
|
40
|
-
|
|
41
|
-
#[derive(Clone)]
|
|
42
|
-
struct AppState {
|
|
43
|
-
routes: Arc<HashMap<String, RouteVal>>,
|
|
44
|
-
dynamic_routes: Arc<Vec<DynamicRoute>>,
|
|
45
|
-
runtime: Arc<RuntimeManager>,
|
|
46
|
-
/// Pre-computed responses for static actions (bypass V8)
|
|
47
|
-
fast_paths: Arc<FastPathRegistry>,
|
|
48
|
-
/// Pre-serialized responses for reply routes (no re-serialization per request)
|
|
49
|
-
precomputed: Arc<HashMap<String, PrecomputedRoute>>,
|
|
50
|
-
/// When true: disable per-request logging and timings injection
|
|
51
|
-
production_mode: bool,
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
async fn root_route(state: State<AppState>, req: Request<Body>) -> impl IntoResponse {
|
|
55
|
-
handler(state, req).await
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
async fn dynamic_route(state: State<AppState>, req: Request<Body>) -> impl IntoResponse {
|
|
59
|
-
handler(state, req).await
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/// Main request handler — optimized with early fast-path bailout.
|
|
63
|
-
async fn handler(State(state): State<AppState>, req: Request<Body>) -> impl IntoResponse {
|
|
64
|
-
let method = req.method().as_str().to_uppercase();
|
|
65
|
-
let path = req.uri().path().to_string();
|
|
66
|
-
let strict_key = format!("{}:{}", method, path);
|
|
67
|
-
|
|
68
|
-
// Phase 1: Fast-Path Check (before ANY body/header parsing)
|
|
69
|
-
// This is the critical optimization. For static actions and reply routes,
|
|
70
|
-
// we return pre-computed bytes without touching the request body, headers,
|
|
71
|
-
// or V8 runtime. This path costs ~2-5µs vs ~50-100µs for the V8 path.
|
|
72
|
-
|
|
73
|
-
let start = Instant::now();
|
|
74
|
-
let log_enabled = !state.production_mode;
|
|
75
|
-
|
|
76
|
-
if let Some(route) = state
|
|
77
|
-
.routes
|
|
78
|
-
.get(&strict_key)
|
|
79
|
-
.or_else(|| state.routes.get(&path))
|
|
80
|
-
{
|
|
81
|
-
match route.r#type.as_str() {
|
|
82
|
-
|
|
83
|
-
// Precomputed reply routes
|
|
84
|
-
"json" | "text" => {
|
|
85
|
-
if let Some(precomputed) = state.precomputed.get(&strict_key) {
|
|
86
|
-
|
|
87
|
-
if state.production_mode {
|
|
88
|
-
// Benchmark mode → zero overhead
|
|
89
|
-
return precomputed.to_axum_response();
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
let mut response = precomputed.to_axum_response();
|
|
93
|
-
let elapsed = start.elapsed();
|
|
94
|
-
|
|
95
|
-
response.headers_mut().insert(
|
|
96
|
-
"Server-Timing",
|
|
97
|
-
format!("reply;dur={:.2}", elapsed.as_secs_f64() * 1000.0)
|
|
98
|
-
.parse()
|
|
99
|
-
.unwrap_or_else(|_| axum::http::HeaderValue::from_static("")),
|
|
100
|
-
);
|
|
101
|
-
|
|
102
|
-
if log_enabled {
|
|
103
|
-
println!(
|
|
104
|
-
"{} {} {} {}",
|
|
105
|
-
blue("[Titan]"),
|
|
106
|
-
green(&format!("{} {}", method, path)),
|
|
107
|
-
white("→ reply"),
|
|
108
|
-
gray(&format!("in {:.2?}", elapsed))
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return response;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Fallback (should never happen)
|
|
116
|
-
if route.r#type == "json" {
|
|
117
|
-
return Json(route.value.clone()).into_response();
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if let Some(s) = route.value.as_str() {
|
|
121
|
-
return s.to_string().into_response();
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// Action routes (Fast path check)
|
|
126
|
-
"action" => {
|
|
127
|
-
let action_name = route.value.as_str().unwrap_or("");
|
|
128
|
-
|
|
129
|
-
if let Some(static_resp) = state.fast_paths.get(action_name) {
|
|
130
|
-
|
|
131
|
-
if state.production_mode {
|
|
132
|
-
// Benchmark mode → zero overhead
|
|
133
|
-
return static_resp.to_axum_response();
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
let mut response = static_resp.to_axum_response();
|
|
137
|
-
let elapsed = start.elapsed();
|
|
138
|
-
|
|
139
|
-
response.headers_mut().insert(
|
|
140
|
-
"Server-Timing",
|
|
141
|
-
format!("fastpath;dur={:.2}", elapsed.as_secs_f64() * 1000.0)
|
|
142
|
-
.parse()
|
|
143
|
-
.unwrap_or_else(|_| axum::http::HeaderValue::from_static("")),
|
|
144
|
-
);
|
|
145
|
-
|
|
146
|
-
if log_enabled {
|
|
147
|
-
println!(
|
|
148
|
-
"{} {} {} {}",
|
|
149
|
-
blue("[Titan]"),
|
|
150
|
-
green(&format!("{} {}", method, path)),
|
|
151
|
-
white("→ fastpath"),
|
|
152
|
-
gray(&format!("in {:.2?}", elapsed))
|
|
153
|
-
);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
return response;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// Not static → continue to dynamic execution
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// String reply routes
|
|
163
|
-
_ => {
|
|
164
|
-
if let Some(s) = route.value.as_str() {
|
|
165
|
-
|
|
166
|
-
if state.production_mode {
|
|
167
|
-
return s.to_string().into_response();
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
let elapsed = start.elapsed();
|
|
171
|
-
|
|
172
|
-
if log_enabled {
|
|
173
|
-
println!(
|
|
174
|
-
"{} {} {} {}",
|
|
175
|
-
blue("[Titan]"),
|
|
176
|
-
green(&format!("{} {}", method, path)),
|
|
177
|
-
white("→ reply"),
|
|
178
|
-
gray(&format!("in {:.2?}", elapsed))
|
|
179
|
-
);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
return s.to_string().into_response();
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
// Phase 2: Dynamic Route Handling (requires body/header parsing)
|
|
190
|
-
// Only reached for actions that actually need V8 execution.
|
|
191
|
-
|
|
192
|
-
let start = Instant::now(); // restart timing for dynamic path
|
|
193
|
-
let log_enabled = !state.production_mode;
|
|
194
|
-
|
|
195
|
-
// Query parsing
|
|
196
|
-
let query_pairs: Vec<(String, String)> = req
|
|
197
|
-
.uri()
|
|
198
|
-
.query()
|
|
199
|
-
.map(|q| {
|
|
200
|
-
q.split('&')
|
|
201
|
-
.filter_map(|pair| {
|
|
202
|
-
let mut it = pair.splitn(2, '=');
|
|
203
|
-
Some((it.next()?.to_string(), it.next().unwrap_or("").to_string()))
|
|
204
|
-
})
|
|
205
|
-
.collect()
|
|
206
|
-
})
|
|
207
|
-
.unwrap_or_default();
|
|
208
|
-
let query_map: HashMap<String, String> = query_pairs.into_iter().collect();
|
|
209
|
-
|
|
210
|
-
// Headers & Body
|
|
211
|
-
let (parts, body) = req.into_parts();
|
|
212
|
-
let headers_map: HashMap<String, String> = parts
|
|
213
|
-
.headers
|
|
214
|
-
.iter()
|
|
215
|
-
.map(|(k, v)| (k.to_string(), v.to_str().unwrap_or("").to_string()))
|
|
216
|
-
.collect();
|
|
217
|
-
|
|
218
|
-
let body_bytes = match to_bytes(body, usize::MAX).await {
|
|
219
|
-
Ok(b) => b,
|
|
220
|
-
Err(_) => return (StatusCode::BAD_REQUEST, "Failed to read request body").into_response(),
|
|
221
|
-
};
|
|
222
|
-
|
|
223
|
-
// Route resolution
|
|
224
|
-
let mut params: HashMap<String, String> = HashMap::new();
|
|
225
|
-
let mut action_name: Option<String> = None;
|
|
226
|
-
let mut route_kind = "none";
|
|
227
|
-
let mut route_label = String::from("not_found");
|
|
228
|
-
|
|
229
|
-
// Exact route lookup (may find action routes not caught in fast-path phase)
|
|
230
|
-
let route = state
|
|
231
|
-
.routes
|
|
232
|
-
.get(&strict_key)
|
|
233
|
-
.or_else(|| state.routes.get(&path));
|
|
234
|
-
if let Some(route) = route {
|
|
235
|
-
route_kind = "exact";
|
|
236
|
-
if route.r#type == "action" {
|
|
237
|
-
let name = route.value.as_str().unwrap_or("unknown").to_string();
|
|
238
|
-
route_label = name.clone();
|
|
239
|
-
action_name = Some(name);
|
|
240
|
-
} else if route.r#type == "json" {
|
|
241
|
-
// This path shouldn't be reached (handled in Phase 1), but keep as safety
|
|
242
|
-
if log_enabled {
|
|
243
|
-
println!(
|
|
244
|
-
"{} {} {} {}",
|
|
245
|
-
blue("[Titan]"),
|
|
246
|
-
white(&format!("{} {}", method, path)),
|
|
247
|
-
white("→ json"),
|
|
248
|
-
gray(&format!("in {:.2?}", start.elapsed()))
|
|
249
|
-
);
|
|
250
|
-
}
|
|
251
|
-
return Json(route.value.clone()).into_response();
|
|
252
|
-
} else if let Some(s) = route.value.as_str() {
|
|
253
|
-
if log_enabled {
|
|
254
|
-
println!(
|
|
255
|
-
"{} {} {} {}",
|
|
256
|
-
blue("[Titan]"),
|
|
257
|
-
white(&format!("{} {}", method, path)),
|
|
258
|
-
white("→ reply"),
|
|
259
|
-
gray(&format!("in {:.2?}", start.elapsed()))
|
|
260
|
-
);
|
|
261
|
-
}
|
|
262
|
-
return s.to_string().into_response();
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// Dynamic route matching
|
|
267
|
-
if action_name.is_none() {
|
|
268
|
-
if let Some((action, p)) =
|
|
269
|
-
match_dynamic_route(&method, &path, state.dynamic_routes.as_slice())
|
|
270
|
-
{
|
|
271
|
-
route_kind = "dynamic";
|
|
272
|
-
route_label = action.clone();
|
|
273
|
-
action_name = Some(action);
|
|
274
|
-
params = p;
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
let action_name = match action_name {
|
|
279
|
-
Some(a) => a,
|
|
280
|
-
None => {
|
|
281
|
-
if log_enabled {
|
|
282
|
-
println!(
|
|
283
|
-
"{} {} {} {}",
|
|
284
|
-
blue("[Titan]"),
|
|
285
|
-
white(&format!("{} {}", method, path)),
|
|
286
|
-
white("→ 404"),
|
|
287
|
-
gray(&format!("in {:.2?}", start.elapsed()))
|
|
288
|
-
);
|
|
289
|
-
}
|
|
290
|
-
return (StatusCode::NOT_FOUND, "Not Found").into_response();
|
|
291
|
-
}
|
|
292
|
-
};
|
|
293
|
-
|
|
294
|
-
// Phase 3: V8 Execution (dispatch to worker pool)
|
|
295
|
-
|
|
296
|
-
let headers_vec: SmallVec<[(String, String); 8]> = headers_map.into_iter().collect();
|
|
297
|
-
let params_vec: SmallVec<[(String, String); 4]> = params.into_iter().collect();
|
|
298
|
-
let query_vec: SmallVec<[(String, String); 4]> = query_map.into_iter().collect();
|
|
299
|
-
|
|
300
|
-
let body_arg = if !body_bytes.is_empty() {
|
|
301
|
-
Some(body_bytes)
|
|
302
|
-
} else {
|
|
303
|
-
None
|
|
304
|
-
};
|
|
305
|
-
|
|
306
|
-
let (result_json, timings) = state
|
|
307
|
-
.runtime
|
|
308
|
-
.execute(
|
|
309
|
-
action_name.clone(),
|
|
310
|
-
method.clone(),
|
|
311
|
-
path.clone(),
|
|
312
|
-
body_arg,
|
|
313
|
-
headers_vec,
|
|
314
|
-
params_vec,
|
|
315
|
-
query_vec,
|
|
316
|
-
)
|
|
317
|
-
.await
|
|
318
|
-
.unwrap_or_else(|e| (serde_json::json!({"error": e}), vec![]));
|
|
319
|
-
|
|
320
|
-
// Phase 4: Response Construction
|
|
321
|
-
|
|
322
|
-
// NOTE: We intentionally do NOT inject _titanTimings into the JSON body.
|
|
323
|
-
// This was corrupting benchmark responses (e.g., adding extra fields to
|
|
324
|
-
// {"message":"Hello, World!"} which fails TechEmpower validation).
|
|
325
|
-
// Timing info is available via the Server-Timing HTTP header instead.
|
|
326
|
-
|
|
327
|
-
// Error handling
|
|
328
|
-
if let Some(err) = result_json.get("error") {
|
|
329
|
-
if log_enabled {
|
|
330
|
-
let prefix = if !timings.is_empty() {
|
|
331
|
-
format!("{} {}", blue("[Titan"), blue("Drift]"))
|
|
332
|
-
} else {
|
|
333
|
-
blue("[Titan]").to_string()
|
|
334
|
-
};
|
|
335
|
-
println!(
|
|
336
|
-
"{} {} {} {}",
|
|
337
|
-
prefix,
|
|
338
|
-
red(&format!("{} {}", method, path)),
|
|
339
|
-
red("→ error"),
|
|
340
|
-
gray(&format!("in {:.2?}", start.elapsed()))
|
|
341
|
-
);
|
|
342
|
-
println!(
|
|
343
|
-
"{} {} {}",
|
|
344
|
-
prefix,
|
|
345
|
-
red("Action Error:"),
|
|
346
|
-
red(err.as_str().unwrap_or("Unknown"))
|
|
347
|
-
);
|
|
348
|
-
}
|
|
349
|
-
let response = (StatusCode::INTERNAL_SERVER_ERROR, Json(result_json)).into_response();
|
|
350
|
-
return response;
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
// Response object construction
|
|
354
|
-
let mut response = if let Some(is_resp) = result_json.get("_isResponse") {
|
|
355
|
-
if is_resp.as_bool().unwrap_or(false) {
|
|
356
|
-
let status_u16 = result_json
|
|
357
|
-
.get("status")
|
|
358
|
-
.and_then(|v| v.as_u64())
|
|
359
|
-
.unwrap_or(200) as u16;
|
|
360
|
-
let status = StatusCode::from_u16(status_u16).unwrap_or(StatusCode::OK);
|
|
361
|
-
let mut builder = axum::http::Response::builder().status(status);
|
|
362
|
-
|
|
363
|
-
if let Some(hmap) = result_json.get("headers").and_then(|v| v.as_object()) {
|
|
364
|
-
for (k, v) in hmap {
|
|
365
|
-
if let Some(vs) = v.as_str() {
|
|
366
|
-
builder = builder.header(k, vs);
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
let mut is_redirect = false;
|
|
372
|
-
if let Some(location) = result_json.get("redirect") {
|
|
373
|
-
if let Some(url) = location.as_str() {
|
|
374
|
-
let mut final_status_u16 = status.as_u16();
|
|
375
|
-
if !(300..400).contains(&final_status_u16) {
|
|
376
|
-
final_status_u16 = 302;
|
|
377
|
-
}
|
|
378
|
-
builder = builder
|
|
379
|
-
.status(StatusCode::from_u16(final_status_u16).unwrap_or(StatusCode::FOUND))
|
|
380
|
-
.header("Location", url);
|
|
381
|
-
is_redirect = true;
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
let body_text = if is_redirect {
|
|
386
|
-
"".to_string()
|
|
387
|
-
} else {
|
|
388
|
-
match result_json.get("body") {
|
|
389
|
-
Some(Value::String(s)) => s.clone(),
|
|
390
|
-
Some(v) => v.to_string(),
|
|
391
|
-
None => "".to_string(),
|
|
392
|
-
}
|
|
393
|
-
};
|
|
394
|
-
builder.body(Body::from(body_text)).unwrap()
|
|
395
|
-
} else {
|
|
396
|
-
Json(result_json).into_response()
|
|
397
|
-
}
|
|
398
|
-
} else {
|
|
399
|
-
Json(result_json).into_response()
|
|
400
|
-
};
|
|
401
|
-
|
|
402
|
-
// Server-Timing header (only outside benchmark mode)
|
|
403
|
-
if !state.production_mode && !timings.is_empty() {
|
|
404
|
-
let server_timing = timings
|
|
405
|
-
.iter()
|
|
406
|
-
.enumerate()
|
|
407
|
-
.map(|(i, (name, duration))| format!("{}_{};dur={:.2}", name, i, duration))
|
|
408
|
-
.collect::<Vec<_>>()
|
|
409
|
-
.join(", ");
|
|
410
|
-
response
|
|
411
|
-
.headers_mut()
|
|
412
|
-
.insert("Server-Timing", server_timing.parse().unwrap_or_else(|_| axum::http::HeaderValue::from_static("")));
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
// Logging
|
|
416
|
-
if log_enabled {
|
|
417
|
-
let total_elapsed = start.elapsed();
|
|
418
|
-
let total_elapsed_ms = total_elapsed.as_secs_f64() * 1000.0;
|
|
419
|
-
let total_drift_ms: f64 = timings
|
|
420
|
-
.iter()
|
|
421
|
-
.filter(|(n, _)| n == "drift" || n == "drift_error")
|
|
422
|
-
.map(|(_, d)| d)
|
|
423
|
-
.sum();
|
|
424
|
-
let compute_ms = (total_elapsed_ms - total_drift_ms).max(0.0);
|
|
425
|
-
|
|
426
|
-
let prefix = if !timings.is_empty() {
|
|
427
|
-
format!("{} {}", blue("[Titan"), blue("Drift]"))
|
|
428
|
-
} else {
|
|
429
|
-
blue("[Titan]").to_string()
|
|
430
|
-
};
|
|
431
|
-
let timing_info = if !timings.is_empty() {
|
|
432
|
-
gray(&format!(
|
|
433
|
-
"(active: {:.2}ms, drift: {:.2}ms) in {:.2?}",
|
|
434
|
-
compute_ms, total_drift_ms, total_elapsed
|
|
435
|
-
))
|
|
436
|
-
} else {
|
|
437
|
-
gray(&format!("in {:.2?}", total_elapsed))
|
|
438
|
-
};
|
|
439
|
-
|
|
440
|
-
match route_kind {
|
|
441
|
-
"dynamic" => println!(
|
|
442
|
-
"{} {} {} {} {} {}",
|
|
443
|
-
prefix,
|
|
444
|
-
green(&format!("{} {}", method, path)),
|
|
445
|
-
white("→"),
|
|
446
|
-
green(&route_label),
|
|
447
|
-
white("(dynamic)"),
|
|
448
|
-
timing_info
|
|
449
|
-
),
|
|
450
|
-
"exact" => println!(
|
|
451
|
-
"{} {} {} {} {}",
|
|
452
|
-
prefix,
|
|
453
|
-
white(&format!("{} {}", method, path)),
|
|
454
|
-
white("→"),
|
|
455
|
-
yellow(&route_label),
|
|
456
|
-
timing_info
|
|
457
|
-
),
|
|
458
|
-
_ => {}
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
response
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
#[tokio::main]
|
|
466
|
-
async fn main() -> Result<()> {
|
|
467
|
-
dotenvy::dotenv().ok();
|
|
468
|
-
|
|
469
|
-
// Configuration
|
|
470
|
-
let production_mode = std::env::var("TITAN_DEV").unwrap_or_default() != "1";
|
|
471
|
-
|
|
472
|
-
let raw = fs::read_to_string("./routes.json").unwrap_or_else(|_| "{}".to_string());
|
|
473
|
-
let json: Value = serde_json::from_str(&raw).unwrap_or_default();
|
|
474
|
-
|
|
475
|
-
let port = std::env::var("PORT")
|
|
476
|
-
.ok()
|
|
477
|
-
.and_then(|p| p.parse::<u64>().ok())
|
|
478
|
-
.or_else(|| json["__config"]["port"].as_u64())
|
|
479
|
-
.unwrap_or(3000);
|
|
480
|
-
|
|
481
|
-
let thread_count = json["__config"]["threads"].as_u64();
|
|
482
|
-
let routes_json = json["routes"].clone();
|
|
483
|
-
let map: HashMap<String, RouteVal> = serde_json::from_value(routes_json).unwrap_or_default();
|
|
484
|
-
let dynamic_routes: Vec<DynamicRoute> =
|
|
485
|
-
serde_json::from_value(json["__dynamic_routes"].clone()).unwrap_or_default();
|
|
486
|
-
|
|
487
|
-
let project_root = resolve_project_root();
|
|
488
|
-
|
|
489
|
-
// Load extensions
|
|
490
|
-
extensions::load_project_extensions(project_root.clone());
|
|
491
|
-
|
|
492
|
-
// Build pre-computed route responses
|
|
493
|
-
let mut precomputed = HashMap::new();
|
|
494
|
-
for (key, route) in &map {
|
|
495
|
-
match route.r#type.as_str() {
|
|
496
|
-
"json" => {
|
|
497
|
-
precomputed.insert(key.clone(), PrecomputedRoute::from_json(&route.value));
|
|
498
|
-
}
|
|
499
|
-
"text" => {
|
|
500
|
-
if let Some(s) = route.value.as_str() {
|
|
501
|
-
precomputed.insert(key.clone(), PrecomputedRoute::from_text(s));
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
_ => {}
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
if !precomputed.is_empty() {
|
|
508
|
-
println!(
|
|
509
|
-
"{} {} reply route(s) pre-computed",
|
|
510
|
-
blue("[Titan]"),
|
|
511
|
-
precomputed.len()
|
|
512
|
-
);
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
// Build fast-path registry (scan action files for static patterns)
|
|
516
|
-
let actions_dir = find_actions_dir(&project_root);
|
|
517
|
-
let fast_paths = FastPathRegistry::build(&actions_dir);
|
|
518
|
-
|
|
519
|
-
// Initialize Runtime Manager (V8 Worker Pool)
|
|
520
|
-
let threads = match thread_count {
|
|
521
|
-
Some(t) if t > 0 => t as usize,
|
|
522
|
-
_ => {
|
|
523
|
-
let cpus = num_cpus::get();
|
|
524
|
-
// Optimal for CPU-bound V8 work: 2x cores
|
|
525
|
-
cpus * 2
|
|
526
|
-
}
|
|
527
|
-
};
|
|
528
|
-
|
|
529
|
-
let stack_mb = json["__config"]["stack_mb"].as_u64().unwrap_or(8);
|
|
530
|
-
let stack_size = (stack_mb as usize) * 1024 * 1024;
|
|
531
|
-
|
|
532
|
-
let runtime_manager = Arc::new(RuntimeManager::new(
|
|
533
|
-
project_root.clone(),
|
|
534
|
-
threads,
|
|
535
|
-
stack_size,
|
|
536
|
-
));
|
|
537
|
-
|
|
538
|
-
// Build AppState
|
|
539
|
-
let state = AppState {
|
|
540
|
-
routes: Arc::new(map),
|
|
541
|
-
dynamic_routes: Arc::new(dynamic_routes),
|
|
542
|
-
runtime: runtime_manager,
|
|
543
|
-
fast_paths: Arc::new(fast_paths),
|
|
544
|
-
precomputed: Arc::new(precomputed),
|
|
545
|
-
production_mode,
|
|
546
|
-
};
|
|
547
|
-
|
|
548
|
-
// Router
|
|
549
|
-
let app = Router::new()
|
|
550
|
-
.route("/", any(root_route))
|
|
551
|
-
.fallback(any(dynamic_route))
|
|
552
|
-
.with_state(state);
|
|
553
|
-
|
|
554
|
-
let listener = TcpListener::bind(format!("0.0.0.0:{}", port)).await?;
|
|
555
|
-
|
|
556
|
-
println!(
|
|
557
|
-
"\x1b[38;5;39mTitan server running at:\x1b[0m http://localhost:{} \x1b[90m(Threads: {}, Stack: {}MB{})\x1b[0m",
|
|
558
|
-
port,
|
|
559
|
-
threads,
|
|
560
|
-
stack_mb,
|
|
561
|
-
if production_mode { "" } else { ", Dev Mode" }
|
|
562
|
-
);
|
|
563
|
-
|
|
564
|
-
axum::serve(listener, app).await?;
|
|
565
|
-
Ok(())
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
fn resolve_project_root() -> PathBuf {
|
|
569
|
-
if let Ok(cwd) = std::env::current_dir() {
|
|
570
|
-
if cwd.join("node_modules").exists()
|
|
571
|
-
|| cwd.join("package.json").exists()
|
|
572
|
-
|| cwd.join(".ext").exists()
|
|
573
|
-
{
|
|
574
|
-
return cwd;
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
if let Ok(exe) = std::env::current_exe() {
|
|
579
|
-
let mut current = exe.parent();
|
|
580
|
-
while let Some(dir) = current {
|
|
581
|
-
if dir.join(".ext").exists() || dir.join("node_modules").exists() {
|
|
582
|
-
return dir.to_path_buf();
|
|
583
|
-
}
|
|
584
|
-
current = dir.parent();
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."))
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
/// Find the actions directory for fast-path scanning.
|
|
592
|
-
fn find_actions_dir(root: &PathBuf) -> PathBuf {
|
|
593
|
-
let candidates = [
|
|
594
|
-
root.join("server").join("src").join("actions"),
|
|
595
|
-
root.join("server").join("actions"),
|
|
596
|
-
root.join("actions"),
|
|
597
|
-
PathBuf::from("/app").join("actions"),
|
|
598
|
-
];
|
|
599
|
-
|
|
600
|
-
for p in &candidates {
|
|
601
|
-
if p.exists() && p.is_dir() {
|
|
602
|
-
return p.clone();
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
root.join("server").join("src").join("actions")
|
|
607
|
-
}
|