@ezetgalaxy/titan 26.10.1 → 26.10.3
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/index.js +4 -8
- package/package.json +3 -1
- package/templates/common/app/titan.d.ts +117 -72
- package/templates/extension/README.md +44 -83
- package/templates/extension/index.d.ts +27 -0
- package/templates/extension/index.js +1 -0
- package/templates/extension/package.json +2 -1
- package/templates/js/jsconfig.json +2 -1
- package/templates/js/server/src/extensions.rs +50 -11
- package/templates/js/titan/bundle.js +1 -1
- package/templates/js/titan/titan.js +4 -0
- package/templates/rust-js/jsconfig.json +2 -1
- package/templates/rust-js/server/src/extensions.rs +59 -11
- package/templates/rust-js/titan/bundle.js +1 -1
- package/templates/rust-js/titan/titan.js +4 -0
- package/templates/rust-ts/server/src/extensions.rs +59 -11
- package/templates/rust-ts/titan/bundle.js +1 -1
- package/templates/rust-ts/titan/titan.d.ts +14 -2
- package/templates/rust-ts/titan/titan.js +4 -0
- package/templates/ts/server/src/extensions.rs +50 -11
- package/templates/ts/titan/bundle.js +1 -1
- package/templates/ts/titan/titan.d.ts +14 -2
- package/templates/ts/titan/titan.js +1 -0
- package/titanpl-sdk/index.d.ts +5 -1
- package/titanpl-sdk/package.json +1 -1
- package/titanpl-sdk/templates/server/src/extensions.rs +50 -11
- package/titanpl-sdk/templates/.dockerignore +0 -3
- package/titanpl-sdk/templates/Dockerfile +0 -53
package/index.js
CHANGED
|
@@ -427,19 +427,15 @@ export function startProd() {
|
|
|
427
427
|
const isWin = process.platform === "win32";
|
|
428
428
|
const bin = isWin ? "titan-server.exe" : "titan-server";
|
|
429
429
|
const root = process.cwd();
|
|
430
|
+
const serverDir = path.join(root, "server");
|
|
430
431
|
|
|
431
|
-
const exe = path.join(
|
|
432
|
+
const exe = path.join(serverDir, "target", "release", bin);
|
|
432
433
|
|
|
433
434
|
if (fs.existsSync(exe)) {
|
|
434
|
-
execSync(`"${exe}"`, { stdio: "inherit" });
|
|
435
|
+
execSync(`"${exe}"`, { stdio: "inherit", cwd: serverDir });
|
|
435
436
|
} else {
|
|
436
437
|
// Fallback to pure node start if no rust binary
|
|
437
438
|
const appJs = path.join(root, "app", "app.js");
|
|
438
|
-
// Actually, typically we run the bundled/compiled app if we don't have rust server?
|
|
439
|
-
// But wait, the pure TS template runs `node .titan/app.js` in Docker.
|
|
440
|
-
// But locally `titan start` relies on `app/app.js` being compiled?
|
|
441
|
-
// In `buildProd` above we compiled to `app/app.js`.
|
|
442
|
-
// Let's check for `.titan/app.js` which is dev artifact? No, use the prod build artifact.
|
|
443
439
|
execSync(`node "${appJs}"`, { stdio: "inherit" });
|
|
444
440
|
}
|
|
445
441
|
}
|
|
@@ -663,7 +659,7 @@ export function runExtension() {
|
|
|
663
659
|
/* -------------------------------------------------------
|
|
664
660
|
* ROUTER
|
|
665
661
|
* ----------------------------------------------------- */
|
|
666
|
-
const isMainModule = process.argv[1] === fileURLToPath(import.meta.url);
|
|
662
|
+
const isMainModule = fs.realpathSync(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
667
663
|
|
|
668
664
|
if (isMainModule) {
|
|
669
665
|
const args = process.argv.slice(2);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ezetgalaxy/titan",
|
|
3
|
-
"version": "26.10.
|
|
3
|
+
"version": "26.10.3",
|
|
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",
|
|
@@ -42,6 +42,8 @@
|
|
|
42
42
|
"super-backend"
|
|
43
43
|
],
|
|
44
44
|
"scripts": {
|
|
45
|
+
"cli-helper": "node ./scripts/cli-helper.mjs",
|
|
46
|
+
"create-ext": "titan create ext",
|
|
45
47
|
"init": "rm -rf build && node index.js init build",
|
|
46
48
|
"build": "cd build && node ../index.js build",
|
|
47
49
|
"dev": "cd build && node ../index.js dev",
|
|
@@ -1,87 +1,132 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TITAN TYPE DEFINITIONS
|
|
3
|
-
* ----------------------
|
|
4
|
-
* These types are globally available in your Titan project.
|
|
5
|
-
*/
|
|
6
1
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
|
|
13
|
-
path: string;
|
|
14
|
-
headers: {
|
|
15
|
-
host?: string;
|
|
16
|
-
"content-type"?: string;
|
|
17
|
-
"user-agent"?: string;
|
|
18
|
-
authorization?: string;
|
|
19
|
-
[key: string]: string | undefined;
|
|
20
|
-
};
|
|
21
|
-
params: Record<string, string>;
|
|
22
|
-
query: Record<string, string>;
|
|
2
|
+
// -- Module Definitions (for imports from "titan") --
|
|
3
|
+
|
|
4
|
+
export interface RouteHandler {
|
|
5
|
+
reply(value: any): void;
|
|
6
|
+
action(name: string): void;
|
|
23
7
|
}
|
|
24
8
|
|
|
25
|
-
interface
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
*/
|
|
31
|
-
query(sql: string, params?: any[]): any[];
|
|
9
|
+
export interface TitanBuilder {
|
|
10
|
+
get(route: string): RouteHandler;
|
|
11
|
+
post(route: string): RouteHandler;
|
|
12
|
+
log(module: string, msg: string): void;
|
|
13
|
+
start(port?: number, msg?: string): Promise<void>;
|
|
32
14
|
}
|
|
33
15
|
|
|
16
|
+
// The default export from titan.js is the Builder
|
|
17
|
+
declare const builder: TitanBuilder;
|
|
18
|
+
export const Titan: TitanBuilder;
|
|
19
|
+
export default builder;
|
|
20
|
+
|
|
34
21
|
/**
|
|
35
22
|
* Define a Titan Action with type inference.
|
|
36
|
-
* @example
|
|
37
|
-
* export const hello = defineAction((req) => {
|
|
38
|
-
* return req.headers;
|
|
39
|
-
* });
|
|
40
23
|
*/
|
|
41
|
-
declare function defineAction<T>(actionFn: (req: TitanRequest) => T): (req: TitanRequest) => T;
|
|
24
|
+
export declare function defineAction<T>(actionFn: (req: TitanRequest) => T): (req: TitanRequest) => T;
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
// -- Global Definitions (Runtime Environment) --
|
|
28
|
+
|
|
29
|
+
declare global {
|
|
30
|
+
/**
|
|
31
|
+
* The Titan Request Object passed to actions.
|
|
32
|
+
*/
|
|
33
|
+
interface TitanRequest {
|
|
34
|
+
body: any;
|
|
35
|
+
method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
|
|
36
|
+
path: string;
|
|
37
|
+
headers: {
|
|
38
|
+
host?: string;
|
|
39
|
+
"content-type"?: string;
|
|
40
|
+
"user-agent"?: string;
|
|
41
|
+
authorization?: string;
|
|
42
|
+
[key: string]: string | undefined;
|
|
43
|
+
};
|
|
44
|
+
params: Record<string, string>;
|
|
45
|
+
query: Record<string, string>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface DbConnection {
|
|
49
|
+
/**
|
|
50
|
+
* Execute a SQL query.
|
|
51
|
+
* @param sql The SQL query string.
|
|
52
|
+
* @param params (Optional) Parameters for the query ($1, $2, etc).
|
|
53
|
+
*/
|
|
54
|
+
query(sql: string, params?: any[]): any[];
|
|
55
|
+
}
|
|
42
56
|
|
|
43
|
-
/**
|
|
44
|
-
* Titan Runtime Utilities
|
|
45
|
-
*/
|
|
46
|
-
declare const t: {
|
|
47
57
|
/**
|
|
48
|
-
*
|
|
58
|
+
* Global defineAction (available without import in runtime)
|
|
49
59
|
*/
|
|
50
|
-
|
|
60
|
+
function defineAction<T>(actionFn: (req: TitanRequest) => T): (req: TitanRequest) => T;
|
|
51
61
|
|
|
52
62
|
/**
|
|
53
|
-
*
|
|
54
|
-
*
|
|
63
|
+
* Global Request Object
|
|
64
|
+
* Available automatically in actions.
|
|
55
65
|
*/
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
fetch(url: string, options?: {
|
|
59
|
-
method?: "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
|
|
60
|
-
headers?: Record<string, string>;
|
|
61
|
-
body?: string | object;
|
|
62
|
-
}): {
|
|
63
|
-
ok: boolean;
|
|
64
|
-
status?: number;
|
|
65
|
-
body?: string;
|
|
66
|
-
error?: string;
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
jwt: {
|
|
70
|
-
sign(
|
|
71
|
-
payload: object,
|
|
72
|
-
secret: string,
|
|
73
|
-
options?: { expiresIn?: string | number }
|
|
74
|
-
): string;
|
|
75
|
-
verify(token: string, secret: string): any;
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
password: {
|
|
79
|
-
hash(password: string): string;
|
|
80
|
-
verify(password: string, hash: string): boolean;
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
db: {
|
|
84
|
-
connect(url: string): DbConnection;
|
|
85
|
-
};
|
|
86
|
-
};
|
|
66
|
+
var req: TitanRequest;
|
|
87
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Titan Runtime Utilities
|
|
70
|
+
* (Available globally in the runtime, e.g. inside actions)
|
|
71
|
+
*/
|
|
72
|
+
interface TitanRuntimeUtils {
|
|
73
|
+
/**
|
|
74
|
+
* Log messages to the server console with Titan formatting.
|
|
75
|
+
*/
|
|
76
|
+
log(...args: any[]): void;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Read a file contents as string.
|
|
80
|
+
* @param path Relative path to the file from project root.
|
|
81
|
+
*/
|
|
82
|
+
read(path: string): string;
|
|
83
|
+
|
|
84
|
+
fetch(url: string, options?: {
|
|
85
|
+
method?: "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
|
|
86
|
+
headers?: Record<string, string>;
|
|
87
|
+
body?: string | object;
|
|
88
|
+
}): {
|
|
89
|
+
ok: boolean;
|
|
90
|
+
status?: number;
|
|
91
|
+
body?: string;
|
|
92
|
+
error?: string;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
jwt: {
|
|
96
|
+
sign(
|
|
97
|
+
payload: object,
|
|
98
|
+
secret: string,
|
|
99
|
+
options?: { expiresIn?: string | number }
|
|
100
|
+
): string;
|
|
101
|
+
verify(token: string, secret: string): any;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
password: {
|
|
105
|
+
hash(password: string): string;
|
|
106
|
+
verify(password: string, hash: string): boolean;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
db: {
|
|
110
|
+
connect(url: string): DbConnection;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Titan Validator (Zod-compatible)
|
|
115
|
+
*/
|
|
116
|
+
valid: any;
|
|
117
|
+
|
|
118
|
+
// Allow extensions
|
|
119
|
+
[key: string]: any;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Titan Runtime Utilities
|
|
124
|
+
* (Available globally in the runtime, e.g. inside actions)
|
|
125
|
+
*/
|
|
126
|
+
const t: TitanRuntimeUtils;
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Titan Runtime Utilities (Alias for t)
|
|
130
|
+
*/
|
|
131
|
+
const Titan: TitanRuntimeUtils;
|
|
132
|
+
}
|
|
@@ -1,104 +1,65 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Titan Extension Template
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This template provides a starting point for building native extensions for Titan.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Directory Structure
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
- `index.js`: The JavaScript entry point for your extension. It runs within the Titan runtime.
|
|
8
|
+
- `index.d.ts`: TypeScript definitions for your extension. This ensures users get autocompletion when using your extension.
|
|
9
|
+
- `native/`: (Optional) Rust source code for native high-performance logic.
|
|
10
|
+
- `titan.json`: Configuration file defining your extension's native ABI (if using Rust).
|
|
8
11
|
|
|
9
|
-
##
|
|
12
|
+
## Type Definitions (`index.d.ts`)
|
|
10
13
|
|
|
11
|
-
|
|
12
|
-
- `titan.json`: Manifest file defining extension metadata and Native module mappings.
|
|
13
|
-
- `native/`: Directory for Rust source code.
|
|
14
|
-
- `src/lib.rs`: Your Native function implementations.
|
|
15
|
-
- `Cargo.toml`: Rust package and dependency configuration.
|
|
16
|
-
- `jsconfig.json`: Enables full IntelliSense for the Titan Runtime API.
|
|
14
|
+
The `index.d.ts` file is crucial for Developer Experience (DX). It allows Titan projects to "see" your extension's API on the global `t` object.
|
|
17
15
|
|
|
18
|
-
|
|
16
|
+
### How it works
|
|
19
17
|
|
|
20
|
-
|
|
18
|
+
Titan uses **Declaration Merging** to extend the global `Titan.Runtime` interface. When a user installs your extension, this file acts as a plugin to their TypeScript environment.
|
|
21
19
|
|
|
22
|
-
###
|
|
23
|
-
Get full type support in your IDE:
|
|
24
|
-
```bash
|
|
25
|
-
npm install
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
### 2. Build Native Module (Optional)
|
|
29
|
-
If your extension uses Rust, compile it to a dynamic library:
|
|
30
|
-
```bash
|
|
31
|
-
cd native
|
|
32
|
-
cargo build --release
|
|
33
|
-
cd ..
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
### 3. Test the Extension
|
|
37
|
-
Use the Titan SDK to run a local test harness:
|
|
38
|
-
```bash
|
|
39
|
-
titan run ext
|
|
40
|
-
```
|
|
41
|
-
*Tip: Visit `http://localhost:3000/test` after starting the runner to see your extension in action!*
|
|
20
|
+
### Customizing Types
|
|
42
21
|
|
|
43
|
-
|
|
22
|
+
Edit `index.d.ts` to match the API you expose in `index.js`.
|
|
44
23
|
|
|
45
|
-
|
|
24
|
+
**Example:**
|
|
46
25
|
|
|
47
|
-
|
|
48
|
-
Extensions interact with the global `t` object. It's best practice to namespace your extension:
|
|
26
|
+
If your `index.js` looks like this:
|
|
49
27
|
|
|
50
28
|
```javascript
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
29
|
+
// index.js
|
|
30
|
+
t.ext.my_cool_ext = {
|
|
31
|
+
greet: (name) => `Hello, ${name}!`,
|
|
32
|
+
compute: (x) => x * 2
|
|
56
33
|
};
|
|
57
34
|
```
|
|
58
35
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
36
|
+
Your `index.d.ts` should look like this:
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
// index.d.ts
|
|
40
|
+
declare global {
|
|
41
|
+
namespace Titan {
|
|
42
|
+
interface Runtime {
|
|
43
|
+
"my-cool-ext": {
|
|
44
|
+
/**
|
|
45
|
+
* Sends a greeting.
|
|
46
|
+
*/
|
|
47
|
+
greet(name: string): string;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Computes a value.
|
|
51
|
+
*/
|
|
52
|
+
compute(x: number): number;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
78
55
|
}
|
|
79
56
|
}
|
|
57
|
+
export { };
|
|
80
58
|
```
|
|
81
59
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
## 🧪 Testing with Titan SDK
|
|
85
|
-
|
|
86
|
-
The `titan run ext` command automates the testing workflow:
|
|
87
|
-
1. It builds your native code.
|
|
88
|
-
2. It sets up a temporary Titan project environment.
|
|
89
|
-
3. It links your extension into `node_modules`.
|
|
90
|
-
4. It starts the Titan Runtime at `http://localhost:3000`.
|
|
91
|
-
|
|
92
|
-
You can modify the test harness or add custom test cases by exploring the generated `.titan_test_run` directory (it is git-ignored).
|
|
93
|
-
|
|
94
|
-
---
|
|
95
|
-
|
|
96
|
-
## 📦 Deployment
|
|
97
|
-
To use your extension in a Titan project:
|
|
98
|
-
1. Publish your extension to npm or link it locally.
|
|
99
|
-
2. In your Titan project: `npm install my-extension`.
|
|
100
|
-
3. The Titan Runtime will automatically detect and load your extension if it contains a `titan.json`.
|
|
101
|
-
|
|
102
|
-
---
|
|
60
|
+
## Native Bindings (Rust)
|
|
103
61
|
|
|
104
|
-
|
|
62
|
+
If your extension requires native performance or system access, use the `native/` directory.
|
|
63
|
+
1. Define functions in `native/src/lib.rs`.
|
|
64
|
+
2. Map them in `titan.json`.
|
|
65
|
+
3. Call them from `index.js` using `Titan.native.invoke(...)` (or the helper provided in the template).
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Type definitions for {{name}}
|
|
2
|
+
// This file facilitates type inference when this extension is installed in a Titan project.
|
|
3
|
+
|
|
4
|
+
declare global {
|
|
5
|
+
namespace Titan {
|
|
6
|
+
interface Runtime {
|
|
7
|
+
/**
|
|
8
|
+
* {{name}} Extension
|
|
9
|
+
*/
|
|
10
|
+
"{{name}}": {
|
|
11
|
+
/**
|
|
12
|
+
* Example hello function
|
|
13
|
+
*/
|
|
14
|
+
hello(name: string): string;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Example calc function (native wrapper)
|
|
18
|
+
*/
|
|
19
|
+
calc(a: number, b: number): number;
|
|
20
|
+
|
|
21
|
+
// Add your extension methods here
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export { };
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
"version": "1.0.0",
|
|
4
4
|
"description": "A Titan Planet extension",
|
|
5
5
|
"main": "index.js",
|
|
6
|
+
"types": "index.d.ts",
|
|
6
7
|
"type": "commonjs",
|
|
7
8
|
"scripts": {
|
|
8
9
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
@@ -19,4 +20,4 @@
|
|
|
19
20
|
"esbuild": "^0.27.2",
|
|
20
21
|
"titanpl-sdk": "latest"
|
|
21
22
|
}
|
|
22
|
-
}
|
|
23
|
+
}
|
|
@@ -535,7 +535,13 @@ fn js_from_value<'a>(
|
|
|
535
535
|
val: serde_json::Value,
|
|
536
536
|
) -> v8::Local<'a, v8::Value> {
|
|
537
537
|
match ret_type {
|
|
538
|
-
ReturnType::String =>
|
|
538
|
+
ReturnType::String => {
|
|
539
|
+
let s = match val.as_str() {
|
|
540
|
+
Some(x) => x,
|
|
541
|
+
None => "",
|
|
542
|
+
};
|
|
543
|
+
v8::String::new(scope, s).unwrap().into()
|
|
544
|
+
},
|
|
539
545
|
ReturnType::F64 => v8::Number::new(scope, val.as_f64().unwrap_or(0.0)).into(),
|
|
540
546
|
ReturnType::Bool => v8::Boolean::new(scope, val.as_bool().unwrap_or(false)).into(),
|
|
541
547
|
ReturnType::Json => {
|
|
@@ -566,10 +572,32 @@ fn js_from_value<'a>(
|
|
|
566
572
|
macro_rules! dispatch_ret {
|
|
567
573
|
($ptr:expr, $ret:expr, ($($arg_ty:ty),*), ($($arg:expr),*)) => {
|
|
568
574
|
match $ret {
|
|
569
|
-
ReturnType::String => {
|
|
575
|
+
ReturnType::String => {
|
|
576
|
+
let f: extern "C" fn($($arg_ty),*) -> *mut std::os::raw::c_char;
|
|
577
|
+
f = std::mem::transmute($ptr);
|
|
578
|
+
let ptr = f($($arg),*);
|
|
579
|
+
if ptr.is_null() {
|
|
580
|
+
serde_json::Value::String(String::new())
|
|
581
|
+
} else {
|
|
582
|
+
let s = unsafe { std::ffi::CStr::from_ptr(ptr).to_string_lossy().into_owned() };
|
|
583
|
+
// We leak the pointer here because we don't have a shared allocator/free function.
|
|
584
|
+
// This prevents the double-free/heap corruption crash.
|
|
585
|
+
serde_json::Value::String(s)
|
|
586
|
+
}
|
|
587
|
+
},
|
|
570
588
|
ReturnType::F64 => { let f: extern "C" fn($($arg_ty),*) -> f64; f = std::mem::transmute($ptr); serde_json::json!(f($($arg),*)) },
|
|
571
589
|
ReturnType::Bool => { let f: extern "C" fn($($arg_ty),*) -> bool; f = std::mem::transmute($ptr); serde_json::json!(f($($arg),*)) },
|
|
572
|
-
ReturnType::Json => {
|
|
590
|
+
ReturnType::Json => {
|
|
591
|
+
let f: extern "C" fn($($arg_ty),*) -> *mut std::os::raw::c_char;
|
|
592
|
+
f = std::mem::transmute($ptr);
|
|
593
|
+
let ptr = f($($arg),*);
|
|
594
|
+
if ptr.is_null() {
|
|
595
|
+
serde_json::Value::Null
|
|
596
|
+
} else {
|
|
597
|
+
let s = unsafe { std::ffi::CStr::from_ptr(ptr).to_string_lossy().into_owned() };
|
|
598
|
+
serde_json::from_str(&s).unwrap_or(serde_json::Value::Null)
|
|
599
|
+
}
|
|
600
|
+
},
|
|
573
601
|
ReturnType::Buffer => { let f: extern "C" fn($($arg_ty),*) -> Vec<u8>; f = std::mem::transmute($ptr);
|
|
574
602
|
let v = f($($arg),*);
|
|
575
603
|
serde_json::Value::Array(v.into_iter().map(serde_json::Value::from).collect())
|
|
@@ -619,10 +647,18 @@ fn native_invoke_extension(scope: &mut v8::HandleScope, args: v8::FunctionCallba
|
|
|
619
647
|
1 => {
|
|
620
648
|
let v0 = vals.remove(0);
|
|
621
649
|
match sig.params[0] {
|
|
622
|
-
ParamType::String => {
|
|
650
|
+
ParamType::String => {
|
|
651
|
+
let s = v0.as_str().unwrap_or("").to_string();
|
|
652
|
+
let c = std::ffi::CString::new(s).unwrap();
|
|
653
|
+
dispatch_ret!(ptr, sig.ret, (*const std::os::raw::c_char), (c.as_ptr()))
|
|
654
|
+
},
|
|
623
655
|
ParamType::F64 => { let a0 = v0.as_f64().unwrap_or(0.0); dispatch_ret!(ptr, sig.ret, (f64), (a0)) },
|
|
624
656
|
ParamType::Bool => { let a0 = v0.as_bool().unwrap_or(false); dispatch_ret!(ptr, sig.ret, (bool), (a0)) },
|
|
625
|
-
ParamType::Json => {
|
|
657
|
+
ParamType::Json => {
|
|
658
|
+
let s = v0.to_string();
|
|
659
|
+
let c = std::ffi::CString::new(s).unwrap();
|
|
660
|
+
dispatch_ret!(ptr, sig.ret, (*const std::os::raw::c_char), (c.as_ptr()))
|
|
661
|
+
},
|
|
626
662
|
ParamType::Buffer => {
|
|
627
663
|
// Extract vec u8
|
|
628
664
|
let a0: Vec<u8> = if let Some(arr) = v0.as_array() {
|
|
@@ -637,14 +673,17 @@ fn native_invoke_extension(scope: &mut v8::HandleScope, args: v8::FunctionCallba
|
|
|
637
673
|
let v1 = vals.remove(0);
|
|
638
674
|
match (sig.params[0].clone(), sig.params[1].clone()) { // Clone to satisfy borrow checker if needed
|
|
639
675
|
(ParamType::String, ParamType::String) => {
|
|
640
|
-
let
|
|
641
|
-
let
|
|
642
|
-
|
|
676
|
+
let s0 = v0.as_str().unwrap_or("").to_string();
|
|
677
|
+
let c0 = std::ffi::CString::new(s0).unwrap();
|
|
678
|
+
let s1 = v1.as_str().unwrap_or("").to_string();
|
|
679
|
+
let c1 = std::ffi::CString::new(s1).unwrap();
|
|
680
|
+
dispatch_ret!(ptr, sig.ret, (*const std::os::raw::c_char, *const std::os::raw::c_char), (c0.as_ptr(), c1.as_ptr()))
|
|
643
681
|
},
|
|
644
682
|
(ParamType::String, ParamType::F64) => {
|
|
645
|
-
let
|
|
683
|
+
let s0 = v0.as_str().unwrap_or("").to_string();
|
|
684
|
+
let c0 = std::ffi::CString::new(s0).unwrap();
|
|
646
685
|
let a1 = v1.as_f64().unwrap_or(0.0);
|
|
647
|
-
dispatch_ret!(ptr, sig.ret, (
|
|
686
|
+
dispatch_ret!(ptr, sig.ret, (*const std::os::raw::c_char, f64), (c0.as_ptr(), a1))
|
|
648
687
|
},
|
|
649
688
|
// Add more combinations as needed.
|
|
650
689
|
_ => { println!("Unsupported 2-arg signature"); serde_json::Value::Null }
|
|
@@ -803,4 +842,4 @@ pub fn inject_extensions(scope: &mut v8::HandleScope, global: v8::Local<v8::Obje
|
|
|
803
842
|
|
|
804
843
|
let t_key = v8_str(scope, "t");
|
|
805
844
|
global.set(scope, t_key.into(), t_obj.into());
|
|
806
|
-
}
|
|
845
|
+
}
|
|
@@ -18,6 +18,15 @@ use std::sync::Mutex;
|
|
|
18
18
|
use std::collections::HashMap;
|
|
19
19
|
use std::fs;
|
|
20
20
|
|
|
21
|
+
// ----------------------------------------------------------------------------
|
|
22
|
+
// T MODULE (Rust API equivalent to JS t object)
|
|
23
|
+
// ----------------------------------------------------------------------------
|
|
24
|
+
pub mod t {
|
|
25
|
+
pub fn log(module: &str, msg: &str) {
|
|
26
|
+
println!("{} {}", crate::utils::blue("[Titan]"), crate::utils::gray(&format!("\x1b[90mlog({})\x1b[0m\x1b[97m: {}\x1b[0m", module, msg)));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
21
30
|
// ----------------------------------------------------------------------------
|
|
22
31
|
// GLOBAL REGISTRY
|
|
23
32
|
// ----------------------------------------------------------------------------
|
|
@@ -535,7 +544,13 @@ fn js_from_value<'a>(
|
|
|
535
544
|
val: serde_json::Value,
|
|
536
545
|
) -> v8::Local<'a, v8::Value> {
|
|
537
546
|
match ret_type {
|
|
538
|
-
ReturnType::String =>
|
|
547
|
+
ReturnType::String => {
|
|
548
|
+
let s = match val.as_str() {
|
|
549
|
+
Some(x) => x,
|
|
550
|
+
None => "",
|
|
551
|
+
};
|
|
552
|
+
v8::String::new(scope, s).unwrap().into()
|
|
553
|
+
},
|
|
539
554
|
ReturnType::F64 => v8::Number::new(scope, val.as_f64().unwrap_or(0.0)).into(),
|
|
540
555
|
ReturnType::Bool => v8::Boolean::new(scope, val.as_bool().unwrap_or(false)).into(),
|
|
541
556
|
ReturnType::Json => {
|
|
@@ -566,10 +581,32 @@ fn js_from_value<'a>(
|
|
|
566
581
|
macro_rules! dispatch_ret {
|
|
567
582
|
($ptr:expr, $ret:expr, ($($arg_ty:ty),*), ($($arg:expr),*)) => {
|
|
568
583
|
match $ret {
|
|
569
|
-
ReturnType::String => {
|
|
584
|
+
ReturnType::String => {
|
|
585
|
+
let f: extern "C" fn($($arg_ty),*) -> *mut std::os::raw::c_char;
|
|
586
|
+
f = std::mem::transmute($ptr);
|
|
587
|
+
let ptr = f($($arg),*);
|
|
588
|
+
if ptr.is_null() {
|
|
589
|
+
serde_json::Value::String(String::new())
|
|
590
|
+
} else {
|
|
591
|
+
let s = unsafe { std::ffi::CStr::from_ptr(ptr).to_string_lossy().into_owned() };
|
|
592
|
+
// We leak the pointer here because we don't have a shared allocator/free function.
|
|
593
|
+
// This prevents the double-free/heap corruption crash.
|
|
594
|
+
serde_json::Value::String(s)
|
|
595
|
+
}
|
|
596
|
+
},
|
|
570
597
|
ReturnType::F64 => { let f: extern "C" fn($($arg_ty),*) -> f64; f = std::mem::transmute($ptr); serde_json::json!(f($($arg),*)) },
|
|
571
598
|
ReturnType::Bool => { let f: extern "C" fn($($arg_ty),*) -> bool; f = std::mem::transmute($ptr); serde_json::json!(f($($arg),*)) },
|
|
572
|
-
ReturnType::Json => {
|
|
599
|
+
ReturnType::Json => {
|
|
600
|
+
let f: extern "C" fn($($arg_ty),*) -> *mut std::os::raw::c_char;
|
|
601
|
+
f = std::mem::transmute($ptr);
|
|
602
|
+
let ptr = f($($arg),*);
|
|
603
|
+
if ptr.is_null() {
|
|
604
|
+
serde_json::Value::Null
|
|
605
|
+
} else {
|
|
606
|
+
let s = unsafe { std::ffi::CStr::from_ptr(ptr).to_string_lossy().into_owned() };
|
|
607
|
+
serde_json::from_str(&s).unwrap_or(serde_json::Value::Null)
|
|
608
|
+
}
|
|
609
|
+
},
|
|
573
610
|
ReturnType::Buffer => { let f: extern "C" fn($($arg_ty),*) -> Vec<u8>; f = std::mem::transmute($ptr);
|
|
574
611
|
let v = f($($arg),*);
|
|
575
612
|
serde_json::Value::Array(v.into_iter().map(serde_json::Value::from).collect())
|
|
@@ -619,10 +656,18 @@ fn native_invoke_extension(scope: &mut v8::HandleScope, args: v8::FunctionCallba
|
|
|
619
656
|
1 => {
|
|
620
657
|
let v0 = vals.remove(0);
|
|
621
658
|
match sig.params[0] {
|
|
622
|
-
ParamType::String => {
|
|
659
|
+
ParamType::String => {
|
|
660
|
+
let s = v0.as_str().unwrap_or("").to_string();
|
|
661
|
+
let c = std::ffi::CString::new(s).unwrap();
|
|
662
|
+
dispatch_ret!(ptr, sig.ret, (*const std::os::raw::c_char), (c.as_ptr()))
|
|
663
|
+
},
|
|
623
664
|
ParamType::F64 => { let a0 = v0.as_f64().unwrap_or(0.0); dispatch_ret!(ptr, sig.ret, (f64), (a0)) },
|
|
624
665
|
ParamType::Bool => { let a0 = v0.as_bool().unwrap_or(false); dispatch_ret!(ptr, sig.ret, (bool), (a0)) },
|
|
625
|
-
ParamType::Json => {
|
|
666
|
+
ParamType::Json => {
|
|
667
|
+
let s = v0.to_string();
|
|
668
|
+
let c = std::ffi::CString::new(s).unwrap();
|
|
669
|
+
dispatch_ret!(ptr, sig.ret, (*const std::os::raw::c_char), (c.as_ptr()))
|
|
670
|
+
},
|
|
626
671
|
ParamType::Buffer => {
|
|
627
672
|
// Extract vec u8
|
|
628
673
|
let a0: Vec<u8> = if let Some(arr) = v0.as_array() {
|
|
@@ -637,14 +682,17 @@ fn native_invoke_extension(scope: &mut v8::HandleScope, args: v8::FunctionCallba
|
|
|
637
682
|
let v1 = vals.remove(0);
|
|
638
683
|
match (sig.params[0].clone(), sig.params[1].clone()) { // Clone to satisfy borrow checker if needed
|
|
639
684
|
(ParamType::String, ParamType::String) => {
|
|
640
|
-
let
|
|
641
|
-
let
|
|
642
|
-
|
|
685
|
+
let s0 = v0.as_str().unwrap_or("").to_string();
|
|
686
|
+
let c0 = std::ffi::CString::new(s0).unwrap();
|
|
687
|
+
let s1 = v1.as_str().unwrap_or("").to_string();
|
|
688
|
+
let c1 = std::ffi::CString::new(s1).unwrap();
|
|
689
|
+
dispatch_ret!(ptr, sig.ret, (*const std::os::raw::c_char, *const std::os::raw::c_char), (c0.as_ptr(), c1.as_ptr()))
|
|
643
690
|
},
|
|
644
691
|
(ParamType::String, ParamType::F64) => {
|
|
645
|
-
let
|
|
692
|
+
let s0 = v0.as_str().unwrap_or("").to_string();
|
|
693
|
+
let c0 = std::ffi::CString::new(s0).unwrap();
|
|
646
694
|
let a1 = v1.as_f64().unwrap_or(0.0);
|
|
647
|
-
dispatch_ret!(ptr, sig.ret, (
|
|
695
|
+
dispatch_ret!(ptr, sig.ret, (*const std::os::raw::c_char, f64), (c0.as_ptr(), a1))
|
|
648
696
|
},
|
|
649
697
|
// Add more combinations as needed.
|
|
650
698
|
_ => { println!("Unsupported 2-arg signature"); serde_json::Value::Null }
|
|
@@ -803,4 +851,4 @@ pub fn inject_extensions(scope: &mut v8::HandleScope, global: v8::Local<v8::Obje
|
|
|
803
851
|
|
|
804
852
|
let t_key = v8_str(scope, "t");
|
|
805
853
|
global.set(scope, t_key.into(), t_obj.into());
|
|
806
|
-
}
|
|
854
|
+
}
|
|
@@ -18,6 +18,15 @@ use std::sync::Mutex;
|
|
|
18
18
|
use std::collections::HashMap;
|
|
19
19
|
use std::fs;
|
|
20
20
|
|
|
21
|
+
// ----------------------------------------------------------------------------
|
|
22
|
+
// T MODULE (Rust API equivalent to JS t object)
|
|
23
|
+
// ----------------------------------------------------------------------------
|
|
24
|
+
pub mod t {
|
|
25
|
+
pub fn log(module: &str, msg: &str) {
|
|
26
|
+
println!("{} {}", crate::utils::blue("[Titan]"), crate::utils::gray(&format!("\x1b[90mlog({})\x1b[0m\x1b[97m: {}\x1b[0m", module, msg)));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
21
30
|
// ----------------------------------------------------------------------------
|
|
22
31
|
// GLOBAL REGISTRY
|
|
23
32
|
// ----------------------------------------------------------------------------
|
|
@@ -535,7 +544,13 @@ fn js_from_value<'a>(
|
|
|
535
544
|
val: serde_json::Value,
|
|
536
545
|
) -> v8::Local<'a, v8::Value> {
|
|
537
546
|
match ret_type {
|
|
538
|
-
ReturnType::String =>
|
|
547
|
+
ReturnType::String => {
|
|
548
|
+
let s = match val.as_str() {
|
|
549
|
+
Some(x) => x,
|
|
550
|
+
None => "",
|
|
551
|
+
};
|
|
552
|
+
v8::String::new(scope, s).unwrap().into()
|
|
553
|
+
},
|
|
539
554
|
ReturnType::F64 => v8::Number::new(scope, val.as_f64().unwrap_or(0.0)).into(),
|
|
540
555
|
ReturnType::Bool => v8::Boolean::new(scope, val.as_bool().unwrap_or(false)).into(),
|
|
541
556
|
ReturnType::Json => {
|
|
@@ -566,10 +581,32 @@ fn js_from_value<'a>(
|
|
|
566
581
|
macro_rules! dispatch_ret {
|
|
567
582
|
($ptr:expr, $ret:expr, ($($arg_ty:ty),*), ($($arg:expr),*)) => {
|
|
568
583
|
match $ret {
|
|
569
|
-
ReturnType::String => {
|
|
584
|
+
ReturnType::String => {
|
|
585
|
+
let f: extern "C" fn($($arg_ty),*) -> *mut std::os::raw::c_char;
|
|
586
|
+
f = std::mem::transmute($ptr);
|
|
587
|
+
let ptr = f($($arg),*);
|
|
588
|
+
if ptr.is_null() {
|
|
589
|
+
serde_json::Value::String(String::new())
|
|
590
|
+
} else {
|
|
591
|
+
let s = unsafe { std::ffi::CStr::from_ptr(ptr).to_string_lossy().into_owned() };
|
|
592
|
+
// We leak the pointer here because we don't have a shared allocator/free function.
|
|
593
|
+
// This prevents the double-free/heap corruption crash.
|
|
594
|
+
serde_json::Value::String(s)
|
|
595
|
+
}
|
|
596
|
+
},
|
|
570
597
|
ReturnType::F64 => { let f: extern "C" fn($($arg_ty),*) -> f64; f = std::mem::transmute($ptr); serde_json::json!(f($($arg),*)) },
|
|
571
598
|
ReturnType::Bool => { let f: extern "C" fn($($arg_ty),*) -> bool; f = std::mem::transmute($ptr); serde_json::json!(f($($arg),*)) },
|
|
572
|
-
ReturnType::Json => {
|
|
599
|
+
ReturnType::Json => {
|
|
600
|
+
let f: extern "C" fn($($arg_ty),*) -> *mut std::os::raw::c_char;
|
|
601
|
+
f = std::mem::transmute($ptr);
|
|
602
|
+
let ptr = f($($arg),*);
|
|
603
|
+
if ptr.is_null() {
|
|
604
|
+
serde_json::Value::Null
|
|
605
|
+
} else {
|
|
606
|
+
let s = unsafe { std::ffi::CStr::from_ptr(ptr).to_string_lossy().into_owned() };
|
|
607
|
+
serde_json::from_str(&s).unwrap_or(serde_json::Value::Null)
|
|
608
|
+
}
|
|
609
|
+
},
|
|
573
610
|
ReturnType::Buffer => { let f: extern "C" fn($($arg_ty),*) -> Vec<u8>; f = std::mem::transmute($ptr);
|
|
574
611
|
let v = f($($arg),*);
|
|
575
612
|
serde_json::Value::Array(v.into_iter().map(serde_json::Value::from).collect())
|
|
@@ -619,10 +656,18 @@ fn native_invoke_extension(scope: &mut v8::HandleScope, args: v8::FunctionCallba
|
|
|
619
656
|
1 => {
|
|
620
657
|
let v0 = vals.remove(0);
|
|
621
658
|
match sig.params[0] {
|
|
622
|
-
ParamType::String => {
|
|
659
|
+
ParamType::String => {
|
|
660
|
+
let s = v0.as_str().unwrap_or("").to_string();
|
|
661
|
+
let c = std::ffi::CString::new(s).unwrap();
|
|
662
|
+
dispatch_ret!(ptr, sig.ret, (*const std::os::raw::c_char), (c.as_ptr()))
|
|
663
|
+
},
|
|
623
664
|
ParamType::F64 => { let a0 = v0.as_f64().unwrap_or(0.0); dispatch_ret!(ptr, sig.ret, (f64), (a0)) },
|
|
624
665
|
ParamType::Bool => { let a0 = v0.as_bool().unwrap_or(false); dispatch_ret!(ptr, sig.ret, (bool), (a0)) },
|
|
625
|
-
ParamType::Json => {
|
|
666
|
+
ParamType::Json => {
|
|
667
|
+
let s = v0.to_string();
|
|
668
|
+
let c = std::ffi::CString::new(s).unwrap();
|
|
669
|
+
dispatch_ret!(ptr, sig.ret, (*const std::os::raw::c_char), (c.as_ptr()))
|
|
670
|
+
},
|
|
626
671
|
ParamType::Buffer => {
|
|
627
672
|
// Extract vec u8
|
|
628
673
|
let a0: Vec<u8> = if let Some(arr) = v0.as_array() {
|
|
@@ -637,14 +682,17 @@ fn native_invoke_extension(scope: &mut v8::HandleScope, args: v8::FunctionCallba
|
|
|
637
682
|
let v1 = vals.remove(0);
|
|
638
683
|
match (sig.params[0].clone(), sig.params[1].clone()) { // Clone to satisfy borrow checker if needed
|
|
639
684
|
(ParamType::String, ParamType::String) => {
|
|
640
|
-
let
|
|
641
|
-
let
|
|
642
|
-
|
|
685
|
+
let s0 = v0.as_str().unwrap_or("").to_string();
|
|
686
|
+
let c0 = std::ffi::CString::new(s0).unwrap();
|
|
687
|
+
let s1 = v1.as_str().unwrap_or("").to_string();
|
|
688
|
+
let c1 = std::ffi::CString::new(s1).unwrap();
|
|
689
|
+
dispatch_ret!(ptr, sig.ret, (*const std::os::raw::c_char, *const std::os::raw::c_char), (c0.as_ptr(), c1.as_ptr()))
|
|
643
690
|
},
|
|
644
691
|
(ParamType::String, ParamType::F64) => {
|
|
645
|
-
let
|
|
692
|
+
let s0 = v0.as_str().unwrap_or("").to_string();
|
|
693
|
+
let c0 = std::ffi::CString::new(s0).unwrap();
|
|
646
694
|
let a1 = v1.as_f64().unwrap_or(0.0);
|
|
647
|
-
dispatch_ret!(ptr, sig.ret, (
|
|
695
|
+
dispatch_ret!(ptr, sig.ret, (*const std::os::raw::c_char, f64), (c0.as_ptr(), a1))
|
|
648
696
|
},
|
|
649
697
|
// Add more combinations as needed.
|
|
650
698
|
_ => { println!("Unsupported 2-arg signature"); serde_json::Value::Null }
|
|
@@ -803,4 +851,4 @@ pub fn inject_extensions(scope: &mut v8::HandleScope, global: v8::Local<v8::Obje
|
|
|
803
851
|
|
|
804
852
|
let t_key = v8_str(scope, "t");
|
|
805
853
|
global.set(scope, t_key.into(), t_obj.into());
|
|
806
|
-
}
|
|
854
|
+
}
|
|
@@ -15,6 +15,7 @@ export interface TitanBuilder {
|
|
|
15
15
|
|
|
16
16
|
// The default export from titan.js is the Builder
|
|
17
17
|
declare const builder: TitanBuilder;
|
|
18
|
+
export const Titan: TitanBuilder;
|
|
18
19
|
export default builder;
|
|
19
20
|
|
|
20
21
|
/**
|
|
@@ -68,7 +69,7 @@ declare global {
|
|
|
68
69
|
* Titan Runtime Utilities
|
|
69
70
|
* (Available globally in the runtime, e.g. inside actions)
|
|
70
71
|
*/
|
|
71
|
-
|
|
72
|
+
interface TitanRuntimeUtils {
|
|
72
73
|
/**
|
|
73
74
|
* Log messages to the server console with Titan formatting.
|
|
74
75
|
*/
|
|
@@ -113,5 +114,16 @@ declare global {
|
|
|
113
114
|
* Titan Validator (Zod-compatible)
|
|
114
115
|
*/
|
|
115
116
|
valid: any;
|
|
116
|
-
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Titan Runtime Utilities
|
|
121
|
+
* (Available globally in the runtime, e.g. inside actions)
|
|
122
|
+
*/
|
|
123
|
+
const t: TitanRuntimeUtils;
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Titan Runtime Utilities (Alias for t)
|
|
127
|
+
*/
|
|
128
|
+
const Titan: TitanRuntimeUtils;
|
|
117
129
|
}
|
|
@@ -535,7 +535,13 @@ fn js_from_value<'a>(
|
|
|
535
535
|
val: serde_json::Value,
|
|
536
536
|
) -> v8::Local<'a, v8::Value> {
|
|
537
537
|
match ret_type {
|
|
538
|
-
ReturnType::String =>
|
|
538
|
+
ReturnType::String => {
|
|
539
|
+
let s = match val.as_str() {
|
|
540
|
+
Some(x) => x,
|
|
541
|
+
None => "",
|
|
542
|
+
};
|
|
543
|
+
v8::String::new(scope, s).unwrap().into()
|
|
544
|
+
},
|
|
539
545
|
ReturnType::F64 => v8::Number::new(scope, val.as_f64().unwrap_or(0.0)).into(),
|
|
540
546
|
ReturnType::Bool => v8::Boolean::new(scope, val.as_bool().unwrap_or(false)).into(),
|
|
541
547
|
ReturnType::Json => {
|
|
@@ -566,10 +572,32 @@ fn js_from_value<'a>(
|
|
|
566
572
|
macro_rules! dispatch_ret {
|
|
567
573
|
($ptr:expr, $ret:expr, ($($arg_ty:ty),*), ($($arg:expr),*)) => {
|
|
568
574
|
match $ret {
|
|
569
|
-
ReturnType::String => {
|
|
575
|
+
ReturnType::String => {
|
|
576
|
+
let f: extern "C" fn($($arg_ty),*) -> *mut std::os::raw::c_char;
|
|
577
|
+
f = std::mem::transmute($ptr);
|
|
578
|
+
let ptr = f($($arg),*);
|
|
579
|
+
if ptr.is_null() {
|
|
580
|
+
serde_json::Value::String(String::new())
|
|
581
|
+
} else {
|
|
582
|
+
let s = unsafe { std::ffi::CStr::from_ptr(ptr).to_string_lossy().into_owned() };
|
|
583
|
+
// We leak the pointer here because we don't have a shared allocator/free function.
|
|
584
|
+
// This prevents the double-free/heap corruption crash.
|
|
585
|
+
serde_json::Value::String(s)
|
|
586
|
+
}
|
|
587
|
+
},
|
|
570
588
|
ReturnType::F64 => { let f: extern "C" fn($($arg_ty),*) -> f64; f = std::mem::transmute($ptr); serde_json::json!(f($($arg),*)) },
|
|
571
589
|
ReturnType::Bool => { let f: extern "C" fn($($arg_ty),*) -> bool; f = std::mem::transmute($ptr); serde_json::json!(f($($arg),*)) },
|
|
572
|
-
ReturnType::Json => {
|
|
590
|
+
ReturnType::Json => {
|
|
591
|
+
let f: extern "C" fn($($arg_ty),*) -> *mut std::os::raw::c_char;
|
|
592
|
+
f = std::mem::transmute($ptr);
|
|
593
|
+
let ptr = f($($arg),*);
|
|
594
|
+
if ptr.is_null() {
|
|
595
|
+
serde_json::Value::Null
|
|
596
|
+
} else {
|
|
597
|
+
let s = unsafe { std::ffi::CStr::from_ptr(ptr).to_string_lossy().into_owned() };
|
|
598
|
+
serde_json::from_str(&s).unwrap_or(serde_json::Value::Null)
|
|
599
|
+
}
|
|
600
|
+
},
|
|
573
601
|
ReturnType::Buffer => { let f: extern "C" fn($($arg_ty),*) -> Vec<u8>; f = std::mem::transmute($ptr);
|
|
574
602
|
let v = f($($arg),*);
|
|
575
603
|
serde_json::Value::Array(v.into_iter().map(serde_json::Value::from).collect())
|
|
@@ -619,10 +647,18 @@ fn native_invoke_extension(scope: &mut v8::HandleScope, args: v8::FunctionCallba
|
|
|
619
647
|
1 => {
|
|
620
648
|
let v0 = vals.remove(0);
|
|
621
649
|
match sig.params[0] {
|
|
622
|
-
ParamType::String => {
|
|
650
|
+
ParamType::String => {
|
|
651
|
+
let s = v0.as_str().unwrap_or("").to_string();
|
|
652
|
+
let c = std::ffi::CString::new(s).unwrap();
|
|
653
|
+
dispatch_ret!(ptr, sig.ret, (*const std::os::raw::c_char), (c.as_ptr()))
|
|
654
|
+
},
|
|
623
655
|
ParamType::F64 => { let a0 = v0.as_f64().unwrap_or(0.0); dispatch_ret!(ptr, sig.ret, (f64), (a0)) },
|
|
624
656
|
ParamType::Bool => { let a0 = v0.as_bool().unwrap_or(false); dispatch_ret!(ptr, sig.ret, (bool), (a0)) },
|
|
625
|
-
ParamType::Json => {
|
|
657
|
+
ParamType::Json => {
|
|
658
|
+
let s = v0.to_string();
|
|
659
|
+
let c = std::ffi::CString::new(s).unwrap();
|
|
660
|
+
dispatch_ret!(ptr, sig.ret, (*const std::os::raw::c_char), (c.as_ptr()))
|
|
661
|
+
},
|
|
626
662
|
ParamType::Buffer => {
|
|
627
663
|
// Extract vec u8
|
|
628
664
|
let a0: Vec<u8> = if let Some(arr) = v0.as_array() {
|
|
@@ -637,14 +673,17 @@ fn native_invoke_extension(scope: &mut v8::HandleScope, args: v8::FunctionCallba
|
|
|
637
673
|
let v1 = vals.remove(0);
|
|
638
674
|
match (sig.params[0].clone(), sig.params[1].clone()) { // Clone to satisfy borrow checker if needed
|
|
639
675
|
(ParamType::String, ParamType::String) => {
|
|
640
|
-
let
|
|
641
|
-
let
|
|
642
|
-
|
|
676
|
+
let s0 = v0.as_str().unwrap_or("").to_string();
|
|
677
|
+
let c0 = std::ffi::CString::new(s0).unwrap();
|
|
678
|
+
let s1 = v1.as_str().unwrap_or("").to_string();
|
|
679
|
+
let c1 = std::ffi::CString::new(s1).unwrap();
|
|
680
|
+
dispatch_ret!(ptr, sig.ret, (*const std::os::raw::c_char, *const std::os::raw::c_char), (c0.as_ptr(), c1.as_ptr()))
|
|
643
681
|
},
|
|
644
682
|
(ParamType::String, ParamType::F64) => {
|
|
645
|
-
let
|
|
683
|
+
let s0 = v0.as_str().unwrap_or("").to_string();
|
|
684
|
+
let c0 = std::ffi::CString::new(s0).unwrap();
|
|
646
685
|
let a1 = v1.as_f64().unwrap_or(0.0);
|
|
647
|
-
dispatch_ret!(ptr, sig.ret, (
|
|
686
|
+
dispatch_ret!(ptr, sig.ret, (*const std::os::raw::c_char, f64), (c0.as_ptr(), a1))
|
|
648
687
|
},
|
|
649
688
|
// Add more combinations as needed.
|
|
650
689
|
_ => { println!("Unsupported 2-arg signature"); serde_json::Value::Null }
|
|
@@ -803,4 +842,4 @@ pub fn inject_extensions(scope: &mut v8::HandleScope, global: v8::Local<v8::Obje
|
|
|
803
842
|
|
|
804
843
|
let t_key = v8_str(scope, "t");
|
|
805
844
|
global.set(scope, t_key.into(), t_obj.into());
|
|
806
|
-
}
|
|
845
|
+
}
|
|
@@ -15,6 +15,7 @@ export interface TitanBuilder {
|
|
|
15
15
|
|
|
16
16
|
// The default export from titan.js is the Builder
|
|
17
17
|
declare const builder: TitanBuilder;
|
|
18
|
+
export const Titan: TitanBuilder;
|
|
18
19
|
export default builder;
|
|
19
20
|
|
|
20
21
|
/**
|
|
@@ -68,7 +69,7 @@ declare global {
|
|
|
68
69
|
* Titan Runtime Utilities
|
|
69
70
|
* (Available globally in the runtime, e.g. inside actions)
|
|
70
71
|
*/
|
|
71
|
-
|
|
72
|
+
interface TitanRuntimeUtils {
|
|
72
73
|
/**
|
|
73
74
|
* Log messages to the server console with Titan formatting.
|
|
74
75
|
*/
|
|
@@ -113,5 +114,16 @@ declare global {
|
|
|
113
114
|
* Titan Validator (Zod-compatible)
|
|
114
115
|
*/
|
|
115
116
|
valid: any;
|
|
116
|
-
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Titan Runtime Utilities
|
|
121
|
+
* (Available globally in the runtime, e.g. inside actions)
|
|
122
|
+
*/
|
|
123
|
+
const t: TitanRuntimeUtils;
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Titan Runtime Utilities (Alias for t)
|
|
127
|
+
*/
|
|
128
|
+
const Titan: TitanRuntimeUtils;
|
|
117
129
|
}
|
package/titanpl-sdk/index.d.ts
CHANGED
|
@@ -5,6 +5,10 @@ declare global {
|
|
|
5
5
|
* Titan Runtime Global Object
|
|
6
6
|
*/
|
|
7
7
|
const t: Titan.Runtime;
|
|
8
|
+
/**
|
|
9
|
+
* Titan Runtime Global Object
|
|
10
|
+
*/
|
|
11
|
+
const Titan: Titan.Runtime;
|
|
8
12
|
}
|
|
9
13
|
|
|
10
14
|
export namespace Titan {
|
|
@@ -18,7 +22,7 @@ export namespace Titan {
|
|
|
18
22
|
* Read file content
|
|
19
23
|
*/
|
|
20
24
|
read(path: string): string;
|
|
21
|
-
|
|
25
|
+
|
|
22
26
|
/**
|
|
23
27
|
* Fetch API wrapper
|
|
24
28
|
*/
|
package/titanpl-sdk/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "titanpl-sdk",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.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",
|
|
@@ -535,7 +535,13 @@ fn js_from_value<'a>(
|
|
|
535
535
|
val: serde_json::Value,
|
|
536
536
|
) -> v8::Local<'a, v8::Value> {
|
|
537
537
|
match ret_type {
|
|
538
|
-
ReturnType::String =>
|
|
538
|
+
ReturnType::String => {
|
|
539
|
+
let s = match val.as_str() {
|
|
540
|
+
Some(x) => x,
|
|
541
|
+
None => "",
|
|
542
|
+
};
|
|
543
|
+
v8::String::new(scope, s).unwrap().into()
|
|
544
|
+
},
|
|
539
545
|
ReturnType::F64 => v8::Number::new(scope, val.as_f64().unwrap_or(0.0)).into(),
|
|
540
546
|
ReturnType::Bool => v8::Boolean::new(scope, val.as_bool().unwrap_or(false)).into(),
|
|
541
547
|
ReturnType::Json => {
|
|
@@ -566,10 +572,32 @@ fn js_from_value<'a>(
|
|
|
566
572
|
macro_rules! dispatch_ret {
|
|
567
573
|
($ptr:expr, $ret:expr, ($($arg_ty:ty),*), ($($arg:expr),*)) => {
|
|
568
574
|
match $ret {
|
|
569
|
-
ReturnType::String => {
|
|
575
|
+
ReturnType::String => {
|
|
576
|
+
let f: extern "C" fn($($arg_ty),*) -> *mut std::os::raw::c_char;
|
|
577
|
+
f = std::mem::transmute($ptr);
|
|
578
|
+
let ptr = f($($arg),*);
|
|
579
|
+
if ptr.is_null() {
|
|
580
|
+
serde_json::Value::String(String::new())
|
|
581
|
+
} else {
|
|
582
|
+
let s = unsafe { std::ffi::CStr::from_ptr(ptr).to_string_lossy().into_owned() };
|
|
583
|
+
// We leak the pointer here because we don't have a shared allocator/free function.
|
|
584
|
+
// This prevents the double-free/heap corruption crash.
|
|
585
|
+
serde_json::Value::String(s)
|
|
586
|
+
}
|
|
587
|
+
},
|
|
570
588
|
ReturnType::F64 => { let f: extern "C" fn($($arg_ty),*) -> f64; f = std::mem::transmute($ptr); serde_json::json!(f($($arg),*)) },
|
|
571
589
|
ReturnType::Bool => { let f: extern "C" fn($($arg_ty),*) -> bool; f = std::mem::transmute($ptr); serde_json::json!(f($($arg),*)) },
|
|
572
|
-
ReturnType::Json => {
|
|
590
|
+
ReturnType::Json => {
|
|
591
|
+
let f: extern "C" fn($($arg_ty),*) -> *mut std::os::raw::c_char;
|
|
592
|
+
f = std::mem::transmute($ptr);
|
|
593
|
+
let ptr = f($($arg),*);
|
|
594
|
+
if ptr.is_null() {
|
|
595
|
+
serde_json::Value::Null
|
|
596
|
+
} else {
|
|
597
|
+
let s = unsafe { std::ffi::CStr::from_ptr(ptr).to_string_lossy().into_owned() };
|
|
598
|
+
serde_json::from_str(&s).unwrap_or(serde_json::Value::Null)
|
|
599
|
+
}
|
|
600
|
+
},
|
|
573
601
|
ReturnType::Buffer => { let f: extern "C" fn($($arg_ty),*) -> Vec<u8>; f = std::mem::transmute($ptr);
|
|
574
602
|
let v = f($($arg),*);
|
|
575
603
|
serde_json::Value::Array(v.into_iter().map(serde_json::Value::from).collect())
|
|
@@ -619,10 +647,18 @@ fn native_invoke_extension(scope: &mut v8::HandleScope, args: v8::FunctionCallba
|
|
|
619
647
|
1 => {
|
|
620
648
|
let v0 = vals.remove(0);
|
|
621
649
|
match sig.params[0] {
|
|
622
|
-
ParamType::String => {
|
|
650
|
+
ParamType::String => {
|
|
651
|
+
let s = v0.as_str().unwrap_or("").to_string();
|
|
652
|
+
let c = std::ffi::CString::new(s).unwrap();
|
|
653
|
+
dispatch_ret!(ptr, sig.ret, (*const std::os::raw::c_char), (c.as_ptr()))
|
|
654
|
+
},
|
|
623
655
|
ParamType::F64 => { let a0 = v0.as_f64().unwrap_or(0.0); dispatch_ret!(ptr, sig.ret, (f64), (a0)) },
|
|
624
656
|
ParamType::Bool => { let a0 = v0.as_bool().unwrap_or(false); dispatch_ret!(ptr, sig.ret, (bool), (a0)) },
|
|
625
|
-
ParamType::Json => {
|
|
657
|
+
ParamType::Json => {
|
|
658
|
+
let s = v0.to_string();
|
|
659
|
+
let c = std::ffi::CString::new(s).unwrap();
|
|
660
|
+
dispatch_ret!(ptr, sig.ret, (*const std::os::raw::c_char), (c.as_ptr()))
|
|
661
|
+
},
|
|
626
662
|
ParamType::Buffer => {
|
|
627
663
|
// Extract vec u8
|
|
628
664
|
let a0: Vec<u8> = if let Some(arr) = v0.as_array() {
|
|
@@ -637,14 +673,17 @@ fn native_invoke_extension(scope: &mut v8::HandleScope, args: v8::FunctionCallba
|
|
|
637
673
|
let v1 = vals.remove(0);
|
|
638
674
|
match (sig.params[0].clone(), sig.params[1].clone()) { // Clone to satisfy borrow checker if needed
|
|
639
675
|
(ParamType::String, ParamType::String) => {
|
|
640
|
-
let
|
|
641
|
-
let
|
|
642
|
-
|
|
676
|
+
let s0 = v0.as_str().unwrap_or("").to_string();
|
|
677
|
+
let c0 = std::ffi::CString::new(s0).unwrap();
|
|
678
|
+
let s1 = v1.as_str().unwrap_or("").to_string();
|
|
679
|
+
let c1 = std::ffi::CString::new(s1).unwrap();
|
|
680
|
+
dispatch_ret!(ptr, sig.ret, (*const std::os::raw::c_char, *const std::os::raw::c_char), (c0.as_ptr(), c1.as_ptr()))
|
|
643
681
|
},
|
|
644
682
|
(ParamType::String, ParamType::F64) => {
|
|
645
|
-
let
|
|
683
|
+
let s0 = v0.as_str().unwrap_or("").to_string();
|
|
684
|
+
let c0 = std::ffi::CString::new(s0).unwrap();
|
|
646
685
|
let a1 = v1.as_f64().unwrap_or(0.0);
|
|
647
|
-
dispatch_ret!(ptr, sig.ret, (
|
|
686
|
+
dispatch_ret!(ptr, sig.ret, (*const std::os::raw::c_char, f64), (c0.as_ptr(), a1))
|
|
648
687
|
},
|
|
649
688
|
// Add more combinations as needed.
|
|
650
689
|
_ => { println!("Unsupported 2-arg signature"); serde_json::Value::Null }
|
|
@@ -803,4 +842,4 @@ pub fn inject_extensions(scope: &mut v8::HandleScope, global: v8::Local<v8::Obje
|
|
|
803
842
|
|
|
804
843
|
let t_key = v8_str(scope, "t");
|
|
805
844
|
global.set(scope, t_key.into(), t_obj.into());
|
|
806
|
-
}
|
|
845
|
+
}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
# ================================================================
|
|
2
|
-
# STAGE 1 — Build Titan (JS → Rust)
|
|
3
|
-
# ================================================================
|
|
4
|
-
FROM rust:1.91.1 AS builder
|
|
5
|
-
|
|
6
|
-
# Install Node for Titan CLI + bundler
|
|
7
|
-
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
|
|
8
|
-
&& apt-get install -y nodejs
|
|
9
|
-
|
|
10
|
-
# Install Titan CLI (latest)
|
|
11
|
-
RUN npm install -g @ezetgalaxy/titan@latest
|
|
12
|
-
|
|
13
|
-
WORKDIR /app
|
|
14
|
-
|
|
15
|
-
# Copy project files
|
|
16
|
-
COPY . .
|
|
17
|
-
|
|
18
|
-
# Install JS dependencies (needed for Titan DSL + bundler)
|
|
19
|
-
RUN npm install
|
|
20
|
-
|
|
21
|
-
# Build Titan metadata + bundle JS actions
|
|
22
|
-
RUN titan build
|
|
23
|
-
|
|
24
|
-
# Build Rust binary
|
|
25
|
-
RUN cd server && cargo build --release
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
# ================================================================
|
|
30
|
-
# STAGE 2 — Runtime Image (Lightweight)
|
|
31
|
-
# ================================================================
|
|
32
|
-
FROM debian:stable-slim
|
|
33
|
-
|
|
34
|
-
WORKDIR /app
|
|
35
|
-
|
|
36
|
-
# Copy Rust binary from builder stage
|
|
37
|
-
COPY --from=builder /app/server/target/release/server ./titan-server
|
|
38
|
-
|
|
39
|
-
# Copy Titan routing metadata
|
|
40
|
-
COPY --from=builder /app/server/routes.json ./routes.json
|
|
41
|
-
COPY --from=builder /app/server/action_map.json ./action_map.json
|
|
42
|
-
|
|
43
|
-
# Copy Titan JS bundles
|
|
44
|
-
RUN mkdir -p /app/actions
|
|
45
|
-
COPY --from=builder /app/server/actions /app/actions
|
|
46
|
-
|
|
47
|
-
COPY --from=builder /app/db /app/assets
|
|
48
|
-
|
|
49
|
-
# Expose Titan port
|
|
50
|
-
EXPOSE 3000
|
|
51
|
-
|
|
52
|
-
# Start Titan
|
|
53
|
-
CMD ["./titan-server"]
|