titanpl-sdk 2.0.1 → 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.
package/bin/run.js CHANGED
@@ -198,24 +198,24 @@ await import("${name}");
198
198
  // Extension test harness for: ${name}
199
199
  const ext = t["${name}"];
200
200
 
201
- console.log("---------------------------------------------------");
202
- console.log("Testing Extension: ${name}");
203
- console.log("---------------------------------------------------");
201
+ t.log("---------------------------------------------------");
202
+ t.log("Testing Extension: ${name}");
203
+ t.log("---------------------------------------------------");
204
204
 
205
205
  if (!ext) {
206
206
  console.log("ERROR: Extension '${name}' not found in global 't'.");
207
207
  } else {
208
- console.log("✓ Extension loaded successfully!");
209
- console.log("✓ Available methods:", Object.keys(ext).join(", "));
208
+ t.log("✓ Extension loaded successfully!");
209
+ t.log("✓ Available methods:", Object.keys(ext).join(", "));
210
210
 
211
211
  // Try 'hello' if it exists
212
212
  if (typeof ext.hello === 'function') {
213
213
  console.log("\\nTesting ext.hello('Titan')...");
214
214
  try {
215
215
  const res = ext.hello("Titan");
216
- console.log("✓ Result:", res);
216
+ t.log("✓ Result:", res);
217
217
  } catch(e) {
218
- console.log("✗ Error:", e.message);
218
+ t.log("✗ Error:", e.message);
219
219
  }
220
220
  }
221
221
 
@@ -224,25 +224,25 @@ if (!ext) {
224
224
  console.log("\\nTesting ext.calc(10, 20)...");
225
225
  try {
226
226
  const res = ext.calc(10, 20);
227
- console.log("✓ Result:", res);
227
+ t.log("✓ Result:", res);
228
228
  } catch(e) {
229
- console.log("✗ Error:", e.message);
229
+ t.log("✗ Error:", e.message);
230
230
  }
231
231
  }
232
232
  }
233
233
 
234
- console.log("---------------------------------------------------");
235
- console.log("✓ Test complete!");
236
- console.log("\\n📍 Routes:");
237
- console.log(" GET http://localhost:3000/ → Test harness info");
238
- console.log(" GET http://localhost:3000/test → Extension test results (JSON)");
239
- console.log("---------------------------------------------------\\n");
234
+ t.log("---------------------------------------------------");
235
+ t.log("✓ Test complete!");
236
+ t.log("\\n📍 Routes:");
237
+ t.log(" GET http://localhost:3000/ → Test harness info");
238
+ t.log(" GET http://localhost:3000/test → Extension test results (JSON)");
239
+ t.log("---------------------------------------------------\\n");
240
240
 
241
241
  // Create routes
242
242
  t.get("/test").action("test");
243
243
  t.get("/").reply("🚀 Extension Test Harness for ${name}\\n\\nVisit /test to see extension test results");
244
244
 
