@ezetgalaxy/titan 26.9.0 → 26.9.1

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 (42) hide show
  1. package/README.md +22 -12
  2. package/index.js +57 -15
  3. package/package.json +1 -2
  4. package/templates/{rust → rust-js}/package.json +1 -1
  5. package/templates/rust-ts/app/actions/hello.ts +10 -4
  6. package/templates/rust-ts/app/app.ts +1 -1
  7. package/templates/rust-ts/titan/bundle.js +15 -9
  8. package/templates/rust-ts/titan/dev.js +2 -2
  9. package/templates/rust-ts/titan/titan.d.ts +117 -0
  10. package/templates/rust-ts/titan/titan.js +95 -95
  11. package/templates/ts/Dockerfile +16 -42
  12. package/templates/ts/app/actions/hello.ts +2 -0
  13. package/templates/ts/app/app.ts +1 -1
  14. package/templates/ts/titan/builder.js +121 -0
  15. package/templates/ts/titan/bundle.js +9 -11
  16. package/templates/ts/titan/dev.js +2 -2
  17. package/templates/ts/titan/runtime.js +1 -0
  18. package/templates/ts/titan/titan.d.ts +117 -0
  19. package/templates/ts/titan/titan.js +95 -95
  20. package/titanpl-sdk/README.md +4 -4
  21. package/titanpl-sdk/bin/run.js +74 -77
  22. package/titanpl-sdk/package.json +1 -1
  23. package/templates/rust/app/titan.d.ts +0 -101
  24. package/templates/ts/app/titan.d.ts +0 -102
  25. /package/templates/{rust → rust-js}/Dockerfile +0 -0
  26. /package/templates/{rust → rust-js}/_dockerignore +0 -0
  27. /package/templates/{rust → rust-js}/_gitignore +0 -0
  28. /package/templates/{rust → rust-js}/app/actions/hello.js +0 -0
  29. /package/templates/{rust → rust-js}/app/actions/rust_hello.rs +0 -0
  30. /package/templates/{rust → rust-js}/app/app.js +0 -0
  31. /package/templates/{rust-ts → rust-js}/app/titan.d.ts +0 -0
  32. /package/templates/{rust → rust-js}/jsconfig.json +0 -0
  33. /package/templates/{rust → rust-js}/server/Cargo.lock +0 -0
  34. /package/templates/{rust → rust-js}/server/Cargo.toml +0 -0
  35. /package/templates/{rust → rust-js}/server/src/action_management.rs +0 -0
  36. /package/templates/{rust → rust-js}/server/src/errors.rs +0 -0
  37. /package/templates/{rust → rust-js}/server/src/extensions.rs +0 -0
  38. /package/templates/{rust → rust-js}/server/src/main.rs +0 -0
  39. /package/templates/{rust → rust-js}/server/src/utils.rs +0 -0
  40. /package/templates/{rust → rust-js}/titan/bundle.js +0 -0
  41. /package/templates/{rust → rust-js}/titan/dev.js +0 -0
  42. /package/templates/{rust → rust-js}/titan/titan.js +0 -0
@@ -1,11 +1,7 @@
1
1
  # ================================================================
2
- # STAGE 1 — Build Titan (JS → Rust)
2
+ # STAGE 1 — Build
3
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
4
+ FROM node:20-slim AS builder
9
5
 
10
6
  # Install Titan CLI (latest)
11
7
  RUN npm install -g @ezetgalaxy/titan@latest
@@ -15,52 +11,30 @@ WORKDIR /app
15
11
  # Copy project files
16
12
  COPY . .
17
13
 
18
- # Install JS dependencies (needed for Titan DSL + bundler)
14
+ # Install JS dependencies
19
15
  RUN npm install
20
16
 
21
- SHELL ["/bin/bash", "-c"]
22
-
23
- # Extract Titan extensions into .ext
24
- RUN mkdir -p /app/.ext && \
25
- find /app/node_modules -maxdepth 5 -type f -name "titan.json" -print0 | \
26
- while IFS= read -r -d '' file; do \
27
- pkg_dir="$(dirname "$file")"; \
28
- pkg_name="$(basename "$pkg_dir")"; \
29
- echo "Copying Titan extension: $pkg_name from $pkg_dir"; \
30
- cp -r "$pkg_dir" "/app/.ext/$pkg_name"; \
31
- done && \
32
- echo "Extensions in .ext:" && \
33
- ls -R /app/.ext
34
-
35
- # Build Titan metadata + bundle JS actions
17
+ # Build Titan App
36
18
  RUN titan build
