@ezetgalaxy/titan 26.3.2 → 26.3.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/package.json +1 -1
- package/templates/Dockerfile +3 -0
- package/templates/server/src/extensions.rs +259 -180
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ezetgalaxy/titan",
|
|
3
|
-
"version": "26.3.
|
|
3
|
+
"version": "26.3.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",
|
package/templates/Dockerfile
CHANGED
|
@@ -44,6 +44,9 @@ COPY --from=builder /app/server/action_map.json ./action_map.json
|
|
|
44
44
|
RUN mkdir -p /app/actions
|
|
45
45
|
COPY --from=builder /app/server/actions /app/actions
|
|
46
46
|
|
|
47
|
+
# Copy static assets for t.read
|
|
48
|
+
COPY app/db /app/assets/db
|
|
49
|
+
|
|
47
50
|
# Expose Titan port
|
|
48
51
|
EXPOSE 3000
|
|
49
52
|
|
|
@@ -1,52 +1,102 @@
|
|
|
1
|
+
use bcrypt::{DEFAULT_COST, hash, verify};
|
|
1
2
|
use boa_engine::{
|
|
2
|
-
|
|
3
|
-
|
|
3
|
+
Context, JsError, JsValue, js_string, native_function::NativeFunction,
|
|
4
|
+
object::ObjectInitializer, property::Attribute,
|
|
4
5
|
};
|
|
6
|
+
use jsonwebtoken::{DecodingKey, EncodingKey, Header, Validation, decode, encode};
|
|
7
|
+
use postgres::types::{ToSql, Type};
|
|
8
|
+
use postgres::{Client as PgClient, NoTls};
|
|
5
9
|
use reqwest::{
|
|
6
10
|
blocking::Client,
|
|
7
11
|
header::{HeaderMap, HeaderName, HeaderValue},
|
|
8
12
|
};
|
|
9
13
|
use serde_json::Value;
|
|
14
|
+
use std::path::{Path, PathBuf};
|
|
10
15
|
use std::time::{SystemTime, UNIX_EPOCH};
|
|
11
16
|
use tokio::task;
|
|
12
|
-
use jsonwebtoken::{encode, decode, Header, EncodingKey, DecodingKey, Validation};
|
|
13
|
-
use bcrypt::{hash, verify, DEFAULT_COST};
|
|
14
|
-
use postgres::{Client as PgClient, NoTls};
|
|
15
|
-
use postgres::types::{ToSql, Type};
|
|
16
|
-
use std::path::PathBuf;
|
|
17
17
|
|
|
18
18
|
use crate::utils::{blue, gray, parse_expires_in};
|
|
19
19
|
|
|
20
|
+
|
|
21
|
+
|
|
20
22
|
/// Here add all the runtime t base things
|
|
21
23
|
/// Injects a synchronous `t.fetch(url, opts?)` function into the Boa `Context`.
|
|
22
24
|
pub fn inject_t_runtime(ctx: &mut Context, action_name: &str, project_root: &PathBuf) {
|
|
23
|
-
|
|
24
25
|
// =========================================================
|
|
25
26
|
// t.read(path)
|
|
26
27
|
// =========================================================
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
.
|
|
32
|
-
|
|
28
|
+
fn resolve_read_root(project_root: &PathBuf) -> PathBuf {
|
|
29
|
+
if cfg!(debug_assertions) {
|
|
30
|
+
project_root.join("app")
|
|
31
|
+
} else {
|
|
32
|
+
project_root.join("assets")
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let root = resolve_read_root(project_root).canonicalize().expect("read root must exist");
|
|
37
|
+
|
|
38
|
+
let t_read_native = unsafe {
|
|
39
|
+
NativeFunction::from_closure(move |_this, args, ctx| {
|
|
40
|
+
// ----------------------------------
|
|
41
|
+
// 1. Read argument
|
|
42
|
+
// ----------------------------------
|
|
43
|
+
let rel = args
|
|
44
|
+
.get(0)
|
|
45
|
+
.and_then(|v| v.to_string(ctx).ok())
|
|
46
|
+
.and_then(|s| s.to_std_string().ok())
|
|
47
|
+
.ok_or_else(|| {
|
|
48
|
+
JsError::from_native(
|
|
49
|
+
boa_engine::JsNativeError::typ()
|
|
50
|
+
.with_message("t.read(path): path is required"),
|
|
51
|
+
)
|
|
52
|
+
})?;
|
|
53
|
+
|
|
54
|
+
if Path::new(&rel).is_absolute() {
|
|
55
|
+
return Err(JsError::from_native(
|
|
56
|
+
boa_engine::JsNativeError::typ()
|
|
57
|
+
.with_message("t.read expects a relative path like 'db/file.sql'"),
|
|
58
|
+
));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
let joined = root.join(&rel);
|
|
66
|
+
|
|
67
|
+
// ----------------------------------
|
|
68
|
+
// 3. Canonicalize (resolves ../)
|
|
69
|
+
// ----------------------------------
|
|
70
|
+
let target = joined.canonicalize().map_err(|_| {
|
|
33
71
|
JsError::from_native(
|
|
34
|
-
boa_engine::JsNativeError::
|
|
72
|
+
boa_engine::JsNativeError::error()
|
|
73
|
+
.with_message(format!("t.read: file not found: {}", rel)),
|
|
35
74
|
)
|
|
36
75
|
})?;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
76
|
+
|
|
77
|
+
// ----------------------------------
|
|
78
|
+
// 4. Enforce root boundary
|
|
79
|
+
// ----------------------------------
|
|
80
|
+
if !target.starts_with(&root) {
|
|
81
|
+
return Err(JsError::from_native(
|
|
82
|
+
boa_engine::JsNativeError::error()
|
|
83
|
+
.with_message("t.read: path escapes allowed root"),
|
|
84
|
+
));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ----------------------------------
|
|
88
|
+
// 5. Read file
|
|
89
|
+
// ----------------------------------
|
|
90
|
+
let content = std::fs::read_to_string(&target).map_err(|e| {
|
|
91
|
+
JsError::from_native(
|
|
92
|
+
boa_engine::JsNativeError::error()
|
|
93
|
+
.with_message(format!("t.read failed: {}", e)),
|
|
94
|
+
)
|
|
95
|
+
})?;
|
|
96
|
+
|
|
97
|
+
Ok(JsValue::from(js_string!(content)))
|
|
98
|
+
})
|
|
99
|
+
};
|
|
50
100
|
|
|
51
101
|
// =========================================================
|
|
52
102
|
// t.log(...) — unsafe by design (Boa requirement)
|
|
@@ -64,7 +114,11 @@ pub fn inject_t_runtime(ctx: &mut Context, action_name: &str, project_root: &Pat
|
|
|
64
114
|
println!(
|
|
65
115
|
"{} {}",
|
|
66
116
|
blue("[Titan]"),
|
|
67
|
-
gray(&format!(
|
|
117
|
+
gray(&format!(
|
|
118
|
+
"\x1b[90mlog({})\x1b[0m\x1b[97m: {}\x1b[0m",
|
|
119
|
+
action,
|
|
120
|
+
parts.join(" ")
|
|
121
|
+
))
|
|
68
122
|
);
|
|
69
123
|
|
|
70
124
|
Ok(JsValue::undefined())
|
|
@@ -104,14 +158,13 @@ pub fn inject_t_runtime(ctx: &mut Context, action_name: &str, project_root: &Pat
|
|
|
104
158
|
.unwrap_or("GET")
|
|
105
159
|
.to_string();
|
|
106
160
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
161
|
+
let body_opt = opts_json.get("body").map(|v| {
|
|
162
|
+
if v.is_string() {
|
|
163
|
+
v.as_str().unwrap().to_string()
|
|
164
|
+
} else {
|
|
165
|
+
serde_json::to_string(v).unwrap_or_default()
|
|
166
|
+
}
|
|
167
|
+
});
|
|
115
168
|
|
|
116
169
|
let mut header_pairs = Vec::new();
|
|
117
170
|
if let Some(Value::Object(map)) = opts_json.get("headers") {
|
|
@@ -127,11 +180,10 @@ pub fn inject_t_runtime(ctx: &mut Context, action_name: &str, project_root: &Pat
|
|
|
127
180
|
// -----------------------------
|
|
128
181
|
let out_json = task::block_in_place(move || {
|
|
129
182
|
let client = Client::builder()
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
183
|
+
.use_rustls_tls()
|
|
184
|
+
.tcp_nodelay(true)
|
|
185
|
+
.build()
|
|
186
|
+
.unwrap();
|
|
135
187
|
|
|
136
188
|
let mut req = client.request(method.parse().unwrap_or(reqwest::Method::GET), &url);
|
|
137
189
|
|
|
@@ -181,7 +233,8 @@ pub fn inject_t_runtime(ctx: &mut Context, action_name: &str, project_root: &Pat
|
|
|
181
233
|
// =========================================================
|
|
182
234
|
let t_jwt_sign = NativeFunction::from_fn_ptr(|_this, args, ctx| {
|
|
183
235
|
// payload (must be object)
|
|
184
|
-
let mut payload = args
|
|
236
|
+
let mut payload = args
|
|
237
|
+
.get(0)
|
|
185
238
|
.and_then(|v| v.to_json(ctx).ok())
|
|
186
239
|
.and_then(|v| v.as_object().cloned())
|
|
187
240
|
.ok_or_else(|| {
|
|
@@ -190,13 +243,13 @@ pub fn inject_t_runtime(ctx: &mut Context, action_name: &str, project_root: &Pat
|
|
|
190
243
|
.with_message("t.jwt.sign(payload, secret[, options])"),
|
|
191
244
|
)
|
|
192
245
|
})?;
|
|
193
|
-
|
|
246
|
+
|
|
194
247
|
// secret
|
|
195
|
-
let secret = args
|
|
248
|
+
let secret = args
|
|
249
|
+
.get(1)
|
|
196
250
|
.and_then(|v| v.to_string(ctx).ok())
|
|
197
251
|
.map(|s| s.to_std_string_escaped())
|
|
198
252
|
.unwrap_or_default();
|
|
199
|
-
|
|
200
253
|
|
|
201
254
|
if let Some(opts) = args.get(2) {
|
|
202
255
|
if let Ok(Value::Object(opts)) = opts.to_json(ctx) {
|
|
@@ -204,23 +257,23 @@ pub fn inject_t_runtime(ctx: &mut Context, action_name: &str, project_root: &Pat
|
|
|
204
257
|
let seconds = match exp {
|
|
205
258
|
Value::Number(n) => n.as_u64(),
|
|
206
259
|
Value::String(s) => parse_expires_in(s),
|
|
207
|
-
|
|
260
|
+
_ => None,
|
|
208
261
|
};
|
|
209
262
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
263
|
+
if let Some(sec) = seconds {
|
|
264
|
+
let now = SystemTime::now()
|
|
265
|
+
.duration_since(UNIX_EPOCH)
|
|
266
|
+
.unwrap()
|
|
267
|
+
.as_secs();
|
|
215
268
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
269
|
+
payload.insert(
|
|
270
|
+
"exp".to_string(),
|
|
271
|
+
Value::Number(serde_json::Number::from(now + sec)),
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
220
275
|
}
|
|
221
276
|
}
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
277
|
|
|
225
278
|
let token = encode(
|
|
226
279
|
&Header::default(),
|
|
@@ -228,28 +281,28 @@ pub fn inject_t_runtime(ctx: &mut Context, action_name: &str, project_root: &Pat
|
|
|
228
281
|
&EncodingKey::from_secret(secret.as_bytes()),
|
|
229
282
|
)
|
|
230
283
|
.map_err(|e| {
|
|
231
|
-
JsError::from_native(
|
|
232
|
-
boa_engine::JsNativeError::error().with_message(e.to_string()),
|
|
233
|
-
)
|
|
284
|
+
JsError::from_native(boa_engine::JsNativeError::error().with_message(e.to_string()))
|
|
234
285
|
})?;
|
|
235
|
-
|
|
286
|
+
|
|
236
287
|
Ok(JsValue::from(js_string!(token)))
|
|
237
288
|
});
|
|
238
|
-
|
|
289
|
+
|
|
239
290
|
let t_jwt_verify = NativeFunction::from_fn_ptr(|_this, args, ctx| {
|
|
240
|
-
let token = args
|
|
291
|
+
let token = args
|
|
292
|
+
.get(0)
|
|
241
293
|
.and_then(|v| v.to_string(ctx).ok())
|
|
242
294
|
.map(|s| s.to_std_string_escaped())
|
|
243
295
|
.unwrap_or_default();
|
|
244
|
-
|
|
245
|
-
let secret = args
|
|
296
|
+
|
|
297
|
+
let secret = args
|
|
298
|
+
.get(1)
|
|
246
299
|
.and_then(|v| v.to_string(ctx).ok())
|
|
247
300
|
.map(|s| s.to_std_string_escaped())
|
|
248
301
|
.unwrap_or_default();
|
|
249
|
-
|
|
302
|
+
|
|
250
303
|
let mut validation = Validation::default();
|
|
251
304
|
validation.validate_exp = true;
|
|
252
|
-
|
|
305
|
+
|
|
253
306
|
let data = decode::<Value>(
|
|
254
307
|
&token,
|
|
255
308
|
&DecodingKey::from_secret(secret.as_bytes()),
|
|
@@ -257,46 +310,45 @@ pub fn inject_t_runtime(ctx: &mut Context, action_name: &str, project_root: &Pat
|
|
|
257
310
|
)
|
|
258
311
|
.map_err(|_| {
|
|
259
312
|
JsError::from_native(
|
|
260
|
-
boa_engine::JsNativeError::error()
|
|
261
|
-
.with_message("Invalid or expired JWT"),
|
|
313
|
+
boa_engine::JsNativeError::error().with_message("Invalid or expired JWT"),
|
|
262
314
|
)
|
|
263
315
|
})?;
|
|
264
|
-
|
|
316
|
+
|
|
265
317
|
JsValue::from_json(&data.claims, ctx).map_err(|e| e.into())
|
|
266
318
|
});
|
|
267
|
-
|
|
268
319
|
|
|
269
|
-
|
|
270
320
|
// =========================================================
|
|
271
321
|
// t.password
|
|
272
322
|
// =========================================================
|
|
273
323
|
let t_password_hash = NativeFunction::from_fn_ptr(|_this, args, ctx| {
|
|
274
|
-
let password = args
|
|
324
|
+
let password = args
|
|
325
|
+
.get(0)
|
|
275
326
|
.and_then(|v| v.to_string(ctx).ok())
|
|
276
327
|
.map(|s| s.to_std_string_escaped())
|
|
277
328
|
.unwrap_or_default();
|
|
278
|
-
|
|
279
|
-
let hashed = hash(password, DEFAULT_COST)
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
Ok(JsValue::from(js_string!(hashed)))
|
|
329
|
+
|
|
330
|
+
let hashed = hash(password, DEFAULT_COST).map_err(|e| {
|
|
331
|
+
JsError::from_native(boa_engine::JsNativeError::error().with_message(e.to_string()))
|
|
332
|
+
})?;
|
|
333
|
+
|
|
334
|
+
Ok(JsValue::from(js_string!(hashed)))
|
|
285
335
|
});
|
|
286
336
|
|
|
287
337
|
let t_password_verify = NativeFunction::from_fn_ptr(|_this, args, ctx| {
|
|
288
|
-
let password = args
|
|
338
|
+
let password = args
|
|
339
|
+
.get(0)
|
|
289
340
|
.and_then(|v| v.to_string(ctx).ok())
|
|
290
341
|
.map(|s| s.to_std_string_escaped())
|
|
291
342
|
.unwrap_or_default();
|
|
292
|
-
|
|
293
|
-
let hash_str = args
|
|
343
|
+
|
|
344
|
+
let hash_str = args
|
|
345
|
+
.get(1)
|
|
294
346
|
.and_then(|v| v.to_string(ctx).ok())
|
|
295
347
|
.map(|s| s.to_std_string_escaped())
|
|
296
348
|
.unwrap_or_default();
|
|
297
|
-
|
|
349
|
+
|
|
298
350
|
let ok = verify(password, &hash_str).unwrap_or(false);
|
|
299
|
-
|
|
351
|
+
|
|
300
352
|
Ok(JsValue::from(ok))
|
|
301
353
|
});
|
|
302
354
|
|
|
@@ -304,7 +356,8 @@ pub fn inject_t_runtime(ctx: &mut Context, action_name: &str, project_root: &Pat
|
|
|
304
356
|
// t.db (Synchronous Postgres)
|
|
305
357
|
// =========================================================
|
|
306
358
|
let t_db_connect = NativeFunction::from_fn_ptr(|_this, args, ctx| {
|
|
307
|
-
let url = args
|
|
359
|
+
let url = args
|
|
360
|
+
.get(0)
|
|
308
361
|
.and_then(|v| v.to_string(ctx).ok())
|
|
309
362
|
.map(|s| s.to_std_string_escaped())
|
|
310
363
|
.ok_or_else(|| {
|
|
@@ -314,112 +367,119 @@ pub fn inject_t_runtime(ctx: &mut Context, action_name: &str, project_root: &Pat
|
|
|
314
367
|
)
|
|
315
368
|
})?;
|
|
316
369
|
|
|
317
|
-
|
|
318
|
-
|
|
319
370
|
let url_clone = url.clone();
|
|
320
|
-
|
|
371
|
+
|
|
321
372
|
let query_fn = unsafe {
|
|
322
373
|
NativeFunction::from_closure(move |_this, args, ctx| {
|
|
323
|
-
let sql = args
|
|
374
|
+
let sql = args
|
|
375
|
+
.get(0)
|
|
324
376
|
.and_then(|v| v.to_string(ctx).ok())
|
|
325
377
|
.map(|s| s.to_std_string_escaped())
|
|
326
378
|
.ok_or_else(|| {
|
|
327
379
|
JsError::from_native(
|
|
328
|
-
boa_engine::JsNativeError::typ()
|
|
380
|
+
boa_engine::JsNativeError::typ()
|
|
381
|
+
.with_message("db.query(sql, params): sql is required"),
|
|
329
382
|
)
|
|
330
383
|
})?;
|
|
331
384
|
|
|
332
|
-
|
|
333
385
|
let params_val = args.get(1).cloned().unwrap_or(JsValue::Null);
|
|
334
|
-
|
|
335
|
-
|
|
386
|
+
|
|
336
387
|
let json_params: Vec<Value> = if let Ok(val) = params_val.to_json(ctx) {
|
|
337
|
-
|
|
388
|
+
if let Value::Array(arr) = val {
|
|
389
|
+
arr
|
|
390
|
+
} else {
|
|
391
|
+
vec![]
|
|
392
|
+
}
|
|
338
393
|
} else {
|
|
339
|
-
|
|
394
|
+
vec![]
|
|
340
395
|
};
|
|
341
|
-
|
|
396
|
+
|
|
342
397
|
let url_for_query = url_clone.clone();
|
|
343
398
|
|
|
344
399
|
let result_json = task::block_in_place(move || {
|
|
345
|
-
|
|
400
|
+
let mut client = match PgClient::connect(&url_for_query, NoTls) {
|
|
346
401
|
Ok(c) => c,
|
|
347
402
|
Err(e) => return Err(format!("Connection failed: {}", e)),
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
.iter()
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
// We need to map `Vec<Value>` to `&[&dyn ToSql]`.
|
|
406
|
+
|
|
407
|
+
let mut typed_params: Vec<Box<dyn ToSql + Sync>> = Vec::new();
|
|
408
|
+
|
|
409
|
+
for p in json_params {
|
|
410
|
+
match p {
|
|
411
|
+
Value::String(s) => typed_params.push(Box::new(s)),
|
|
412
|
+
Value::Number(n) => {
|
|
413
|
+
if let Some(i) = n.as_i64() {
|
|
414
|
+
typed_params.push(Box::new(i));
|
|
415
|
+
} else if let Some(f) = n.as_f64() {
|
|
416
|
+
typed_params.push(Box::new(f));
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
Value::Bool(b) => typed_params.push(Box::new(b)),
|
|
420
|
+
Value::Null => typed_params.push(Box::new(Option::<String>::None)), // Typed null?
|
|
421
|
+
// Fallback others to JSON
|
|
422
|
+
obj => typed_params.push(Box::new(obj)),
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
let param_refs: Vec<&(dyn ToSql + Sync)> =
|
|
427
|
+
typed_params.iter().map(|x| x.as_ref()).collect();
|
|
428
|
+
|
|
429
|
+
let rows = client.query(&sql, ¶m_refs).map_err(|e| e.to_string())?;
|
|
430
|
+
|
|
431
|
+
// Convert rows to JSON
|
|
432
|
+
let mut out_rows = Vec::new();
|
|
433
|
+
for row in rows {
|
|
434
|
+
let mut map = serde_json::Map::new();
|
|
435
|
+
// We need column names.
|
|
436
|
+
for (i, col) in row.columns().iter().enumerate() {
|
|
437
|
+
let name = col.name().to_string();
|
|
438
|
+
|
|
439
|
+
let val: Value = match *col.type_() {
|
|
440
|
+
Type::BOOL => Value::Bool(row.get(i)),
|
|
441
|
+
Type::INT2 | Type::INT4 | Type::INT8 => {
|
|
442
|
+
let v: Option<i64> = row.get::<_, Option<i64>>(i);
|
|
443
|
+
v.map(|n| Value::Number(n.into())).unwrap_or(Value::Null)
|
|
444
|
+
}
|
|
445
|
+
Type::FLOAT4 | Type::FLOAT8 => {
|
|
446
|
+
let v: Option<f64> = row.get::<_, Option<f64>>(i);
|
|
447
|
+
v.map(|n| {
|
|
448
|
+
serde_json::Number::from_f64(n)
|
|
449
|
+
.map(Value::Number)
|
|
450
|
+
.unwrap_or(Value::Null)
|
|
451
|
+
})
|
|
452
|
+
.unwrap_or(Value::Null)
|
|
453
|
+
}
|
|
454
|
+
Type::TEXT | Type::VARCHAR | Type::BPCHAR | Type::NAME => {
|
|
455
|
+
let v: Option<String> = row.get(i);
|
|
456
|
+
v.map(Value::String).unwrap_or(Value::Null)
|
|
457
|
+
}
|
|
458
|
+
Type::JSON | Type::JSONB => {
|
|
459
|
+
let v: Option<Value> = row.get(i);
|
|
460
|
+
v.unwrap_or(Value::Null)
|
|
461
|
+
}
|
|
462
|
+
_ => Value::Null,
|
|
463
|
+
};
|
|
464
|
+
map.insert(name, val);
|
|
465
|
+
}
|
|
466
|
+
out_rows.push(Value::Object(map));
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
Ok(out_rows)
|
|
412
470
|
});
|
|
413
|
-
|
|
471
|
+
|
|
414
472
|
match result_json {
|
|
415
473
|
Ok(rows) => JsValue::from_json(&Value::Array(rows), ctx),
|
|
416
|
-
Err(e) => Err(JsError::from_native(
|
|
474
|
+
Err(e) => Err(JsError::from_native(
|
|
475
|
+
boa_engine::JsNativeError::error().with_message(e),
|
|
476
|
+
)),
|
|
417
477
|
}
|
|
418
478
|
})
|
|
419
479
|
};
|
|
420
|
-
|
|
480
|
+
|
|
421
481
|
// Build object
|
|
422
|
-
|
|
482
|
+
|
|
423
483
|
let realm = ctx.realm().clone(); // Fix context borrow
|
|
424
484
|
let obj = ObjectInitializer::new(ctx)
|
|
425
485
|
.property(
|
|
@@ -428,29 +488,48 @@ pub fn inject_t_runtime(ctx: &mut Context, action_name: &str, project_root: &Pat
|
|
|
428
488
|
Attribute::all(),
|
|
429
489
|
)
|
|
430
490
|
.build();
|
|
431
|
-
|
|
491
|
+
|
|
432
492
|
Ok(JsValue::from(obj))
|
|
433
493
|
});
|
|
434
494
|
|
|
435
|
-
|
|
436
495
|
// =========================================================
|
|
437
496
|
// Build global `t`
|
|
438
497
|
// =========================================================
|
|
439
498
|
let realm = ctx.realm().clone();
|
|
440
499
|
|
|
441
500
|
let jwt_obj = ObjectInitializer::new(ctx)
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
501
|
+
.property(
|
|
502
|
+
js_string!("sign"),
|
|
503
|
+
t_jwt_sign.to_js_function(&realm),
|
|
504
|
+
Attribute::all(),
|
|
505
|
+
)
|
|
506
|
+
.property(
|
|
507
|
+
js_string!("verify"),
|
|
508
|
+
t_jwt_verify.to_js_function(&realm),
|
|
509
|
+
Attribute::all(),
|
|
510
|
+
)
|
|
511
|
+
.build();
|
|
445
512
|
|
|
446
513
|
let password_obj = ObjectInitializer::new(ctx)
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
514
|
+
.property(
|
|
515
|
+
js_string!("hash"),
|
|
516
|
+
t_password_hash.to_js_function(&realm),
|
|
517
|
+
Attribute::all(),
|
|
518
|
+
)
|
|
519
|
+
.property(
|
|
520
|
+
js_string!("verify"),
|
|
521
|
+
t_password_verify.to_js_function(&realm),
|
|
522
|
+
Attribute::all(),
|
|
523
|
+
)
|
|
524
|
+
.build();
|
|
525
|
+
|
|
451
526
|
let db_obj = ObjectInitializer::new(ctx)
|
|
452
|
-
|
|
453
|
-
|
|
527
|
+
.property(
|
|
528
|
+
js_string!("connect"),
|
|
529
|
+
t_db_connect.to_js_function(&realm),
|
|
530
|
+
Attribute::all(),
|
|
531
|
+
)
|
|
532
|
+
.build();
|
|
454
533
|
|
|
455
534
|
let t_obj = ObjectInitializer::new(ctx)
|
|
456
535
|
.property(
|
|
@@ -462,11 +541,11 @@ pub fn inject_t_runtime(ctx: &mut Context, action_name: &str, project_root: &Pat
|
|
|
462
541
|
js_string!("fetch"),
|
|
463
542
|
t_fetch_native.to_js_function(&realm),
|
|
464
543
|
Attribute::all(),
|
|
465
|
-
)
|
|
544
|
+
)
|
|
466
545
|
.property(
|
|
467
546
|
js_string!("read"),
|
|
468
547
|
t_read_native.to_js_function(&realm),
|
|
469
|
-
Attribute::all()
|
|
548
|
+
Attribute::all(),
|
|
470
549
|
)
|
|
471
550
|
.property(js_string!("jwt"), jwt_obj, Attribute::all())
|
|
472
551
|
.property(js_string!("password"), password_obj, Attribute::all())
|