@ezetgalaxy/titan 26.12.1 → 26.12.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.
- package/package.json +1 -1
- package/templates/js/server/src/extensions/builtin.rs +155 -0
- package/templates/js/server/src/extensions/mod.rs +0 -5
- package/templates/js/server/src/extensions/titan_core.js +5 -0
- package/templates/ts/server/src/extensions/builtin.rs +155 -0
- package/templates/ts/server/src/extensions/mod.rs +0 -5
- package/templates/ts/server/src/extensions/titan_core.js +5 -0
- package/titanpl-sdk/package.json +1 -1
- package/titanpl-sdk/templates/server/src/extensions/builtin.rs +155 -0
- package/titanpl-sdk/templates/server/src/extensions/mod.rs +0 -5
- package/titanpl-sdk/templates/server/src/extensions/titan_core.js +5 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ezetgalaxy/titan",
|
|
3
|
-
"version": "26.12.
|
|
3
|
+
"version": "26.12.2",
|
|
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",
|
|
@@ -8,12 +8,19 @@ use std::time::{SystemTime, UNIX_EPOCH};
|
|
|
8
8
|
use serde_json::Value;
|
|
9
9
|
use jsonwebtoken::{encode, decode, Header, EncodingKey, DecodingKey, Validation};
|
|
10
10
|
use bcrypt::{hash, verify, DEFAULT_COST};
|
|
11
|
+
use postgres::{Client as PgClient, NoTls};
|
|
12
|
+
use std::sync::Mutex;
|
|
13
|
+
use std::collections::HashMap;
|
|
11
14
|
|
|
12
15
|
use crate::utils::{blue, gray, parse_expires_in};
|
|
13
16
|
use super::{TitanRuntime, v8_str, v8_to_string, throw, ShareContextStore};
|
|
14
17
|
|
|
15
18
|
const TITAN_CORE_JS: &str = include_str!("titan_core.js");
|
|
16
19
|
|
|
20
|
+
// Database connection pool
|
|
21
|
+
static DB_POOL: Mutex<Option<HashMap<String, PgClient>>> = Mutex::new(None);
|
|
22
|
+
|
|
23
|
+
|
|
17
24
|
pub fn inject_builtin_extensions(scope: &mut v8::HandleScope, global: v8::Local<v8::Object>, t_obj: v8::Local<v8::Object>) {
|
|
18
25
|
// 1. Native API Bindings
|
|
19
26
|
|
|
@@ -43,6 +50,11 @@ pub fn inject_builtin_extensions(scope: &mut v8::HandleScope, global: v8::Local<
|
|
|
43
50
|
let fetch_key = v8_str(scope, "fetch");
|
|
44
51
|
t_obj.set(scope, fetch_key.into(), fetch_fn.into());
|
|
45
52
|
|
|
53
|
+
// t.loadEnv
|
|
54
|
+
let env_fn = v8::Function::new(scope, native_load_env).unwrap();
|
|
55
|
+
let env_key = v8_str(scope, "loadEnv");
|
|
56
|
+
t_obj.set(scope, env_key.into(), env_fn.into());
|
|
57
|
+
|
|
46
58
|
// auth, jwt, password ... (keep native)
|
|
47
59
|
setup_native_utils(scope, t_obj);
|
|
48
60
|
|
|
@@ -102,6 +114,17 @@ fn setup_native_utils(scope: &mut v8::HandleScope, t_obj: v8::Local<v8::Object>)
|
|
|
102
114
|
let sc_key = v8_str(scope, "shareContext");
|
|
103
115
|
let sc_val = sc_obj.into();
|
|
104
116
|
t_obj.set(scope, sc_key.into(), sc_val);
|
|
117
|
+
|
|
118
|
+
// t.db (Database operations)
|
|
119
|
+
println!("[DEBUG] Setting up t.db...");
|
|
120
|
+
let db_obj = v8::Object::new(scope);
|
|
121
|
+
let db_connect_fn = v8::Function::new(scope, native_db_connect).unwrap();
|
|
122
|
+
let connect_key = v8_str(scope, "connect");
|
|
123
|
+
db_obj.set(scope, connect_key.into(), db_connect_fn.into());
|
|
124
|
+
|
|
125
|
+
let db_key = v8_str(scope, "db");
|
|
126
|
+
t_obj.set(scope, db_key.into(), db_obj.into());
|
|
127
|
+
println!("[DEBUG] t.db setup complete!");
|
|
105
128
|
}
|
|
106
129
|
|
|
107
130
|
fn native_read(scope: &mut v8::HandleScope, args: v8::FunctionCallbackArguments, mut retval: v8::ReturnValue) {
|
|
@@ -428,6 +451,138 @@ fn native_password_verify(scope: &mut v8::HandleScope, args: v8::FunctionCallbac
|
|
|
428
451
|
retval.set(v8::Boolean::new(scope, ok).into());
|
|
429
452
|
}
|
|
430
453
|
|
|
454
|
+
fn native_load_env(scope: &mut v8::HandleScope, _args: v8::FunctionCallbackArguments, mut retval: v8::ReturnValue) {
|
|
455
|
+
use serde_json::json;
|
|
456
|
+
|
|
457
|
+
let mut map = serde_json::Map::new();
|
|
458
|
+
|
|
459
|
+
for (key, value) in std::env::vars() {
|
|
460
|
+
map.insert(key, json!(value));
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
let json_str = serde_json::to_string(&map).unwrap();
|
|
464
|
+
let v8_str = v8::String::new(scope, &json_str).unwrap();
|
|
465
|
+
|
|
466
|
+
if let Some(obj) = v8::json::parse(scope, v8_str) {
|
|
467
|
+
retval.set(obj);
|
|
468
|
+
} else {
|
|
469
|
+
retval.set(v8::null(scope).into());
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
431
473
|
fn native_define_action(_scope: &mut v8::HandleScope, args: v8::FunctionCallbackArguments, mut retval: v8::ReturnValue) {
|
|
432
474
|
retval.set(args.get(0));
|
|
433
475
|
}
|
|
476
|
+
|
|
477
|
+
fn native_db_connect(scope: &mut v8::HandleScope, args: v8::FunctionCallbackArguments, mut retval: v8::ReturnValue) {
|
|
478
|
+
let conn_string = v8_to_string(scope, args.get(0));
|
|
479
|
+
|
|
480
|
+
if conn_string.is_empty() {
|
|
481
|
+
throw(scope, "t.db.connect(): connection string is required");
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Test connection immediately
|
|
486
|
+
match PgClient::connect(&conn_string, NoTls) {
|
|
487
|
+
Ok(mut client) => {
|
|
488
|
+
// Store in pool
|
|
489
|
+
let mut pool = DB_POOL.lock().unwrap();
|
|
490
|
+
let map = pool.get_or_insert_with(HashMap::new);
|
|
491
|
+
map.insert(conn_string.clone(), client);
|
|
492
|
+
},
|
|
493
|
+
Err(e) => {
|
|
494
|
+
throw(scope, &format!("Database connection failed: {}", e));
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Return a DB connection object with methods
|
|
500
|
+
let db_conn_obj = v8::Object::new(scope);
|
|
501
|
+
|
|
502
|
+
// Store connection string in a hidden property
|
|
503
|
+
let conn_key = v8_str(scope, "__conn_string");
|
|
504
|
+
let conn_val = v8_str(scope, &conn_string);
|
|
505
|
+
db_conn_obj.set(scope, conn_key.into(), conn_val.into());
|
|
506
|
+
|
|
507
|
+
// Add query method
|
|
508
|
+
let query_fn = v8::Function::new(scope, native_db_query).unwrap();
|
|
509
|
+
let query_key = v8_str(scope, "query");
|
|
510
|
+
db_conn_obj.set(scope, query_key.into(), query_fn.into());
|
|
511
|
+
|
|
512
|
+
retval.set(db_conn_obj.into());
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
fn native_db_query(scope: &mut v8::HandleScope, args: v8::FunctionCallbackArguments, mut retval: v8::ReturnValue) {
|
|
516
|
+
// Get 'this' context (the db connection object)
|
|
517
|
+
let this = args.this();
|
|
518
|
+
let this_obj = this.to_object(scope).unwrap();
|
|
519
|
+
|
|
520
|
+
// Retrieve connection string
|
|
521
|
+
let conn_key = v8_str(scope, "__conn_string");
|
|
522
|
+
let conn_val = this_obj.get(scope, conn_key.into()).unwrap();
|
|
523
|
+
let conn_string = v8_to_string(scope, conn_val);
|
|
524
|
+
|
|
525
|
+
// Get query string
|
|
526
|
+
let query = v8_to_string(scope, args.get(0));
|
|
527
|
+
|
|
528
|
+
if query.is_empty() {
|
|
529
|
+
throw(scope, "db.query(): SQL query is required");
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// Get connection from pool
|
|
534
|
+
let mut pool = DB_POOL.lock().unwrap();
|
|
535
|
+
let map = pool.as_mut().unwrap();
|
|
536
|
+
|
|
537
|
+
let client = match map.get_mut(&conn_string) {
|
|
538
|
+
Some(c) => c,
|
|
539
|
+
None => {
|
|
540
|
+
throw(scope, "Database connection not found in pool");
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
};
|
|
544
|
+
|
|
545
|
+
// Execute query
|
|
546
|
+
match client.query(&query, &[]) {
|
|
547
|
+
Ok(rows) => {
|
|
548
|
+
let mut result = Vec::new();
|
|
549
|
+
|
|
550
|
+
for row in rows {
|
|
551
|
+
let mut obj = serde_json::Map::new();
|
|
552
|
+
|
|
553
|
+
for (i, column) in row.columns().iter().enumerate() {
|
|
554
|
+
let col_name = column.name();
|
|
555
|
+
let col_value: serde_json::Value = if let Ok(val) = row.try_get::<_, Option<String>>(i) {
|
|
556
|
+
serde_json::json!(val)
|
|
557
|
+
} else if let Ok(val) = row.try_get::<_, Option<i32>>(i) {
|
|
558
|
+
serde_json::json!(val)
|
|
559
|
+
} else if let Ok(val) = row.try_get::<_, Option<i64>>(i) {
|
|
560
|
+
serde_json::json!(val)
|
|
561
|
+
} else if let Ok(val) = row.try_get::<_, Option<f64>>(i) {
|
|
562
|
+
serde_json::json!(val)
|
|
563
|
+
} else if let Ok(val) = row.try_get::<_, Option<bool>>(i) {
|
|
564
|
+
serde_json::json!(val)
|
|
565
|
+
} else {
|
|
566
|
+
serde_json::Value::Null
|
|
567
|
+
};
|
|
568
|
+
|
|
569
|
+
obj.insert(col_name.to_string(), col_value);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
result.push(serde_json::Value::Object(obj));
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
let json_str = serde_json::to_string(&result).unwrap();
|
|
576
|
+
let v8_json_str = v8_str(scope, &json_str);
|
|
577
|
+
|
|
578
|
+
if let Some(val) = v8::json::parse(scope, v8_json_str) {
|
|
579
|
+
retval.set(val);
|
|
580
|
+
} else {
|
|
581
|
+
retval.set(v8::Array::new(scope, 0).into());
|
|
582
|
+
}
|
|
583
|
+
},
|
|
584
|
+
Err(e) => {
|
|
585
|
+
throw(scope, &format!("Query failed: {}", e));
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
@@ -132,11 +132,6 @@ pub fn inject_extensions(scope: &mut v8::HandleScope, global: v8::Local<v8::Obje
|
|
|
132
132
|
// Call individual injectors
|
|
133
133
|
builtin::inject_builtin_extensions(scope, global, t_obj);
|
|
134
134
|
external::inject_external_extensions(scope, global, t_obj);
|
|
135
|
-
|
|
136
|
-
// Inject t.db (Stub)
|
|
137
|
-
let db_obj = v8::Object::new(scope);
|
|
138
|
-
let db_key = v8_str(scope, "db");
|
|
139
|
-
t_obj.set(scope, db_key.into(), db_obj.into());
|
|
140
135
|
|
|
141
136
|
global.set(scope, t_key.into(), t_obj.into());
|
|
142
137
|
}
|
|
@@ -8,12 +8,19 @@ use std::time::{SystemTime, UNIX_EPOCH};
|
|
|
8
8
|
use serde_json::Value;
|
|
9
9
|
use jsonwebtoken::{encode, decode, Header, EncodingKey, DecodingKey, Validation};
|
|
10
10
|
use bcrypt::{hash, verify, DEFAULT_COST};
|
|
11
|
+
use postgres::{Client as PgClient, NoTls};
|
|
12
|
+
use std::sync::Mutex;
|
|
13
|
+
use std::collections::HashMap;
|
|
11
14
|
|
|
12
15
|
use crate::utils::{blue, gray, parse_expires_in};
|
|
13
16
|
use super::{TitanRuntime, v8_str, v8_to_string, throw, ShareContextStore};
|
|
14
17
|
|
|
15
18
|
const TITAN_CORE_JS: &str = include_str!("titan_core.js");
|
|
16
19
|
|
|
20
|
+
// Database connection pool
|
|
21
|
+
static DB_POOL: Mutex<Option<HashMap<String, PgClient>>> = Mutex::new(None);
|
|
22
|
+
|
|
23
|
+
|
|
17
24
|
pub fn inject_builtin_extensions(scope: &mut v8::HandleScope, global: v8::Local<v8::Object>, t_obj: v8::Local<v8::Object>) {
|
|
18
25
|
// 1. Native API Bindings
|
|
19
26
|
|
|
@@ -43,6 +50,11 @@ pub fn inject_builtin_extensions(scope: &mut v8::HandleScope, global: v8::Local<
|
|
|
43
50
|
let fetch_key = v8_str(scope, "fetch");
|
|
44
51
|
t_obj.set(scope, fetch_key.into(), fetch_fn.into());
|
|
45
52
|
|
|
53
|
+
// t.loadEnv
|
|
54
|
+
let env_fn = v8::Function::new(scope, native_load_env).unwrap();
|
|
55
|
+
let env_key = v8_str(scope, "loadEnv");
|
|
56
|
+
t_obj.set(scope, env_key.into(), env_fn.into());
|
|
57
|
+
|
|
46
58
|
// auth, jwt, password ... (keep native)
|
|
47
59
|
setup_native_utils(scope, t_obj);
|
|
48
60
|
|
|
@@ -102,6 +114,17 @@ fn setup_native_utils(scope: &mut v8::HandleScope, t_obj: v8::Local<v8::Object>)
|
|
|
102
114
|
let sc_key = v8_str(scope, "shareContext");
|
|
103
115
|
let sc_val = sc_obj.into();
|
|
104
116
|
t_obj.set(scope, sc_key.into(), sc_val);
|
|
117
|
+
|
|
118
|
+
// t.db (Database operations)
|
|
119
|
+
println!("[DEBUG] Setting up t.db...");
|
|
120
|
+
let db_obj = v8::Object::new(scope);
|
|
121
|
+
let db_connect_fn = v8::Function::new(scope, native_db_connect).unwrap();
|
|
122
|
+
let connect_key = v8_str(scope, "connect");
|
|
123
|
+
db_obj.set(scope, connect_key.into(), db_connect_fn.into());
|
|
124
|
+
|
|
125
|
+
let db_key = v8_str(scope, "db");
|
|
126
|
+
t_obj.set(scope, db_key.into(), db_obj.into());
|
|
127
|
+
println!("[DEBUG] t.db setup complete!");
|
|
105
128
|
}
|
|
106
129
|
|
|
107
130
|
fn native_read(scope: &mut v8::HandleScope, args: v8::FunctionCallbackArguments, mut retval: v8::ReturnValue) {
|
|
@@ -428,6 +451,138 @@ fn native_password_verify(scope: &mut v8::HandleScope, args: v8::FunctionCallbac
|
|
|
428
451
|
retval.set(v8::Boolean::new(scope, ok).into());
|
|
429
452
|
}
|
|
430
453
|
|
|
454
|
+
fn native_load_env(scope: &mut v8::HandleScope, _args: v8::FunctionCallbackArguments, mut retval: v8::ReturnValue) {
|
|
455
|
+
use serde_json::json;
|
|
456
|
+
|
|
457
|
+
let mut map = serde_json::Map::new();
|
|
458
|
+
|
|
459
|
+
for (key, value) in std::env::vars() {
|
|
460
|
+
map.insert(key, json!(value));
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
let json_str = serde_json::to_string(&map).unwrap();
|
|
464
|
+
let v8_str = v8::String::new(scope, &json_str).unwrap();
|
|
465
|
+
|
|
466
|
+
if let Some(obj) = v8::json::parse(scope, v8_str) {
|
|
467
|
+
retval.set(obj);
|
|
468
|
+
} else {
|
|
469
|
+
retval.set(v8::null(scope).into());
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
431
473
|
fn native_define_action(_scope: &mut v8::HandleScope, args: v8::FunctionCallbackArguments, mut retval: v8::ReturnValue) {
|
|
432
474
|
retval.set(args.get(0));
|
|
433
475
|
}
|
|
476
|
+
|
|
477
|
+
fn native_db_connect(scope: &mut v8::HandleScope, args: v8::FunctionCallbackArguments, mut retval: v8::ReturnValue) {
|
|
478
|
+
let conn_string = v8_to_string(scope, args.get(0));
|
|
479
|
+
|
|
480
|
+
if conn_string.is_empty() {
|
|
481
|
+
throw(scope, "t.db.connect(): connection string is required");
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Test connection immediately
|
|
486
|
+
match PgClient::connect(&conn_string, NoTls) {
|
|
487
|
+
Ok(mut client) => {
|
|
488
|
+
// Store in pool
|
|
489
|
+
let mut pool = DB_POOL.lock().unwrap();
|
|
490
|
+
let map = pool.get_or_insert_with(HashMap::new);
|
|
491
|
+
map.insert(conn_string.clone(), client);
|
|
492
|
+
},
|
|
493
|
+
Err(e) => {
|
|
494
|
+
throw(scope, &format!("Database connection failed: {}", e));
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Return a DB connection object with methods
|
|
500
|
+
let db_conn_obj = v8::Object::new(scope);
|
|
501
|
+
|
|
502
|
+
// Store connection string in a hidden property
|
|
503
|
+
let conn_key = v8_str(scope, "__conn_string");
|
|
504
|
+
let conn_val = v8_str(scope, &conn_string);
|
|
505
|
+
db_conn_obj.set(scope, conn_key.into(), conn_val.into());
|
|
506
|
+
|
|
507
|
+
// Add query method
|
|
508
|
+
let query_fn = v8::Function::new(scope, native_db_query).unwrap();
|
|
509
|
+
let query_key = v8_str(scope, "query");
|
|
510
|
+
db_conn_obj.set(scope, query_key.into(), query_fn.into());
|
|
511
|
+
|
|
512
|
+
retval.set(db_conn_obj.into());
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
fn native_db_query(scope: &mut v8::HandleScope, args: v8::FunctionCallbackArguments, mut retval: v8::ReturnValue) {
|
|
516
|
+
// Get 'this' context (the db connection object)
|
|
517
|
+
let this = args.this();
|
|
518
|
+
let this_obj = this.to_object(scope).unwrap();
|
|
519
|
+
|
|
520
|
+
// Retrieve connection string
|
|
521
|
+
let conn_key = v8_str(scope, "__conn_string");
|
|
522
|
+
let conn_val = this_obj.get(scope, conn_key.into()).unwrap();
|
|
523
|
+
let conn_string = v8_to_string(scope, conn_val);
|
|
524
|
+
|
|
525
|
+
// Get query string
|
|
526
|
+
let query = v8_to_string(scope, args.get(0));
|
|
527
|
+
|
|
528
|
+
if query.is_empty() {
|
|
529
|
+
throw(scope, "db.query(): SQL query is required");
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// Get connection from pool
|
|
534
|
+
let mut pool = DB_POOL.lock().unwrap();
|
|
535
|
+
let map = pool.as_mut().unwrap();
|
|
536
|
+
|
|
537
|
+
let client = match map.get_mut(&conn_string) {
|
|
538
|
+
Some(c) => c,
|
|
539
|
+
None => {
|
|
540
|
+
throw(scope, "Database connection not found in pool");
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
};
|
|
544
|
+
|
|
545
|
+
// Execute query
|
|
546
|
+
match client.query(&query, &[]) {
|
|
547
|
+
Ok(rows) => {
|
|
548
|
+
let mut result = Vec::new();
|
|
549
|
+
|
|
550
|
+
for row in rows {
|
|
551
|
+
let mut obj = serde_json::Map::new();
|
|
552
|
+
|
|
553
|
+
for (i, column) in row.columns().iter().enumerate() {
|
|
554
|
+
let col_name = column.name();
|
|
555
|
+
let col_value: serde_json::Value = if let Ok(val) = row.try_get::<_, Option<String>>(i) {
|
|
556
|
+
serde_json::json!(val)
|
|
557
|
+
} else if let Ok(val) = row.try_get::<_, Option<i32>>(i) {
|
|
558
|
+
serde_json::json!(val)
|
|
559
|
+
} else if let Ok(val) = row.try_get::<_, Option<i64>>(i) {
|
|
560
|
+
serde_json::json!(val)
|
|
561
|
+
} else if let Ok(val) = row.try_get::<_, Option<f64>>(i) {
|
|
562
|
+
serde_json::json!(val)
|
|
563
|
+
} else if let Ok(val) = row.try_get::<_, Option<bool>>(i) {
|
|
564
|
+
serde_json::json!(val)
|
|
565
|
+
} else {
|
|
566
|
+
serde_json::Value::Null
|
|
567
|
+
};
|
|
568
|
+
|
|
569
|
+
obj.insert(col_name.to_string(), col_value);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
result.push(serde_json::Value::Object(obj));
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
let json_str = serde_json::to_string(&result).unwrap();
|
|
576
|
+
let v8_json_str = v8_str(scope, &json_str);
|
|
577
|
+
|
|
578
|
+
if let Some(val) = v8::json::parse(scope, v8_json_str) {
|
|
579
|
+
retval.set(val);
|
|
580
|
+
} else {
|
|
581
|
+
retval.set(v8::Array::new(scope, 0).into());
|
|
582
|
+
}
|
|
583
|
+
},
|
|
584
|
+
Err(e) => {
|
|
585
|
+
throw(scope, &format!("Query failed: {}", e));
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
@@ -132,11 +132,6 @@ pub fn inject_extensions(scope: &mut v8::HandleScope, global: v8::Local<v8::Obje
|
|
|
132
132
|
// Call individual injectors
|
|
133
133
|
builtin::inject_builtin_extensions(scope, global, t_obj);
|
|
134
134
|
external::inject_external_extensions(scope, global, t_obj);
|
|
135
|
-
|
|
136
|
-
// Inject t.db (Stub)
|
|
137
|
-
let db_obj = v8::Object::new(scope);
|
|
138
|
-
let db_key = v8_str(scope, "db");
|
|
139
|
-
t_obj.set(scope, db_key.into(), db_obj.into());
|
|
140
135
|
|
|
141
136
|
global.set(scope, t_key.into(), t_obj.into());
|
|
142
137
|
}
|
package/titanpl-sdk/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "titanpl-sdk",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Development SDK for Titan Planet. Provides TypeScript type definitions for the global 't' runtime object and a 'lite' test-harness runtime for building and verifying extensions.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -8,12 +8,19 @@ use std::time::{SystemTime, UNIX_EPOCH};
|
|
|
8
8
|
use serde_json::Value;
|
|
9
9
|
use jsonwebtoken::{encode, decode, Header, EncodingKey, DecodingKey, Validation};
|
|
10
10
|
use bcrypt::{hash, verify, DEFAULT_COST};
|
|
11
|
+
use postgres::{Client as PgClient, NoTls};
|
|
12
|
+
use std::sync::Mutex;
|
|
13
|
+
use std::collections::HashMap;
|
|
11
14
|
|
|
12
15
|
use crate::utils::{blue, gray, parse_expires_in};
|
|
13
16
|
use super::{TitanRuntime, v8_str, v8_to_string, throw, ShareContextStore};
|
|
14
17
|
|
|
15
18
|
const TITAN_CORE_JS: &str = include_str!("titan_core.js");
|
|
16
19
|
|
|
20
|
+
// Database connection pool
|
|
21
|
+
static DB_POOL: Mutex<Option<HashMap<String, PgClient>>> = Mutex::new(None);
|
|
22
|
+
|
|
23
|
+
|
|
17
24
|
pub fn inject_builtin_extensions(scope: &mut v8::HandleScope, global: v8::Local<v8::Object>, t_obj: v8::Local<v8::Object>) {
|
|
18
25
|
// 1. Native API Bindings
|
|
19
26
|
|
|
@@ -43,6 +50,11 @@ pub fn inject_builtin_extensions(scope: &mut v8::HandleScope, global: v8::Local<
|
|
|
43
50
|
let fetch_key = v8_str(scope, "fetch");
|
|
44
51
|
t_obj.set(scope, fetch_key.into(), fetch_fn.into());
|
|
45
52
|
|
|
53
|
+
// t.loadEnv
|
|
54
|
+
let env_fn = v8::Function::new(scope, native_load_env).unwrap();
|
|
55
|
+
let env_key = v8_str(scope, "loadEnv");
|
|
56
|
+
t_obj.set(scope, env_key.into(), env_fn.into());
|
|
57
|
+
|
|
46
58
|
// auth, jwt, password ... (keep native)
|
|
47
59
|
setup_native_utils(scope, t_obj);
|
|
48
60
|
|
|
@@ -102,6 +114,17 @@ fn setup_native_utils(scope: &mut v8::HandleScope, t_obj: v8::Local<v8::Object>)
|
|
|
102
114
|
let sc_key = v8_str(scope, "shareContext");
|
|
103
115
|
let sc_val = sc_obj.into();
|
|
104
116
|
t_obj.set(scope, sc_key.into(), sc_val);
|
|
117
|
+
|
|
118
|
+
// t.db (Database operations)
|
|
119
|
+
println!("[DEBUG] Setting up t.db...");
|
|
120
|
+
let db_obj = v8::Object::new(scope);
|
|
121
|
+
let db_connect_fn = v8::Function::new(scope, native_db_connect).unwrap();
|
|
122
|
+
let connect_key = v8_str(scope, "connect");
|
|
123
|
+
db_obj.set(scope, connect_key.into(), db_connect_fn.into());
|
|
124
|
+
|
|
125
|
+
let db_key = v8_str(scope, "db");
|
|
126
|
+
t_obj.set(scope, db_key.into(), db_obj.into());
|
|
127
|
+
println!("[DEBUG] t.db setup complete!");
|
|
105
128
|
}
|
|
106
129
|
|
|
107
130
|
fn native_read(scope: &mut v8::HandleScope, args: v8::FunctionCallbackArguments, mut retval: v8::ReturnValue) {
|
|
@@ -428,6 +451,138 @@ fn native_password_verify(scope: &mut v8::HandleScope, args: v8::FunctionCallbac
|
|
|
428
451
|
retval.set(v8::Boolean::new(scope, ok).into());
|
|
429
452
|
}
|
|
430
453
|
|
|
454
|
+
fn native_load_env(scope: &mut v8::HandleScope, _args: v8::FunctionCallbackArguments, mut retval: v8::ReturnValue) {
|
|
455
|
+
use serde_json::json;
|
|
456
|
+
|
|
457
|
+
let mut map = serde_json::Map::new();
|
|
458
|
+
|
|
459
|
+
for (key, value) in std::env::vars() {
|
|
460
|
+
map.insert(key, json!(value));
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
let json_str = serde_json::to_string(&map).unwrap();
|
|
464
|
+
let v8_str = v8::String::new(scope, &json_str).unwrap();
|
|
465
|
+
|
|
466
|
+
if let Some(obj) = v8::json::parse(scope, v8_str) {
|
|
467
|
+
retval.set(obj);
|
|
468
|
+
} else {
|
|
469
|
+
retval.set(v8::null(scope).into());
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
431
473
|
fn native_define_action(_scope: &mut v8::HandleScope, args: v8::FunctionCallbackArguments, mut retval: v8::ReturnValue) {
|
|
432
474
|
retval.set(args.get(0));
|
|
433
475
|
}
|
|
476
|
+
|
|
477
|
+
fn native_db_connect(scope: &mut v8::HandleScope, args: v8::FunctionCallbackArguments, mut retval: v8::ReturnValue) {
|
|
478
|
+
let conn_string = v8_to_string(scope, args.get(0));
|
|
479
|
+
|
|
480
|
+
if conn_string.is_empty() {
|
|
481
|
+
throw(scope, "t.db.connect(): connection string is required");
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Test connection immediately
|
|
486
|
+
match PgClient::connect(&conn_string, NoTls) {
|
|
487
|
+
Ok(mut client) => {
|
|
488
|
+
// Store in pool
|
|
489
|
+
let mut pool = DB_POOL.lock().unwrap();
|
|
490
|
+
let map = pool.get_or_insert_with(HashMap::new);
|
|
491
|
+
map.insert(conn_string.clone(), client);
|
|
492
|
+
},
|
|
493
|
+
Err(e) => {
|
|
494
|
+
throw(scope, &format!("Database connection failed: {}", e));
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Return a DB connection object with methods
|
|
500
|
+
let db_conn_obj = v8::Object::new(scope);
|
|
501
|
+
|
|
502
|
+
// Store connection string in a hidden property
|
|
503
|
+
let conn_key = v8_str(scope, "__conn_string");
|
|
504
|
+
let conn_val = v8_str(scope, &conn_string);
|
|
505
|
+
db_conn_obj.set(scope, conn_key.into(), conn_val.into());
|
|
506
|
+
|
|
507
|
+
// Add query method
|
|
508
|
+
let query_fn = v8::Function::new(scope, native_db_query).unwrap();
|
|
509
|
+
let query_key = v8_str(scope, "query");
|
|
510
|
+
db_conn_obj.set(scope, query_key.into(), query_fn.into());
|
|
511
|
+
|
|
512
|
+
retval.set(db_conn_obj.into());
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
fn native_db_query(scope: &mut v8::HandleScope, args: v8::FunctionCallbackArguments, mut retval: v8::ReturnValue) {
|
|
516
|
+
// Get 'this' context (the db connection object)
|
|
517
|
+
let this = args.this();
|
|
518
|
+
let this_obj = this.to_object(scope).unwrap();
|
|
519
|
+
|
|
520
|
+
// Retrieve connection string
|
|
521
|
+
let conn_key = v8_str(scope, "__conn_string");
|
|
522
|
+
let conn_val = this_obj.get(scope, conn_key.into()).unwrap();
|
|
523
|
+
let conn_string = v8_to_string(scope, conn_val);
|
|
524
|
+
|
|
525
|
+
// Get query string
|
|
526
|
+
let query = v8_to_string(scope, args.get(0));
|
|
527
|
+
|
|
528
|
+
if query.is_empty() {
|
|
529
|
+
throw(scope, "db.query(): SQL query is required");
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// Get connection from pool
|
|
534
|
+
let mut pool = DB_POOL.lock().unwrap();
|
|
535
|
+
let map = pool.as_mut().unwrap();
|
|
536
|
+
|
|
537
|
+
let client = match map.get_mut(&conn_string) {
|
|
538
|
+
Some(c) => c,
|
|
539
|
+
None => {
|
|
540
|
+
throw(scope, "Database connection not found in pool");
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
};
|
|
544
|
+
|
|
545
|
+
// Execute query
|
|
546
|
+
match client.query(&query, &[]) {
|
|
547
|
+
Ok(rows) => {
|
|
548
|
+
let mut result = Vec::new();
|
|
549
|
+
|
|
550
|
+
for row in rows {
|
|
551
|
+
let mut obj = serde_json::Map::new();
|
|
552
|
+
|
|
553
|
+
for (i, column) in row.columns().iter().enumerate() {
|
|
554
|
+
let col_name = column.name();
|
|
555
|
+
let col_value: serde_json::Value = if let Ok(val) = row.try_get::<_, Option<String>>(i) {
|
|
556
|
+
serde_json::json!(val)
|
|
557
|
+
} else if let Ok(val) = row.try_get::<_, Option<i32>>(i) {
|
|
558
|
+
serde_json::json!(val)
|
|
559
|
+
} else if let Ok(val) = row.try_get::<_, Option<i64>>(i) {
|
|
560
|
+
serde_json::json!(val)
|
|
561
|
+
} else if let Ok(val) = row.try_get::<_, Option<f64>>(i) {
|
|
562
|
+
serde_json::json!(val)
|
|
563
|
+
} else if let Ok(val) = row.try_get::<_, Option<bool>>(i) {
|
|
564
|
+
serde_json::json!(val)
|
|
565
|
+
} else {
|
|
566
|
+
serde_json::Value::Null
|
|
567
|
+
};
|
|
568
|
+
|
|
569
|
+
obj.insert(col_name.to_string(), col_value);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
result.push(serde_json::Value::Object(obj));
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
let json_str = serde_json::to_string(&result).unwrap();
|
|
576
|
+
let v8_json_str = v8_str(scope, &json_str);
|
|
577
|
+
|
|
578
|
+
if let Some(val) = v8::json::parse(scope, v8_json_str) {
|
|
579
|
+
retval.set(val);
|
|
580
|
+
} else {
|
|
581
|
+
retval.set(v8::Array::new(scope, 0).into());
|
|
582
|
+
}
|
|
583
|
+
},
|
|
584
|
+
Err(e) => {
|
|
585
|
+
throw(scope, &format!("Query failed: {}", e));
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
@@ -132,11 +132,6 @@ pub fn inject_extensions(scope: &mut v8::HandleScope, global: v8::Local<v8::Obje
|
|
|
132
132
|
// Call individual injectors
|
|
133
133
|
builtin::inject_builtin_extensions(scope, global, t_obj);
|
|
134
134
|
external::inject_external_extensions(scope, global, t_obj);
|
|
135
|
-
|
|
136
|
-
// Inject t.db (Stub)
|
|
137
|
-
let db_obj = v8::Object::new(scope);
|
|
138
|
-
let db_key = v8_str(scope, "db");
|
|
139
|
-
t_obj.set(scope, db_key.into(), db_obj.into());
|
|
140
135
|
|
|
141
136
|
global.set(scope, t_key.into(), t_obj.into());
|
|
142
137
|
}
|