37
19
 
38
- # Build Rust binary
39
- RUN cd server && cargo build --release
40
-
41
-
42
-
43
20
  # ================================================================
44
- # STAGE 2 — Runtime Image (Lightweight)
21
+ # STAGE 2 — Runtime
45
22
  # ================================================================
46
- FROM debian:stable-slim
23
+ FROM node:20-slim
47
24
 
48
25
  WORKDIR /app
49
26
 
50
- # Copy Rust binary from builder stage
51
- COPY --from=builder /app/server/target/release/titan-server ./titan-server
52
-
53
- # Copy Titan routing metadata
54
- COPY --from=builder /app/server/routes.json ./routes.json
55
- COPY --from=builder /app/server/action_map.json ./action_map.json
56
-
57
- # Copy Titan JS bundles
58
- RUN mkdir -p /app/actions
59
- COPY --from=builder /app/server/actions /app/actions
27
+ # Copy Titan Build Artifacts
28
+ COPY --from=builder /app/.titan ./.titan
29
+ COPY --from=builder /app/server ./server
30
+ COPY --from=builder /app/package.json ./package.json
60
31
 
61
- # Copy only Titan extensions
62
- COPY --from=builder /app/.ext ./.ext
32
+ # Install dependencies (production only, but for now we might need all if not careful)
33
+ # Actually, the runtime depends on `express`-like behavior?
34
+ # No, Titan pure TS runs via `node .titan/app.js` which might need deps?
35
+ # The user's package.json has dependencies.
36
+ RUN npm install --omit=dev
63
37
 
64
38
  EXPOSE 3000
65
39
 
66
- CMD ["./titan-server"]
40
+ CMD ["node", ".titan/app.js"]
@@ -2,6 +2,8 @@ interface HelloResponse {
2
2
  message: string;
3
3
  }
4
4
 
