js part compiles
This commit is contained in:
@@ -7,11 +7,14 @@ edition = "2021"
|
||||
[dependencies]
|
||||
clap = { version = "4.5.59", optional = true }
|
||||
deno_core = { version = "0.385.0", optional = true }
|
||||
flume = "0.12.0"
|
||||
futures = "0.3.32"
|
||||
mize = { path = "../mize/", default-features = false }
|
||||
tokio = "1.49.0"
|
||||
|
||||
[features]
|
||||
default = ["target-os"]
|
||||
target-os = [ "clap", "mize/target-os" ]
|
||||
target-os = [ "clap", "mize/target-os", "deno_core" ]
|
||||
target-wasm = [ "mize/target-wasm" ]
|
||||
|
||||
[lib]
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
"name": "@ppc/marts",
|
||||
"imports": {},
|
||||
"exports": {
|
||||
".": "./index.ts"
|
||||
".": "./index.ts",
|
||||
"stub_deno": "./js/stub_deno.ts"
|
||||
},
|
||||
"tasks": {
|
||||
"build": "deno bundle -o ./deno_dist/habitica.js ./habitica.ts"
|
||||
}
|
||||
}
|
||||
|
||||
75
packages/marts/deno_dist/habitica.js
Normal file
75
packages/marts/deno_dist/habitica.js
Normal file
@@ -0,0 +1,75 @@
|
||||
// habitica.ts
|
||||
var opts = {
|
||||
"habitica.api_url": {
|
||||
env_var_name: "PPC_HABITICA_API_URL",
|
||||
default_val: "https://habitica.com/api/v3"
|
||||
},
|
||||
"habitica.user_id": {
|
||||
env_var_name: "PPC_HABITICA_USER_ID"
|
||||
},
|
||||
"habitica.client_name": {
|
||||
default_val: "3544a0b8-d71a-46e0-9bb1-6ddbb2abcddb-PPC-Software"
|
||||
},
|
||||
"habitica.api_token": {
|
||||
env_var_name: "PPC_HABITICA_API_TOKEN"
|
||||
}
|
||||
};
|
||||
async function habitica(mize) {
|
||||
console.log("hiiiiiiiiiiiiiiiiiiii");
|
||||
mize.add_opts(opts);
|
||||
mize.add_part(new Habitica(mize));
|
||||
}
|
||||
async function handleRateLimit(response) {
|
||||
const limit = response.headers.get("X-RateLimit-Limit") || "NONE";
|
||||
const remaining = parseInt(response.headers.get("X-RateLimit-Remaining") || "10", 10);
|
||||
const reset = response.headers.get("X-RateLimit-Reset");
|
||||
console.log(`RateLimit: ${limit} | Remaining: ${remaining} | Reset: ${reset}`);
|
||||
if (remaining < 2 && reset) {
|
||||
const resetDate = new Date(reset);
|
||||
const now = /* @__PURE__ */ new Date();
|
||||
const waitMs = resetDate.getTime() - now.getTime() + 1e3;
|
||||
if (waitMs > 0) {
|
||||
console.log(`Waiting ${Math.round(waitMs / 1e3)} secs for next rate limit window...`);
|
||||
await new Promise((resolve) => setTimeout(resolve, waitMs));
|
||||
}
|
||||
}
|
||||
}
|
||||
var Habitica = class {
|
||||
mize;
|
||||
constructor(mize) {
|
||||
this.mize = mize;
|
||||
}
|
||||
async get_tasks(type = "todos") {
|
||||
return await this.api_request("GET", `tasks/user?type=${type}`);
|
||||
}
|
||||
async delete_task(id) {
|
||||
await this.api_request("DELETE", `tasks/${id}`);
|
||||
}
|
||||
async api_request(method, path, extraHeaders = {}, data = {}) {
|
||||
const mize = this.mize;
|
||||
if (!path.startsWith("/")) {
|
||||
path = "/" + path;
|
||||
}
|
||||
const headers = Object.assign({
|
||||
"Content-Type": "application/json",
|
||||
"x-api-user": mize.get_config("habitica.user_id"),
|
||||
"x-api-key": mize.get_config("habitica.api_token"),
|
||||
"x-client": mize.get_config("habitica.client_name")
|
||||
}, extraHeaders);
|
||||
const response = await fetch(mize.get_config("habitica.api_url") + path, {
|
||||
method,
|
||||
headers,
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
await handleRateLimit(response);
|
||||
if (!response.ok) {
|
||||
console.log("request_url:", mize.get_config("habitica.api_url") + path);
|
||||
throw new Error(`Habitica API error: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
return (await response.json()).data;
|
||||
}
|
||||
};
|
||||
export {
|
||||
habitica,
|
||||
opts
|
||||
};
|
||||
105
packages/marts/habitica.ts
Normal file
105
packages/marts/habitica.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import { Mize } from "@ppc/marts/stub_deno";
|
||||
|
||||
export const opts = {
|
||||
"habitica.api_url": {
|
||||
env_var_name: "PPC_HABITICA_API_URL",
|
||||
default_val: "https://habitica.com/api/v3",
|
||||
},
|
||||
"habitica.user_id": {
|
||||
env_var_name: "PPC_HABITICA_USER_ID",
|
||||
},
|
||||
"habitica.client_name": {
|
||||
default_val: "3544a0b8-d71a-46e0-9bb1-6ddbb2abcddb-PPC-Software",
|
||||
},
|
||||
"habitica.api_token": {
|
||||
env_var_name: "PPC_HABITICA_API_TOKEN",
|
||||
},
|
||||
};
|
||||
|
||||
export async function habitica(mize: Mize) {
|
||||
console.log("hiiiiiiiiiiiiiiiiiiii");
|
||||
mize.add_opts(opts);
|
||||
|
||||
mize.add_part(new Habitica(mize));
|
||||
}
|
||||
|
||||
async function handleRateLimit(response) {
|
||||
const limit = response.headers.get("X-RateLimit-Limit") || "NONE";
|
||||
const remaining = parseInt(
|
||||
response.headers.get("X-RateLimit-Remaining") || "10",
|
||||
10,
|
||||
);
|
||||
const reset = response.headers.get("X-RateLimit-Reset");
|
||||
|
||||
console.log(
|
||||
`RateLimit: ${limit} | Remaining: ${remaining} | Reset: ${reset}`,
|
||||
);
|
||||
|
||||
if (remaining < 2 && reset) {
|
||||
const resetDate = new Date(reset);
|
||||
const now = new Date();
|
||||
const waitMs = resetDate.getTime() - now.getTime() + 1000;
|
||||
|
||||
if (waitMs > 0) {
|
||||
console.log(
|
||||
`Waiting ${Math.round(waitMs / 1000)} secs for next rate limit window...`,
|
||||
);
|
||||
await new Promise((resolve) => setTimeout(resolve, waitMs));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Habitica {
|
||||
mize: Mize;
|
||||
|
||||
constructor(mize: Mize) {
|
||||
this.mize = mize;
|
||||
}
|
||||
|
||||
async get_tasks(type: string = "todos") {
|
||||
return await this.api_request("GET", `tasks/user?type=${type}`);
|
||||
}
|
||||
|
||||
async delete_task(id: string) {
|
||||
await this.api_request("DELETE", `tasks/${id}`);
|
||||
}
|
||||
|
||||
async api_request(
|
||||
method: string,
|
||||
path: string,
|
||||
extraHeaders: object = {},
|
||||
data: object = {},
|
||||
) {
|
||||
const mize = this.mize;
|
||||
|
||||
if (!path.startsWith("/")) {
|
||||
path = "/" + path;
|
||||
}
|
||||
const headers = Object.assign(
|
||||
{
|
||||
"Content-Type": "application/json",
|
||||
"x-api-user": mize.get_config("habitica.user_id"),
|
||||
"x-api-key": mize.get_config("habitica.api_token"),
|
||||
"x-client": mize.get_config("habitica.client_name"),
|
||||
},
|
||||
extraHeaders,
|
||||
);
|
||||
|
||||
const response = await fetch(mize.get_config("habitica.api_url") + path, {
|
||||
method,
|
||||
headers,
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
|
||||
await handleRateLimit(response);
|
||||
|
||||
if (!response.ok) {
|
||||
console.log("request_url:", mize.get_config("habitica.api_url") + path);
|
||||
throw new Error(
|
||||
`Habitica API error: ${response.status} ${response.statusText}`,
|
||||
);
|
||||
}
|
||||
|
||||
return (await response.json()).data;
|
||||
}
|
||||
}
|
||||
0
packages/marts/js/glue_deno.js
Normal file
0
packages/marts/js/glue_deno.js
Normal file
35
packages/marts/js/glue_deno.rs
Normal file
35
packages/marts/js/glue_deno.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
use deno_core::extension;
|
||||
use deno_core::op2;
|
||||
use deno_core::OpState;
|
||||
use mize::{Mize, MizeError};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[op2(fast)]
|
||||
fn op_mize_get_part(#[string] name: &str) {
|
||||
println!("js wants the part {}", name);
|
||||
}
|
||||
|
||||
#[op2]
|
||||
#[string]
|
||||
fn op_mize_get_config(state: &mut OpState, #[string] key: &str) -> String {
|
||||
let mize: Mize = state.borrow::<Mize>().clone();
|
||||
format!("config_value_for_{}", key)
|
||||
}
|
||||
|
||||
#[op2(fast)]
|
||||
fn op_mize_add_part(state: &mut OpState, #[string] key: &str) {
|
||||
let mize: Mize = state.borrow::<Mize>().clone();
|
||||
println!("adding part {}", key);
|
||||
}
|
||||
|
||||
// Create an extension with your custom ops
|
||||
extension!(
|
||||
my_extension,
|
||||
ops = [op_mize_get_part, op_mize_get_config],
|
||||
options = {
|
||||
mize: Mize,
|
||||
},
|
||||
state = |state: &mut OpState, config: Config| {
|
||||
state.put::<Mize>(config.mize);
|
||||
},
|
||||
);
|
||||
@@ -1,32 +1,30 @@
|
||||
use deno_core::error::AnyError;
|
||||
use deno_core::FsModuleLoader;
|
||||
use deno_core::url::Url;
|
||||
use deno_core::{extension, op2, JsRuntime, ModuleSpecifier, PollEventLoopOptions, RuntimeOptions};
|
||||
use deno_core::{FastStaticString, FsModuleLoader};
|
||||
use mize::async_trait;
|
||||
use mize::instance::MizePartCreate;
|
||||
use mize::{mize_part, Mize, MizePart, MizeResult};
|
||||
use std::future::Future;
|
||||
use std::path::Path;
|
||||
use std::pin::Pin;
|
||||
use std::rc::Rc;
|
||||
use std::str::FromStr;
|
||||
use tokio::runtime::Builder;
|
||||
use tokio::task::{spawn_local, LocalSet};
|
||||
|
||||
mod glue_deno;
|
||||
|
||||
#[mize_part("js")]
|
||||
#[derive(Default)]
|
||||
pub struct JsPart {
|
||||
mize: Mize,
|
||||
js_runtime: JsRuntime,
|
||||
closure_sender: Option<flume::Sender<Box<dyn FnOnce(&mut JsRuntime) -> MizeResult<()> + Send>>>,
|
||||
async_closure_sender: Option<flume::Sender<BoxClosure>>,
|
||||
}
|
||||
|
||||
// Define your custom ops for the "mize" object inside deno
|
||||
#[op2(fast)]
|
||||
fn op_mize_get_part(#[string] name: &str) {
|
||||
println!("js wants the part {}", name);
|
||||
}
|
||||
|
||||
#[op2]
|
||||
#[string]
|
||||
fn op_mize_get_config(#[string] key: &str) -> String {
|
||||
format!("config_value_for_{}", key)
|
||||
}
|
||||
|
||||
// Create an extension with your custom ops
|
||||
extension!(mize_ext, ops = [op_mize_get_part, op_mize_get_config]);
|
||||
pub type BoxFuture<'a> = Pin<Box<dyn Future<Output = MizeResult<()>> + 'a>>;
|
||||
pub type BoxClosure = Box<dyn for<'a> FnOnce(&'a mut JsRuntime) -> BoxFuture<'a> + Send + 'static>;
|
||||
|
||||
impl MizePart for JsPart {
|
||||
fn init(&mut self, mize: &mut Mize) -> MizeResult<()> {
|
||||
@@ -35,26 +33,44 @@ impl MizePart for JsPart {
|
||||
}
|
||||
}
|
||||
|
||||
fn js(mize: &mut Mize) -> Box<dyn MizePart> {
|
||||
let mut js_runtime = JsRuntime::new(RuntimeOptions {
|
||||
module_loader: Some(Rc::new(FsModuleLoader)),
|
||||
extensions: vec![mize_ext::init_ops()],
|
||||
..Default::default()
|
||||
});
|
||||
let bootstrap_code = r#"
|
||||
globalThis.mize = {
|
||||
get_part: (name) => Deno.core.ops.op_mize_get_part(name),
|
||||
get_config: (key) => Deno.core.ops.op_mize_get_config(key),
|
||||
version: "1.0.0",
|
||||
// Add more methods as needed
|
||||
};
|
||||
"#;
|
||||
pub fn js(mize: &mut Mize) -> MizeResult<()> {
|
||||
let (closure_sender, closure_receiver) =
|
||||
flume::unbounded::<Box<dyn FnOnce(&mut JsRuntime) -> MizeResult<()> + Send>>();
|
||||
let (async_closure_sender, async_closure_receiver) = flume::unbounded::<BoxClosure>();
|
||||
let mize_clone = mize.clone();
|
||||
|
||||
js_runtime.execute_script("[bootstrap]", bootstrap_code)?;
|
||||
JsPart { mize, js_runtime }
|
||||
// the thread which will run any js
|
||||
mize.spawn("js_runtime_thread", || {
|
||||
js_runtime_thread(mize_clone, closure_receiver, async_closure_receiver)
|
||||
})?;
|
||||
|
||||
mize.add_part(Box::new(JsPart {
|
||||
mize: mize.clone(),
|
||||
closure_sender: Some(closure_sender),
|
||||
async_closure_sender: Some(async_closure_sender),
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn part_from_file(
|
||||
mize: &mut Mize,
|
||||
name: &'static str,
|
||||
code: FastStaticString,
|
||||
) -> MizeResult<()> {
|
||||
let mut js = mize.get_part_native::<JsPart>("js")?;
|
||||
js.with_runtime_async(move |runtime: &mut JsRuntime| {
|
||||
let module_spec = ModuleSpecifier::from_str(name).unwrap();
|
||||
return Box::pin(async move {
|
||||
runtime
|
||||
.load_side_es_module_from_code(&module_spec, code)
|
||||
.await;
|
||||
Ok(())
|
||||
});
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl JsPart {
|
||||
/*
|
||||
pub fn part_from_js_file(
|
||||
&mut self,
|
||||
name: &'static str,
|
||||
@@ -66,12 +82,32 @@ impl JsPart {
|
||||
js_file: path,
|
||||
})
|
||||
}
|
||||
pub fn runtime(&mut self) -> &mut JsRuntime {
|
||||
&mut self.js_runtime
|
||||
*/
|
||||
pub fn with_runtime<T: FnOnce(&mut JsRuntime) -> MizeResult<()> + Send + 'static>(
|
||||
&mut self,
|
||||
func: T,
|
||||
) {
|
||||
self.closure_sender
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.send(Box::new(func))
|
||||
.unwrap();
|
||||
}
|
||||
pub fn with_runtime_async<F>(&mut self, func: F)
|
||||
where
|
||||
F: for<'a> FnOnce(&'a mut JsRuntime) -> BoxFuture<'a> + Send + 'static,
|
||||
{
|
||||
self.async_closure_sender
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.send(Box::new(func))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
#[mize_part]
|
||||
#[derive(Default)]
|
||||
struct PartFromJsFileAdapter {
|
||||
mize: Mize,
|
||||
name: &'static str,
|
||||
@@ -92,22 +128,22 @@ impl MizePart for PartFromJsFileAdapter {
|
||||
let js_code = format!(
|
||||
r#"
|
||||
import {{ opts, deps, create }} from "{path}";
|
||||
|
||||
const isPromise = (value) => {
|
||||
|
||||
const isPromise = (value) => {{
|
||||
return !!value && (typeof value === 'object' || typeof value === 'function') && typeof value.then === 'function';
|
||||
};
|
||||
|
||||
}};
|
||||
|
||||
// Call the async function
|
||||
let create_result = create(mize);
|
||||
if (isPromise(create_result)) {
|
||||
if (isPromise(create_result)) {{
|
||||
create_result = await create_result;
|
||||
}
|
||||
const result = {
|
||||
}}
|
||||
const result = {{
|
||||
opts: opts(mize),
|
||||
deps: deps(mize),
|
||||
create: create_result,
|
||||
};
|
||||
|
||||
}};
|
||||
|
||||
result;
|
||||
"#
|
||||
);
|
||||
@@ -132,9 +168,9 @@ impl MizePart for PartFromJsFileAdapter {
|
||||
let js_code = format!(
|
||||
r#"
|
||||
import {{ run }} from "{path}";
|
||||
|
||||
|
||||
const result = run()
|
||||
|
||||
|
||||
result;
|
||||
"#
|
||||
);
|
||||
@@ -142,7 +178,7 @@ impl MizePart for PartFromJsFileAdapter {
|
||||
let result = js.runtime().execute_script("[main]", js_code)?;
|
||||
|
||||
// Resolve the promise and run event loop
|
||||
let resolved_value = js.runtime.resolve_value(result).await?;
|
||||
let resolved_value = js.runtime().resolve_value(result).await?;
|
||||
|
||||
// Run the event loop to completion
|
||||
js.runtime()
|
||||
@@ -153,3 +189,37 @@ impl MizePart for PartFromJsFileAdapter {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
fn js_runtime_thread(
|
||||
mize: Mize,
|
||||
closure_receiver: flume::Receiver<Box<dyn FnOnce(&mut JsRuntime) -> MizeResult<()> + Send>>,
|
||||
async_closure_receiver: flume::Receiver<BoxClosure>,
|
||||
) -> MizeResult<()> {
|
||||
let mut js_runtime = JsRuntime::new(RuntimeOptions {
|
||||
module_loader: Some(Rc::new(FsModuleLoader)),
|
||||
extensions: vec![glue_deno::my_extension::init(mize.clone())],
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let poll_opts = PollEventLoopOptions::default();
|
||||
|
||||
let tokio_runtime = Builder::new_current_thread().enable_all().build().unwrap();
|
||||
|
||||
let local = LocalSet::new();
|
||||
local.block_on(&tokio_runtime, async move {
|
||||
loop {
|
||||
// Drain queued closures quickly (no await here if possible)
|
||||
if let Ok(func) = closure_receiver.recv_async().await {
|
||||
if let Err(e) = func(&mut js_runtime) {
|
||||
mize.report_err(e);
|
||||
}
|
||||
}
|
||||
|
||||
// Drive JS one tick
|
||||
let _ = futures::future::poll_fn(|cx| js_runtime.poll_event_loop(cx, poll_opts)).await;
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
10
packages/marts/js/stub_deno.ts
Normal file
10
packages/marts/js/stub_deno.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export class Mize {
|
||||
async get_config(key: string): Promise<any> {
|
||||
return "hiiiiiii";
|
||||
}
|
||||
async add_opts(opts: any): Promise<void> {}
|
||||
async add_part(part: any): Promise<void> {}
|
||||
async report_err(err: MizeError) {}
|
||||
}
|
||||
|
||||
type MizeError = string;
|
||||
@@ -1,6 +1,17 @@
|
||||
#[cfg(feature = "target-os")]
|
||||
mod cli;
|
||||
#[cfg(feature = "target-os")]
|
||||
pub use cli::*;
|
||||
pub use cli::cli;
|
||||
|
||||
//mod js;
|
||||
mod js;
|
||||
|
||||
#[cfg(feature = "target-os")]
|
||||
pub fn habitica(mize: &mut mize::Mize) {
|
||||
use deno_core::{ascii_str_include, include_js_files};
|
||||
|
||||
js::part_from_file(
|
||||
mize,
|
||||
"habitica",
|
||||
ascii_str_include!("./deno_dist/habitica.js"),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user