titanpl 4.0.2 → 7.0.0-beta
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 +11 -5
- package/packages/cli/index.js +25 -11
- package/packages/cli/package.json +5 -5
- 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/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 +10 -0
- package/packages/native/index.js +4 -0
- package/packages/native/package.json +1 -1
- package/packages/native/t.native.d.ts +175 -44
- package/packages/packet/README.md +0 -1
- package/packages/packet/index.js +19 -2
- package/packages/packet/package.json +1 -1
- package/packages/route/README.md +21 -0
- package/packages/route/index.d.ts +1 -0
- package/packages/route/index.js +22 -0
- 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/Dockerfile +9 -45
- package/templates/common/_tanfig.json +17 -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 +4 -4
- package/templates/rust-ts/package.json +4 -4
- package/templates/ts/package.json +8 -8
- package/templates/common/app/t.native.d.ts +0 -2043
- package/templates/common/app/t.native.js +0 -39
- package/titanpl-sdk/LICENSE +0 -15
- 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 -1055
- 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,580 +0,0 @@
|
|
|
1
|
-
//! V8 Runtime & Execution Engine (Performance Optimized)
|
|
2
|
-
//!
|
|
3
|
-
//! Key optimizations:
|
|
4
|
-
//! 1. Uses `v8::json::stringify` for response serialization (3-5x faster).
|
|
5
|
-
//! 2. Pre-internalized V8 strings for common property names.
|
|
6
|
-
//! 3. `execute_action_optimized` uses pre-internalized keys (~4µs saved/request).
|
|
7
|
-
//! 4. Inlined hot-path functions.
|
|
8
|
-
|
|
9
|
-
#![allow(unused)]
|
|
10
|
-
pub mod builtin;
|
|
11
|
-
pub mod external;
|
|
12
|
-
|
|
13
|
-
use crate::action_management::scan_actions;
|
|
14
|
-
use crate::utils::{blue, gray, green, red};
|
|
15
|
-
use bytes::Bytes;
|
|
16
|
-
use crossbeam::channel::Sender;
|
|
17
|
-
use dashmap::DashMap;
|
|
18
|
-
use serde_json::Value;
|
|
19
|
-
use std::collections::{HashMap, HashSet};
|
|
20
|
-
use std::fs;
|
|
21
|
-
use std::path::PathBuf;
|
|
22
|
-
use std::sync::Once;
|
|
23
|
-
use std::sync::{Arc, Mutex, OnceLock};
|
|
24
|
-
use tokio::sync::broadcast;
|
|
25
|
-
use v8;
|
|
26
|
-
|
|
27
|
-
// GLOBALS
|
|
28
|
-
|
|
29
|
-
pub static SHARE_CONTEXT: OnceLock<ShareContextStore> = OnceLock::new();
|
|
30
|
-
pub static PROJECT_ROOT: OnceLock<PathBuf> = OnceLock::new();
|
|
31
|
-
|
|
32
|
-
pub struct ShareContextStore {
|
|
33
|
-
pub kv: DashMap<String, serde_json::Value>,
|
|
34
|
-
pub broadcast_tx: broadcast::Sender<(String, serde_json::Value)>,
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
impl ShareContextStore {
|
|
38
|
-
pub fn get() -> &'static Self {
|
|
39
|
-
SHARE_CONTEXT.get_or_init(|| {
|
|
40
|
-
let (tx, _) = broadcast::channel(1000);
|
|
41
|
-
Self {
|
|
42
|
-
kv: DashMap::new(),
|
|
43
|
-
broadcast_tx: tx,
|
|
44
|
-
}
|
|
45
|
-
})
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
pub fn load_project_extensions(root: PathBuf) {
|
|
50
|
-
PROJECT_ROOT.get_or_init(|| root.clone());
|
|
51
|
-
external::load_project_extensions(root);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// ASYNC OP TYPES
|
|
55
|
-
|
|
56
|
-
pub enum TitanAsyncOp {
|
|
57
|
-
Fetch {
|
|
58
|
-
url: String,
|
|
59
|
-
method: String,
|
|
60
|
-
body: Option<String>,
|
|
61
|
-
headers: Vec<(String, String)>,
|
|
62
|
-
},
|
|
63
|
-
DbQuery {
|
|
64
|
-
conn: String,
|
|
65
|
-
query: String,
|
|
66
|
-
params: Vec<String>,
|
|
67
|
-
},
|
|
68
|
-
FsRead {
|
|
69
|
-
path: String,
|
|
70
|
-
},
|
|
71
|
-
Batch(Vec<TitanAsyncOp>),
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
pub struct WorkerAsyncResult {
|
|
75
|
-
pub drift_id: u32,
|
|
76
|
-
pub result: serde_json::Value,
|
|
77
|
-
pub duration_ms: f64,
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
pub struct AsyncOpRequest {
|
|
81
|
-
pub op: TitanAsyncOp,
|
|
82
|
-
pub drift_id: u32,
|
|
83
|
-
pub request_id: u32,
|
|
84
|
-
pub op_type: String,
|
|
85
|
-
pub respond_tx: tokio::sync::oneshot::Sender<WorkerAsyncResult>,
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// PRE-INTERNALIZED V8 STRINGS
|
|
89
|
-
|
|
90
|
-
/// Common V8 string keys pre-created once per isolate to avoid repeated allocation.
|
|
91
|
-
/// Each v8::Global is O(1) to clone (refcount bump) and O(1) to convert to Local.
|
|
92
|
-
pub struct InternedKeys {
|
|
93
|
-
pub method: v8::Global<v8::String>,
|
|
94
|
-
pub path: v8::Global<v8::String>,
|
|
95
|
-
pub headers: v8::Global<v8::String>,
|
|
96
|
-
pub params: v8::Global<v8::String>,
|
|
97
|
-
pub query: v8::Global<v8::String>,
|
|
98
|
-
pub raw_body: v8::Global<v8::String>,
|
|
99
|
-
pub request_id: v8::Global<v8::String>,
|
|
100
|
-
pub titan_req: v8::Global<v8::String>,
|
|
101
|
-
pub titan_action: v8::Global<v8::String>,
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// TITAN RUNTIME
|
|
105
|
-
|
|
106
|
-
pub struct TitanRuntime {
|
|
107
|
-
pub id: usize,
|
|
108
|
-
pub isolate: v8::OwnedIsolate,
|
|
109
|
-
pub context: v8::Global<v8::Context>,
|
|
110
|
-
pub actions: HashMap<String, v8::Global<v8::Function>>,
|
|
111
|
-
pub worker_tx: crossbeam::channel::Sender<crate::runtime::WorkerCommand>,
|
|
112
|
-
|
|
113
|
-
// Pre-internalized string keys for zero-alloc property access
|
|
114
|
-
pub interned_keys: Option<InternedKeys>,
|
|
115
|
-
|
|
116
|
-
// Action metadata: tracks which req fields each action uses
|
|
117
|
-
pub action_field_usage: HashMap<String, Option<HashSet<String>>>,
|
|
118
|
-
|
|
119
|
-
// Async State
|
|
120
|
-
pub async_rx: crossbeam::channel::Receiver<WorkerAsyncResult>,
|
|
121
|
-
pub async_tx: crossbeam::channel::Sender<WorkerAsyncResult>,
|
|
122
|
-
pub pending_drifts: HashMap<u32, v8::Global<v8::PromiseResolver>>,
|
|
123
|
-
pub pending_requests: HashMap<u32, tokio::sync::oneshot::Sender<crate::runtime::WorkerResult>>,
|
|
124
|
-
pub drift_counter: u32,
|
|
125
|
-
pub request_counter: u32,
|
|
126
|
-
|
|
127
|
-
pub tokio_handle: tokio::runtime::Handle,
|
|
128
|
-
pub global_async_tx: tokio::sync::mpsc::Sender<AsyncOpRequest>,
|
|
129
|
-
pub request_timings: HashMap<u32, Vec<(String, f64)>>,
|
|
130
|
-
pub drift_to_request: HashMap<u32, u32>,
|
|
131
|
-
pub completed_drifts: HashMap<u32, serde_json::Value>,
|
|
132
|
-
pub active_requests: HashMap<u32, RequestData>,
|
|
133
|
-
pub request_start_counters: HashMap<u32, u32>,
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
#[derive(Clone)]
|
|
137
|
-
pub struct RequestData {
|
|
138
|
-
pub action_name: String,
|
|
139
|
-
pub body: Option<Bytes>,
|
|
140
|
-
pub method: String,
|
|
141
|
-
pub path: String,
|
|
142
|
-
pub headers: Vec<(String, String)>,
|
|
143
|
-
pub params: Vec<(String, String)>,
|
|
144
|
-
pub query: Vec<(String, String)>,
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
unsafe impl Send for TitanRuntime {}
|
|
148
|
-
unsafe impl Sync for TitanRuntime {}
|
|
149
|
-
|
|
150
|
-
impl TitanRuntime {
|
|
151
|
-
pub fn bind_to_isolate(&mut self) {
|
|
152
|
-
let ptr = self as *mut TitanRuntime as *mut std::ffi::c_void;
|
|
153
|
-
self.isolate.set_data(0, ptr);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// V8 INITIALIZATION
|
|
158
|
-
|
|
159
|
-
static V8_INIT: Once = Once::new();
|
|
160
|
-
|
|
161
|
-
pub fn init_v8() {
|
|
162
|
-
V8_INIT.call_once(|| {
|
|
163
|
-
let platform = v8::new_default_platform(0, false).make_shared();
|
|
164
|
-
v8::V8::initialize_platform(platform);
|
|
165
|
-
v8::V8::initialize();
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// WORKER INITIALIZATION
|
|
170
|
-
|
|
171
|
-
pub fn init_runtime_worker(
|
|
172
|
-
id: usize,
|
|
173
|
-
root: PathBuf,
|
|
174
|
-
worker_tx: crossbeam::channel::Sender<crate::runtime::WorkerCommand>,
|
|
175
|
-
tokio_handle: tokio::runtime::Handle,
|
|
176
|
-
global_async_tx: tokio::sync::mpsc::Sender<AsyncOpRequest>,
|
|
177
|
-
stack_size: usize,
|
|
178
|
-
) -> TitanRuntime {
|
|
179
|
-
init_v8();
|
|
180
|
-
|
|
181
|
-
let params = v8::CreateParams::default();
|
|
182
|
-
let mut isolate = v8::Isolate::new(params);
|
|
183
|
-
|
|
184
|
-
let (global_context, actions_map, interned) = {
|
|
185
|
-
let handle_scope = &mut v8::HandleScope::new(&mut isolate);
|
|
186
|
-
let context = v8::Context::new(handle_scope, v8::ContextOptions::default());
|
|
187
|
-
let scope = &mut v8::ContextScope::new(handle_scope, context);
|
|
188
|
-
let global = context.global(scope);
|
|
189
|
-
|
|
190
|
-
// Inject Titan Runtime APIs
|
|
191
|
-
inject_extensions(scope, global);
|
|
192
|
-
|
|
193
|
-
// Root Metadata
|
|
194
|
-
let root_str = v8::String::new(scope, root.to_str().unwrap_or(".")).unwrap();
|
|
195
|
-
let root_key = v8_str(scope, "__titan_root");
|
|
196
|
-
global.set(scope, root_key.into(), root_str.into());
|
|
197
|
-
|
|
198
|
-
// Pre-internalize common V8 string keys (created once, reused every request)
|
|
199
|
-
let s_method = v8::String::new(scope, "method").unwrap();
|
|
200
|
-
let s_path = v8::String::new(scope, "path").unwrap();
|
|
201
|
-
let s_headers = v8::String::new(scope, "headers").unwrap();
|
|
202
|
-
let s_params = v8::String::new(scope, "params").unwrap();
|
|
203
|
-
let s_query = v8::String::new(scope, "query").unwrap();
|
|
204
|
-
let s_raw_body = v8::String::new(scope, "rawBody").unwrap();
|
|
205
|
-
let s_request_id = v8::String::new(scope, "__titan_request_id").unwrap();
|
|
206
|
-
let s_titan_req = v8::String::new(scope, "__titan_req").unwrap();
|
|
207
|
-
let s_titan_action = v8::String::new(scope, "__titan_action").unwrap();
|
|
208
|
-
|
|
209
|
-
let interned = InternedKeys {
|
|
210
|
-
method: v8::Global::new(scope, s_method),
|
|
211
|
-
path: v8::Global::new(scope, s_path),
|
|
212
|
-
headers: v8::Global::new(scope, s_headers),
|
|
213
|
-
params: v8::Global::new(scope, s_params),
|
|
214
|
-
query: v8::Global::new(scope, s_query),
|
|
215
|
-
raw_body: v8::Global::new(scope, s_raw_body),
|
|
216
|
-
request_id: v8::Global::new(scope, s_request_id),
|
|
217
|
-
titan_req: v8::Global::new(scope, s_titan_req),
|
|
218
|
-
titan_action: v8::Global::new(scope, s_titan_action),
|
|
219
|
-
};
|
|
220
|
-
|
|
221
|
-
// Load Actions
|
|
222
|
-
let mut map = HashMap::new();
|
|
223
|
-
let action_files = scan_actions(&root);
|
|
224
|
-
for (name, path) in action_files {
|
|
225
|
-
if let Ok(code) = fs::read_to_string(&path) {
|
|
226
|
-
let wrapped_source =
|
|
227
|
-
format!("(function() {{ {} }})(); globalThis[\"{}\"];", code, name);
|
|
228
|
-
let source_str = v8_str(scope, &wrapped_source);
|
|
229
|
-
let try_catch = &mut v8::TryCatch::new(scope);
|
|
230
|
-
if let Some(script) = v8::Script::compile(try_catch, source_str, None) {
|
|
231
|
-
if let Some(val) = script.run(try_catch) {
|
|
232
|
-
if val.is_function() {
|
|
233
|
-
let func = v8::Local::<v8::Function>::try_from(val).unwrap();
|
|
234
|
-
map.insert(name.clone(), v8::Global::new(try_catch, func));
|
|
235
|
-
} else if id == 0 {
|
|
236
|
-
println!(
|
|
237
|
-
"[V8] Action '{}' did not evaluate to a function: {:?}",
|
|
238
|
-
name,
|
|
239
|
-
val.to_rust_string_lossy(try_catch)
|
|
240
|
-
);
|
|
241
|
-
}
|
|
242
|
-
} else if id == 0 {
|
|
243
|
-
let msg = try_catch
|
|
244
|
-
.message()
|
|
245
|
-
.map(|m| m.get(try_catch).to_rust_string_lossy(try_catch))
|
|
246
|
-
.unwrap_or("Unknown run error".to_string());
|
|
247
|
-
println!("[V8] Failed to run action '{}': {}", name, msg);
|
|
248
|
-
}
|
|
249
|
-
} else if id == 0 {
|
|
250
|
-
let msg = try_catch
|
|
251
|
-
.message()
|
|
252
|
-
.map(|m| m.get(try_catch).to_rust_string_lossy(try_catch))
|
|
253
|
-
.unwrap_or("Unknown compile error".to_string());
|
|
254
|
-
println!("[V8] Failed to compile action '{}': {}", name, msg);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
(v8::Global::new(scope, context), map, interned)
|
|
259
|
-
};
|
|
260
|
-
|
|
261
|
-
let (async_tx, async_rx) = crossbeam::channel::unbounded();
|
|
262
|
-
|
|
263
|
-
TitanRuntime {
|
|
264
|
-
id,
|
|
265
|
-
isolate,
|
|
266
|
-
context: global_context,
|
|
267
|
-
actions: actions_map,
|
|
268
|
-
worker_tx,
|
|
269
|
-
interned_keys: Some(interned),
|
|
270
|
-
action_field_usage: HashMap::new(),
|
|
271
|
-
async_rx,
|
|
272
|
-
async_tx,
|
|
273
|
-
pending_drifts: HashMap::new(),
|
|
274
|
-
pending_requests: HashMap::new(),
|
|
275
|
-
drift_counter: 0,
|
|
276
|
-
request_counter: 0,
|
|
277
|
-
tokio_handle,
|
|
278
|
-
global_async_tx,
|
|
279
|
-
request_timings: HashMap::new(),
|
|
280
|
-
drift_to_request: HashMap::new(),
|
|
281
|
-
completed_drifts: HashMap::new(),
|
|
282
|
-
active_requests: HashMap::new(),
|
|
283
|
-
request_start_counters: HashMap::new(),
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// EXTENSION INJECTION
|
|
288
|
-
|
|
289
|
-
pub fn inject_extensions(scope: &mut v8::HandleScope, global: v8::Local<v8::Object>) {
|
|
290
|
-
let gt_key = v8_str(scope, "globalThis");
|
|
291
|
-
global.set(scope, gt_key.into(), global.into());
|
|
292
|
-
|
|
293
|
-
let t_obj = v8::Object::new(scope);
|
|
294
|
-
let t_key = v8_str(scope, "t");
|
|
295
|
-
global
|
|
296
|
-
.create_data_property(scope, t_key.into(), t_obj.into())
|
|
297
|
-
.unwrap();
|
|
298
|
-
|
|
299
|
-
builtin::inject_builtin_extensions(scope, global, t_obj);
|
|
300
|
-
external::inject_external_extensions(scope, global, t_obj);
|
|
301
|
-
|
|
302
|
-
global.set(scope, t_key.into(), t_obj.into());
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
// V8 ↔ JSON CONVERSION (Optimized)
|
|
306
|
-
|
|
307
|
-
/// Convert a V8 value to serde_json::Value.
|
|
308
|
-
/// Uses JSON.stringify for objects (V8-native, faster than recursive extraction).
|
|
309
|
-
#[inline]
|
|
310
|
-
pub fn v8_to_json<'s>(
|
|
311
|
-
scope: &mut v8::HandleScope<'s>,
|
|
312
|
-
value: v8::Local<v8::Value>,
|
|
313
|
-
) -> serde_json::Value {
|
|
314
|
-
if value.is_null_or_undefined() {
|
|
315
|
-
return serde_json::Value::Null;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
if value.is_boolean() {
|
|
319
|
-
return serde_json::Value::Bool(value.boolean_value(scope));
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
if value.is_number() {
|
|
323
|
-
let n = value.number_value(scope).unwrap_or(0.0);
|
|
324
|
-
return serde_json::Value::Number(
|
|
325
|
-
serde_json::Number::from_f64(n).unwrap_or_else(|| serde_json::Number::from(0)),
|
|
326
|
-
);
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
if value.is_string() {
|
|
330
|
-
let s = value.to_string(scope).unwrap().to_rust_string_lossy(scope);
|
|
331
|
-
return serde_json::Value::String(s);
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
// For arrays and objects: use V8's native JSON.stringify
|
|
335
|
-
if value.is_object() || value.is_array() {
|
|
336
|
-
if let Some(json_str) = v8::json::stringify(scope, value) {
|
|
337
|
-
let rust_str = json_str.to_rust_string_lossy(scope);
|
|
338
|
-
if let Ok(parsed) = serde_json::from_str(&rust_str) {
|
|
339
|
-
return parsed;
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
return v8_to_json_recursive(scope, value);
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
serde_json::Value::Null
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
/// Recursive fallback for v8_to_json (used when JSON.stringify fails).
|
|
349
|
-
fn v8_to_json_recursive<'s>(
|
|
350
|
-
scope: &mut v8::HandleScope<'s>,
|
|
351
|
-
value: v8::Local<v8::Value>,
|
|
352
|
-
) -> serde_json::Value {
|
|
353
|
-
if value.is_null_or_undefined() {
|
|
354
|
-
return serde_json::Value::Null;
|
|
355
|
-
}
|
|
356
|
-
if value.is_boolean() {
|
|
357
|
-
return serde_json::Value::Bool(value.boolean_value(scope));
|
|
358
|
-
}
|
|
359
|
-
if value.is_number() {
|
|
360
|
-
let n = value.number_value(scope).unwrap_or(0.0);
|
|
361
|
-
return serde_json::Value::Number(
|
|
362
|
-
serde_json::Number::from_f64(n).unwrap_or_else(|| serde_json::Number::from(0)),
|
|
363
|
-
);
|
|
364
|
-
}
|
|
365
|
-
if value.is_string() {
|
|
366
|
-
let s = value.to_string(scope).unwrap().to_rust_string_lossy(scope);
|
|
367
|
-
return serde_json::Value::String(s);
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
if value.is_array() {
|
|
371
|
-
let arr = v8::Local::<v8::Array>::try_from(value).unwrap();
|
|
372
|
-
let mut list = Vec::with_capacity(arr.length() as usize);
|
|
373
|
-
for i in 0..arr.length() {
|
|
374
|
-
let element = arr
|
|
375
|
-
.get_index(scope, i)
|
|
376
|
-
.unwrap_or_else(|| v8::null(scope).into());
|
|
377
|
-
list.push(v8_to_json_recursive(scope, element));
|
|
378
|
-
}
|
|
379
|
-
return serde_json::Value::Array(list);
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
if value.is_object() {
|
|
383
|
-
let obj = value.to_object(scope).unwrap();
|
|
384
|
-
let props = obj
|
|
385
|
-
.get_own_property_names(scope, v8::GetPropertyNamesArgs::default())
|
|
386
|
-
.unwrap();
|
|
387
|
-
let mut map = serde_json::Map::new();
|
|
388
|
-
for i in 0..props.length() {
|
|
389
|
-
let key_val = props
|
|
390
|
-
.get_index(scope, i)
|
|
391
|
-
.unwrap_or_else(|| v8::null(scope).into());
|
|
392
|
-
let key = key_val
|
|
393
|
-
.to_string(scope)
|
|
394
|
-
.unwrap()
|
|
395
|
-
.to_rust_string_lossy(scope);
|
|
396
|
-
let val = obj
|
|
397
|
-
.get(scope, key_val.into())
|
|
398
|
-
.unwrap_or_else(|| v8::null(scope).into());
|
|
399
|
-
map.insert(key, v8_to_json_recursive(scope, val));
|
|
400
|
-
}
|
|
401
|
-
return serde_json::Value::Object(map);
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
serde_json::Value::Null
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
// ACTION EXECUTION (Optimized with Pre-Internalized Keys)
|
|
408
|
-
|
|
409
|
-
/// Execute a JavaScript action in the V8 isolate.
|
|
410
|
-
///
|
|
411
|
-
/// Optimizations:
|
|
412
|
-
/// - Uses pre-internalized string keys via v8::Global → v8::Local conversion
|
|
413
|
-
/// (each conversion is O(1) pointer deref, vs ~0.5µs per v8::String::new)
|
|
414
|
-
/// - Body passed as ArrayBuffer with zero-copy backing store
|
|
415
|
-
/// - Saves ~4µs per request from eliminated string allocations
|
|
416
|
-
#[inline]
|
|
417
|
-
pub fn execute_action_optimized(
|
|
418
|
-
runtime: &mut TitanRuntime,
|
|
419
|
-
request_id: u32,
|
|
420
|
-
action_name: &str,
|
|
421
|
-
req_body: Option<bytes::Bytes>,
|
|
422
|
-
req_method: &str,
|
|
423
|
-
req_path: &str,
|
|
424
|
-
headers: &[(String, String)],
|
|
425
|
-
params: &[(String, String)],
|
|
426
|
-
query: &[(String, String)],
|
|
427
|
-
) {
|
|
428
|
-
// =========================================================================
|
|
429
|
-
// STEP 1: Extract all data from runtime BEFORE borrowing isolate.
|
|
430
|
-
// v8::Global::clone() is O(1) refcount bump — no V8 heap allocation.
|
|
431
|
-
// =========================================================================
|
|
432
|
-
let context_global = runtime.context.clone();
|
|
433
|
-
let actions_map = runtime.actions.clone();
|
|
434
|
-
|
|
435
|
-
let ik = runtime.interned_keys.as_ref().unwrap();
|
|
436
|
-
let gk_method = ik.method.clone();
|
|
437
|
-
let gk_path = ik.path.clone();
|
|
438
|
-
let gk_headers = ik.headers.clone();
|
|
439
|
-
let gk_params = ik.params.clone();
|
|
440
|
-
let gk_query = ik.query.clone();
|
|
441
|
-
let gk_raw_body = ik.raw_body.clone();
|
|
442
|
-
let gk_request_id = ik.request_id.clone();
|
|
443
|
-
let gk_titan_req = ik.titan_req.clone();
|
|
444
|
-
let gk_titan_action = ik.titan_action.clone();
|
|
445
|
-
|
|
446
|
-
let isolate = &mut runtime.isolate;
|
|
447
|
-
let handle_scope = &mut v8::HandleScope::new(isolate);
|
|
448
|
-
let context = v8::Local::new(handle_scope, context_global);
|
|
449
|
-
let scope = &mut v8::ContextScope::new(handle_scope, context);
|
|
450
|
-
|
|
451
|
-
// =========================================================================
|
|
452
|
-
// STEP 2: Build request object with pre-internalized keys.
|
|
453
|
-
// v8::Local::new(scope, &global) is a pointer deref — no allocation.
|
|
454
|
-
// Before: v8_str(scope, "method") allocated a new V8 string every request.
|
|
455
|
-
// After: v8::Local::new(scope, &gk_method) reuses the pre-allocated one.
|
|
456
|
-
// =========================================================================
|
|
457
|
-
let req_obj = v8::Object::new(scope);
|
|
458
|
-
|
|
459
|
-
// __titan_request_id
|
|
460
|
-
let req_id_key = v8::Local::new(scope, &gk_request_id);
|
|
461
|
-
let req_id_val = v8::Integer::new(scope, request_id as i32);
|
|
462
|
-
req_obj.set(scope, req_id_key.into(), req_id_val.into());
|
|
463
|
-
|
|
464
|
-
// method
|
|
465
|
-
let m_key = v8::Local::new(scope, &gk_method);
|
|
466
|
-
let m_val = v8_str(scope, req_method);
|
|
467
|
-
req_obj.set(scope, m_key.into(), m_val.into());
|
|
468
|
-
|
|
469
|
-
// path
|
|
470
|
-
let p_key = v8::Local::new(scope, &gk_path);
|
|
471
|
-
let p_val = v8_str(scope, req_path);
|
|
472
|
-
req_obj.set(scope, p_key.into(), p_val.into());
|
|
473
|
-
|
|
474
|
-
// body — attach raw bytes as ArrayBuffer under "rawBody" key
|
|
475
|
-
let rb_key = v8::Local::new(scope, &gk_raw_body);
|
|
476
|
-
let body_val: v8::Local<v8::Value> = if let Some(bytes) = req_body {
|
|
477
|
-
let backing = v8::ArrayBuffer::new_backing_store_from_vec(bytes.to_vec());
|
|
478
|
-
let ab = v8::ArrayBuffer::with_backing_store(scope, &backing.make_shared());
|
|
479
|
-
ab.into()
|
|
480
|
-
} else {
|
|
481
|
-
v8::null(scope).into()
|
|
482
|
-
};
|
|
483
|
-
req_obj.set(scope, rb_key.into(), body_val);
|
|
484
|
-
|
|
485
|
-
// headers
|
|
486
|
-
let h_key = v8::Local::new(scope, &gk_headers);
|
|
487
|
-
let h_obj = v8::Object::new(scope);
|
|
488
|
-
for (k, v) in headers {
|
|
489
|
-
let k_v8 = v8_str(scope, k);
|
|
490
|
-
let v_v8 = v8_str(scope, v);
|
|
491
|
-
h_obj.set(scope, k_v8.into(), v_v8.into());
|
|
492
|
-
}
|
|
493
|
-
req_obj.set(scope, h_key.into(), h_obj.into());
|
|
494
|
-
|
|
495
|
-
// params
|
|
496
|
-
let params_key = v8::Local::new(scope, &gk_params);
|
|
497
|
-
let p_obj = v8::Object::new(scope);
|
|
498
|
-
for (k, v) in params {
|
|
499
|
-
let k_v8 = v8_str(scope, k);
|
|
500
|
-
let v_v8 = v8_str(scope, v);
|
|
501
|
-
p_obj.set(scope, k_v8.into(), v_v8.into());
|
|
502
|
-
}
|
|
503
|
-
req_obj.set(scope, params_key.into(), p_obj.into());
|
|
504
|
-
|
|
505
|
-
// query
|
|
506
|
-
let q_key = v8::Local::new(scope, &gk_query);
|
|
507
|
-
let q_obj = v8::Object::new(scope);
|
|
508
|
-
for (k, v) in query {
|
|
509
|
-
let k_v8 = v8_str(scope, k);
|
|
510
|
-
let v_v8 = v8_str(scope, v);
|
|
511
|
-
q_obj.set(scope, k_v8.into(), v_v8.into());
|
|
512
|
-
}
|
|
513
|
-
req_obj.set(scope, q_key.into(), q_obj.into());
|
|
514
|
-
|
|
515
|
-
// Set __titan_req on global
|
|
516
|
-
let global = context.global(scope);
|
|
517
|
-
let req_tr_key = v8::Local::new(scope, &gk_titan_req);
|
|
518
|
-
global.set(scope, req_tr_key.into(), req_obj.into());
|
|
519
|
-
|
|
520
|
-
// =========================================================================
|
|
521
|
-
// STEP 3: Execute action function
|
|
522
|
-
// =========================================================================
|
|
523
|
-
if let Some(action_global) = actions_map.get(action_name) {
|
|
524
|
-
let action_fn = v8::Local::new(scope, action_global);
|
|
525
|
-
let tr_act_key = v8::Local::new(scope, &gk_titan_action);
|
|
526
|
-
let tr_act_val = v8_str(scope, action_name);
|
|
527
|
-
global.set(scope, tr_act_key.into(), tr_act_val.into());
|
|
528
|
-
let try_catch = &mut v8::TryCatch::new(scope);
|
|
529
|
-
|
|
530
|
-
if action_fn
|
|
531
|
-
.call(try_catch, global.into(), &[req_obj.into()])
|
|
532
|
-
.is_some()
|
|
533
|
-
{
|
|
534
|
-
return;
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
let msg = try_catch
|
|
538
|
-
.message()
|
|
539
|
-
.map(|m| m.get(try_catch).to_rust_string_lossy(try_catch))
|
|
540
|
-
.unwrap_or("Unknown error".to_string());
|
|
541
|
-
|
|
542
|
-
if msg.contains("SUSPEND") {
|
|
543
|
-
return;
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
println!("[Isolate {}] Action Error: {}", runtime.id, msg);
|
|
547
|
-
if let Some(tx) = runtime.pending_requests.remove(&request_id) {
|
|
548
|
-
let _ = tx.send(crate::runtime::WorkerResult {
|
|
549
|
-
json: serde_json::json!({"error": msg}),
|
|
550
|
-
timings: vec![],
|
|
551
|
-
});
|
|
552
|
-
}
|
|
553
|
-
} else {
|
|
554
|
-
if let Some(tx) = runtime.pending_requests.remove(&request_id) {
|
|
555
|
-
let _ = tx.send(crate::runtime::WorkerResult {
|
|
556
|
-
json: serde_json::json!({"error": format!("Action '{}' not found", action_name)}),
|
|
557
|
-
timings: vec![],
|
|
558
|
-
});
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
// V8 HELPERS
|
|
564
|
-
|
|
565
|
-
#[inline(always)]
|
|
566
|
-
pub fn v8_str<'s>(scope: &mut v8::HandleScope<'s>, s: &str) -> v8::Local<'s, v8::String> {
|
|
567
|
-
v8::String::new(scope, s).unwrap()
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
#[inline(always)]
|
|
571
|
-
pub fn v8_to_string(scope: &mut v8::HandleScope, value: v8::Local<v8::Value>) -> String {
|
|
572
|
-
value.to_string(scope).unwrap().to_rust_string_lossy(scope)
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
#[inline]
|
|
576
|
-
pub fn throw(scope: &mut v8::HandleScope, msg: &str) {
|
|
577
|
-
let message = v8_str(scope, msg);
|
|
578
|
-
let exception = v8::Exception::error(scope, message);
|
|
579
|
-
scope.throw_exception(exception);
|
|
580
|
-
}
|