5
+ import { defineAction } from "../../titan/titan";
6
+
5
7
  export const hello = defineAction((req): HelloResponse => {
6
8
  return {
7
9
  message: `Hello from Titan ${req.body.name || "World"}`,
@@ -1,4 +1,4 @@
1
- import t from "../titan/titan.js";
1
+ import t from "../titan/titan";
2
2
 
3
3
 
4
4
 
@@ -0,0 +1,121 @@
1
+ import { bundle } from "./bundle.js";
2
+ import fs from "fs";
3
+ import path from "path";
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
+ export default t;
@@ -2,27 +2,25 @@ import fs from "fs";
2
2
  import path from "path";
3
3
  import esbuild from "esbuild";
4
4
 
5
- const root = process.cwd();
6
- const actionsDir = path.join(root, "app", "actions");
7
- const outDir = path.join(root, "server", "actions");
8
-
9
5
  export async function bundle() {
6
+ const root = process.cwd();
7
+ const actionsDir = path.join(root, "app", "actions");
8
+ const outDir = path.join(root, "server", "actions");
9
+
10
10
  const start = Date.now();
11
- await bundleJs();
11
+ await bundleJs(actionsDir, outDir);
12
12
  // console.log(`[Titan] Bundle finished in ${((Date.now() - start) / 1000).toFixed(2)}s`);
13
13
  }
14
14
 
15
- async function bundleJs() {
15
+ async function bundleJs(actionsDir, outDir) {
16
16
  // console.log("[Titan] Bundling JS actions...");
17
17
 
18
18
  fs.mkdirSync(outDir, { recursive: true });
19
19
 
20
20
  // Clean old bundles
21
- if (fs.existsSync(outDir)) {
22
- const oldFiles = fs.readdirSync(outDir);
23
- for (const file of oldFiles) {
24
- fs.unlinkSync(path.join(outDir, file));
25
- }
21
+ const oldFiles = fs.readdirSync(outDir);
22
+ for (const file of oldFiles) {
23
+ fs.unlinkSync(path.join(outDir, file));
26
24
  }
27
25
 
28
26
  const files = fs.readdirSync(actionsDir).filter(f => f.endsWith(".js") || f.endsWith(".ts"));
@@ -231,11 +231,11 @@ async function rebuild() {
231
231
  bundle: true,
232
232
  platform: "node",
233
233
  format: "esm",
234
- packages: "external",
234
+ external: ["fs", "path", "esbuild", "chokidar", "typescript"],
235
235
  logLevel: "silent"
236
236
  });
237
237
 
238
- execSync(`node "${compiledApp}"`, { stdio: "ignore" });
238
+ execSync(`node "${compiledApp}"`, { stdio: "inherit" });
239
239
  } else {
240
240
  execSync("node app/app.js", { stdio: "ignore" });
241
241
  }
@@ -0,0 +1 @@
1
+ export const defineAction = (handler) => handler;
@@ -0,0 +1,117 @@
1
+
2
+ // -- Module Definitions (for imports from "titan") --
3
+
4
+ export interface RouteHandler {
5
+ reply(value: any): void;
6
+ action(name: string): void;
7
+ }
8
+
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>;
14
+ }
15
+
16
+ // The default export from titan.js is the Builder
17
+ declare const builder: TitanBuilder;
18
+ export default builder;
19
+
20
+ /**
21
+ * Define a Titan Action with type inference.
22
+ */
23
+ export declare function defineAction<T>(actionFn: (req: TitanRequest) => T): (req: TitanRequest) => T;
24
+
25
+
26
+ // -- Global Definitions (Runtime Environment) --
27
+
28
+ declare global {
29
+ /**
30
+ * The Titan Request Object passed to actions.
31
+ */
32
+ interface TitanRequest {
33
+ body: any;
34
+ method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
35
+ path: string;
36
+ headers: {
37
+ host?: string;
38
+ "content-type"?: string;
39
+ "user-agent"?: string;
40
+ authorization?: string;
41
+ [key: string]: string | undefined;
42
+ };
43
+ params: Record<string, string>;
44
+ query: Record<string, string>;
45
+ }
46
+
47
+ interface DbConnection {
48
+ /**
49
+ * Execute a SQL query.
50
+ * @param sql The SQL query string.
51
+ * @param params (Optional) Parameters for the query ($1, $2, etc).
52
+ */
53
+ query(sql: string, params?: any[]): any[];
54
+ }
55
+
56
+ /**
57
+ * Global defineAction (available without import in runtime, though imports are preferred in TS)
58
+ */
59
+ function defineAction<T>(actionFn: (req: TitanRequest) => T): (req: TitanRequest) => T;
60
+
61
+ /**
62
+ * Global Request Object
63
+ * Available automatically in actions.
64
+ */
65
+ var req: TitanRequest;
66
+
67
+ /**
68
+ * Titan Runtime Utilities
69
+ * (Available globally in the runtime, e.g. inside actions)
70
+ */
71
+ const t: {
72
+ /**
73
+ * Log messages to the server console with Titan formatting.
74
+ */
75
+ log(...args: any[]): void;
76
+
77
+ /**
78
+ * Read a file contents as string.
79
+ * @param path Relative path to the file from project root.
80
+ */
81
+ read(path: string): string;
82
+
83
+ fetch(url: string, options?: {
84
+ method?: "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
85
+ headers?: Record<string, string>;
86
+ body?: string | object;
87
+ }): {
88
+ ok: boolean;
89
+ status?: number;
90
+ body?: string;
91
+ error?: string;
92
+ };
93
+
94
+ jwt: {
95
+ sign(
96
+ payload: object,
97
+ secret: string,
98
+ options?: { expiresIn?: string | number }
99
+ ): string;
100
+ verify(token: string, secret: string): any;
101
+ };
102
+
103
+ password: {
104
+ hash(password: string): string;
105
+ verify(password: string, hash: string): boolean;
106
+ };
107
+
108
+ db: {
109
+ connect(url: string): DbConnection;
110
+ };
111
+
112
+ /**
113
+ * Titan Validator (Zod-compatible)
114
+ */
115
+ valid: any;
116
+ };
117
+ }
@@ -1,6 +1,8 @@
1
+ import { bundle } from "./bundle.js";
1
2
  import fs from "fs";
2
3
  import path from "path";
3
- import { bundle } from "./bundle.js";
4
+
5
+ export const defineAction = (handler) => handler;
4
6
 
5
7
  const cyan = (t) => `\x1b[36m${t}\x1b[0m`;
6
8
  const green = (t) => `\x1b[32m${t}\x1b[0m`;
@@ -10,34 +12,33 @@ const dynamicRoutes = {};
10
12
  const actionMap = {};
11
13
 
12
14
  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
- };
15
+ const key = `${method.toUpperCase()}:${route}`;
16
+
17
+ return {
18
+ reply(value) {
19
+ routes[key] = {
20
+ type: typeof value === "object" ? "json" : "text",
21
+ value
22
+ };
23
+ },
24
+
25
+ action(name) {
26
+ if (route.includes(":")) {
27
+ if (!dynamicRoutes[method]) dynamicRoutes[method] = [];
28
+ dynamicRoutes[method].push({
29
+ method: method.toUpperCase(),
30
+ pattern: route,
31
+ action: name
32
+ });
33
+ } else {
34
+ routes[key] = {
35
+ type: "action",
36
+ value: name
37
+ };
38
+ actionMap[key] = name;
39
+ }
40
+ }
41
+ };
41
42
  }
42
43
 
43
44
  /**
@@ -50,73 +51,72 @@ function addRoute(method, route) {
50
51
  * Titan App Builder
51
52
  */
52
53
  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);
