titanpl-sdk 2.0.3 → 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.
@@ -1,186 +1,249 @@
1
- // Titan Core Runtime JS
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
- };
46
-
47
- wrapped.__titanWrapped = true;
48
- return wrapped;
49
- };
50
-
51
-
52
- // -----------------------------
53
- // TextDecoder Polyfill
54
- // -----------------------------
55
- globalThis.TextDecoder = class TextDecoder {
56
- decode(buffer) {
57
- return t.decodeUtf8(buffer);
58
- }
59
- };
60
-
61
- // -----------------------------
62
- // process.env
63
- // -----------------------------
64
- globalThis.process = {
65
- env: t.loadEnv ? t.loadEnv() : {}
66
- };
67
-
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
- );
87
- }
88
- });
89
- }
90
-
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
- };
128
-
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
- }
140
- }
141
- } else if (!value || !value.__titanAsync) {
142
- throw new Error("drift() must wrap async ops.");
143
- }
144
-
145
- return t._drift_call(value);
146
- };
147
-
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));
157
- };
158
- t.fetch.__titanWrapped = true;
159
- }
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
- }
179
-
180
- return conn;
181
- };
182
-
183
- t.db.__titanWrapped = true;
184
- }
185
-
186
- }
1
+ // Titan Core Runtime JS
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
+ // defineAction identity helper
12
+ globalThis.defineAction = (fn) => {
13
+ if (fn.__titanWrapped) return fn;
14
+
15
+ const wrapped = function (req) {
16
+ const requestId = req.__titan_request_id;
17
+
18
+ if (req.rawBody && req.rawBody.byteLength !== undefined) {
19
+ try {
20
+ const decoder = new TextDecoder();
21
+ const text = decoder.decode(req.rawBody);
22
+
23
+ const contentType =
24
+ (req.headers && req.headers["content-type"]) ||
25
+ (req.headers && req.headers["Content-Type"]) ||
26
+ "";
27
+
28
+ if (contentType.includes("application/json")) {
29
+ req.body = text ? JSON.parse(text) : {};
30
+ } else if (contentType.includes("application/x-www-form-urlencoded")) {
31
+ req.body = Object.fromEntries(new URLSearchParams(text));
32
+ } else {
33
+ req.body = text;
34
+ }
35
+ } catch (e) {
36
+ req.body = {};
37
+ }
38
+ } else {
39
+ req.body = {};
40
+ }
41
+
42
+ // ===============================
43
+
44
+ const isSuspend = (err) => {
45
+ const msg = err && (err.message || String(err));
46
+ return msg && (msg.includes("__SUSPEND__") || msg.includes("SUSPEND"));
47
+ };
48
+
49
+ try {
50
+ const result = fn(req);
51
+
52
+ if (result && typeof result.then === 'function') {
53
+ result.then(
54
+ (data) => t._finish_request(requestId, data),
55
+ (err) => {
56
+ if (isSuspend(err)) return;
57
+ t._finish_request(requestId, { error: err.message || String(err) });
58
+ }
59
+ );
60
+ } else {
61
+ t._finish_request(requestId, result);
62
+ }
63
+ } catch (err) {
64
+ if (isSuspend(err)) return;
65
+ t._finish_request(requestId, { error: err.message || String(err) });
66
+ }
67
+ };
68
+
69
+ wrapped.__titanWrapped = true;
70
+ return wrapped;
71
+ };
72
+
73
+
74
+ // TextDecoder Polyfill
75
+ globalThis.TextDecoder = class TextDecoder {
76
+ decode(buffer) {
77
+ return t.decodeUtf8(buffer);
78
+ }
79
+ };
80
+
81
+ // Titan Environment API
82
+ t.env = t.loadEnv ? t.loadEnv() : {};
83
+
84
+ // Async Proxy Creator
85
+ function createAsyncOp(op) {
86
+ return new Proxy(op, {
87
+ get(target, prop) {
88
+ if (
89
+ prop === "__titanAsync" ||
90
+ prop === "type" ||
91
+ prop === "data" ||
92
+ typeof prop === 'symbol'
93
+ ) {
94
+ return target[prop];
95
+ }
96
+
97
+ throw new Error(
98
+ `[Titan Error] Accessed '${String(prop)}' without drift(). `
99
+ );
100
+ }
101
+ });
102
+ }
103
+
104
+ // Response API (Dual-Signature)
105
+ // Supports TWO calling conventions for compatibility with fast-path parser:
106
+ //
107
+ // Positional (legacy):
108
+ // t.response.json(data, 201, { "X-Custom": "val" })
109
+ //
110
+ // Options object (preferred — matches fast-path syntax):
111
+ // t.response.json(data, { status: 201, headers: { "X-Custom": "val" } })
112
+ //
113
+ // The fast-path scanner parses the source code and expects the options-object
114
+ // form. Using the positional form works at runtime but won't be detected by
115
+ // fast-path. The options-object form works in BOTH paths.
116
+ //
117
+ // Internal helper to normalize the second argument:
118
+ function _parseResponseOpts(secondArg, thirdArg) {
119
+ let status = 200;
120
+ let extraHeaders = {};
121
+
122
+ if (secondArg !== undefined && secondArg !== null && typeof secondArg === 'object') {
123
+ // Options object form: { status: N, headers: {...} }
124
+ status = secondArg.status || 200;
125
+ extraHeaders = secondArg.headers || {};
126
+ // Also merge thirdArg if provided (defensive)
127
+ if (thirdArg && typeof thirdArg === 'object') {
128
+ extraHeaders = { ...extraHeaders, ...thirdArg };
129
+ }
130
+ } else {
131
+ // Positional form: (status, extraHeaders)
132
+ status = secondArg || 200;
133
+ extraHeaders = thirdArg || {};
134
+ }
135
+
136
+ return { status, extraHeaders };
137
+ }
138
+
139
+ const titanResponse = {
140
+ json(data, second, third) {
141
+ const { status, extraHeaders } = _parseResponseOpts(second, third);
142
+ return {
143
+ _isResponse: true,
144
+ status,
145
+ headers: { "Content-Type": "application/json", ...extraHeaders },
146
+ body: JSON.stringify(data)
147
+ };
148
+ },
149
+ text(data, second, third) {
150
+ const { status, extraHeaders } = _parseResponseOpts(second, third);
151
+ return {
152
+ _isResponse: true,
153
+ status,
154
+ headers: { "Content-Type": "text/plain", ...extraHeaders },
155
+ body: String(data)
156
+ };
157
+ },
158
+ html(data, second, third) {
159
+ const { status, extraHeaders } = _parseResponseOpts(second, third);
160
+ return {
161
+ _isResponse: true,
162
+ status,
163
+ headers: { "Content-Type": "text/html", ...extraHeaders },
164
+ body: String(data)
165
+ };
166
+ },
167
+ redirect(url, second, third) {
168
+ const { status: rawStatus, extraHeaders } = _parseResponseOpts(second, third);
169
+ // For redirects, default to 302 and ensure 3xx range
170
+ let status = rawStatus;
171
+ if (status < 300 || status >= 400) status = 302;
172
+ return {
173
+ _isResponse: true,
174
+ status,
175
+ headers: { "Location": url, ...extraHeaders },
176
+ redirect: url
177
+ };
178
+ }
179
+ };
180
+
181
+ t.response = titanResponse;
182
+
183
+ // Drift Support
184
+ globalThis.drift = function (value) {
185
+ if (Array.isArray(value)) {
186
+ for (const item of value) {
187
+ if (!item || !item.__titanAsync) {
188
+ throw new Error("drift() array must contain async ops only.");
189
+ }
190
+ }
191
+ } else if (!value || !value.__titanAsync) {
192
+ throw new Error("drift() must wrap async ops.");
193
+ }
194
+
195
+ return t._drift_call(value);
196
+ };
197
+
198
+ // Safe Wrappers
199
+
200
+ // fetch
201
+ if (t.fetch && !t.fetch.__titanWrapped) {
202
+ const nativeFetch = t.fetch;
203
+ t.fetch = function (...args) {
204
+ return createAsyncOp(nativeFetch(...args));
205
+ };
206
+ t.fetch.__titanWrapped = true;
207
+ }
208
+
209
+ // db.connect
210
+ // db.connect
211
+ if (t.db && !t.db.__titanWrapped) {
212
+ const nativeDbConnect = t.db.connect;
213
+
214
+ t.db.connect = function (connString, options = {}) {
215
+ const conn = nativeDbConnect(connString, options);
216
+
217
+ if (!conn.query.__titanWrapped) {
218
+ const nativeQuery = conn.query;
219
+
220
+ conn.query = function (sql, params = []) {
221
+ if (typeof sql !== "string" || !sql.trim()) {
222
+ throw new Error("db.query(): SQL string required");
223
+ }
224
+
225
+ if (!Array.isArray(params)) {
226
+ throw new Error("db.query(): params must be array");
227
+ }
228
+
229
+ return createAsyncOp({
230
+ __titanAsync: true,
231
+ type: "db_query",
232
+ data: {
233
+ conn: connString,
234
+ query: sql,
235
+ params
236
+ }
237
+ });
238
+ };
239
+
240
+ conn.query.__titanWrapped = true;
241
+ }
242
+
243
+ return conn;
244
+ };
245
+
246
+ t.db.__titanWrapped = true;
247
+ }
248
+
249
+ }