Skip to content

Commit 6187d41

Browse files
authored
Automatic deployment testing for Javascript SDK (#788)
1 parent 2a02e46 commit 6187d41

File tree

13 files changed

+395
-22
lines changed

13 files changed

+395
-22
lines changed

.github/workflows/javascript-sdk.yml

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
name: deploy javascript sdk
2+
on:
3+
workflow_dispatch:
4+
jobs:
5+
build-javascript-sdk-linux:
6+
strategy:
7+
matrix:
8+
os: ["ubuntu-22.04", "buildjet-4vcpu-ubuntu-2204-arm", "macos-latest", "windows-latest"]
9+
include:
10+
- neon-out-name: "x86_64-unknown-linux-gnu-index.node"
11+
os: "ubuntu-22.04"
12+
- neon-out-name: "aarch64-unknown-linux-gnu-index.node"
13+
os: "buildjet-4vcpu-ubuntu-2204-arm"
14+
- neon-out-name: "x86_64-apple-darwin-index.node"
15+
os: "macos-latest"
16+
- neon-out-name: "x86_64-pc-windows-gnu.node"
17+
os: "windows-latest"
18+
runs-on: ${{ matrix.os }}
19+
defaults:
20+
run:
21+
working-directory: pgml-sdks/rust/pgml/javascript
22+
steps:
23+
- uses: actions/checkout@v3
24+
- uses: actions-rs/toolchain@v1
25+
with:
26+
toolchain: stable
27+
- name: Validate cargo is working
28+
uses: postgresml/gh-actions-cargo@master
29+
with:
30+
command: version
31+
- name: Do build
32+
env:
33+
NEON_OUT_NAME: ${{ matrix.neon-out-name }}
34+
run: |
35+
npm i
36+
npm run build-named
37+
- name: Upload built .node file
38+
uses: actions/upload-artifact@v3
39+
with:
40+
name: node-artifacts
41+
path: ${{ matrix.neon-out-name }}
42+
retention-days: 1
43+
publish-javascript-sdk:
44+
runs-on: "ubuntu-22.04"
45+
defaults:
46+
run:
47+
working-directory: pgml-sdks/rust/pgml/javascript
48+
steps:
49+
- name: Create artifact directory
50+
run: mkdir dist
51+
- uses: actions/download-artifact@v3
52+
with:
53+
name: node-artifacts
54+
path: dist
55+
- name: Display structure of download-artifacts
56+
run: ls -R dist

pgml-sdks/rust/pgml-macros/src/javascript.rs

Lines changed: 120 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
use quote::{format_ident, quote, ToTokens};
22
use syn::{visit::Visit, DeriveInput, ItemImpl, Type};
3+
use std::fs::OpenOptions;
4+
use std::io::{Read, Write};
35

46
use crate::common::{AttributeArgs, GetImplMethod};
5-
use crate::types::{OutputType, SupportedType};
7+
use crate::types::{OutputType, SupportedType, GetSupportedType};
68

79
pub fn generate_custom_into_js_result(parsed: DeriveInput) -> proc_macro::TokenStream {
810
let name = parsed.ident;
@@ -14,19 +16,43 @@ pub fn generate_custom_into_js_result(parsed: DeriveInput) -> proc_macro::TokenS
1416
_ => panic!("custom_into_js proc_macro should only be used on structs"),
1517
};
1618

17-
let sets: Vec<proc_macro2::TokenStream> = fields_named
19+
let mut sets = Vec::new();
20+
let mut interface = format!("\ninterface {} {{\n", name);
21+
22+
fields_named
1823
.named
1924
.into_pairs()
20-
.map(|p| {
25+
.for_each(|p| {
2126
let v = p.into_value();
2227
let name = v.ident.to_token_stream().to_string();
2328
let name_ident = v.ident;
24-
quote! {
29+
sets.push(quote! {
2530
let js_item = self.#name_ident.into_js_result(cx)?;
2631
js_object.set(cx, #name, js_item)?;
27-
}
28-
})
29-
.collect();
32+
});
33+
let ty = GetSupportedType::get_type(&v.ty);
34+
let decleration = match &ty {
35+
SupportedType::Option(o) => format!("{}?", get_typescript_type(o)),
36+
_ => get_typescript_type(&ty)
37+
};
38+
interface.push_str(&format!("\t{}: {},\n", name, decleration));
39+
});
40+
41+
interface.push('}');
42+
let mut file = OpenOptions::new()
43+
.create(true)
44+
.write(true)
45+
.append(true)
46+
.read(true)
47+
.open("javascript/index.d.ts")
48+
.unwrap();
49+
let mut contents = String::new();
50+
file.read_to_string(&mut contents)
51+
.expect("Unable to read typescript decleration file");
52+
if !contents.contains(&interface) {
53+
file.write_all(interface.as_bytes())
54+
.expect("Unable to write typescript decleration file");
55+
}
3056

3157
let out = quote! {
3258
impl IntoJsResult for #name {
@@ -77,6 +103,9 @@ pub fn generate_javascript_methods(
77103
};
78104
let name_ident = format_ident!("{}Javascript", wrapped_type_ident);
79105

106+
let javascript_class_name = wrapped_type_ident.to_string();
107+
let mut typescript_declarations = format!("\ndeclare class {} {{\n", javascript_class_name);
108+
80109
// Iterate over the items - see: https://docs.rs/syn/latest/syn/enum.ImplItem.html
81110
for item in parsed.items {
82111
// We only create methods for functions listed in the attribute args
@@ -106,6 +135,34 @@ pub fn generate_javascript_methods(
106135
OutputType::Default => (None, None),
107136
};
108137

138+
let p1 = method_ident.to_string();
139+
let p2 = method
140+
.method_arguments
141+
.iter()
142+
.filter(|a| !matches!(a.1, SupportedType::S))
143+
.map(|a| {
144+
match &a.1 {
145+
SupportedType::Option(o) => format!("{}?: {}", a.0, get_typescript_type(o)),
146+
_ => format!("{}: {}", a.0, get_typescript_type(&a.1))
147+
}
148+
})
149+
.collect::<Vec<String>>()
150+
.join(", ");
151+
let p3 = match &method.output_type {
152+
OutputType::Result(v) | OutputType::Other(v) => {
153+
match v {
154+
SupportedType::S => wrapped_type_ident.to_string(),
155+
_ => get_typescript_type(v),
156+
}
157+
},
158+
OutputType::Default => "void".to_string(),
159+
};
160+
if method.is_async {
161+
typescript_declarations.push_str(&format!("\n\t{}({}): Promise<{}>;\n", p1, p2, p3));
162+
} else {
163+
typescript_declarations.push_str(&format!("\n\t{}({}): {};\n", p1, p2, p3));
164+
}
165+
109166
let method_name_string = method_ident.to_string();
110167
object_sets.push(quote! {
111168
let f: Handle<JsFunction> = JsFunction::new(cx, #name_ident::#method_ident)?;
@@ -193,6 +250,23 @@ pub fn generate_javascript_methods(
193250
methods.push(mq);
194251
}
195252

253+
typescript_declarations.push('}');
254+
255+
let mut file = OpenOptions::new()
256+
.create(true)
257+
.write(true)
258+
.append(true)
259+
.read(true)
260+
.open("javascript/index.d.ts")
261+
.unwrap();
262+
let mut contents = String::new();
263+
file.read_to_string(&mut contents)
264+
.expect("Unable to read typescript declaration file for python");
265+
if !contents.contains(&format!("declare class {}", javascript_class_name)) {
266+
file.write_all(typescript_declarations.as_bytes())
267+
.expect("Unable to write typescript declaration file for python");
268+
}
269+
196270
proc_macro::TokenStream::from(quote! {
197271
impl #name_ident {
198272
#(#methods)*
@@ -230,7 +304,7 @@ fn get_method_wrapper_arguments_javascript(
230304
argument_ident.clone(),
231305
argument_type,
232306
);
233-
let argument_type_js = get_javascript_type(argument_type);
307+
let argument_type_js = get_neon_type(argument_type);
234308
let method_argument = match argument_type {
235309
SupportedType::Option(_o) => quote! {
236310
let #argument_ident = cx.argument_opt(#i as i32);
@@ -267,9 +341,9 @@ fn convert_method_wrapper_arguments(
267341
}
268342
}
269343

270-
fn get_javascript_type(ty: &SupportedType) -> syn::Type {
344+
fn get_neon_type(ty: &SupportedType) -> syn::Type {
271345
match ty {
272-
SupportedType::Reference(r) => get_javascript_type(r),
346+
SupportedType::Reference(r) => get_neon_type(r),
273347
SupportedType::str | SupportedType::String => syn::parse_str("JsString").unwrap(),
274348
SupportedType::Vec(_v) => syn::parse_str("JsArray").unwrap(),
275349
SupportedType::S => syn::parse_str("JsObject").unwrap(),
@@ -285,7 +359,7 @@ fn get_javascript_type(ty: &SupportedType) -> syn::Type {
285359
}
286360
}
287361

288-
pub fn convert_output_type_convert_from_javascript(
362+
fn convert_output_type_convert_from_javascript(
289363
ty: &SupportedType,
290364
method: &GetImplMethod,
291365
) -> (
@@ -302,7 +376,7 @@ pub fn convert_output_type_convert_from_javascript(
302376
Some(format_ident!("{}Javascript", t.to_string()).into_token_stream()),
303377
),
304378
t => {
305-
let ty = get_javascript_type(t);
379+
let ty = get_neon_type(t);
306380
(Some(quote! {JsResult<'a, #ty>}), None)
307381
}
308382
};
@@ -313,3 +387,37 @@ pub fn convert_output_type_convert_from_javascript(
313387
(output_type, convert_from)
314388
}
315389
}
390+
391+
fn get_typescript_type(ty: &SupportedType) -> String {
392+
match ty {
393+
SupportedType::Reference(r) => get_typescript_type(r),
394+
SupportedType::str | SupportedType::String => "string".to_string(),
395+
SupportedType::Option(o) => get_typescript_type(o),
396+
SupportedType::Vec(v) => format!("{}[]", get_typescript_type(v)),
397+
SupportedType::HashMap((k, v)) => {
398+
format!("Map<{}, {}>", get_typescript_type(k), get_typescript_type(v))
399+
},
400+
SupportedType::JsonHashMap => "Map<string, string>".to_string(),
401+
SupportedType::DateTime => "Date".to_string(),
402+
SupportedType::Tuple(t) => {
403+
let mut types = Vec::new();
404+
for ty in t {
405+
types.push(get_typescript_type(ty));
406+
}
407+
// Rust's unit type is represented as an empty tuple
408+
if types.is_empty() {
409+
"void".to_string()
410+
} else {
411+
format!("[{}]", types.join(", "))
412+
}
413+
}
414+
SupportedType::i64 | SupportedType::f64 => "number".to_string(),
415+
// Our own types
416+
t @ SupportedType::Database
417+
| t @ SupportedType::Collection
418+
| t @ SupportedType::Splitter => t.to_string(),
419+
| t @ SupportedType::Model => t.to_string(),
420+
// Add more types as required
421+
_ => "any".to_string(),
422+
}
423+
}

pgml-sdks/rust/pgml/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ node_modules/
1313

1414
# Distribution / packaging
1515
.Python
16+
*.pyi
1617
build/
1718
develop-eggs/
1819
dist/

pgml-sdks/rust/pgml/build.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,20 @@
11
use std::fs::remove_file;
2+
use std::fs::OpenOptions;
3+
use std::io::Write;
24

35
fn main() {
46
// Remove python stub file that is auto generated each build
5-
remove_file("python/pgml/pgml.pyi").ok();
7+
remove_file("./python/pgml/pgml.pyi").ok();
8+
9+
// Remove typescript declaration file that is auto generated each build
10+
remove_file("./javascript/index.d.ts").ok();
11+
let mut file = OpenOptions::new()
12+
.create(true)
13+
.write(true)
14+
.append(true)
15+
.open("./javascript/index.d.ts")
16+
.unwrap();
17+
// Add our opening function declaration here
18+
file.write_all(b"\nexport function newDatabase(connection_string: string): Promise<Database>;\n")
19+
.unwrap();
620
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
const os = require("os")
2+
3+
const type = os.type()
4+
const arch = os.arch()
5+
6+
// if (type == "Darwin" && arch == "arm64") {
7+
// const pgml = require("./index.node")
8+
// module.exports = pgml
9+
// } else {
10+
// console.log("UNSUPPORTED TYPE OR ARCH:", type, arch)
11+
// }
12+
13+
14+
const pgml = require("./index.node")
15+
module.exports = pgml

pgml-sdks/rust/pgml/javascript/package-lock.json

Lines changed: 7 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pgml-sdks/rust/pgml/javascript/package.json

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,17 @@
22
"name": "pgml",
33
"version": "0.1.0",
44
"description": "",
5-
"main": "index.node",
6-
"type": "module",
5+
"main": "index.js",
76
"scripts": {
87
"build": "cargo-cp-artifact -nc index.node -- cargo build --message-format=json-render-diagnostics",
98
"build-debug": "npm run build --",
109
"build-release": "npm run build -- --release",
11-
"install": "npm run build-release",
12-
"test": "cargo test"
10+
"build-named": "cargo-cp-artifact -nc $NEON_OUT_NAME -- cargo build --message-format=json-render-diagnostics --release"
1311
},
14-
"author": "",
15-
"license": "ISC",
12+
"author": "PostgresML",
13+
"license": "MIT",
1614
"devDependencies": {
15+
"@types/node": "^20.3.1",
1716
"cargo-cp-artifact": "^0.1"
1817
}
1918
}

pgml-sdks/rust/pgml/javascript/tests/test.js renamed to pgml-sdks/rust/pgml/javascript/tests/javascript-tests/test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const pgml = require('../index.node');
1+
import pgml from '../../index.js'
22

33
const CONNECTION_STRING = process.env.DATABASE_URL;
44

pgml-sdks/rust/pgml/javascript/tests/package-lock.json

Lines changed: 28 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy