@ezetgalaxy/titan 26.14.1 → 26.15.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 (47) hide show
  1. package/package.json +3 -2
  2. package/templates/common/Dockerfile +1 -1
  3. package/templates/common/app/t.native.d.ts +60 -0
  4. package/templates/js/package.json +1 -0
  5. package/templates/js/server/Cargo.toml +1 -1
  6. package/templates/js/server/src/action_management.rs +175 -159
  7. package/templates/js/server/src/errors.rs +12 -10
  8. package/templates/js/server/src/extensions/builtin.rs +928 -778
  9. package/templates/js/server/src/extensions/external.rs +9 -0
  10. package/templates/js/server/src/extensions/mod.rs +580 -522
  11. package/templates/js/server/src/extensions/titan_core.js +13 -0
  12. package/templates/js/server/src/fast_path.rs +719 -654
  13. package/templates/js/server/src/main.rs +607 -555
  14. package/templates/js/server/src/runtime.rs +284 -262
  15. package/templates/js/server/src/utils.rs +33 -32
  16. package/templates/js/titan/bundle.js +194 -199
  17. package/templates/ts/package.json +1 -0
  18. package/templates/ts/server/Cargo.toml +1 -1
  19. package/templates/ts/server/src/action_management.rs +175 -159
  20. package/templates/ts/server/src/errors.rs +12 -10
  21. package/templates/ts/server/src/extensions/builtin.rs +928 -778
  22. package/templates/ts/server/src/extensions/external.rs +9 -0
  23. package/templates/ts/server/src/extensions/mod.rs +580 -522
  24. package/templates/ts/server/src/extensions/titan_core.js +13 -0
  25. package/templates/ts/server/src/fast_path.rs +719 -654
  26. package/templates/ts/server/src/main.rs +607 -555
  27. package/templates/ts/server/src/runtime.rs +284 -262
  28. package/templates/ts/server/src/utils.rs +33 -32
  29. package/templates/ts/titan/bundle.js +69 -19
  30. package/titanpl-sdk/package.json +3 -2
  31. package/titanpl-sdk/templates/app/app.js +1 -4
  32. package/titanpl-sdk/templates/server/Cargo.toml +1 -1
  33. package/titanpl-sdk/templates/server/src/action_management.rs +175 -159
  34. package/titanpl-sdk/templates/server/src/errors.rs +12 -10
  35. package/titanpl-sdk/templates/server/src/extensions/builtin.rs +928 -778
  36. package/titanpl-sdk/templates/server/src/extensions/external.rs +9 -0
  37. package/titanpl-sdk/templates/server/src/extensions/mod.rs +580 -522
  38. package/titanpl-sdk/templates/server/src/extensions/titan_core.js +13 -0
  39. package/titanpl-sdk/templates/server/src/fast_path.rs +719 -654
  40. package/titanpl-sdk/templates/server/src/main.rs +607 -555
  41. package/titanpl-sdk/templates/server/src/runtime.rs +284 -262
  42. package/titanpl-sdk/templates/server/src/utils.rs +33 -32
  43. package/titanpl-sdk/templates/titan/bundle.js +194 -199
  44. package/titanpl-sdk/templates/titan/dev.js +44 -4
  45. package/titanpl-sdk/templates/titan/error-box.js +10 -1
  46. package/titanpl-sdk/templates/app/t.native.d.ts +0 -1983
  47. package/titanpl-sdk/templates/app/t.native.js +0 -39
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ezetgalaxy/titan",
3
- "version": "26.14.1",
3
+ "version": "26.15.1",
4
4
  "description": "Titan Planet is a JavaScript-first backend framework that embeds JS actions into a Rust + Axum server and ships as a single native binary. Routes are compiled to static metadata; only actions run in the embedded JS runtime. No Node.js. No event loop in production.",
5
5
  "license": "ISC",
6
6
  "author": "ezetgalaxy",
