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 +16 -16
- package/package.json +1 -1
- package/templates/server/src/extensions/external.rs +80 -10
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
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
-
|
|
209
|
-
|
|
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
|
-
|
|
216
|
+
t.log("✓ Result:", res);
|
|
217
217
|
} catch(e) {
|
|
218
|
-
|
|
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
|
-
|
|
227
|
+
t.log("✓ Result:", res);
|
|
228
228
|
} catch(e) {
|
|
229
|
-
|
|
229
|
+
t.log("✗ Error:", e.message);
|
|
230
230
|
}
|
|
231
231
|
}
|
|
232
232
|
}
|
|
233
233
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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.
|
|
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 {
|
|
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)),
|