@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 +1 -0
- package/package.json +1 -1
- package/templates/js/server/src/extensions/mod.rs +139 -50
- package/templates/js/server/src/main.rs +188 -199
- package/templates/js/titan/dev.js +5 -5
- package/templates/js/titan/error-box.js +30 -5
- package/templates/js/titan/titan.js +2 -2
- package/templates/ts/server/src/extensions/mod.rs +139 -50
- package/templates/ts/server/src/main.rs +188 -199
- package/templates/ts/titan/titan.js +2 -2
- package/titanpl-sdk/bin/run.js +9 -3
- package/titanpl-sdk/package.json +1 -1
- package/titanpl-sdk/templates/server/src/extensions/mod.rs +139 -50
- package/titanpl-sdk/templates/server/src/main.rs +188 -199
- package/titanpl-sdk/templates/titan/dev.js +9 -8
- package/titanpl-sdk/templates/titan/error-box.js +30 -5
- package/titanpl-sdk/templates/titan/titan.js +2 -2
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.
|
|
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
|
|
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(
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
|
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 {
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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)})
|