245
- await t.start(3000, "Titan Extension Test Running!");
245
+ await t.start(3000, "Titan Extension Test Running!", 10, 16);
246
246
  `;
247
247
  fs.writeFileSync(appJsPath, testScript);
248
248
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "titanpl-sdk",
3
- "version": "2.0.1",
3
+ "version": "2.0.2",
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",
@@ -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() {
@@ -105,7 +120,8 @@ pub fn load_project_extensions(root: PathBuf) {
105
120
  }
106
121
 
107
122
  // Generic scanner helper
108
- let scan_dir = |path: PathBuf, modules: &mut Vec<ModuleDef>, libs: &mut Vec<Library>, all_natives: &mut Vec<NativeFnEntry>| {
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
+
109
125
  if !path.exists() { return; }
110
126
  for entry in WalkDir::new(&path).follow_links(true).min_depth(1).max_depth(4) {
111
127
  let entry = match entry { Ok(e) => e, Err(_) => continue };
@@ -117,7 +133,9 @@ pub fn load_project_extensions(root: PathBuf) {
117
133
  Err(_) => continue,
118
134
  };
119
135
  let mut mod_natives_map = HashMap::new();
136
+ let mut mod_v8_natives_map = HashMap::new();
120
137
  if let Some(native_conf) = config.native {
138
+
121
139
  let lib_path = dir.join(&native_conf.path);
122
140
  unsafe {
123
141
  // Try loading library
@@ -126,17 +144,28 @@ pub fn load_project_extensions(root: PathBuf) {
126
144
  // But usually absolute path from `dir` works.
127
145
  match lib_load {
128
146
  Ok(lib) => {
129
- for (fn_name, fn_conf) in native_conf.functions {
147
+ for (fn_name, fn_conf) in &native_conf.functions {
130
148
  let params = fn_conf.parameters.iter().map(|p| parse_type(&p.to_lowercase())).collect();
131
149
  let ret = parse_return(&fn_conf.result.to_lowercase());
132
150
  if let Ok(symbol) = lib.get::<*const ()>(fn_conf.symbol.as_bytes()) {
133
151
  let idx = all_natives.len();
134
152
  all_natives.push(NativeFnEntry { symbol_ptr: *symbol as usize, sig: Signature { params, ret } });
135
- mod_natives_map.insert(fn_name, idx);
153
+ mod_natives_map.insert(fn_name.clone(), idx);
136
154
  } else {
137
155
  println!("{} {} {} -> {}", blue("[Titan]"), red("Symbol not found:"), fn_conf.symbol, config.name);
138
156
  }
139
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
+ }
167
+ }
168
+
140
169
  libs.push(lib);
141
170
  },
142
171
  Err(e) => {
@@ -146,7 +175,13 @@ pub fn load_project_extensions(root: PathBuf) {
146
175
  }
147
176
  }
148
177
  let js_path = dir.join(&config.main);
149
- 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
+
150
185
  println!("{} {} {}", blue("[Titan]"), green("Extension loaded:"), config.name);
151
186
  }
152
187
  }
@@ -154,16 +189,18 @@ pub fn load_project_extensions(root: PathBuf) {
154
189
 
155
190
  // Scan node_modules
156
191
  if node_modules.exists() {
157
- scan_dir(node_modules, &mut modules, &mut libs, &mut all_natives);
192
+ scan_dir(node_modules, &mut modules, &mut libs, &mut all_natives, &mut all_v8_natives);
158
193
  }
159
194
 
195
+
160
196
  // Scan .ext (Production / Docker)
161
197
  let ext_dir = root.join(".ext");
162
198
  if ext_dir.exists() {
163
- scan_dir(ext_dir, &mut modules, &mut libs, &mut all_natives);
199
+ scan_dir(ext_dir, &mut modules, &mut libs, &mut all_natives, &mut all_v8_natives);
164
200
  }
165
201
 
166
- *REGISTRY.lock().unwrap() = Some(Registry { _libs: libs, modules, natives: all_natives });
202
+ *REGISTRY.lock().unwrap() = Some(Registry { _libs: libs, modules, natives: all_natives, v8_natives: all_v8_natives });
203
+
167
204
  }
168
205
 
169
206
  pub fn inject_external_extensions(scope: &mut v8::HandleScope, global: v8::Local<v8::Object>, t_obj: v8::Local<v8::Object>) {
@@ -171,9 +208,10 @@ pub fn inject_external_extensions(scope: &mut v8::HandleScope, global: v8::Local
171
208
  let invoke_key = v8_str(scope, "__titan_invoke_native");
172
209
  global.set(scope, invoke_key.into(), invoke_fn.into());
173
210
 
174
- let modules = if let Ok(guard) = REGISTRY.lock() {
175
- guard.as_ref().map(|r| r.modules.clone()).unwrap_or_default()
176
- } 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
+
177
215
 
178
216
  for module in modules {
179
217
  let mod_obj = v8::Object::new(scope);
@@ -187,6 +225,24 @@ pub fn inject_external_extensions(scope: &mut v8::HandleScope, global: v8::Local
187
225
  }
188
226
  }
189
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
+ }
190
246
  let mod_key = v8_str(scope, &module.name);
191
247
  t_obj.set(scope, mod_key.into(), mod_obj.into());
192
248
 
@@ -274,6 +330,20 @@ fn native_invoke_extension(scope: &mut v8::HandleScope, args: v8::FunctionCallba
274
330
  }
275
331
  }
276
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
+
277
347
  fn arg_from_v8(scope: &mut v8::HandleScope, val: v8::Local<v8::Value>, ty: &ParamType) -> serde_json::Value {
278
348
  match ty {
279
349
  ParamType::String => serde_json::Value::String(val.to_rust_string_lossy(scope)),