diff --git a/Cargo.lock b/Cargo.lock index 22f2e18..a7c5dc6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1081,9 +1081,9 @@ dependencies = [ [[package]] name = "mrubyedge" -version = "1.1.9" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8196b89b47357c9d189abfdcb6024b0842817d089742cb127f5a223c11592a2b" +checksum = "7d5fb7c3a4987ec0c40b6363c463393f7550859e1963c6a4d4716d6169320160" dependencies = [ "plain", "rand_core 0.10.0", diff --git a/uzumibi-cli/templates/cloudflare/__features__/enable-external/src/index.js b/uzumibi-cli/templates/cloudflare/__features__/enable-external/src/index.js index 558cb9f..7930514 100644 --- a/uzumibi-cli/templates/cloudflare/__features__/enable-external/src/index.js +++ b/uzumibi-cli/templates/cloudflare/__features__/enable-external/src/index.js @@ -252,9 +252,20 @@ export default { const resResult = await exports.uzumibi_start_request(); const resOffset = Number(resResult & 0xFFFFFFFFn); - if (resOffset === 0) { - const errOffset = Number((resResult >> 32n) & 0xFFFFFFFFn); - const buffer = new Uint8Array(exports.memory.buffer, errOffset); + const upperBits = Number((resResult >> 32n) & 0xFFFFFFFFn); + + if (upperBits !== 0) { + const upperTag = (upperBits >> 16) & 0xFFFF; + if (upperTag === 0xFEFF) { + // Special route + if (upperBits === 0xFEFFFFFF) { + // Pass through to assets + return env.ASSETS.fetch(request); + } + throw new Error(`Unknown routing bits: 0x${upperBits.toString(16)}`); + } + // Error case + const buffer = new Uint8Array(exports.memory.buffer, upperBits); let errStr = ""; for (let i = 0; buffer[i] !== 0; i++) { errStr += String.fromCharCode(buffer[i]); diff --git a/uzumibi-cli/templates/cloudflare/__features__/enable-external/wasm-app/Cargo.toml_ b/uzumibi-cli/templates/cloudflare/__features__/enable-external/wasm-app/Cargo.toml_ index 9bce1c4..a765759 100644 --- a/uzumibi-cli/templates/cloudflare/__features__/enable-external/wasm-app/Cargo.toml_ +++ b/uzumibi-cli/templates/cloudflare/__features__/enable-external/wasm-app/Cargo.toml_ @@ -7,7 +7,7 @@ edition = "2024" crate-type = ["cdylib", "rlib"] [dependencies] -mrubyedge = { version = ">= 1.1", features = [ +mrubyedge = { version = ">= 1.1.10", features = [ "no-wasi", ], default-features = false } uzumibi-gem = ">= 0.5.0" diff --git a/uzumibi-cli/templates/cloudflare/__features__/enable-external/wasm-app/src/lib.rs b/uzumibi-cli/templates/cloudflare/__features__/enable-external/wasm-app/src/lib.rs index de76de9..6ee35d3 100644 --- a/uzumibi-cli/templates/cloudflare/__features__/enable-external/wasm-app/src/lib.rs +++ b/uzumibi-cli/templates/cloudflare/__features__/enable-external/wasm-app/src/lib.rs @@ -350,6 +350,18 @@ fn uzumibi_queue_class_send( Ok(RObject::boolean(true).to_refcount_assigned()) } +// ---- Assets pass-through ---- + +fn uzumibi_fetch_assets( + _vm: &mut VM, + _args: &[Rc], +) -> Result, mrubyedge::Error> { + Err(mrubyedge::Error::TaggedError( + "UzumibiPassAssets", + "pass assets to platform".to_string(), + )) +} + // ---- VM initialization ---- fn init_vm() -> Result { @@ -357,13 +369,23 @@ fn init_vm() -> Result { .map_err(|e| mrubyedge::Error::RuntimeError(format!("Failed to load mruby: {:?}", e)))?; let mut vm = VM::open(&mut rite); uzumibi_gem::init::init_uzumibi(&mut vm); + + let runtime_error = vm.get_class_by_name("RuntimeError"); + vm.define_class("UzumibiPassAssets", Some(runtime_error), None); + let object = vm.object_class.clone(); mrb_define_cmethod( &mut vm, - object, + object.clone(), "debug_console", Box::new(uzumibi_kernel_debug_console_log), ); + mrb_define_cmethod( + &mut vm, + object, + "fetch_assets", + Box::new(uzumibi_fetch_assets), + ); #[cfg(feature = "enable-external")] { @@ -457,10 +479,13 @@ unsafe extern "C" fn uzumibi_initialize_request(size: i32) -> u64 { } } +const PASS_ASSETS: u64 = 0xFEFFFFFF; + #[unsafe(export_name = "uzumibi_start_request")] unsafe extern "C" fn uzumibi_start_request() -> u64 { match do_uzumibi_start_request() { Ok(ptr) => (ptr as u32) as u64, + Err(mrubyedge::Error::TaggedError("UzumibiPassAssets", _)) => PASS_ASSETS << 32, Err(e) => { let err_buf = set_error_to_buf(format!("Error in start_request: {}", e)); ((err_buf as u32) as u64) << 32 diff --git a/uzumibi-cli/templates/cloudflare/__features__/enable-external/wrangler.jsonc b/uzumibi-cli/templates/cloudflare/__features__/enable-external/wrangler.jsonc index d194163..08e3213 100644 --- a/uzumibi-cli/templates/cloudflare/__features__/enable-external/wrangler.jsonc +++ b/uzumibi-cli/templates/cloudflare/__features__/enable-external/wrangler.jsonc @@ -10,6 +10,15 @@ "observability": { "enabled": true }, + /** + * Static Assets + * Served via env.ASSETS.fetch() when fetch_assets is called in a route + * https://developers.cloudflare.com/workers/static-assets/ + */ + "assets": { + "directory": "./public", + "binding": "ASSETS" + }, /** * Queues * Used for Uzumibi::Queue.send diff --git a/uzumibi-cli/templates/cloudflare/__features__/queue/wasm-app/Cargo.toml_ b/uzumibi-cli/templates/cloudflare/__features__/queue/wasm-app/Cargo.toml_ index d5978d1..7d83aca 100644 --- a/uzumibi-cli/templates/cloudflare/__features__/queue/wasm-app/Cargo.toml_ +++ b/uzumibi-cli/templates/cloudflare/__features__/queue/wasm-app/Cargo.toml_ @@ -7,7 +7,7 @@ edition = "2024" crate-type = ["cdylib", "rlib"] [dependencies] -mrubyedge = { version = ">= 1.1", features = [ +mrubyedge = { version = ">= 1.1.10", features = [ "no-wasi", ], default-features = false } uzumibi-gem = ">= 0.5.0" diff --git a/uzumibi-cli/templates/cloudflare/public/assets/index.html b/uzumibi-cli/templates/cloudflare/public/assets/index.html new file mode 100644 index 0000000..aa2b4fa --- /dev/null +++ b/uzumibi-cli/templates/cloudflare/public/assets/index.html @@ -0,0 +1,11 @@ + + + + + Uzumibi Assets + + +

Hello from Uzumibi static assets!

+

This page is served via Cloudflare Workers Assets.

+ + diff --git a/uzumibi-cli/templates/cloudflare/src/index.js b/uzumibi-cli/templates/cloudflare/src/index.js index 37fd491..943618f 100644 --- a/uzumibi-cli/templates/cloudflare/src/index.js +++ b/uzumibi-cli/templates/cloudflare/src/index.js @@ -118,11 +118,21 @@ export default { const resResult = exports.uzumibi_start_request(); const resOffset = Number(resResult & 0xFFFFFFFFn); - if (resOffset === 0) { - const errOffset = Number((resResult >> 32n) & 0xFFFFFFFFn); - const decoder = new TextDecoder(); + const upperBits = Number((resResult >> 32n) & 0xFFFFFFFFn); + + if (upperBits !== 0) { + const upperTag = (upperBits >> 16) & 0xFFFF; + if (upperTag === 0xFEFF) { + // Special route + if (upperBits === 0xFEFFFFFF) { + // Pass through to assets + return env.ASSETS.fetch(request); + } + throw new Error(`Unknown routing bits: 0x${upperBits.toString(16)}`); + } + // Error case + const buffer = new Uint8Array(exports.memory.buffer, upperBits); let errStr = ""; - const buffer = new Uint8Array(exports.memory.buffer, errOffset); for (let i = 0; buffer[i] !== 0; i++) { errStr += String.fromCharCode(buffer[i]); } diff --git a/uzumibi-cli/templates/cloudflare/wasm-app/Cargo.toml_ b/uzumibi-cli/templates/cloudflare/wasm-app/Cargo.toml_ index af2ec7d..9417c6c 100644 --- a/uzumibi-cli/templates/cloudflare/wasm-app/Cargo.toml_ +++ b/uzumibi-cli/templates/cloudflare/wasm-app/Cargo.toml_ @@ -7,7 +7,7 @@ edition = "2024" crate-type = ["cdylib", "rlib"] [dependencies] -mrubyedge = { version = ">= 1.1", features = [ +mrubyedge = { version = ">= 1.1.10", features = [ "no-wasi", ], default-features = false } uzumibi-gem = ">= 0.5.0" diff --git a/uzumibi-cli/templates/cloudflare/wasm-app/src/lib.rs b/uzumibi-cli/templates/cloudflare/wasm-app/src/lib.rs index 5714d38..6a9929b 100644 --- a/uzumibi-cli/templates/cloudflare/wasm-app/src/lib.rs +++ b/uzumibi-cli/templates/cloudflare/wasm-app/src/lib.rs @@ -53,18 +53,42 @@ fn uzumibi_kernel_debug_console_log( Ok(RObject::nil().to_refcount_assigned()) } +// ---- Assets pass-through ---- + +fn uzumibi_fetch_assets( + _vm: &mut VM, + _args: &[Rc], +) -> Result, mrubyedge::Error> { + Err(mrubyedge::Error::TaggedError( + "UzumibiPassAssets", + "pass assets to platform".to_string(), + )) +} + +// ---- VM initialization ---- + fn init_vm() -> Result { let mut rite = rite::load(MRB) .map_err(|e| mrubyedge::Error::RuntimeError(format!("Failed to load mruby: {:?}", e)))?; let mut vm = VM::open(&mut rite); uzumibi_gem::init::init_uzumibi(&mut vm); + + let runtime_error = vm.get_class_by_name("RuntimeError"); + vm.define_class("UzumibiPassAssets", Some(runtime_error), None); + let object = vm.object_class.clone(); mrb_define_cmethod( &mut vm, - object, + object.clone(), "debug_console", Box::new(uzumibi_kernel_debug_console_log), ); + mrb_define_cmethod( + &mut vm, + object, + "fetch_assets", + Box::new(uzumibi_fetch_assets), + ); vm.run() .map_err(|e| mrubyedge::Error::RuntimeError(format!("Failed to init VM: {:?}", e)))?; @@ -125,10 +149,13 @@ unsafe extern "C" fn uzumibi_initialize_request(size: i32) -> u64 { } } +const PASS_ASSETS: u64 = 0xFEFFFFFF; + #[unsafe(export_name = "uzumibi_start_request")] unsafe extern "C" fn uzumibi_start_request() -> u64 { match do_uzumibi_start_request() { Ok(ptr) => (ptr as u32) as u64, + Err(mrubyedge::Error::TaggedError("UzumibiPassAssets", _)) => PASS_ASSETS << 32, Err(e) => { let err_buf = set_error_to_buf(format!("Error in start_request: {}", e)); ((err_buf as u32) as u64) << 32 diff --git a/uzumibi-cli/templates/cloudflare/wrangler.jsonc b/uzumibi-cli/templates/cloudflare/wrangler.jsonc index 32fc199..382141a 100644 --- a/uzumibi-cli/templates/cloudflare/wrangler.jsonc +++ b/uzumibi-cli/templates/cloudflare/wrangler.jsonc @@ -9,33 +9,14 @@ "compatibility_date": "2025-12-30", "observability": { "enabled": true - } - /** - * Smart Placement - * https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement - */ - // "placement": { "mode": "smart" } - /** - * Bindings - * Bindings allow your Worker to interact with resources on the Cloudflare Developer Platform, including - * databases, object storage, AI inference, real-time communication and more. - * https://developers.cloudflare.com/workers/runtime-apis/bindings/ - */ - /** - * Environment Variables - * https://developers.cloudflare.com/workers/wrangler/configuration/#environment-variables - * Note: Use secrets to store sensitive data. - * https://developers.cloudflare.com/workers/configuration/secrets/ - */ - // "vars": { "MY_VARIABLE": "production_value" } + }, /** * Static Assets - * https://developers.cloudflare.com/workers/static-assets/binding/ + * Served via env.ASSETS.fetch() when fetch_assets is called in a route + * https://developers.cloudflare.com/workers/static-assets/ */ - // "assets": { "directory": "./public/", "binding": "ASSETS" } - /** - * Service Bindings (communicate between multiple Workers) - * https://developers.cloudflare.com/workers/wrangler/configuration/#service-bindings - */ - // "services": [ { "binding": "MY_SERVICE", "service": "my-service" } ] + "assets": { + "directory": "./public", + "binding": "ASSETS" + } } \ No newline at end of file diff --git a/uzumibi-cli/templates/cloudrun/Cargo.toml_ b/uzumibi-cli/templates/cloudrun/Cargo.toml_ index 368f062..7e10f79 100644 --- a/uzumibi-cli/templates/cloudrun/Cargo.toml_ +++ b/uzumibi-cli/templates/cloudrun/Cargo.toml_ @@ -5,7 +5,7 @@ edition = "2024" publish = false [dependencies] -mrubyedge = { version = ">= 1.1", features = [ +mrubyedge = { version = ">= 1.1.10", features = [ "no-wasi", ], default-features = false } uzumibi-gem = ">= 0.5.0" diff --git a/uzumibi-cli/templates/fastly/Cargo.toml_ b/uzumibi-cli/templates/fastly/Cargo.toml_ index 93f4bb6..931bf54 100644 --- a/uzumibi-cli/templates/fastly/Cargo.toml_ +++ b/uzumibi-cli/templates/fastly/Cargo.toml_ @@ -9,7 +9,7 @@ publish = false crate-type = ["cdylib", "rlib"] [dependencies] -mrubyedge = { version = ">= 1.1", features = [ +mrubyedge = { version = ">= 1.1.10", features = [ "wasi", ], default-features = false } uzumibi-gem = ">= 0.5.0" diff --git a/uzumibi-cli/templates/spin/Cargo.toml_ b/uzumibi-cli/templates/spin/Cargo.toml_ index d82dc84..8f52372 100644 --- a/uzumibi-cli/templates/spin/Cargo.toml_ +++ b/uzumibi-cli/templates/spin/Cargo.toml_ @@ -12,7 +12,7 @@ crate-type = ["cdylib"] [dependencies] anyhow = "1" spin-sdk = "5.0.0" -mrubyedge = { version = ">= 1.1", features = [ +mrubyedge = { version = ">= 1.1.10", features = [ "wasi", ], default-features = false } uzumibi-gem = ">= 0.5.0" diff --git a/uzumibi-cli/tests/runn/new_cloudflare.yml b/uzumibi-cli/tests/runn/new_cloudflare.yml index 5aff7b3..bc3f64a 100644 --- a/uzumibi-cli/tests/runn/new_cloudflare.yml +++ b/uzumibi-cli/tests/runn/new_cloudflare.yml @@ -54,6 +54,12 @@ steps: command: test -f {{ vars.tmpdir }}/{{ vars.project_name }}/wrangler.jsonc test: current.exit_code == 0 + check_assets_index_html: + desc: Check public/assets/index.html exists and contains expected content + exec: + command: grep -l 'Uzumibi' {{ vars.tmpdir }}/{{ vars.project_name }}/public/assets/index.html + test: current.exit_code == 0 + pnpm_install: desc: Install Node.js dependencies exec: diff --git a/uzumibi-on-cloudflare-spike/lib/app.rb b/uzumibi-on-cloudflare-spike/lib/app.rb index c75a54f..4d72843 100644 --- a/uzumibi-on-cloudflare-spike/lib/app.rb +++ b/uzumibi-on-cloudflare-spike/lib/app.rb @@ -45,6 +45,10 @@ class App < Uzumibi::Router res end + get "/assets/*" do |req, res| + fetch_assets + end + get "/healthz" do |req, res| res.status_code = 200 res.headers = { diff --git a/uzumibi-on-cloudflare-spike/public/assets/hello.html b/uzumibi-on-cloudflare-spike/public/assets/hello.html new file mode 100644 index 0000000..aa2b4fa --- /dev/null +++ b/uzumibi-on-cloudflare-spike/public/assets/hello.html @@ -0,0 +1,11 @@ + + + + + Uzumibi Assets + + +

Hello from Uzumibi static assets!

+

This page is served via Cloudflare Workers Assets.

+ + diff --git a/uzumibi-on-cloudflare-spike/public/assets/hello.txt b/uzumibi-on-cloudflare-spike/public/assets/hello.txt new file mode 100644 index 0000000..ed41fcd --- /dev/null +++ b/uzumibi-on-cloudflare-spike/public/assets/hello.txt @@ -0,0 +1 @@ +Hello from Uzumibi static assets! diff --git a/uzumibi-on-cloudflare-spike/public/assets/index.html b/uzumibi-on-cloudflare-spike/public/assets/index.html new file mode 100644 index 0000000..9aecdcd --- /dev/null +++ b/uzumibi-on-cloudflare-spike/public/assets/index.html @@ -0,0 +1 @@ +

Hello, index.html!

\ No newline at end of file diff --git a/uzumibi-on-cloudflare-spike/src/index.js b/uzumibi-on-cloudflare-spike/src/index.js index 8f3920f..eb1e5cb 100644 --- a/uzumibi-on-cloudflare-spike/src/index.js +++ b/uzumibi-on-cloudflare-spike/src/index.js @@ -252,9 +252,20 @@ export default { const resResult = await exports.uzumibi_start_request(); const resOffset = Number(resResult & 0xFFFFFFFFn); - if (resOffset === 0) { - const errOffset = Number((resResult >> 32n) & 0xFFFFFFFFn); - const buffer = new Uint8Array(exports.memory.buffer, errOffset); + const upperBits = Number((resResult >> 32n) & 0xFFFFFFFFn); + + if (upperBits !== 0) { + const upperTag = (upperBits >> 16) & 0xFFFF; + if (upperTag === 0xFEFF) { + // Special route + if (upperBits === 0xFEFFFFFF) { + // Pass through to assets + return env.ASSETS.fetch(request); + } + throw new Error(`Unknown routing bits: 0x${upperBits.toString(16)}`); + } + // Error case + const buffer = new Uint8Array(exports.memory.buffer, upperBits); let errStr = ""; for (let i = 0; buffer[i] !== 0; i++) { errStr += String.fromCharCode(buffer[i]); diff --git a/uzumibi-on-cloudflare-spike/wasm-app/Cargo.toml b/uzumibi-on-cloudflare-spike/wasm-app/Cargo.toml index cbe62cd..d8b0554 100644 --- a/uzumibi-on-cloudflare-spike/wasm-app/Cargo.toml +++ b/uzumibi-on-cloudflare-spike/wasm-app/Cargo.toml @@ -7,7 +7,7 @@ edition = "2024" crate-type = ["cdylib", "rlib"] [dependencies] -mrubyedge = { version = ">= 1.1", features = [ +mrubyedge = { version = ">= 1.1.10", features = [ "no-wasi", ], default-features = false } uzumibi-gem = ">= 0.5.0" diff --git a/uzumibi-on-cloudflare-spike/wasm-app/src/lib.rs b/uzumibi-on-cloudflare-spike/wasm-app/src/lib.rs index 3443aa4..00c2cf7 100644 --- a/uzumibi-on-cloudflare-spike/wasm-app/src/lib.rs +++ b/uzumibi-on-cloudflare-spike/wasm-app/src/lib.rs @@ -440,6 +440,18 @@ fn uzumibi_consumer_on_receive( )) } +// ---- Assets pass-through ---- + +fn uzumibi_fetch_assets( + _vm: &mut VM, + _args: &[Rc], +) -> Result, mrubyedge::Error> { + Err(mrubyedge::Error::TaggedError( + "UzumibiPassAssets", + "pass assets to platform".to_string(), + )) +} + // ---- VM initialization ---- fn init_vm() -> Result { @@ -447,13 +459,23 @@ fn init_vm() -> Result { .map_err(|e| mrubyedge::Error::RuntimeError(format!("Failed to load mruby: {:?}", e)))?; let mut vm = VM::open(&mut rite); uzumibi_gem::init::init_uzumibi(&mut vm); + + let runtime_error = vm.get_class_by_name("RuntimeError"); + vm.define_class("UzumibiPassAssets", Some(runtime_error), None); + let object = vm.object_class.clone(); mrb_define_cmethod( &mut vm, - object, + object.clone(), "debug_console", Box::new(uzumibi_kernel_debug_console_log), ); + mrb_define_cmethod( + &mut vm, + object, + "fetch_assets", + Box::new(uzumibi_fetch_assets), + ); #[cfg(feature = "enable-external")] { @@ -586,10 +608,13 @@ unsafe extern "C" fn uzumibi_initialize_request(size: i32) -> u64 { } } +const PASS_ASSETS: u64 = 0xFEFFFFFF; + #[unsafe(export_name = "uzumibi_start_request")] unsafe extern "C" fn uzumibi_start_request() -> u64 { match do_uzumibi_start_request() { Ok(ptr) => (ptr as u32) as u64, + Err(mrubyedge::Error::TaggedError("UzumibiPassAssets", _)) => PASS_ASSETS << 32, Err(e) => { let err_buf = set_error_to_buf(format!("Error in start_request: {}", e)); ((err_buf as u32) as u64) << 32 diff --git a/uzumibi-on-cloudflare-spike/wrangler.jsonc b/uzumibi-on-cloudflare-spike/wrangler.jsonc index 7954502..b0db148 100644 --- a/uzumibi-on-cloudflare-spike/wrangler.jsonc +++ b/uzumibi-on-cloudflare-spike/wrangler.jsonc @@ -10,6 +10,15 @@ "observability": { "enabled": true }, + /** + * Assets + * Static files served via env.ASSETS.fetch() + * https://developers.cloudflare.com/workers/static-assets/ + */ + "assets": { + "directory": "./public", + "binding": "ASSETS" + }, /** * Durable Objects * Used for Uzumibi::KV.get/set