@@ -65,7 +65,8 @@
65
65
  "test:all": "vitest run && vitest run --config vitest.config.e2e.ts"
66
66
  },
67
67
  "dependencies": {
68
- "@titanpl/core": "^1.0.1",
68
+ "@titanpl/core": "latest",
69
+ "@titanpl/node": "latest",
69
70
  "chokidar": "^5.0.0",
70
71
  "esbuild": "^0.27.2",
71
72
  "prompts": "^2.4.2"
@@ -46,7 +46,7 @@ RUN mkdir -p /app/.ext && \
46
46
  rm -rf "/app/.ext/$pkg_name/node_modules"; \
47
47
  done
48
48
 
49
- RUN cd server && cargo build --release
49
+ RUN titan build
50
50
 
51
51
 
52
52
 
@@ -1058,6 +1058,8 @@ declare global {
1058
1058
  * @see https://titan-docs-ez.vercel.app/docs/10-extensions — Extensions documentation
1059
1059
  */
1060
1060
  [key: string]: any;
1061
+
1062
+
1061
1063
  }
1062
1064
 
1063
1065
  /**
@@ -1978,6 +1980,64 @@ declare global {
1978
1980
  }
1979
1981
 
1980
1982
  }
1983
+
1984
+ /**
1985
+ * Node-compatible `process` global (Titan Shim).
1986
+ *
1987
+ * This is a lightweight compatibility layer intended
1988
+ * for supporting common Node libraries.
1989
+ *
1990
+ * Internally backed by:
1991
+ * - t.proc
1992
+ * - t.os
1993
+ * - t.time
1994
+ */
1995
+ const process: {
1996
+ /** Process ID */
1997
+ pid: number;
1998
+
1999
+ /** Platform (linux, win32, darwin) */
2000
+ platform: string;
2001
+
2002
+ /** CPU architecture */
2003
+ arch: string;
2004
+
2005
+ /** Node version string (shimmed) */
2006
+ version: string;
2007
+
2008
+ /** Version object */
2009
+ versions: {
2010
+ node: string;
2011
+ titan: string;
2012
+ };
2013
+
2014
+ /** Environment variables */
2015
+ env: Record<string, string | undefined>;
2016
+
2017
+ /** CLI arguments */
2018
+ argv: string[];
2019
+
2020
+ /** Current working directory */
2021
+ cwd(): string;
2022
+
2023
+ /** Uptime in seconds */
2024
+ uptime(): number;
2025
+
2026
+ /** High resolution time */
2027
+ hrtime: {
2028
+ (time?: [number, number]): [number, number];
2029
+ bigint(): bigint;
2030
+ };
2031
+
2032
+ /** Memory usage info */
2033
+ memoryUsage(): Record<string, any>;
2034
+
2035
+ /** No-op event listener (compat only) */
2036
+ on(event: string, listener: (...args: any[]) => void): void;
2037
+
2038
+ /** Exit stub (throws in Titan runtime) */
2039
+ exit(code?: number): never;
2040
+ };
1981
2041
  }
1982
2042
 
1983
2043
  export { };
@@ -8,6 +8,7 @@
8
8
  },
9
9
  "dependencies": {
10
10
  "@titanpl/core": "latest",
11
+ "@titanpl/node": "latest",
11
12
  "chokidar": "^5.0.0",
12
13
  "esbuild": "^0.27.2"
13
14
  },
