@ezetgalaxy/titan 26.13.1 → 26.13.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 +19 -10
- package/package.json +5 -1
- package/templates/common/Dockerfile +60 -37
- package/templates/common/_dockerignore +36 -0
- package/templates/common/app/titan.d.ts +1 -1
- package/templates/js/server/src/action_management.rs +20 -14
- package/templates/js/server/src/extensions/builtin.rs +54 -5
- package/templates/js/server/src/extensions/external.rs +37 -12
- package/templates/js/server/src/extensions/mod.rs +28 -10
- package/templates/js/server/src/extensions/titan_core.js +163 -155
- package/templates/js/server/src/main.rs +50 -77
- package/templates/js/server/src/runtime.rs +141 -210
- package/templates/js/titan/bundle.js +3 -3
- package/templates/js/titan/dev.js +19 -2
- package/templates/js/titan/titan.js +2 -2
- package/templates/ts/server/src/action_management.rs +20 -14
- package/templates/ts/server/src/extensions/builtin.rs +54 -5
- package/templates/ts/server/src/extensions/external.rs +37 -12
- package/templates/ts/server/src/extensions/mod.rs +28 -10
- package/templates/ts/server/src/extensions/titan_core.js +163 -155
- package/templates/ts/server/src/main.rs +50 -77
- package/templates/ts/server/src/runtime.rs +141 -210
- package/templates/ts/titan/dev.js +19 -2
- package/templates/ts/titan/titan.d.ts +1 -1
- package/templates/ts/titan/titan.js +1 -0
- package/titanpl-sdk/package.json +1 -1
- package/titanpl-sdk/templates/server/src/action_management.rs +21 -14
- package/titanpl-sdk/templates/server/src/extensions/builtin.rs +469 -180
- package/titanpl-sdk/templates/server/src/extensions/external.rs +37 -12
- package/titanpl-sdk/templates/server/src/extensions/mod.rs +143 -21
- package/titanpl-sdk/templates/server/src/extensions/titan_core.js +179 -15
- package/titanpl-sdk/templates/server/src/main.rs +113 -71
- package/titanpl-sdk/templates/server/src/runtime.rs +172 -85
- package/titanpl-sdk/templates/titan/bundle.js +3 -3
- package/titanpl-sdk/templates/titan/titan.js +2 -2
|
@@ -1,178 +1,186 @@
|
|
|
1
|
-
|
|
2
1
|
// Titan Core Runtime JS
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
globalThis.
|
|
2
|
+
// Safe Bootstrap — runs only once
|
|
3
|
+
if (!globalThis.__TITAN_CORE_LOADED__) {
|
|
4
|
+
globalThis.__TITAN_CORE_LOADED__ = true;
|
|
5
|
+
|
|
6
|
+
globalThis.global = globalThis;
|
|
7
|
+
|
|
8
|
+
// ensure t exists early
|
|
9
|
+
if (!globalThis.t) globalThis.t = {};
|
|
10
|
+
|
|
11
|
+
// -----------------------------
|
|
12
|
+
// defineAction identity helper
|
|
13
|
+
// -----------------------------
|
|
14
|
+
globalThis.defineAction = (fn) => {
|
|
15
|
+
if (fn.__titanWrapped) return fn;
|
|
16
|
+
|
|
17
|
+
const wrapped = function (req) {
|
|
18
|
+
const requestId = req.__titan_request_id;
|
|
19
|
+
|
|
20
|
+
const isSuspend = (err) => {
|
|
21
|
+
const msg = err && (err.message || String(err));
|
|
22
|
+
return msg && (msg.includes("__SUSPEND__") || msg.includes("SUSPEND"));
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const result = fn(req);
|
|
27
|
+
|
|
28
|
+
if (result && typeof result.then === 'function') {
|
|
29
|
+
result.then(
|
|
30
|
+
(data) => {
|
|
31
|
+
t._finish_request(requestId, data);
|
|
32
|
+
},
|
|
33
|
+
(err) => {
|
|
34
|
+
if (isSuspend(err)) return;
|
|
35
|
+
t._finish_request(requestId, { error: err.message || String(err) });
|
|
36
|
+
}
|
|
37
|
+
);
|
|
38
|
+
} else {
|
|
39
|
+
t._finish_request(requestId, result);
|
|
40
|
+
}
|
|
41
|
+
} catch (err) {
|
|
42
|
+
if (isSuspend(err)) return;
|
|
43
|
+
t._finish_request(requestId, { error: err.message || String(err) });
|
|
44
|
+
}
|
|
45
|
+
};
|
|
6
46
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const wrapped = function (req) {
|
|
11
|
-
const requestId = req.__titan_request_id;
|
|
47
|
+
wrapped.__titanWrapped = true;
|
|
48
|
+
return wrapped;
|
|
49
|
+
};
|
|
12
50
|
|
|
13
|
-
const isSuspend = (err) => {
|
|
14
|
-
const msg = err && (err.message || String(err));
|
|
15
|
-
return msg && (msg.includes("__SUSPEND__") || msg.includes("SUSPEND"));
|
|
16
|
-
};
|
|
17
51
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
(data) => t._finish_request(requestId, data),
|
|
25
|
-
(err) => {
|
|
26
|
-
if (isSuspend(err)) return;
|
|
27
|
-
t._finish_request(requestId, { error: err.message || String(err) })
|
|
28
|
-
}
|
|
29
|
-
);
|
|
30
|
-
} else {
|
|
31
|
-
// Synchronous direct return
|
|
32
|
-
t._finish_request(requestId, result);
|
|
33
|
-
}
|
|
34
|
-
} catch (err) {
|
|
35
|
-
if (isSuspend(err)) return;
|
|
36
|
-
t._finish_request(requestId, { error: err.message || String(err) });
|
|
52
|
+
// -----------------------------
|
|
53
|
+
// TextDecoder Polyfill
|
|
54
|
+
// -----------------------------
|
|
55
|
+
globalThis.TextDecoder = class TextDecoder {
|
|
56
|
+
decode(buffer) {
|
|
57
|
+
return t.decodeUtf8(buffer);
|
|
37
58
|
}
|
|
38
59
|
};
|
|
39
|
-
wrapped.__titanWrapped = true;
|
|
40
|
-
return wrapped;
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
// TextDecoder Polyfill using native t.decodeUtf8
|
|
44
|
-
globalThis.TextDecoder = class TextDecoder {
|
|
45
|
-
decode(buffer) {
|
|
46
|
-
return t.decodeUtf8(buffer);
|
|
47
|
-
}
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
// Process environment variables
|
|
51
|
-
globalThis.process = {
|
|
52
|
-
env: t.loadEnv()
|
|
53
|
-
};
|
|
54
60
|
|
|
55
|
-
//
|
|
61
|
+
// -----------------------------
|
|
62
|
+
// process.env
|
|
63
|
+
// -----------------------------
|
|
64
|
+
globalThis.process = {
|
|
65
|
+
env: t.loadEnv ? t.loadEnv() : {}
|
|
66
|
+
};
|
|
56
67
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
68
|
+
// -----------------------------
|
|
69
|
+
// Async Proxy Creator
|
|
70
|
+
// -----------------------------
|
|
71
|
+
function createAsyncOp(op) {
|
|
72
|
+
return new Proxy(op, {
|
|
73
|
+
get(target, prop) {
|
|
74
|
+
if (
|
|
75
|
+
prop === "__titanAsync" ||
|
|
76
|
+
prop === "type" ||
|
|
77
|
+
prop === "data" ||
|
|
78
|
+
typeof prop === 'symbol'
|
|
79
|
+
) {
|
|
80
|
+
return target[prop];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
throw new Error(
|
|
84
|
+
`[Titan Error] Accessed '${String(prop)}' without drift(). ` +
|
|
85
|
+
`Fix: const res = drift(t.fetch(...));`
|
|
86
|
+
);
|
|
63
87
|
}
|
|
64
|
-
|
|
65
|
-
throw new Error(`[Titan Error] Attempted to access response property '${String(prop)}' without using drift(). \n` +
|
|
66
|
-
`Fix: const result = drift(t.fetch(...));`);
|
|
67
|
-
}
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// --- Response API ---
|
|
72
|
-
const titanResponse = {
|
|
73
|
-
json(data, status = 200, extraHeaders = {}) {
|
|
74
|
-
return {
|
|
75
|
-
_isResponse: true,
|
|
76
|
-
status,
|
|
77
|
-
headers: { "Content-Type": "application/json", ...extraHeaders },
|
|
78
|
-
body: JSON.stringify(data)
|
|
79
|
-
};
|
|
80
|
-
},
|
|
81
|
-
text(data, status = 200, extraHeaders = {}) {
|
|
82
|
-
return {
|
|
83
|
-
_isResponse: true,
|
|
84
|
-
status,
|
|
85
|
-
headers: { "Content-Type": "text/plain", ...extraHeaders },
|
|
86
|
-
body: String(data)
|
|
87
|
-
};
|
|
88
|
-
},
|
|
89
|
-
html(data, status = 200, extraHeaders = {}) {
|
|
90
|
-
return {
|
|
91
|
-
_isResponse: true,
|
|
92
|
-
status,
|
|
93
|
-
headers: { "Content-Type": "text/html", ...extraHeaders },
|
|
94
|
-
body: String(data)
|
|
95
|
-
};
|
|
96
|
-
},
|
|
97
|
-
redirect(url, status = 302, extraHeaders = {}) {
|
|
98
|
-
return {
|
|
99
|
-
_isResponse: true,
|
|
100
|
-
status,
|
|
101
|
-
headers: { "Location": url, ...extraHeaders },
|
|
102
|
-
redirect: url
|
|
103
|
-
};
|
|
88
|
+
});
|
|
104
89
|
}
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
if (globalThis.t) {
|
|
108
|
-
globalThis.t.response = titanResponse;
|
|
109
|
-
} else {
|
|
110
|
-
globalThis.t = { response: titanResponse };
|
|
111
|
-
}
|
|
112
90
|
|
|
113
|
-
//
|
|
91
|
+
// -----------------------------
|
|
92
|
+
// Response API
|
|
93
|
+
// -----------------------------
|
|
94
|
+
const titanResponse = {
|
|
95
|
+
json(data, status = 200, extraHeaders = {}) {
|
|
96
|
+
return {
|
|
97
|
+
_isResponse: true,
|
|
98
|
+
status,
|
|
99
|
+
headers: { "Content-Type": "application/json", ...extraHeaders },
|
|
100
|
+
body: JSON.stringify(data)
|
|
101
|
+
};
|
|
102
|
+
},
|
|
103
|
+
text(data, status = 200, extraHeaders = {}) {
|
|
104
|
+
return {
|
|
105
|
+
_isResponse: true,
|
|
106
|
+
status,
|
|
107
|
+
headers: { "Content-Type": "text/plain", ...extraHeaders },
|
|
108
|
+
body: String(data)
|
|
109
|
+
};
|
|
110
|
+
},
|
|
111
|
+
html(data, status = 200, extraHeaders = {}) {
|
|
112
|
+
return {
|
|
113
|
+
_isResponse: true,
|
|
114
|
+
status,
|
|
115
|
+
headers: { "Content-Type": "text/html", ...extraHeaders },
|
|
116
|
+
body: String(data)
|
|
117
|
+
};
|
|
118
|
+
},
|
|
119
|
+
redirect(url, status = 302, extraHeaders = {}) {
|
|
120
|
+
return {
|
|
121
|
+
_isResponse: true,
|
|
122
|
+
status,
|
|
123
|
+
headers: { "Location": url, ...extraHeaders },
|
|
124
|
+
redirect: url
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
};
|
|
114
128
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
129
|
+
t.response = titanResponse;
|
|
130
|
+
|
|
131
|
+
// -----------------------------
|
|
132
|
+
// Drift Support
|
|
133
|
+
// -----------------------------
|
|
134
|
+
globalThis.drift = function (value) {
|
|
135
|
+
if (Array.isArray(value)) {
|
|
136
|
+
for (const item of value) {
|
|
137
|
+
if (!item || !item.__titanAsync) {
|
|
138
|
+
throw new Error("drift() array must contain async ops only.");
|
|
139
|
+
}
|
|
120
140
|
}
|
|
141
|
+
} else if (!value || !value.__titanAsync) {
|
|
142
|
+
throw new Error("drift() must wrap async ops.");
|
|
121
143
|
}
|
|
122
|
-
} else if (!value || !value.__titanAsync) {
|
|
123
|
-
throw new Error("drift() must wrap t.fetch/t.db.query/t.read async ops only.");
|
|
124
|
-
}
|
|
125
|
-
return t._drift_call(value);
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
// Wrap native fetch
|
|
129
|
-
if (t.fetch && !t.fetch.__titanWrapped) {
|
|
130
|
-
const nativeFetch = t.fetch;
|
|
131
|
-
t.fetch = function (...args) {
|
|
132
|
-
return createAsyncOp(nativeFetch(...args));
|
|
133
|
-
};
|
|
134
|
-
t.fetch.__titanWrapped = true;
|
|
135
|
-
}
|
|
136
144
|
|
|
137
|
-
|
|
138
|
-
if (t.read && !t.read.__titanWrapped) {
|
|
139
|
-
const nativeRead = t.read;
|
|
140
|
-
t.read = function (path) {
|
|
141
|
-
return createAsyncOp(nativeRead(path));
|
|
145
|
+
return t._drift_call(value);
|
|
142
146
|
};
|
|
143
|
-
t.read.__titanWrapped = true;
|
|
144
|
-
}
|
|
145
147
|
|
|
146
|
-
//
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
148
|
+
// -----------------------------
|
|
149
|
+
// Safe Wrappers
|
|
150
|
+
// -----------------------------
|
|
151
|
+
|
|
152
|
+
// fetch
|
|
153
|
+
if (t.fetch && !t.fetch.__titanWrapped) {
|
|
154
|
+
const nativeFetch = t.fetch;
|
|
155
|
+
t.fetch = function (...args) {
|
|
156
|
+
return createAsyncOp(nativeFetch(...args));
|
|
152
157
|
};
|
|
153
|
-
t.
|
|
154
|
-
// Alias
|
|
155
|
-
t.core.fs.readFile = t.core.fs.read;
|
|
158
|
+
t.fetch.__titanWrapped = true;
|
|
156
159
|
}
|
|
157
|
-
}
|
|
158
160
|
|
|
161
|
+
// db.connect
|
|
162
|
+
if (t.db && !t.db.__titanWrapped) {
|
|
163
|
+
const nativeDbConnect = t.db.connect;
|
|
164
|
+
|
|
165
|
+
t.db.connect = function (connString) {
|
|
166
|
+
const conn = nativeDbConnect(connString);
|
|
167
|
+
|
|
168
|
+
if (!conn.query.__titanWrapped) {
|
|
169
|
+
const nativeQuery = conn.query;
|
|
170
|
+
conn.query = (sql) => {
|
|
171
|
+
return createAsyncOp({
|
|
172
|
+
__titanAsync: true,
|
|
173
|
+
type: "db_query",
|
|
174
|
+
data: { conn: connString, query: sql }
|
|
175
|
+
});
|
|
176
|
+
};
|
|
177
|
+
conn.query.__titanWrapped = true;
|
|
178
|
+
}
|
|
159
179
|
|
|
180
|
+
return conn;
|
|
181
|
+
};
|
|
160
182
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
t.db.connect = function (connString) {
|
|
164
|
-
const conn = nativeDbConnect(connString);
|
|
165
|
-
const nativeQuery = conn.query;
|
|
166
|
-
conn.query = (sql) => {
|
|
167
|
-
return createAsyncOp({
|
|
168
|
-
__titanAsync: true,
|
|
169
|
-
type: "db_query",
|
|
170
|
-
data: {
|
|
171
|
-
conn: connString,
|
|
172
|
-
query: sql
|
|
173
|
-
}
|
|
174
|
-
});
|
|
175
|
-
};
|
|
176
|
-
return conn;
|
|
177
|
-
};
|
|
183
|
+
t.db.__titanWrapped = true;
|
|
184
|
+
}
|
|
178
185
|
|
|
186
|
+
}
|
|
@@ -185,7 +185,7 @@ async fn dynamic_handler_inner(
|
|
|
185
185
|
// This sends a pointer-sized message through the ring buffer, triggering
|
|
186
186
|
// the V8 thread to wake up and process the request immediately.
|
|
187
187
|
|
|
188
|
-
// Dispatch to the
|
|
188
|
+
// Dispatch to the worker pool for V8 execution
|
|
189
189
|
let (mut result_json, timings) = state
|
|
190
190
|
.runtime
|
|
191
191
|
.execute(
|
|
@@ -198,7 +198,10 @@ async fn dynamic_handler_inner(
|
|
|
198
198
|
query_vec
|
|
199
199
|
)
|
|
200
200
|
.await
|
|
201
|
-
.unwrap_or_else(|e|
|
|
201
|
+
.unwrap_or_else(|e| {
|
|
202
|
+
// Log catastrophic runtime errors
|
|
203
|
+
(serde_json::json!({"error": e}), vec![])
|
|
204
|
+
});
|
|
202
205
|
|
|
203
206
|
// Construct Server-Timing header
|
|
204
207
|
let server_timing = timings.iter().enumerate().map(|(i, (name, duration))| {
|
|
@@ -210,14 +213,16 @@ async fn dynamic_handler_inner(
|
|
|
210
213
|
obj.insert("_titanTimings".to_string(), serde_json::json!(timings));
|
|
211
214
|
}
|
|
212
215
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
blue("[Titan]").to_string()
|
|
219
|
-
};
|
|
216
|
+
let prefix = if !timings.is_empty() {
|
|
217
|
+
format!("{} {}", blue("[Titan"), blue("Drift]"))
|
|
218
|
+
} else {
|
|
219
|
+
blue("[Titan]").to_string()
|
|
220
|
+
};
|
|
220
221
|
|
|
222
|
+
// ---------------------------
|
|
223
|
+
// ERROR HANDLING
|
|
224
|
+
// ---------------------------
|
|
225
|
+
if let Some(err) = result_json.get("error") {
|
|
221
226
|
println!(
|
|
222
227
|
"{} {} {} {}",
|
|
223
228
|
prefix,
|
|
@@ -225,29 +230,25 @@ async fn dynamic_handler_inner(
|
|
|
225
230
|
red("→ error"),
|
|
226
231
|
gray(&format!("in {:.2?}", start.elapsed()))
|
|
227
232
|
);
|
|
228
|
-
|
|
229
|
-
"{} {} {}
|
|
233
|
+
println!(
|
|
234
|
+
"{} {} {}",
|
|
230
235
|
prefix,
|
|
231
236
|
red("Action Error:"),
|
|
232
|
-
red(err.as_str().unwrap_or("Unknown"))
|
|
233
|
-
gray(&format!("in {:.2?}", start.elapsed()))
|
|
237
|
+
red(err.as_str().unwrap_or("Unknown"))
|
|
234
238
|
);
|
|
235
|
-
(StatusCode::INTERNAL_SERVER_ERROR, Json(result_json.clone())).into_response()
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
u as u16
|
|
242
|
-
} else if let Some(f) = n.as_f64() {
|
|
243
|
-
f as u16
|
|
244
|
-
} else {
|
|
245
|
-
200
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
_ => 200,
|
|
249
|
-
};
|
|
239
|
+
let mut response = (StatusCode::INTERNAL_SERVER_ERROR, Json(result_json.clone())).into_response();
|
|
240
|
+
if !server_timing.is_empty() {
|
|
241
|
+
response.headers_mut().insert("Server-Timing", server_timing.parse().unwrap());
|
|
242
|
+
}
|
|
243
|
+
return response;
|
|
244
|
+
}
|
|
250
245
|
|
|
246
|
+
// ---------------------------
|
|
247
|
+
// RESPONSE CONSTRUCTION
|
|
248
|
+
// ---------------------------
|
|
249
|
+
let mut response = if let Some(is_resp) = result_json.get("_isResponse") {
|
|
250
|
+
if is_resp.as_bool().unwrap_or(false) {
|
|
251
|
+
let status_u16 = result_json.get("status").and_then(|v| v.as_u64()).unwrap_or(200) as u16;
|
|
251
252
|
let status = StatusCode::from_u16(status_u16).unwrap_or(StatusCode::OK);
|
|
252
253
|
let mut builder = axum::http::Response::builder().status(status);
|
|
253
254
|
|
|
@@ -260,31 +261,22 @@ async fn dynamic_handler_inner(
|
|
|
260
261
|
}
|
|
261
262
|
|
|
262
263
|
let mut is_redirect = false;
|
|
263
|
-
|
|
264
264
|
if let Some(location) = result_json.get("redirect") {
|
|
265
265
|
if let Some(url) = location.as_str() {
|
|
266
266
|
let mut final_status_u16 = status.as_u16();
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
final_status_u16 = 302; // Default to 302 Found
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
builder = builder.status(StatusCode::from_u16(final_status_u16).unwrap_or(StatusCode::FOUND))
|
|
273
|
-
.header("Location", url);
|
|
267
|
+
if final_status_u16 < 300 || final_status_u16 > 399 { final_status_u16 = 302; }
|
|
268
|
+
builder = builder.status(StatusCode::from_u16(final_status_u16).unwrap_or(StatusCode::FOUND)).header("Location", url);
|
|
274
269
|
is_redirect = true;
|
|
275
270
|
}
|
|
276
271
|
}
|
|
277
272
|
|
|
278
|
-
let body_text = if is_redirect {
|
|
279
|
-
"".to_string()
|
|
280
|
-
} else {
|
|
273
|
+
let body_text = if is_redirect { "".to_string() } else {
|
|
281
274
|
match result_json.get("body") {
|
|
282
275
|
Some(Value::String(s)) => s.clone(),
|
|
283
276
|
Some(v) => v.to_string(),
|
|
284
277
|
None => "".to_string(),
|
|
285
278
|
}
|
|
286
279
|
};
|
|
287
|
-
|
|
288
280
|
builder.body(Body::from(body_text)).unwrap()
|
|
289
281
|
} else {
|
|
290
282
|
Json(result_json.clone()).into_response()
|
|
@@ -293,7 +285,6 @@ async fn dynamic_handler_inner(
|
|
|
293
285
|
Json(result_json.clone()).into_response()
|
|
294
286
|
};
|
|
295
287
|
|
|
296
|
-
// Add Server-Timing Header
|
|
297
288
|
if !server_timing.is_empty() {
|
|
298
289
|
response.headers_mut().insert("Server-Timing", server_timing.parse().unwrap());
|
|
299
290
|
}
|
|
@@ -303,20 +294,9 @@ async fn dynamic_handler_inner(
|
|
|
303
294
|
// ---------------------------
|
|
304
295
|
let total_elapsed = start.elapsed();
|
|
305
296
|
let total_elapsed_ms = total_elapsed.as_secs_f64() * 1000.0;
|
|
306
|
-
|
|
307
|
-
let total_drift_ms: f64 = timings.iter()
|
|
308
|
-
.filter(|(n, _)| n == "drift" || n == "drift_error")
|
|
309
|
-
.map(|(_, d)| d)
|
|
310
|
-
.sum();
|
|
311
|
-
|
|
297
|
+
let total_drift_ms: f64 = timings.iter().filter(|(n, _)| n == "drift" || n == "drift_error").map(|(_, d)| d).sum();
|
|
312
298
|
let compute_ms = (total_elapsed_ms - total_drift_ms).max(0.0);
|
|
313
299
|
|
|
314
|
-
let prefix = if !timings.is_empty() {
|
|
315
|
-
format!("{} {}", blue("[Titan"), blue("Drift]"))
|
|
316
|
-
} else {
|
|
317
|
-
blue("[Titan]").to_string()
|
|
318
|
-
};
|
|
319
|
-
|
|
320
300
|
let timing_info = if !timings.is_empty() {
|
|
321
301
|
gray(&format!("(active: {:.2}ms, drift: {:.2}ms) in {:.2?}", compute_ms, total_drift_ms, total_elapsed))
|
|
322
302
|
} else {
|
|
@@ -324,23 +304,8 @@ async fn dynamic_handler_inner(
|
|
|
324
304
|
};
|
|
325
305
|
|
|
326
306
|
match route_kind {
|
|
327
|
-
"dynamic" => println!(
|
|
328
|
-
|
|
329
|
-
prefix,
|
|
330
|
-
green(&format!("{} {}", method, path)),
|
|
331
|
-
white("→"),
|
|
332
|
-
green(&route_label),
|
|
333
|
-
white("(dynamic)"),
|
|
334
|
-
timing_info
|
|
335
|
-
),
|
|
336
|
-
"exact" => println!(
|
|
337
|
-
"{} {} {} {} {}",
|
|
338
|
-
prefix,
|
|
339
|
-
white(&format!("{} {}", method, path)),
|
|
340
|
-
white("→"),
|
|
341
|
-
yellow(&route_label),
|
|
342
|
-
timing_info
|
|
343
|
-
),
|
|
307
|
+
"dynamic" => println!("{} {} {} {} {} {}", prefix, green(&format!("{} {}", method, path)), white("→"), green(&route_label), white("(dynamic)"), timing_info),
|
|
308
|
+
"exact" => println!("{} {} {} {} {}", prefix, white(&format!("{} {}", method, path)), white("→"), yellow(&route_label), timing_info),
|
|
344
309
|
_ => {}
|
|
345
310
|
}
|
|
346
311
|
|
|
@@ -358,18 +323,23 @@ async fn main() -> Result<()> {
|
|
|
358
323
|
let raw = fs::read_to_string("./routes.json").unwrap_or_else(|_| "{}".to_string());
|
|
359
324
|
let json: Value = serde_json::from_str(&raw).unwrap_or_default();
|
|
360
325
|
|
|
361
|
-
let port =
|
|
326
|
+
let port = std::env::var("PORT")
|
|
327
|
+
.ok()
|
|
328
|
+
.and_then(|p| p.parse::<u64>().ok())
|
|
329
|
+
.or_else(|| json["__config"]["port"].as_u64())
|
|
330
|
+
.unwrap_or(3000);
|
|
362
331
|
let thread_count = json["__config"]["threads"].as_u64();
|
|
363
332
|
let routes_json = json["routes"].clone();
|
|
364
333
|
let map: HashMap<String, RouteVal> = serde_json::from_value(routes_json).unwrap_or_default();
|
|
365
334
|
let dynamic_routes: Vec<DynamicRoute> =
|
|
366
335
|
serde_json::from_value(json["__dynamic_routes"].clone()).unwrap_or_default();
|
|
367
336
|
|
|
368
|
-
// Identify project root
|
|
337
|
+
// Identify project root
|
|
369
338
|
let project_root = resolve_project_root();
|
|
370
|
-
|
|
371
|
-
// Load extensions
|
|
339
|
+
|
|
340
|
+
// Load extensions and action definitions
|
|
372
341
|
extensions::load_project_extensions(project_root.clone());
|
|
342
|
+
|
|
373
343
|
|
|
374
344
|
// Initialize Runtime Manager (Worker Pool)
|
|
375
345
|
let threads = match thread_count {
|
|
@@ -377,8 +347,10 @@ async fn main() -> Result<()> {
|
|
|
377
347
|
_ => num_cpus::get() * 4, // default
|
|
378
348
|
};
|
|
379
349
|
|
|
350
|
+
let stack_mb = json["__config"]["stack_mb"].as_u64().unwrap_or(8);
|
|
351
|
+
let stack_size = (stack_mb as usize) * 1024 * 1024;
|
|
380
352
|
|
|
381
|
-
let runtime_manager = Arc::new(RuntimeManager::new(project_root.clone(), threads));
|
|
353
|
+
let runtime_manager = Arc::new(RuntimeManager::new(project_root.clone(), threads, stack_size));
|
|
382
354
|
|
|
383
355
|
let state = AppState {
|
|
384
356
|
routes: Arc::new(map),
|
|
@@ -395,9 +367,10 @@ async fn main() -> Result<()> {
|
|
|
395
367
|
|
|
396
368
|
|
|
397
369
|
println!(
|
|
398
|
-
"\x1b[38;5;39mTitan server running at:\x1b[0m http://localhost:{} \x1b[90m(Threads: {})\x1b[0m",
|
|
370
|
+
"\x1b[38;5;39mTitan server running at:\x1b[0m http://localhost:{} \x1b[90m(Threads: {}, Stack: {}MB)\x1b[0m",
|
|
399
371
|
port,
|
|
400
|
-
threads
|
|
372
|
+
threads,
|
|
373
|
+
stack_mb
|
|
401
374
|
);
|
|
402
375
|
|
|
403
376
|
|