@titanpl/cli 2.0.2 → 2.0.4

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.
Files changed (46) hide show
  1. package/package.json +5 -5
  2. package/src/commands/init.js +23 -2
  3. package/src/engine.js +18 -1
  4. package/templates/common/Dockerfile +66 -0
  5. package/templates/common/_dockerignore +35 -0
  6. package/templates/common/_gitignore +33 -0
  7. package/templates/common/app/t.native.d.ts +2043 -0
  8. package/templates/common/app/t.native.js +39 -0
  9. package/templates/extension/README.md +69 -0
  10. package/templates/extension/index.d.ts +27 -0
  11. package/templates/extension/index.js +17 -0
  12. package/templates/extension/jsconfig.json +14 -0
  13. package/templates/extension/native/Cargo.toml +9 -0
  14. package/templates/extension/native/src/lib.rs +5 -0
  15. package/templates/extension/package-lock.json +522 -0
  16. package/templates/extension/package.json +26 -0
  17. package/templates/extension/titan.json +18 -0
  18. package/templates/js/app/actions/getuser.js +9 -0
  19. package/templates/js/app/app.js +7 -0
  20. package/templates/js/eslint.config.js +5 -0
  21. package/templates/js/jsconfig.json +27 -0
  22. package/templates/js/package.json +28 -0
  23. package/templates/rust-js/app/actions/getuser.js +9 -0
  24. package/templates/rust-js/app/actions/rust_hello.rs +14 -0
  25. package/templates/rust-js/app/app.js +9 -0
  26. package/templates/rust-js/eslint.config.js +5 -0
  27. package/templates/rust-js/jsconfig.json +27 -0
  28. package/templates/rust-js/package.json +27 -0
  29. package/templates/rust-js/titan/bundle.js +157 -0
  30. package/templates/rust-js/titan/dev.js +323 -0
  31. package/templates/rust-js/titan/titan.js +126 -0
  32. package/templates/rust-ts/app/actions/getuser.ts +9 -0
  33. package/templates/rust-ts/app/actions/rust_hello.rs +14 -0
  34. package/templates/rust-ts/app/app.ts +9 -0
  35. package/templates/rust-ts/eslint.config.js +12 -0
  36. package/templates/rust-ts/package.json +29 -0
  37. package/templates/rust-ts/titan/bundle.js +163 -0
  38. package/templates/rust-ts/titan/dev.js +435 -0
  39. package/templates/rust-ts/titan/titan.d.ts +19 -0
  40. package/templates/rust-ts/titan/titan.js +124 -0
  41. package/templates/rust-ts/tsconfig.json +28 -0
  42. package/templates/ts/app/actions/getuser.ts +9 -0
  43. package/templates/ts/app/app.ts +7 -0
  44. package/templates/ts/eslint.config.js +12 -0
  45. package/templates/ts/package.json +30 -0
  46. package/templates/ts/tsconfig.json +28 -0
