@ezetgalaxy/titan 26.12.5 → 26.12.7

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/README.md CHANGED
@@ -14,6 +14,7 @@
14
14
  💟 **Titan Planet docs:** https://titan-docs-ez.vercel.app/docs
15
15
  🚀 **CLI: `titan` is now the canonical command. `tit` remains supported as an alias.**
16
16
  🛡️ **Strict Mode:** Titan now enforces zero type errors before running.
17
+ ✅ **For text response from a action file use t.response.text("Hii TitanPl")
17
18
 
18
19
  ---
19
20
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ezetgalaxy/titan",
3
- "version": "26.12.5",
3
+ "version": "26.12.7",
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",
@@ -2,19 +2,19 @@
2
2
  pub mod builtin;
3
3
  pub mod external;
4
4
 
5
- use v8;
6
- use std::sync::Once;
7
- use std::path::PathBuf;
8
- use std::sync::{Mutex, Arc, OnceLock};
9
- use std::collections::HashMap;
10
- use std::fs;
11
- use dashmap::DashMap;
12
- use tokio::sync::broadcast;
13
5
  use crate::action_management::scan_actions;
6
+ use crate::utils::{blue, gray, green, red};
14
7
  use bytes::Bytes;
15
8
  use crossbeam::channel::Sender;
9
+ use dashmap::DashMap;
16
10
  use serde_json::Value;
17
- use crate::utils::{blue, red, gray, green};
11
+ use std::collections::HashMap;
12
+ use std::fs;
13
+ use std::path::PathBuf;
14
+ use std::sync::Once;
15
+ use std::sync::{Arc, Mutex, OnceLock};
16
+ use tokio::sync::broadcast;
17
+ use v8;
18
18
 
19
19
  // ----------------------------------------------------------------------------
20
20
  // GLOBALS
@@ -66,23 +66,26 @@ pub fn init_v8() {
66
66
  });
67
67
  }
68
68
 