@@ -10,7 +10,7 @@ reqwest = { version = "0.12.24", features = ["json", "rustls-tls", "gzip", "brot
10
10
  serde = { version = "1.0.228", features = ["derive"] }
11
11
  serde_json = "1.0.145"
12
12
  thiserror = "2.0.17"
13
- tokio = { version = "1.48.0", features = ["rt-multi-thread", "macros", "process"] }
13
+ tokio = { version = "1.48.0", features = ["rt-multi-thread", "macros", "process", "fs"] }
14
14
  tower-http = { version = "0.6.7", features = ["cors"] }
15
15
  tracing = "0.1.43"
16
16
  tracing-subscriber = "0.3.22"
@@ -1,159 +1,175 @@
1
-
2
- use std::collections::HashMap;
3
- use std::env;
4
- use std::path::{Path, PathBuf};
5
- use serde::Deserialize;
6
- use serde_json::Value;
7
-
8
- #[derive(Debug, Deserialize, Clone)]
9
- pub struct RouteVal {
10
- pub r#type: String,
11
- #[serde(alias = "target")]
12
- pub value: Value,
13
- }
14
-
15
- #[derive(Debug, Deserialize, Clone)]
16
- pub struct DynamicRoute {
17
- pub method: String,
18
- pub pattern: String,
19
- pub action: String,
20
- }
21
-
22
- pub fn resolve_actions_dir() -> PathBuf {
23
- if let Ok(override_dir) = env::var("TITAN_ACTIONS_DIR") {
24
- return PathBuf::from(override_dir);
25
- }
26
-
27
- if Path::new("/app/actions").exists() {
28
- return PathBuf::from("/app/actions");
29
- }
30
-
31
- if let Ok(exe) = std::env::current_exe() {
32
- if let Some(parent) = exe.parent() {
33
- if let Some(target_dir) = parent.parent() {
34
- if let Some(server_dir) = target_dir.parent() {
35
- let candidate = server_dir.join("src").join("actions");
36
- if candidate.exists() {
37
- return candidate;
38
- }
39
- let candidate2 = server_dir.join("actions");
40
- if candidate2.exists() {
41
- return candidate2;
42
- }
43
- }
44
- }
45
- }
46
- }
47
-
48
- PathBuf::from("./src/actions")
49
- }
50
-
51
- pub fn find_actions_dir(project_root: &PathBuf) -> Option<PathBuf> {
52
- let candidates = [
53
- project_root.join("server").join("src").join("actions"),
54
- project_root.join("server").join("actions"),
55
- project_root.join("app").join("actions"),
56
- project_root.join("actions"),
57
-
58
- project_root.join("..").join("server").join("actions"),
59
- PathBuf::from("/app").join("actions"),
60
- PathBuf::from("actions"),
61
- ];
62
-
63
- for p in &candidates {
64
- if p.exists() && p.is_dir() {
65
- return Some(p.clone());
66
- }
67
- }
68
-
69
- None
70
- }
71
-
72
- pub fn match_dynamic_route(
73
- method: &str,
74
- path: &str,
75
- routes: &[DynamicRoute],
76
- ) -> Option<(String, HashMap<String, String>)> {
77
- let path_segments: Vec<&str> =
78
- path.trim_matches('/').split('/').collect();
79
-
80
- for route in routes {
81
- if route.method != method {
82
- continue;
83
- }
84
-
85
- let pattern_segments: Vec<&str> =
86
- route.pattern.trim_matches('/').split('/').collect();
87
-
88
- if pattern_segments.len() != path_segments.len() {
89
- continue;
90
- }
91
-
92
- let mut params = HashMap::new();
93
- let mut matched = true;
94
-
95
- for (pat, val) in pattern_segments.iter().zip(path_segments.iter()) {
96
- if pat.starts_with(':') {
97
- let inner = &pat[1..];
98
-
99
- let (name, ty) = inner
100
- .split_once('<')
101
- .map(|(n, t)| (n, t.trim_end_matches('>')))
102
- .unwrap_or((inner, "string"));
103
-
104
- let valid = match ty {
105
- "number" => val.parse::<i64>().is_ok(),
106
- "string" => true,
107
- _ => false,
108
- };
109
-
110
- if !valid {
111
- matched = false;
112
- break;
113
- }
114
-
115
- params.insert(name.to_string(), (*val).to_string());
116
- } else if pat != val {
117
- matched = false;
118
- break;
119
- }
120
- }
121
-
122
- if matched {
123
- return Some((route.action.clone(), params));
124
- }
125
- }
126
-
127
- None
128
- }
129
-
130
- pub fn scan_actions(root: &PathBuf) -> HashMap<String, PathBuf> {
131
- let mut map = HashMap::new();
132
-
133
- let dir = match find_actions_dir(root) {
134
- Some(d) => d,
135
- None => {
136
- let ad = resolve_actions_dir();
137
- if ad.exists() { ad } else { return map; }
138
- }
139
- };
140
-
141
- if let Ok(entries) = std::fs::read_dir(dir) {
142
- for entry in entries.flatten() {
143
- let path = entry.path();
144
- if !path.is_file() { continue; }
145
-
146
- let ext = path.extension().and_then(|s| s.to_str()).unwrap_or("");
147
- if ext != "js" && ext != "jsbundle" {
148
- continue;
149
- }
150
-
151
- let file_stem = path.file_stem().and_then(|s| s.to_str()).unwrap_or("");
152
- if file_stem.is_empty() { continue; }
153
-
154
- map.insert(file_stem.to_string(), path);
155
- }
156
- }
157
-
158
- map
159
- }
1
+ //! Action Management and Dynamic Routing
2
+ //!
3
+ //! Handles resolution of action directories, scanning for available actions,
4
+ //! and matching dynamic routes (e.g. `/users/:id`).
5
+
6
+ use std::collections::HashMap;
7
+ use std::env;
8
+ use std::path::{Path, PathBuf};
9
+ use serde::Deserialize;
10
+ use serde_json::Value;
11
+
12
+ /// Route configuration (loaded from routes.json)
13
+ #[derive(Debug, Deserialize, Clone)]
14
+ pub struct RouteVal {
15
+ pub r#type: String,
16
+ #[serde(alias = "target")]
17
+ pub value: Value,
18
+ }
19
+
20
+ #[derive(Debug, Deserialize, Clone)]
21
+ pub struct DynamicRoute {
22
+ pub method: String,
23
+ pub pattern: String,
24
+ pub action: String,
25
+ }
26
+
27
+ /// Resolve the directory path where actions are stored.
28
+ pub fn resolve_actions_dir() -> PathBuf {
29
+ // Respect explicit override first
30
+ if let Ok(override_dir) = env::var("TITAN_ACTIONS_DIR") {
31
+ return PathBuf::from(override_dir);
32
+ }
33
+
34
+ // Production container layout
35
+ if Path::new("/app/actions").exists() {
36
+ return PathBuf::from("/app/actions");
37
+ }
38
+
39
+ // Try to walk up from the executing binary to discover `<...>/server/src/actions`
40
+ if let Ok(exe) = std::env::current_exe() {
41
+ if let Some(parent) = exe.parent() {
42
+ if let Some(target_dir) = parent.parent() {
43
+ if let Some(server_dir) = target_dir.parent() {
44
+ let candidate = server_dir.join("src").join("actions");
45
+ if candidate.exists() {
46
+ return candidate;
47
+ }
48
+ let candidate2 = server_dir.join("actions");
49
+ if candidate2.exists() {
50
+ return candidate2;
51
+ }
52
+ }
53
+ }
54
+ }
55
+ }
56
+
57
+ // Fall back to local ./src/actions
58
+ PathBuf::from("./src/actions")
59
+ }
60
+
61
+ /// Try to find the directory that contains compiled action bundles.
62
+ pub fn find_actions_dir(project_root: &PathBuf) -> Option<PathBuf> {
63
+ let candidates = [
64
+ project_root.join("server").join("src").join("actions"),
65
+ project_root.join("server").join("actions"),
66
+ project_root.join("app").join("actions"),
67
+ project_root.join("actions"),
68
+
69
+ project_root.join("..").join("server").join("actions"),
70
+ PathBuf::from("/app").join("actions"),
71
+ PathBuf::from("actions"),
72
+ ];
73
+
74
+ for p in &candidates {
75
+ if p.exists() && p.is_dir() {
76
+ return Some(p.clone());
77
+ }
78
+ }
79
+
80
+ None
81
+ }
82
+
83
+ /// Match a dynamic route against the current request path.
84
+ pub fn match_dynamic_route(
85
+ method: &str,
86
+ path: &str,
87
+ routes: &[DynamicRoute],
88
+ ) -> Option<(String, HashMap<String, String>)> {
89
+ let path_segments: Vec<&str> =
90
+ path.trim_matches('/').split('/').collect();
91
+
92
+ for route in routes {
93
+ if route.method != method {
94
+ continue;
95
+ }
96
+
97
+ let pattern_segments: Vec<&str> =
98
+ route.pattern.trim_matches('/').split('/').collect();
99
+
100
+ if pattern_segments.len() != path_segments.len() {
101
+ continue;
102
+ }
103
+
104
+ let mut params = HashMap::new();
105
+ let mut matched = true;
106
+
107
+ for (pat, val) in pattern_segments.iter().zip(path_segments.iter()) {
108
+ if pat.starts_with(':') {
109
+ let inner = &pat[1..];
110
+
111
+ let (name, ty) = inner
112
+ .split_once('<')
113
+ .map(|(n, t)| (n, t.trim_end_matches('>')))
114
+ .unwrap_or((inner, "string"));
115
+
116
+ let valid = match ty {
117
+ "number" => val.parse::<i64>().is_ok(),
118
+ "string" => true,
119
+ _ => false,
120
+ };
121
+
122
+ if !valid {
123
+ matched = false;
124
+ break;
125
+ }
126
+
127
+ params.insert(name.to_string(), (*val).to_string());
128
+ } else if pat != val {
129
+ matched = false;
130
+ break;
131
+ }
132
+ }
133
+
134
+ if matched {
135
+ return Some((route.action.clone(), params));
136
+ }
137
+ }
138
+
139
+ None
140
+ }
141
+
142
+ /// Scan the resolved actions directory and return a map of action names to file paths.
143
+ pub fn scan_actions(root: &PathBuf) -> HashMap<String, PathBuf> {
144
+ let mut map = HashMap::new();
145
+
146
+ // Locate actions dir - Priority: project root relative paths
147
+ let dir = match find_actions_dir(root) {
148
+ Some(d) => d,
149
+ None => {
150
+ let ad = resolve_actions_dir();
151
+ if ad.exists() { ad } else { return map; }
152
+ }
153
+ };
154
+
155
+ // Scanning actions
156
+ if let Ok(entries) = std::fs::read_dir(dir) {
157
+ for entry in entries.flatten() {
158
+ let path = entry.path();
159
+ if !path.is_file() { continue; }
160
+
161
+ let ext = path.extension().and_then(|s| s.to_str()).unwrap_or("");
162
+ if ext != "js" && ext != "jsbundle" {
163
+ continue;
164
+ }
165
+
166
+ let file_stem = path.file_stem().and_then(|s| s.to_str()).unwrap_or("");
167
+ if file_stem.is_empty() { continue; }
168
+
169
+ // Found action
170
+ map.insert(file_stem.to_string(), path);
171
+ }
172
+ }
173
+
174
+ map
175
+ }
@@ -1,10 +1,12 @@
1
-
2
- use v8::JsError;
3
-
4
- pub fn format_js_error(err: JsError, action: &str) -> String {
5
- format!(
6
- "Action: {}\n{}",
7
- action,
8
- err.to_string()
9
- )
10
- }
1
+ //! Error handling utilities.
2
+
3
+ use v8::JsError;
4
+
5
+ /// A helper to Format v8 Errors
6
+ pub fn format_js_error(err: JsError, action: &str) -> String {
7
+ format!(
8
+ "Action: {}\n{}",
9
+ action,
10
+ err.to_string()
11
+ )
12
+ }