54
+ /**
55
+ * Define a GET route
56
+ * @param {string} route
57
+ * @returns {RouteHandler}
58
+ */
59
+ get(route) {
60
+ return addRoute("GET", route);
61
+ },
62
+
63
+ /**
64
+ * Define a POST route
65
+ * @param {string} route
66
+ * @returns {RouteHandler}
67
+ */
68
+ post(route) {
69
+ return addRoute("POST", route);
70
+ },
71
+
72
+ log(module, msg) {
73
+ console.log(`[\x1b[35m${module}\x1b[0m] ${msg}`);
74
+ },
75
+
76
+ /**
77
+ * Start the Titan Server
78
+ * @param {number} [port=3000]
79
+ * @param {string} [msg=""]
80
+ */
81
+ async start(port = 3000, msg = "") {
82
+ try {
83
+ console.log(cyan("[Titan] Preparing runtime..."));
84
+ await bundle();
85
+
86
+ const base = path.join(process.cwd(), "server");
87
+ if (!fs.existsSync(base)) {
88
+ fs.mkdirSync(base, { recursive: true });
89
+ }
90
+
91
+ const routesPath = path.join(base, "routes.json");
92
+ const actionMapPath = path.join(base, "action_map.json");
93
+
94
+ fs.writeFileSync(
95
+ routesPath,
96
+ JSON.stringify(
97
+ {
98
+ __config: { port },
99
+ routes,
100
+ __dynamic_routes: Object.values(dynamicRoutes).flat()
101
+ },
102
+ null,
103
+ 2
104
+ )
105
+ );
106
+
107
+ fs.writeFileSync(
108
+ actionMapPath,
109
+ JSON.stringify(actionMap, null, 2)
110
+ );
111
+
112
+ console.log(green("✔ Titan metadata written successfully"));
113
+ if (msg) console.log(cyan(msg));
114
+
115
+ } catch (e) {
116
+ console.error(`\x1b[31m[Titan] Build Error: ${e.message}\x1b[0m`);
117
+ process.exit(1);
118
+ }
117
119
  }
118
- }
119
120
  };
120
121
 
121
-
122
122
  export default t;
@@ -75,11 +75,11 @@ Since the `t` object is injected globally by the Titan engine at runtime, your I
75
75
  ```
76
76
 
77
77
  Now your editor will treat `t` as a first-class citizen:
78
- ```js
79
- export function myAction(req) {
80
- t.log.info("Request received", req.path); // Autocomplete works!
78
+ ```ts
79
+ export const myAction = defineAction((req) => {
80
+ t.log("Request received", req.path); // Autocomplete works!
81
81
  return { status: "ok" };
82
- }
82
+ });
83
83
  ```
84
84
 
85
85
  ---