69
- pub fn init_runtime_worker(root: PathBuf, worker_tx: crossbeam::channel::Sender<crate::runtime::WorkerCommand>) -> TitanRuntime {
69
+ pub fn init_runtime_worker(
70
+ root: PathBuf,
71
+ worker_tx: crossbeam::channel::Sender<crate::runtime::WorkerCommand>,
72
+ ) -> TitanRuntime {
70
73
  init_v8();
71
-
74
+
72
75
  // Memory optimization strategy (v8 0.106.0 limitations):
73
76
  // - V8 snapshots reduce memory footprint by sharing compiled code
74
77
  // - Each isolate still has its own heap, but the snapshot reduces base overhead
75
78
  // - For explicit heap limits, use V8 flags: --max-old-space-size=128
76
-
79
+
77
80
  let params = v8::CreateParams::default();
78
81
  let mut isolate = v8::Isolate::new(params);
79
-
82
+
80
83
  let (global_context, actions_map) = {
81
84
  let handle_scope = &mut v8::HandleScope::new(&mut isolate);
82
85
  let context = v8::Context::new(handle_scope, v8::ContextOptions::default());
83
86
  let scope = &mut v8::ContextScope::new(handle_scope, context);
84
87
  let global = context.global(scope);
85
-
88
+
86
89
  // Inject Titan Runtime APIs
87
90
  inject_extensions(scope, global);
88
91
 
@@ -95,19 +98,20 @@ pub fn init_runtime_worker(root: PathBuf, worker_tx: crossbeam::channel::Sender<
95
98
  let mut map = HashMap::new();
96
99
  let action_files = scan_actions(&root);
97
100
  for (name, path) in action_files {
98
- if let Ok(code) = fs::read_to_string(&path) {
99
- let wrapped_source = format!("(function() {{ {} }})(); globalThis[\"{}\"];", code, name);
100
- let source_str = v8_str(scope, &wrapped_source);
101
- let try_catch = &mut v8::TryCatch::new(scope);
102
- if let Some(script) = v8::Script::compile(try_catch, source_str, None) {
103
- if let Some(val) = script.run(try_catch) {
104
- if val.is_function() {
105
- let func = v8::Local::<v8::Function>::try_from(val).unwrap();
106
- map.insert(name.clone(), v8::Global::new(try_catch, func));
107
- }
108
- }
109
- }
110
- }
101
+ if let Ok(code) = fs::read_to_string(&path) {
102
+ let wrapped_source =
103
+ format!("(function() {{ {} }})(); globalThis[\"{}\"];", code, name);
104
+ let source_str = v8_str(scope, &wrapped_source);
105
+ let try_catch = &mut v8::TryCatch::new(scope);
106
+ if let Some(script) = v8::Script::compile(try_catch, source_str, None) {
107
+ if let Some(val) = script.run(try_catch) {
108
+ if val.is_function() {
109
+ let func = v8::Local::<v8::Function>::try_from(val).unwrap();
110
+ map.insert(name.clone(), v8::Global::new(try_catch, func));
111
+ }
112
+ }
113
+ }
114
+ }
111
115
  }
112
116
  (v8::Global::new(scope, context), map)
113
117
  };
@@ -127,7 +131,9 @@ pub fn inject_extensions(scope: &mut v8::HandleScope, global: v8::Local<v8::Obje
127
131
 
128
132
  let t_obj = v8::Object::new(scope);
129
133
  let t_key = v8_str(scope, "t");
130
- global.create_data_property(scope, t_key.into(), t_obj.into()).unwrap();
134
+ global
135
+ .create_data_property(scope, t_key.into(), t_obj.into())
136
+ .unwrap();
131
137
 
132
138
  // Call individual injectors
133
139
  builtin::inject_builtin_extensions(scope, global, t_obj);
@@ -136,31 +142,109 @@ pub fn inject_extensions(scope: &mut v8::HandleScope, global: v8::Local<v8::Obje
136
142
  global.set(scope, t_key.into(), t_obj.into());
137
143
  }
138
144
 
145
+ fn v8_to_json<'s>(
146
+ scope: &mut v8::HandleScope<'s>,
147
+ value: v8::Local<v8::Value>,
148
+ ) -> serde_json::Value {
149
+ if value.is_null_or_undefined() {
150
+ return serde_json::Value::Null;
151
+ }
152
+
153
+ // Boolean
154
+ if value.is_boolean() {
155
+ return serde_json::Value::Bool(value.boolean_value(scope));
156
+ }
157
+
158
+ // Number
159
+ if value.is_number() {
160
+ let n = value.number_value(scope).unwrap_or(0.0);
161
+ return serde_json::Value::Number(
162
+ serde_json::Number::from_f64(n).unwrap_or_else(|| serde_json::Number::from(0)),
163
+ );
164
+ }
165
+
166
+ // String
167
+ if value.is_string() {
168
+ let s = value.to_string(scope).unwrap().to_rust_string_lossy(scope);
169
+ return serde_json::Value::String(s);
170
+ }
171
+
172
+ // Array
173
+ if value.is_array() {
174
+ let arr = v8::Local::<v8::Array>::try_from(value).unwrap();
175
+ let mut list = Vec::with_capacity(arr.length() as usize);
176
+ for i in 0..arr.length() {
177
+ let element = arr
178
+ .get_index(scope, i)
179
+ .unwrap_or_else(|| v8::null(scope).into());
180
+ list.push(v8_to_json(scope, element));
181
+ }
182
+ return serde_json::Value::Array(list);
183
+ }
184
+
185
+ // Object
186
+ if value.is_object() {
187
+ let obj = value.to_object(scope).unwrap();
188
+
189
+ let props = obj
190
+ .get_own_property_names(scope, v8::GetPropertyNamesArgs::default())
191
+ .unwrap();
192
+
193
+ let mut map = serde_json::Map::new();
194
+
195
+ for i in 0..props.length() {
196
+ let key_val = props
197
+ .get_index(scope, i)
198
+ .unwrap_or_else(|| v8::null(scope).into());
199
+
200
+ let key = key_val
201
+ .to_string(scope)
202
+ .unwrap()
203
+ .to_rust_string_lossy(scope);
204
+
205
+ let val = obj
206
+ .get(scope, key_val.into())
207
+ .unwrap_or_else(|| v8::null(scope).into());
208
+
209
+ map.insert(key, v8_to_json(scope, val));
210
+ }
211
+
212
+ return serde_json::Value::Object(map);
213
+ }
214
+
215
+ serde_json::Value::Null
216
+ }
217
+
139
218
  // ----------------------------------------------------------------------------
140
219
  // EXECUTION HELPERS
141
220
  // ----------------------------------------------------------------------------
142
221
 
143
222
  pub fn execute_action_optimized(
144
223
  runtime: &mut TitanRuntime,
145
- action_name: &str,
146
- req_body: Option<bytes::Bytes>,
147
- req_method: &str,
148
- req_path: &str,
149
- headers: &[(String, String)],
150
- params: &[(String, String)],
151
- query: &[(String, String)]
224
+ action_name: &str,
225
+ req_body: Option<bytes::Bytes>,
226
+ req_method: &str,
227
+ req_path: &str,
228
+ headers: &[(String, String)],
229
+ params: &[(String, String)],
230
+ query: &[(String, String)],
152
231
  ) -> serde_json::Value {
153
- let TitanRuntime { isolate, context: global_context, actions: actions_map, .. } = runtime;
232
+ let TitanRuntime {
233
+ isolate,
234
+ context: global_context,
235
+ actions: actions_map,
236
+ ..
237
+ } = runtime;
154
238
  let handle_scope = &mut v8::HandleScope::new(isolate);
155
239
  let context = v8::Local::new(handle_scope, &*global_context);
156
240
  let scope = &mut v8::ContextScope::new(handle_scope, context);
157
-
241
+
158
242
  let req_obj = v8::Object::new(scope);
159
-
243
+
160
244
  let m_key = v8_str(scope, "method");
161
245
  let m_val = v8_str(scope, req_method);
162
246
  req_obj.set(scope, m_key.into(), m_val.into());
163
-
247
+
164
248
  let p_key = v8_str(scope, "path");
165
249
  let p_val = v8_str(scope, req_path);
166
250
  req_obj.set(scope, p_key.into(), p_val.into());
@@ -170,33 +254,35 @@ pub fn execute_action_optimized(
170
254
  let store = v8::ArrayBuffer::new_backing_store_from_boxed_slice(vec.into_boxed_slice());
171
255
  let ab = v8::ArrayBuffer::with_backing_store(scope, &store.make_shared());
172
256
  ab.into()
173
- } else { v8::null(scope).into() };
257
+ } else {
258
+ v8::null(scope).into()
259
+ };
174
260
  let rb_key = v8_str(scope, "rawBody");
175
261
  req_obj.set(scope, rb_key.into(), body_val);
176
262
 
177
263
  let h_obj = v8::Object::new(scope);
178
- for (k, v) in headers {
264
+ for (k, v) in headers {
179
265
  let k_v8 = v8_str(scope, k);
180
266
  let v_v8 = v8_str(scope, v);
181
- h_obj.set(scope, k_v8.into(), v_v8.into());
267
+ h_obj.set(scope, k_v8.into(), v_v8.into());
182
268
  }
183
269
  let h_key = v8_str(scope, "headers");
184
270
  req_obj.set(scope, h_key.into(), h_obj.into());
185
271
 
186
272
  let p_obj = v8::Object::new(scope);
187
- for (k, v) in params {
273
+ for (k, v) in params {
188
274
  let k_v8 = v8_str(scope, k);
189
275
  let v_v8 = v8_str(scope, v);
190
- p_obj.set(scope, k_v8.into(), v_v8.into());
276
+ p_obj.set(scope, k_v8.into(), v_v8.into());
191
277
  }
192
278
  let params_key = v8_str(scope, "params");
193
279
  req_obj.set(scope, params_key.into(), p_obj.into());
194
280
 
195
281
  let q_obj = v8::Object::new(scope);
196
- for (k, v) in query {
282
+ for (k, v) in query {
197
283
  let k_v8 = v8_str(scope, k);
198
284
  let v_v8 = v8_str(scope, v);
199
- q_obj.set(scope, k_v8.into(), v_v8.into());
285
+ q_obj.set(scope, k_v8.into(), v_v8.into());
200
286
  }
201
287
  let q_key = v8_str(scope, "query");
202
288
  req_obj.set(scope, q_key.into(), q_obj.into());
@@ -211,12 +297,15 @@ pub fn execute_action_optimized(
211
297
  let tr_act_val = v8_str(scope, action_name);
212
298
  global.set(scope, tr_act_key.into(), tr_act_val.into());
213
299
  let try_catch = &mut v8::TryCatch::new(scope);
300
+
214
301
  if let Some(result) = action_fn.call(try_catch, global.into(), &[req_obj.into()]) {
215
- if let Some(json) = v8::json::stringify(try_catch, result) {
216
- return serde_json::from_str(&json.to_rust_string_lossy(try_catch)).unwrap_or(serde_json::Value::Null);
217
- }
302
+ return v8_to_json(try_catch, result);
218
303
  }
219
- let msg = try_catch.message().map(|m| m.get(try_catch).to_rust_string_lossy(try_catch)).unwrap_or("Unknown error".to_string());
304
+
305
+ let msg = try_catch
306
+ .message()
307
+ .map(|m| m.get(try_catch).to_rust_string_lossy(try_catch))
308
+ .unwrap_or("Unknown error".to_string());
220
309
  return serde_json::json!({"error": msg});
221
310
  }
222
311
  serde_json::json!({"error": format!("Action '{}' not found", action_name)})