titanpl-sdk 2.0.0 → 2.0.2

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.
@@ -16,15 +16,19 @@ pub struct Registry {
16
16
  pub _libs: Vec<Library>,
17
17
  pub modules: Vec<ModuleDef>,
18
18
  pub natives: Vec<NativeFnEntry>,
19
+ pub v8_natives: Vec<usize>,
19
20
  }
20
21
 
22
+
21
23
  #[derive(Clone)]
22
24
  pub struct ModuleDef {
23
25
  pub name: String,
24
26
  pub js: String,
25
27
  pub native_indices: HashMap<String, usize>,
28
+ pub v8_native_indices: HashMap<String, usize>,
26
29
  }
27
30
 
31
+
28
32
  #[derive(Clone, Debug, PartialEq)]
29
33
  pub enum ParamType {
30
34
  String, F64, Bool, Json, Buffer,
@@ -56,9 +60,18 @@ struct TitanConfig {
56
60
  #[derive(serde::Deserialize)]
57
61
  struct TitanNativeConfig {
58
62
  path: String,
63
+ #[serde(default)]
59
64
  functions: HashMap<String, TitanNativeFunc>,
65
+ #[serde(default)]
66
+ v8_functions: HashMap<String, TitanV8Func>,
60
67
  }
61
68
 
69
+ #[derive(serde::Deserialize)]
70
+ struct TitanV8Func {
71
+ symbol: String,
72
+ }
73
+
74
+
62
75
  #[derive(serde::Deserialize)]
63
76
  struct TitanNativeFunc {
64
77
  symbol: String,
@@ -95,6 +108,8 @@ pub fn load_project_extensions(root: PathBuf) {
95
108
  let mut modules = Vec::new();
96
109
  let mut libs = Vec::new();
97
110
  let mut all_natives = Vec::new();
111
+ let mut all_v8_natives = Vec::new();
112
+
98
113
 
99
114
  let mut node_modules = root.join("node_modules");
100
115
  if !node_modules.exists() {
@@ -104,8 +119,11 @@ pub fn load_project_extensions(root: PathBuf) {
104
119
  }
105
120
  }
106
121
 
107
- if node_modules.exists() {
108
- for entry in WalkDir::new(&node_modules).follow_links(true).min_depth(1).max_depth(4) {
122
+ // Generic scanner helper
123
+ let scan_dir = |path: PathBuf, modules: &mut Vec<ModuleDef>, libs: &mut Vec<Library>, all_natives: &mut Vec<NativeFnEntry>, all_v8_natives: &mut Vec<usize>| {
124
+
125
+ if !path.exists() { return; }
126
+ for entry in WalkDir::new(&path).follow_links(true).min_depth(1).max_depth(4) {
109
127
  let entry = match entry { Ok(e) => e, Err(_) => continue };
110
128
  if entry.file_type().is_file() && entry.file_name() == "titan.json" {
111
129
  let dir = entry.path().parent().unwrap();
@@ -115,30 +133,74 @@ pub fn load_project_extensions(root: PathBuf) {
115
133
  Err(_) => continue,
116
134
  };
117
135
  let mut mod_natives_map = HashMap::new();
136
+ let mut mod_v8_natives_map = HashMap::new();
118
137
  if let Some(native_conf) = config.native {
138
+
119
139
  let lib_path = dir.join(&native_conf.path);
120
140
  unsafe {
121
- if let Ok(lib) = Library::new(&lib_path) {
122
- for (fn_name, fn_conf) in native_conf.functions {
123
- let params = fn_conf.parameters.iter().map(|p| parse_type(&p.to_lowercase())).collect();
124
- let ret = parse_return(&fn_conf.result.to_lowercase());
125
- if let Ok(symbol) = lib.get::<*const ()>(fn_conf.symbol.as_bytes()) {
126
- let idx = all_natives.len();
127
- all_natives.push(NativeFnEntry { symbol_ptr: *symbol as usize, sig: Signature { params, ret } });
128
- mod_natives_map.insert(fn_name, idx);
141
+ // Try loading library
142
+ let lib_load = Library::new(&lib_path);
143
+ // If failed, try resolving relative to current dir or LD_LIBRARY_PATH implicit
144
+ // But usually absolute path from `dir` works.
145
+ match lib_load {
146
+ Ok(lib) => {
147
+ for (fn_name, fn_conf) in &native_conf.functions {
148
+ let params = fn_conf.parameters.iter().map(|p| parse_type(&p.to_lowercase())).collect();
149
+ let ret = parse_return(&fn_conf.result.to_lowercase());
150
+ if let Ok(symbol) = lib.get::<*const ()>(fn_conf.symbol.as_bytes()) {
151
+ let idx = all_natives.len();
152
+ all_natives.push(NativeFnEntry { symbol_ptr: *symbol as usize, sig: Signature { params, ret } });
153
+ mod_natives_map.insert(fn_name.clone(), idx);
154
+ } else {
155
+ println!("{} {} {} -> {}", blue("[Titan]"), red("Symbol not found:"), fn_conf.symbol, config.name);
156
+ }
157
+ }
158
+
159
+ for (fn_name, fn_conf) in &native_conf.v8_functions {
160
+ if let Ok(symbol) = lib.get::<*const ()>(fn_conf.symbol.as_bytes()) {
161
+ let idx = all_v8_natives.len();
162
+ all_v8_natives.push(*symbol as usize);
163
+ mod_v8_natives_map.insert(fn_name.clone(), idx);
164
+ } else {
165
+ println!("{} {} {} -> {}", blue("[Titan]"), red("V8 Symbol not found:"), fn_conf.symbol, config.name);
166
+ }
129
167
  }
130
- }
131
- libs.push(lib);
168
+
169
+ libs.push(lib);
170
+ },
171
+ Err(e) => {
172
+ println!("{} {} {} -> {:?}", blue("[Titan]"), red("Failed to load native lib:"), config.name, e);
173
+ }
132
174
  }
133
175
  }
134
176
  }
135
177
  let js_path = dir.join(&config.main);
136
- modules.push(ModuleDef { name: config.name.clone(), js: fs::read_to_string(js_path).unwrap_or_default(), native_indices: mod_natives_map });
178
+ modules.push(ModuleDef {
179
+ name: config.name.clone(),
180
+ js: fs::read_to_string(js_path).unwrap_or_default(),
181
+ native_indices: mod_natives_map,
182
+ v8_native_indices: mod_v8_natives_map
183
+ });
184
+
137
185
  println!("{} {} {}", blue("[Titan]"), green("Extension loaded:"), config.name);
138
186
  }
139
187
  }
188
+ };
189
+
190
+ // Scan node_modules
191
+ if node_modules.exists() {
192
+ scan_dir(node_modules, &mut modules, &mut libs, &mut all_natives, &mut all_v8_natives);
140
193
  }
141
- *REGISTRY.lock().unwrap() = Some(Registry { _libs: libs, modules, natives: all_natives });
194
+
195
+
196
+ // Scan .ext (Production / Docker)
197
+ let ext_dir = root.join(".ext");
198
+ if ext_dir.exists() {
199
+ scan_dir(ext_dir, &mut modules, &mut libs, &mut all_natives, &mut all_v8_natives);
200
+ }
201
+
202
+ *REGISTRY.lock().unwrap() = Some(Registry { _libs: libs, modules, natives: all_natives, v8_natives: all_v8_natives });
203
+
142
204
  }
143
205
 
144
206
  pub fn inject_external_extensions(scope: &mut v8::HandleScope, global: v8::Local<v8::Object>, t_obj: v8::Local<v8::Object>) {
@@ -146,9 +208,10 @@ pub fn inject_external_extensions(scope: &mut v8::HandleScope, global: v8::Local
146
208
  let invoke_key = v8_str(scope, "__titan_invoke_native");
147
209
  global.set(scope, invoke_key.into(), invoke_fn.into());
148
210
 
149
- let modules = if let Ok(guard) = REGISTRY.lock() {
150
- guard.as_ref().map(|r| r.modules.clone()).unwrap_or_default()
151
- } else { vec![] };
211
+ let (modules, v8_native_ptrs) = if let Ok(guard) = REGISTRY.lock() {
212
+ (guard.as_ref().map(|r| r.modules.clone()).unwrap_or_default(), guard.as_ref().map(|r| r.v8_natives.clone()).unwrap_or_default())
213
+ } else { (vec![], vec![]) };
214
+
152
215
 
153
216
  for module in modules {
154
217
  let mod_obj = v8::Object::new(scope);
@@ -162,6 +225,24 @@ pub fn inject_external_extensions(scope: &mut v8::HandleScope, global: v8::Local
162
225
  }
163
226
  }
164
227
  }
228
+
229
+ for (fn_name, &idx) in &module.v8_native_indices {
230
+ if let Some(&ptr) = v8_native_ptrs.get(idx) {
231
+ if ptr != 0 {
232
+ unsafe {
233
+ let ext = v8::External::new(scope, ptr as *mut std::ffi::c_void);
234
+ let templ = v8::FunctionTemplate::builder(native_invoke_v8_proxy)
235
+ .data(ext.into())
236
+ .build(scope);
237
+
238
+ if let Some(func) = templ.get_function(scope) {
239
+ let key = v8_str(scope, fn_name);
240
+ mod_obj.set(scope, key.into(), func.into());
241
+ }
242
+ }
243
+ }
244
+ }
245
+ }
165
246
  let mod_key = v8_str(scope, &module.name);
166
247
  t_obj.set(scope, mod_key.into(), mod_obj.into());
167
248
 
@@ -249,6 +330,20 @@ fn native_invoke_extension(scope: &mut v8::HandleScope, args: v8::FunctionCallba
249
330
  }
250
331
  }
251
332
 
333
+ fn native_invoke_v8_proxy(scope: &mut v8::HandleScope, args: v8::FunctionCallbackArguments, retval: v8::ReturnValue) {
334
+ let val = args.data();
335
+ if let Ok(ext) = v8::Local::<v8::External>::try_from(val) {
336
+ let ptr = ext.value() as *mut std::ffi::c_void;
337
+ if !ptr.is_null() {
338
+ unsafe {
339
+ type TitanV8Handler = extern "C" fn(&mut v8::HandleScope, v8::FunctionCallbackArguments, v8::ReturnValue);
340
+ let handler: TitanV8Handler = std::mem::transmute(ptr);
341
+ handler(scope, args, retval);
342
+ }
343
+ }
344
+ }
345
+ }
346
+
252
347
  fn arg_from_v8(scope: &mut v8::HandleScope, val: v8::Local<v8::Value>, ty: &ParamType) -> serde_json::Value {
253
348
  match ty {
254
349
  ParamType::String => serde_json::Value::String(val.to_rust_string_lossy(scope)),
@@ -21,6 +21,7 @@ use v8;
21
21
  // ----------------------------------------------------------------------------
22
22
 
23
23
  pub static SHARE_CONTEXT: OnceLock<ShareContextStore> = OnceLock::new();
24
+ pub static PROJECT_ROOT: OnceLock<PathBuf> = OnceLock::new();
24
25
 
25
26
  pub struct ShareContextStore {
26
27
  pub kv: DashMap<String, serde_json::Value>,
@@ -40,22 +41,91 @@ impl ShareContextStore {
40
41
  }
41
42
 
42
43
  // Re-exports for easier access
43
- pub use external::load_project_extensions;
44
+ pub fn load_project_extensions(root: PathBuf) {
45
+ PROJECT_ROOT.get_or_init(|| root.clone());
46
+ external::load_project_extensions(root);
47
+ }
44
48
 
45
49
  // ----------------------------------------------------------------------------
46
50
  // TITAN RUNTIME
47
51
  // ----------------------------------------------------------------------------
48
52
 
53
+ pub enum TitanAsyncOp {
54
+ Fetch {
55
+ url: String,
56
+ method: String,
57
+ body: Option<String>,
58
+ headers: Vec<(String, String)>,
59
+ },
60
+ DbQuery {
61
+ conn: String,
62
+ query: String,
63
+ },
64
+ FsRead {
65
+ path: String,
66
+ },
67
+ Batch(Vec<TitanAsyncOp>),
68
+ }
69
+
70
+ pub struct WorkerAsyncResult {
71
+ pub drift_id: u32,
72
+ pub result: serde_json::Value,
73
+ pub duration_ms: f64,
74
+ }
75
+
76
+ pub struct AsyncOpRequest {
77
+ pub op: TitanAsyncOp,
78
+ pub drift_id: u32,
79
+ pub request_id: u32,
80
+ pub op_type: String,
81
+ pub respond_tx: tokio::sync::oneshot::Sender<WorkerAsyncResult>,
82
+ }
83
+
49
84
  pub struct TitanRuntime {
85
+ pub id: usize,
50
86
  pub isolate: v8::OwnedIsolate,
51
87
  pub context: v8::Global<v8::Context>,
52
88
  pub actions: HashMap<String, v8::Global<v8::Function>>,
53
89
  pub worker_tx: crossbeam::channel::Sender<crate::runtime::WorkerCommand>,
90
+
91
+ // Async State
92
+ pub async_rx: crossbeam::channel::Receiver<WorkerAsyncResult>,
93
+ pub async_tx: crossbeam::channel::Sender<WorkerAsyncResult>,
94
+ pub pending_drifts: HashMap<u32, v8::Global<v8::PromiseResolver>>,
95
+ pub pending_requests: HashMap<u32, tokio::sync::oneshot::Sender<crate::runtime::WorkerResult>>,
96
+ pub drift_counter: u32,
97
+ pub request_counter: u32,
98
+
99
+ pub tokio_handle: tokio::runtime::Handle,
100
+ pub global_async_tx: tokio::sync::mpsc::Sender<AsyncOpRequest>,
101
+ pub request_timings: HashMap<u32, Vec<(String, f64)>>,
102
+ pub drift_to_request: HashMap<u32, u32>,
103
+ pub completed_drifts: HashMap<u32, serde_json::Value>,
104
+ pub active_requests: HashMap<u32, RequestData>,
105
+ pub request_start_counters: HashMap<u32, u32>,
106
+ }
107
+
108
+ #[derive(Clone)]
109
+ pub struct RequestData {
110
+ pub action_name: String,
111
+ pub body: Option<Bytes>,
112
+ pub method: String,
113
+ pub path: String,
114
+ pub headers: Vec<(String, String)>,
115
+ pub params: Vec<(String, String)>,
116
+ pub query: Vec<(String, String)>,
54
117
  }
55
118
 
56
119
  unsafe impl Send for TitanRuntime {}
57
120
  unsafe impl Sync for TitanRuntime {}
58
121
 
122
+ impl TitanRuntime {
123
+ pub fn bind_to_isolate(&mut self) {
124
+ let ptr = self as *mut TitanRuntime as *mut std::ffi::c_void;
125
+ self.isolate.set_data(0, ptr);
126
+ }
127
+ }
128
+
59
129
  static V8_INIT: Once = Once::new();
60
130
 
61
131
  pub fn init_v8() {
@@ -67,19 +137,19 @@ pub fn init_v8() {
67
137
  }
68
138
 
69
139
  pub fn init_runtime_worker(
140
+ id: usize,
70
141
  root: PathBuf,
71
142
  worker_tx: crossbeam::channel::Sender<crate::runtime::WorkerCommand>,
143
+ tokio_handle: tokio::runtime::Handle,
144
+ global_async_tx: tokio::sync::mpsc::Sender<AsyncOpRequest>,
145
+ stack_size: usize,
72
146
  ) -> TitanRuntime {
73
147
  init_v8();
74
148
 
75
- // Memory optimization strategy (v8 0.106.0 limitations):
76
- // - V8 snapshots reduce memory footprint by sharing compiled code
77
- // - Each isolate still has its own heap, but the snapshot reduces base overhead
78
- // - For explicit heap limits, use V8 flags: --max-old-space-size=128
79
-
149
+ // Memory optimization strategy
80
150
  let params = v8::CreateParams::default();
81
151
  let mut isolate = v8::Isolate::new(params);
82
-
152
+
83
153
  let (global_context, actions_map) = {
84
154
  let handle_scope = &mut v8::HandleScope::new(&mut isolate);
85
155
  let context = v8::Context::new(handle_scope, v8::ContextOptions::default());
@@ -99,6 +169,7 @@ pub fn init_runtime_worker(
99
169
  let action_files = scan_actions(&root);
100
170
  for (name, path) in action_files {
101
171
  if let Ok(code) = fs::read_to_string(&path) {
172
+ // Wrap action in an IIFE to capture its exports and register it globally
102
173
  let wrapped_source =
103
174
  format!("(function() {{ {} }})(); globalThis[\"{}\"];", code, name);
104
175
  let source_str = v8_str(scope, &wrapped_source);
@@ -108,19 +179,49 @@ pub fn init_runtime_worker(
108
179
  if val.is_function() {
109
180
  let func = v8::Local::<v8::Function>::try_from(val).unwrap();
110
181
  map.insert(name.clone(), v8::Global::new(try_catch, func));
182
+ } else if id == 0 {
183
+ println!("[V8] Action '{}' did not evaluate to a function: {:?}", name, val.to_rust_string_lossy(try_catch));
111
184
  }
185
+ } else if id == 0 {
186
+ let msg = try_catch
187
+ .message()
188
+ .map(|m| m.get(try_catch).to_rust_string_lossy(try_catch))
189
+ .unwrap_or("Unknown run error".to_string());
190
+ println!("[V8] Failed to run action '{}': {}", name, msg);
112
191
  }
192
+ } else if id == 0 {
193
+ let msg = try_catch
194
+ .message()
195
+ .map(|m| m.get(try_catch).to_rust_string_lossy(try_catch))
196
+ .unwrap_or("Unknown compile error".to_string());
197
+ println!("[V8] Failed to compile action '{}': {}", name, msg);
113
198
  }
114
199
  }
115
200
  }
116
201
  (v8::Global::new(scope, context), map)
117
202
  };
118
203
 
204
+ let (async_tx, async_rx) = crossbeam::channel::unbounded();
205
+
119
206
  TitanRuntime {
207
+ id,
120
208
  isolate,
121
209
  context: global_context,
122
210
  actions: actions_map,
123
211
  worker_tx,
212
+ async_rx,
213
+ async_tx,
214
+ pending_drifts: HashMap::new(),
215
+ pending_requests: HashMap::new(),
216
+ drift_counter: 0,
217
+ request_counter: 0,
218
+ tokio_handle,
219
+ global_async_tx,
220
+ request_timings: HashMap::new(),
221
+ drift_to_request: HashMap::new(),
222
+ completed_drifts: HashMap::new(),
223
+ active_requests: HashMap::new(),
224
+ request_start_counters: HashMap::new(),
124
225
  }
125
226
  }
126
227
 
@@ -142,7 +243,7 @@ pub fn inject_extensions(scope: &mut v8::HandleScope, global: v8::Local<v8::Obje
142
243
  global.set(scope, t_key.into(), t_obj.into());
143
244
  }
144
245
 
145
- fn v8_to_json<'s>(
246
+ pub fn v8_to_json<'s>(
146
247
  scope: &mut v8::HandleScope<'s>,
147
248
  value: v8::Local<v8::Value>,
148
249
  ) -> serde_json::Value {
@@ -221,6 +322,7 @@ fn v8_to_json<'s>(
221
322
 
222
323
  pub fn execute_action_optimized(
223
324
  runtime: &mut TitanRuntime,
325
+ request_id: u32,
224
326
  action_name: &str,
225
327
  req_body: Option<bytes::Bytes>,
226
328
  req_method: &str,
@@ -228,19 +330,22 @@ pub fn execute_action_optimized(
228
330
  headers: &[(String, String)],
229
331
  params: &[(String, String)],
230
332
  query: &[(String, String)],
231
- ) -> serde_json::Value {
232
- let TitanRuntime {
233
- isolate,
234
- context: global_context,
235
- actions: actions_map,
236
- ..
237
- } = runtime;
333
+ ) {
334
+ // Execute action in V8
335
+ let context_global = runtime.context.clone();
336
+ let actions_map = runtime.actions.clone(); // Clone the map of globals (cheap)
337
+ let isolate = &mut runtime.isolate;
338
+
238
339
  let handle_scope = &mut v8::HandleScope::new(isolate);
239
- let context = v8::Local::new(handle_scope, &*global_context);
340
+ let context = v8::Local::new(handle_scope, context_global);
240
341
  let scope = &mut v8::ContextScope::new(handle_scope, context);
241
342
 
242
343
  let req_obj = v8::Object::new(scope);
243
344
 
345
+ let req_id_key = v8_str(scope, "__titan_request_id");
346
+ let req_id_val = v8::Integer::new(scope, request_id as i32);
347
+ req_obj.set(scope, req_id_key.into(), req_id_val.into());
348
+
244
349
  let m_key = v8_str(scope, "method");
245
350
  let m_val = v8_str(scope, req_method);
246
351
  req_obj.set(scope, m_key.into(), m_val.into());
@@ -298,17 +403,34 @@ pub fn execute_action_optimized(
298
403
  global.set(scope, tr_act_key.into(), tr_act_val.into());
299
404
  let try_catch = &mut v8::TryCatch::new(scope);
300
405
 
301
- if let Some(result) = action_fn.call(try_catch, global.into(), &[req_obj.into()]) {
302
- return v8_to_json(try_catch, result);
406
+ if let Some(_) = action_fn.call(try_catch, global.into(), &[req_obj.into()]) {
407
+ return;
303
408
  }
304
-
409
+
305
410
  let msg = try_catch
306
411
  .message()
307
412
  .map(|m| m.get(try_catch).to_rust_string_lossy(try_catch))
308
413
  .unwrap_or("Unknown error".to_string());
309
- return serde_json::json!({"error": msg});
414
+
415
+ if msg.contains("SUSPEND") {
416
+ return;
417
+ }
418
+
419
+ println!("[Isolate {}] Action Error: {}", runtime.id, msg);
420
+ if let Some(tx) = runtime.pending_requests.remove(&request_id) {
421
+ let _ = tx.send(crate::runtime::WorkerResult {
422
+ json: serde_json::json!({"error": msg}),
423
+ timings: vec![]
424
+ });
425
+ }
426
+ } else {
427
+ if let Some(tx) = runtime.pending_requests.remove(&request_id) {
428
+ let _ = tx.send(crate::runtime::WorkerResult {
429
+ json: serde_json::json!({"error": format!("Action '{}' not found", action_name)}),
430
+ timings: vec![]
431
+ });
432
+ }
310
433
  }
311
- serde_json::json!({"error": format!("Action '{}' not found", action_name)})
312
434
  }
313
435
 
314
436
  pub fn v8_str<'s>(scope: &mut v8::HandleScope<'s>, s: &str) -> v8::Local<'s, v8::String> {
@@ -1,22 +1,186 @@
1
-
2
1
  // Titan Core Runtime JS
3
- // This is embedded in the binary for ultra-fast startup.
2
+ // Safe Bootstrap runs only once
3
+ if (!globalThis.__TITAN_CORE_LOADED__) {
4
+ globalThis.__TITAN_CORE_LOADED__ = true;
5
+
6
+ globalThis.global = globalThis;
7
+
8
+ // ensure t exists early
9
+ if (!globalThis.t) globalThis.t = {};
10
+
11
+ // -----------------------------
12
+ // defineAction identity helper
13
+ // -----------------------------
14
+ globalThis.defineAction = (fn) => {
15
+ if (fn.__titanWrapped) return fn;
16
+
17
+ const wrapped = function (req) {
18
+ const requestId = req.__titan_request_id;
19
+
20
+ const isSuspend = (err) => {
21
+ const msg = err && (err.message || String(err));
22
+ return msg && (msg.includes("__SUSPEND__") || msg.includes("SUSPEND"));
23
+ };
4
24
 
5
- globalThis.global = globalThis;
25
+ try {
26
+ const result = fn(req);
6
27
 
7
- // defineAction identity helper
8
- globalThis.defineAction = (fn) => fn;
28
+ if (result && typeof result.then === 'function') {
29
+ result.then(
30
+ (data) => {
31
+ t._finish_request(requestId, data);
32
+ },
33
+ (err) => {
34
+ if (isSuspend(err)) return;
35
+ t._finish_request(requestId, { error: err.message || String(err) });
36
+ }
37
+ );
38
+ } else {
39
+ t._finish_request(requestId, result);
40
+ }
41
+ } catch (err) {
42
+ if (isSuspend(err)) return;
43
+ t._finish_request(requestId, { error: err.message || String(err) });
44
+ }
45
+ };
9
46
 
10
- // TextDecoder Polyfill using native t.decodeUtf8
11
- globalThis.TextDecoder = class TextDecoder {
12
- decode(buffer) {
13
- return t.decodeUtf8(buffer);
47
+ wrapped.__titanWrapped = true;
48
+ return wrapped;
49
+ };
50
+
51
+
52
+ // -----------------------------
53
+ // TextDecoder Polyfill
54
+ // -----------------------------
55
+ globalThis.TextDecoder = class TextDecoder {
56
+ decode(buffer) {
57
+ return t.decodeUtf8(buffer);
58
+ }
59
+ };
60
+
61
+ // -----------------------------
62
+ // process.env
63
+ // -----------------------------
64
+ globalThis.process = {
65
+ env: t.loadEnv ? t.loadEnv() : {}
66
+ };
67
+
68
+ // -----------------------------
69
+ // Async Proxy Creator
70
+ // -----------------------------
71
+ function createAsyncOp(op) {
72
+ return new Proxy(op, {
73
+ get(target, prop) {
74
+ if (
75
+ prop === "__titanAsync" ||
76
+ prop === "type" ||
77
+ prop === "data" ||
78
+ typeof prop === 'symbol'
79
+ ) {
80
+ return target[prop];
81
+ }
82
+
83
+ throw new Error(
84
+ `[Titan Error] Accessed '${String(prop)}' without drift(). ` +
85
+ `Fix: const res = drift(t.fetch(...));`
86
+ );
87
+ }
88
+ });
14
89
  }
15
- };
16
90
 
17
- // Process environment variables
18
- globalThis.process = {
19
- env: t.loadEnv()
20
- };
91
+ // -----------------------------
92
+ // Response API
93
+ // -----------------------------
94
+ const titanResponse = {
95
+ json(data, status = 200, extraHeaders = {}) {
96
+ return {
97
+ _isResponse: true,
98
+ status,
99
+ headers: { "Content-Type": "application/json", ...extraHeaders },
100
+ body: JSON.stringify(data)
101
+ };
102
+ },
103
+ text(data, status = 200, extraHeaders = {}) {
104
+ return {
105
+ _isResponse: true,
106
+ status,
107
+ headers: { "Content-Type": "text/plain", ...extraHeaders },
108
+ body: String(data)
109
+ };
110
+ },
111
+ html(data, status = 200, extraHeaders = {}) {
112
+ return {
113
+ _isResponse: true,
114
+ status,
115
+ headers: { "Content-Type": "text/html", ...extraHeaders },
116
+ body: String(data)
117
+ };
118
+ },
119
+ redirect(url, status = 302, extraHeaders = {}) {
120
+ return {
121
+ _isResponse: true,
122
+ status,
123
+ headers: { "Location": url, ...extraHeaders },
124
+ redirect: url
125
+ };
126
+ }
127
+ };
128
+
129
+ t.response = titanResponse;
130
+
131
+ // -----------------------------
132
+ // Drift Support
133
+ // -----------------------------
134
+ globalThis.drift = function (value) {
135
+ if (Array.isArray(value)) {
136
+ for (const item of value) {
137
+ if (!item || !item.__titanAsync) {
138
+ throw new Error("drift() array must contain async ops only.");
139
+ }
140
+ }
141
+ } else if (!value || !value.__titanAsync) {
142
+ throw new Error("drift() must wrap async ops.");
143
+ }
144
+
145
+ return t._drift_call(value);
146
+ };
147
+
148
+ // -----------------------------
149
+ // Safe Wrappers
150
+ // -----------------------------
151
+
152
+ // fetch
153
+ if (t.fetch && !t.fetch.__titanWrapped) {
154
+ const nativeFetch = t.fetch;
155
+ t.fetch = function (...args) {
156
+ return createAsyncOp(nativeFetch(...args));
157
+ };
158
+ t.fetch.__titanWrapped = true;
159
+ }
160
+
161
+ // db.connect
162
+ if (t.db && !t.db.__titanWrapped) {
163
+ const nativeDbConnect = t.db.connect;
164
+
165
+ t.db.connect = function (connString) {
166
+ const conn = nativeDbConnect(connString);
167
+
168
+ if (!conn.query.__titanWrapped) {
169
+ const nativeQuery = conn.query;
170
+ conn.query = (sql) => {
171
+ return createAsyncOp({
172
+ __titanAsync: true,
173
+ type: "db_query",
174
+ data: { conn: connString, query: sql }
175
+ });
176
+ };
177
+ conn.query.__titanWrapped = true;
178
+ }
179
+
180
+ return conn;
181
+ };
182
+
183
+ t.db.__titanWrapped = true;
184
+ }
21
185
 
22
- // Everything is strictly synchronous and request-driven.
186
+ }