@@ -0,0 +1,9 @@
1
+ import t from "@titanpl/route";
2
+
3
+ t.post("/hello").action("hello") // pass a json payload { "name": "titan" }
4
+
5
+ t.get("/rust").action("rust_hello") // This route uses a rust action
6
+
7
+ t.get("/").reply("Ready to land on Titan Planet 🚀");
8
+
9
+ t.start(5100, "Titan Running!");
@@ -0,0 +1,5 @@
1
+ import { titanpl } from 'eslint-plugin-titanpl';
2
+
3
+ export default [
4
+ titanpl
5
+ ];
@@ -0,0 +1,27 @@
1
+ {
2
+ "compilerOptions": {
3
+ "module": "esnext",
4
+ "target": "esnext",
5
+ "checkJs": false,
6
+ "noImplicitAny": false,
7
+ "allowJs": true,
8
+ "moduleResolution": "node",
9
+ "baseUrl": ".",
10
+ "paths": {
11
+ "@titanpl/native": [
12
+ "app/t.native"
13
+ ],
14
+ "@titanpl/route": [
15
+ "./titan/titan"
16
+ ],
17
+ "*": [
18
+ "./app/*"
19
+ ]
20
+ }
21
+ },
22
+ "include": [
23
+ "app/**/*",
24
+ "titan/**/*",
25
+ "node_modules/**/titan-ext.d.ts"
26
+ ]
27
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "titanpl",
3
+ "version": "2.0.4",
4
+ "description": "A Titan Planet server",
5
+ "type": "module",
6
+ "titan": {
7
+ "template": "rust-js"
8
+ },
9
+ "dependencies": {
10
+ "@titanpl/cli": "2.0.4",
11
+ "@titanpl/route": "2.0.4",
12
+ "@titanpl/native": "2.0.4",
13
+ "@titanpl/core": "latest",
14
+ "@titanpl/node": "latest"
15
+ },
16
+ "scripts": {
17
+ "build": "titan build",
18
+ "dev": "titan dev",
19
+ "start": "titan start",
20
+ "lint": "eslint .",
21
+ "lint:fix": "eslint . --fix"
22
+ },
23
+ "devDependencies": {
24
+ "eslint": "^9.39.2",
25
+ "eslint-plugin-titanpl": "latest"
26
+ }
27
+ }
@@ -0,0 +1,157 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import esbuild from "esbuild";
4
+
5
+ const root = process.cwd();
6
+ const actionsDir = path.join(root, "app", "actions");
7
+ const outDir = path.join(root, "server", "actions");
8
+ const rustOutDir = path.join(root, "server", "src", "actions_rust");
9
+
10
+ export async function bundle() {
11
+ const start = Date.now();
12
+ await bundleJs();
13
+ await bundleRust();
14
+ // console.log(`[Titan] Bundle finished in ${((Date.now() - start) / 1000).toFixed(2)}s`);
15
+ }
16
+
17
+ async function bundleJs() {
18
+ // console.log("[Titan] Bundling JS actions...");
19
+
20
+ fs.mkdirSync(outDir, { recursive: true });
21
+
22
+ // Clean old bundles
23
+ const oldFiles = fs.readdirSync(outDir);
24
+ for (const file of oldFiles) {
25
+ fs.unlinkSync(path.join(outDir, file));
26
+ }
27
+
28
+ const files = fs.readdirSync(actionsDir).filter(f => f.endsWith(".js") || f.endsWith(".ts"));
29
+ if (files.length === 0) return;
30
+
31
+ // console.log(`[Titan] Bundling ${files.length} JS actions...`);
32
+
33
+ for (const file of files) {
34
+ const actionName = path.basename(file, path.extname(file));
35
+
36
+ const entry = path.join(actionsDir, file);
37
+
38
+ // Rust runtime expects `.jsbundle` extension — consistent with previous design
39
+ const outfile = path.join(outDir, actionName + ".jsbundle");
40
+
41
+ // console.log(`[Titan] Bundling ${entry} → ${outfile}`);
42
+
43
+ await esbuild.build({
44
+ entryPoints: [entry],
45
+ outfile,
46
+ bundle: true,
47
+ format: "iife",
48
+ globalName: "__titan_exports",
49
+ platform: "neutral",
50
+ target: "es2020",
51
+ logLevel: "silent",
52
+ banner: {
53
+ js: "const defineAction = (fn) => fn; const Titan = t;"
54
+ },
55
+
56
+ footer: {
57
+ js: `
58
+ (function () {
59
+ const fn =
60
+ __titan_exports["${actionName}"] ||
61
+ __titan_exports.default;
62
+
63
+ if (typeof fn !== "function") {
64
+ throw new Error("[Titan] Action '${actionName}' not found or not a function");
65
+ }
66
+
67
+ globalThis["${actionName}"] = function(request_arg) {
68
+ globalThis.req = request_arg;
69
+ return fn(request_arg);
70
+ };
71
+ })();
72
+ `
73
+ }
74
+ });
75
+ }
76
+
77
+ // console.log("[Titan] JS Bundling finished.");
78
+ }
79
+
80
+ export async function bundleRust() {
81
+ // console.log("[Titan] Bundling Rust actions...");
82
+
83
+ if (!fs.existsSync(rustOutDir)) {
84
+ fs.mkdirSync(rustOutDir, { recursive: true });
85
+ }
86
+
87
+ // Clean old rust actions
88
+ for (const file of fs.readdirSync(rustOutDir)) {
89
+ fs.unlinkSync(path.join(rustOutDir, file));
90
+ }
91
+
92
+ const files = fs.readdirSync(actionsDir).filter(f => f.endsWith(".rs"));
93
+ if (files.length > 0) {
94
+ // console.log(`[Titan] Bundling ${files.length} Rust actions...`);
95
+ }
96
+
97
+ const modules = [];
98
+
99
+ for (const file of files) {
100
+ const actionName = path.basename(file, ".rs");
101
+ const entry = path.join(actionsDir, file);
102
+ let outfile = path.join(rustOutDir, file);
103
+
104
+ let content = fs.readFileSync(entry, 'utf-8');
105
+
106
+ // Prepend implicit imports if not present
107
+ let finalContent = content;
108
+ if (!content.includes("use crate::extensions::t;")) {
109
+ finalContent = "use crate::extensions::t;\n" + content;
110
+ }
111
+
112
+ // Basic validation - check if it has a run function
113
+ if (!content.includes("async fn run")) {
114
+ console.warn(`[Titan] Warning: ${file} does not appear to have an 'async fn run'. It might fail to compile.`);
115
+ }
116
+
117
+ fs.writeFileSync(outfile, finalContent);
118
+ modules.push(actionName);
119
+ // console.log(`[Titan] Copied Rust action ${actionName}`);
120
+ }
121
+
122
+ // Generate mod.rs
123
+ let modContent = `// Auto-generated by Titan. Do not edit.
124
+ use axum::response::IntoResponse;
125
+ use axum::http::Request;
126
+ use axum::body::Body;
127
+ use std::future::Future;
128
+ use std::pin::Pin;
129
+
130
+ `;
131
+
132
+ // Add mod declarations
133
+ for (const mod of modules) {
134
+ modContent += `pub mod ${mod};\n`;
135
+ }
136
+
137
+ modContent += `
138
+ pub type ActionFn = fn(Request<Body>) -> Pin<Box<dyn Future<Output = axum::response::Response> + Send>>;
139
+
140
+ pub fn get_action(name: &str) -> Option<ActionFn> {
141
+ match name {
142
+ `;
143
+
144
+ for (const mod of modules) {
145
+ modContent += ` "${mod}" => Some(|req| Box::pin(async move {
146
+ ${mod}::run(req).await.into_response()
147
+ })),\n`;
148
+ }
149
+
150
+ modContent += ` _ => None
151
+ }
152
+ }
153
+ `;
154
+
155
+ fs.writeFileSync(path.join(rustOutDir, "mod.rs"), modContent);
156
+ // console.log("[Titan] Rust Bundling finished.");
157
+ }
@@ -0,0 +1,323 @@
1
+ import chokidar from "chokidar";
2
+ import { spawn, execSync } from "child_process";
3
+ import path from "path";
4
+ import { fileURLToPath } from "url";
5
+ import fs from "fs";
6
+
7
+ // Required for __dirname in ES modules
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
+
11
+
12
+ // Colors
13
+ import { createRequire } from "module";
14
+
15
+ // Colors
16
+ const cyan = (t) => `\x1b[36m${t}\x1b[0m`;
17
+ const green = (t) => `\x1b[32m${t}\x1b[0m`;
18
+ const yellow = (t) => `\x1b[33m${t}\x1b[0m`;
19
+ const red = (t) => `\x1b[31m${t}\x1b[0m`;
20
+ const gray = (t) => `\x1b[90m${t}\x1b[0m`;
21
+ const bold = (t) => `\x1b[1m${t}\x1b[0m`;
22
+
23
+ function getTitanVersion() {
24
+ try {
25
+ const require = createRequire(import.meta.url);
26
+ const pkgPath = require.resolve("titanpl/package.json");
27
+ return JSON.parse(fs.readFileSync(pkgPath, "utf-8")).version;
28
+ } catch (e) {
29
+ try {
30
+ // Check levels up to find the framework root
31
+ let cur = __dirname;
32
+ for (let i = 0; i < 5; i++) {
33
+ const pkgPath = path.join(cur, "package.json");
34
+ if (fs.existsSync(pkgPath)) {
35
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
36
+ if (pkg.name === "titanpl") return pkg.version;
37
+ }
38
+ cur = path.join(cur, "..");
39
+ }
40
+ } catch (e2) { }
41
+
42
+ try {
43
+ // Fallback to calling tit --version
44
+ const output = execSync("tit --version", { encoding: "utf-8" }).trim();
45
+ const match = output.match(/v(\d+\.\d+\.\d+)/);
46
+ if (match) return match[1];
47
+ } catch (e3) { }
48
+ }
49
+ return "0.1.0";
50
+ }
51
+
52
+ let serverProcess = null;
53
+ let isKilling = false;
54
+ let isFirstBoot = true;
55
+
56
+ // ... (killServer same as before)
57
+ async function killServer() {
58
+ if (!serverProcess) return;
59
+
60
+ isKilling = true;
61
+ const pid = serverProcess.pid;
62
+ const killPromise = new Promise((resolve) => {
63
+ if (serverProcess.exitCode !== null) return resolve();
64
+ serverProcess.once("close", resolve);
65
+ });
66
+
67
+ if (process.platform === "win32") {
68
+ try {
69
+ execSync(`taskkill /pid ${pid} /f /t`, { stdio: 'ignore' });
70
+ } catch (e) {
71
+ // Ignore errors if process is already dead
72
+ }
73
+ } else {
74
+ serverProcess.kill();
75
+ }
76
+
77
+ try {
78
+ await killPromise;
79
+ } catch (e) { }
80
+ serverProcess = null;
81
+ isKilling = false;
82
+ }
83
+
84
+ const delay = (ms) => new Promise(res => setTimeout(res, ms));
85
+
86
+ let spinnerTimer = null;
87
+ const frames = ["⏣", "⟐", "⟡", "⟠", "⟡", "⟐"];
88
+ let frameIdx = 0;
89
+
90
+ function startSpinner(text) {
91
+ if (spinnerTimer) clearInterval(spinnerTimer);
92
+ process.stdout.write("\x1B[?25l"); // Hide cursor
93
+ spinnerTimer = setInterval(() => {
94
+ process.stdout.write(`\r ${cyan(frames[frameIdx])} ${gray(text)}`);
95
+ frameIdx = (frameIdx + 1) % frames.length;
96
+ }, 80);
97
+ }
98
+
99
+ function stopSpinner(success = true, text = "") {
100
+ if (spinnerTimer) {
101
+ clearInterval(spinnerTimer);
102
+ spinnerTimer = null;
103
+ }
104
+ process.stdout.write("\r\x1B[K"); // Clear line
105
+ process.stdout.write("\x1B[?25h"); // Show cursor
106
+ if (text) {
107
+ if (success) {
108
+ console.log(` ${green("✔")} ${green(text)}`);
109
+ } else {
110
+ console.log(` ${red("✖")} ${red(text)}`);
111
+ }
112
+ }
113
+ }
114
+
115
+ async function startRustServer(retryCount = 0) {
116
+ const waitTime = retryCount > 0 ? 500 : 200;
117
+
118
+ await killServer();
119
+ await delay(waitTime);
120
+
121
+ const serverPath = path.join(process.cwd(), "server");
122
+ const startTime = Date.now();
123
+
124
+ startSpinner("Stabilizing your app on its orbit...");
125
+
126
+ let isReady = false;
127
+ let stdoutBuffer = "";
128
+ let buildLogs = "";
129
+
130
+ // If it takes more than 15s, update the message
131
+ const slowTimer = setTimeout(() => {
132
+ if (!isReady && !isKilling) {
133
+ startSpinner("Still stabilizing... (the first orbit takes longer)");
134
+ }
135
+ }, 15000);
136
+
137
+ serverProcess = spawn("cargo", ["run", "--quiet"], {
138
+ cwd: serverPath,
139
+ stdio: ["ignore", "pipe", "pipe"],
140
+ env: { ...process.env, CARGO_INCREMENTAL: "1" }
141
+ });
142
+
143
+ serverProcess.on("error", (err) => {
144
+ stopSpinner(false, "Failed to start orbit");
145
+ console.error(red(`[Titan] Error: ${err.message}`));
146
+ });
147
+
148
+ serverProcess.stderr.on("data", (data) => {
149
+ const str = data.toString();
150
+ if (isReady) {
151
+ process.stderr.write(data);
152
+ } else {
153
+ buildLogs += str;
154
+ }
155
+ });
156
+
157
+ serverProcess.stdout.on("data", (data) => {
158
+ const out = data.toString();
159
+
160
+ if (!isReady) {
161
+ stdoutBuffer += out;
162
+ if (stdoutBuffer.includes("Titan server running") || stdoutBuffer.includes("████████╗")) {
163
+ isReady = true;
164
+ clearTimeout(slowTimer);
165
+ stopSpinner(true, "Your app is now orbiting Titan Planet");
166
+
167
+ if (isFirstBoot) {
168
+ process.stdout.write(stdoutBuffer);
169
+ isFirstBoot = false;
170
+ } else {
171
+ // On subsequent reloads, only print non-banner lines from the buffer
172
+ const lines = stdoutBuffer.split("\n");
173
+ for (const line of lines) {
174
+ const isBanner = line.includes("Titan server running") ||
175
+ line.includes("████████╗") ||
176
+ line.includes("╚══") ||
177
+ line.includes(" ██║") ||
178
+ line.includes(" ╚═╝");
179
+ if (!isBanner && line.trim()) {
180
+ process.stdout.write(line + "\n");
181
+ }
182
+ }
183
+ }
184
+ stdoutBuffer = "";
185
+ }
186
+ } else {
187
+ process.stdout.write(data);
188
+ }
189
+ });
190
+
191
+ // Monitor stderr for port binding errors
192
+ serverProcess.stderr.on("data", (data) => {
193
+ stderrBuffer += data.toString();
194
+ });
195
+
196
+ serverProcess.on("close", async (code) => {
197
+ clearTimeout(slowTimer);
198
+ if (isKilling) return;
199
+ const runTime = Date.now() - startTime;
200
+
201
+ if (code !== 0 && code !== null) {
202
+ // Check for port binding errors
203
+ const isPortError = stderrBuffer.includes("Address already in use") ||
204
+ stderrBuffer.includes("address in use") ||
205
+ stderrBuffer.includes("os error 10048") || // Windows
206
+ stderrBuffer.includes("EADDRINUSE") ||
207
+ stderrBuffer.includes("AddrInUse");
208
+
209
+ if (isPortError) {
210
+ stopSpinner(false, "Orbit stabilization failed");
211
+ console.log("");
212
+
213
+ console.log(red("⏣ Your application cannot enter this orbit"));
214
+ console.log(red("↳ Another application is already bound to this port."));
215
+ console.log("");
216
+
217
+ console.log(yellow("Recommended Actions:"));
218
+ console.log(yellow(" 1.") + " Release the occupied orbit (stop the other service).");
219
+ console.log(yellow(" 2.") + " Assign your application to a new orbit in " + cyan("app/app.js"));
220
+ console.log(yellow(" Example: ") + cyan('t.start(3001, "Titan Running!")'));
221
+ console.log("");
222
+
223
+ return;
224
+ }
225
+
226
+
227
+ stopSpinner(false, "Orbit stabilization failed");
228
+
229
+ // Debug: Show stderr if it's not empty and not a port error
230
+ if (stderrBuffer && stderrBuffer.trim()) {
231
+ console.log(gray("\n[Debug] Cargo stderr:"));
232
+ console.log(gray(stderrBuffer.substring(0, 500))); // Show first 500 chars
233
+ }
234
+
235
+ if (runTime < 15000 && retryCount < maxRetries) {
236
+ await delay(2000);
237
+ await startRustServer(retryCount + 1);
238
+ } else if (retryCount >= maxRetries) {
239
+ console.log(gray("\n[Titan] Waiting for changes to retry..."));
240
+ }
241
+ }
242
+ });
243
+ }
244
+
245
+ async function rebuild() {
246
+ try {
247
+ execSync("node app/app.js", { stdio: "ignore" });
248
+ // bundle is called inside app.js (t.start)
249
+ } catch (e) {
250
+ stopSpinner(false, "Failed to prepare runtime");
251
+ console.log(red(`[Titan] Error: ${e.message}`));
252
+ }
253
+ }
254
+
255
+ async function startDev() {
256
+ const root = process.cwd();
257
+ const actionsDir = path.join(root, "app", "actions");
258
+ let hasRust = false;
259
+ if (fs.existsSync(actionsDir)) {
260
+ hasRust = fs.readdirSync(actionsDir).some(f => f.endsWith(".rs"));
261
+ }
262
+
263
+ const isTs = fs.existsSync(path.join(root, "tsconfig.json")) ||
264
+ fs.existsSync(path.join(root, "app", "app.ts"));
265
+
266
+ let mode = "";
267
+ if (hasRust) {
268
+ mode = isTs ? "Rust + TS Actions" : "Rust + JS Actions";
269
+ } else {
270
+ mode = isTs ? "TS Actions" : "JS Actions";
271
+ }
272
+ const version = getTitanVersion();
273
+
274
+ console.clear();
275
+ console.log("");
276
+ console.log(` ${bold(cyan("⏣ Titan Planet"))} ${gray("v" + version)} ${yellow("[ Dev Mode ]")}`);
277
+ console.log("");
278
+ console.log(` ${gray("Type: ")} ${mode}`);
279
+ console.log(` ${gray("Hot Reload: ")} ${green("Enabled")}`);
280
+
281
+ if (fs.existsSync(path.join(root, ".env"))) {
282
+ console.log(` ${gray("Env: ")} ${yellow("Loaded")}`);
283
+ }
284
+ console.log("");
285
+
286
+ try {
287
+ await rebuild();
288
+ await startRustServer();
289
+ } catch (e) {
290
+ // console.log(red("[Titan] Initial build failed. Waiting for changes..."));
291
+ }
292
+
293
+ const watcher = chokidar.watch(["app", ".env"], {
294
+ ignoreInitial: true,
295
+ awaitWriteFinish: { stabilityThreshold: 500, pollInterval: 100 }
296
+ });
297
+
298
+ let timer = null;
299
+ watcher.on("all", async (event, file) => {
300
+ if (timer) clearTimeout(timer);
301
+ timer = setTimeout(async () => {
302
+ try {
303
+ await killServer();
304
+ await rebuild();
305
+ await startRustServer();
306
+ } catch (e) {
307
+ // console.log(red("[Titan] Build failed -- waiting for changes..."));
308
+ }
309
+ }, 300);
310
+ });
311
+ }
312
+
313
+ async function handleExit() {
314
+ stopSpinner();
315
+ console.log(gray("\n[Titan] Stopping server..."));
316
+ await killServer();
317
+ process.exit(0);
318
+ }
319
+
320
+ process.on("SIGINT", handleExit);
321
+ process.on("SIGTERM", handleExit);
322
+
323
+ startDev();
@@ -0,0 +1,126 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { bundle } from "./bundle.js";
4
+
5
+ const cyan = (t) => `\x1b[36m${t}\x1b[0m`;
6
+ const green = (t) => `\x1b[32m${t}\x1b[0m`;
7
+
8
+ const routes = {};
9
+ const dynamicRoutes = {};
10
+ const actionMap = {};
11
+
12
+ function addRoute(method, route) {
13
+ const key = `${method.toUpperCase()}:${route}`;
14
+
15
+
16
+ return {
17
+ reply(value) {
18
+ routes[key] = {
19
+ type: typeof value === "object" ? "json" : "text",
20
+ value
21
+ };
22
+ },
23
+
24
+ action(name) {
25
+ if (route.includes(":")) {
26
+ if (!dynamicRoutes[method]) dynamicRoutes[method] = [];
27
+ dynamicRoutes[method].push({
28
+ method: method.toUpperCase(),
29
+ pattern: route,
30
+ action: name
31
+ });
32
+ } else {
33
+ routes[key] = {
34
+ type: "action",
35
+ value: name
36
+ };
37
+ actionMap[key] = name;
38
+ }
39
+ }
40
+ };
41
+ }
42
+
43
+ /**
44
+ * @typedef {Object} RouteHandler
45
+ * @property {(value: any) => void} reply - Send a direct response
46
+ * @property {(name: string) => void} action - Bind to a server-side action
47
+ */
48
+
49
+ /**
50
+ * Titan App Builder
51
+ */
52
+ const t = {
53
+ /**
54
+ * Define a GET route
55
+ * @param {string} route
56
+ * @returns {RouteHandler}
57
+ */
58
+ get(route) {
59
+ return addRoute("GET", route);
60
+ },
61
+
62
+ /**
63
+ * Define a POST route
64
+ * @param {string} route
65
+ * @returns {RouteHandler}
66
+ */
67
+ post(route) {
68
+ return addRoute("POST", route);
69
+ },
70
+
71
+ log(module, msg) {
72
+ console.log(`[\x1b[35m${module}\x1b[0m] ${msg}`);
73
+ },
74
+
75
+ /**
76
+ * Start the Titan Server
77
+ * @param {number} [port=3000]
78
+ * @param {string} [msg=""]
79
+ */
80
+ async start(port = 3000, msg = "") {
81
+ try {
82
+ console.log(cyan("[Titan] Preparing runtime..."));
83
+ await bundle();
84
+
85
+ const base = path.join(process.cwd(), "server");
86
+ if (!fs.existsSync(base)) {
87
+ fs.mkdirSync(base, { recursive: true });
88
+ }
89
+
90
+ const routesPath = path.join(base, "routes.json");
91
+ const actionMapPath = path.join(base, "action_map.json");
92
+
93
+ fs.writeFileSync(
94
+ routesPath,
95
+ JSON.stringify(
96
+ {
97
+ __config: { port },
98
+ routes,
99
+ __dynamic_routes: Object.values(dynamicRoutes).flat()
100
+ },
101
+ null,
102
+ 2
103
+ )
104
+ );
105
+
106
+ fs.writeFileSync(
107
+ actionMapPath,
108
+ JSON.stringify(actionMap, null, 2)
109
+ );
110
+
111
+ console.log(green("✔ Titan metadata written successfully"));
112
+ if (msg) console.log(cyan(msg));
113
+
114
+ } catch (e) {
115
+ console.error(`\x1b[31m[Titan] Build Error: ${e.message}\x1b[0m`);
116
+ process.exit(1);
117
+ }
118
+ }
119
+ };
120
+
121
+
122
+ /**
123
+ * Titan App Builder (Alias for t)
124
+ */
125
+ export const Titan = t;
126
+ export default t;
@@ -0,0 +1,9 @@
1
+ import { log, defineAction } from "@titanpl/native";
2
+
3
+ export const getuser = defineAction((req: any) => {
4
+ log("Handling user request...");
5
+ return {
6
+ message: "Hello from TypeScript action!",
7
+ user_id: req.params.id
8
+ };
9
+ });
@@ -0,0 +1,14 @@
1
+ use axum::{response::{IntoResponse, Json}, http::Request, body::Body};
2
+ use serde_json::json;
3
+
4
+ pub async fn run(_req: Request<Body>) -> impl IntoResponse {
5
+ // let _token = t.jwt.sign(json!({"id": 1}), "secret", Some(json!({"expiresIn": "1h"}))).unwrap_or_default();
6
+ // let decoded = t.jwt.verify(&_token, "secret").unwrap_or_default();
7
+
8
+ Json(json!({
9
+ "message": "Hello from Rust Action! 🦀",
10
+ "status": "blazing fast test",
11
+ // "token": _token,
12
+ // "decoded": decoded
13
+ }))
14
+ }