Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Bridge between integration apps and our integration plattform.

Notifications You must be signed in to change notification settings

standout/app_bridge

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

105 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Standout App Bridge

app_bridge is a Ruby gem designed to facilitate communication with WebAssembly components that implement the WIT specification standout:app. This gem is developed for use in Standout's products.

Installation

Add the following line to your Gemfile:

gem 'app_bridge'

Then, install the gem by running:

bundle install

Usage

To use this gem, you need a WebAssembly component that adheres to the specification defined in ext/app_bridge/wit/v4/world.wit (or an older supported version like v3).

You can check out the example components in spec/fixtures/components to see how such a component should be structured.

Once you have a WebAssembly component, you can use the gem as follows:

require 'app_bridge'

app = AppBridge::App.new('path/to/your/component.wasm')
app.triggers # => ['trigger1', 'trigger2']

File Handling

The gem provides a file.normalize function for handling files in connectors. It automatically detects the input format (URL, data URI, or base64) and returns normalized file data.

In your WASM connector (JavaScript):

import { normalize } from 'standout:app/[email protected]';

// Normalize any file source - input type is auto-detected
const fileData = normalize(
  input.fileUrl,           // URL, data URI, or base64 string
  [["Authorization", token]], // Optional headers for URL requests
  "invoice.pdf"            // Optional filename override
);

// Returns: { base64, contentType, filename }

// Include in your action output
return {
  serializedOutput: JSON.stringify({
    document: fileData  // Will be replaced with blob ID by platform
  })
};

In your WASM connector (Rust):

use crate::standout::app::file::normalize;

let file_data = normalize(
    &input.file_url,
    Some(&[("Authorization".to_string(), token)]),
    Some("invoice.pdf"),
)?;

// file_data contains { base64, content_type, filename }

Output schema:

Mark file fields with format: "file-output" so the platform knows to process them:

{
  "properties": {
    "document": {
      "type": "object",
      "format": "file-output"
    }
  }
}

Platform configuration:

Configure the file uploader in your Rails app:

AppBridge.file_uploader = ->(file_data) {
  blob = ActiveStorage::Blob.create_and_upload!(
    io: StringIO.new(Base64.decode64(file_data['base64'])),
    filename: file_data['filename'],
    content_type: file_data['content_type']
  )
  blob.signed_id
}

The gem automatically replaces file data with the return value (in this example blob IDs) before returning the action response.

Multipart Form Data

For multipart/form-data, you must build the body manually and set the Content-Type header with a boundary. In standout:[email protected] you can send raw bytes via body-bytes; earlier versions only support a string body.

In your WASM connector (Rust):

let boundary = "----app-bridge-boundary";

let mut body = Vec::new();
body.extend_from_slice(format!(
    "--{b}\r\n\
Content-Disposition: form-data; name=\"metadata\"\r\n\r\n\
{metadata}\r\n\
--{b}\r\n\
Content-Disposition: form-data; name=\"file\"; filename=\"invoice.pdf\"\r\n\
Content-Type: application/pdf\r\n\r\n",
    b = boundary,
    metadata = metadata_json,
).as_bytes());
body.extend_from_slice(&file_bytes);
body.extend_from_slice(format!("\r\n--{b}--\r\n", b = boundary).as_bytes());

let response = RequestBuilder::new()
    .method(Method::Post)
    .url(upload_url)
    .header("Content-Type", format!("multipart/form-data; boundary={}", boundary))
    .body_bytes(body)
    .send()?;

If you are targeting standout:[email protected] or earlier, you can only send a string body. That means multipart uploads must be base64-encoded and the receiving API must support Content-Transfer-Encoding: base64.

Retry With Reference

standout:[email protected] introduces a structured retry error for actions. Connectors can return a retry error with a reference and status, and the platform can pass that back on subsequent retries via action-context.reference-object.

In your WASM connector (Rust)

return Err(AppError {
    code: ErrorCode::RetryWithReference(ReferenceObject {
        reference: request_id.to_string(),
        status: status.to_string(),
    }),
    message: "Background request still processing".to_string(),
});

In the Ruby platform

When the connector returns retry-with-reference, the bridge raises AppBridge::RetryWithReferenceError. You can access the structured fields:

rescue AppBridge::RetryWithReferenceError => e
  e.reference # => "ref-123"
  e.status    # => "queued"
  e.message   # => "Background request still processing after 1 attempts."
end

Backward Compatibility

The gem supports multi-version WIT interfaces, allowing connectors built against older WIT versions to continue working when the gem is updated.

How it works

When loading a WASM component, the gem automatically detects which WIT version it was built against:

  1. V4 components (current, standout:[email protected]): Full feature support including the file interface
  2. V3 components (standout:[email protected]): Legacy support without file interface

Adding support for new WIT versions

When adding a new WIT version (e.g., v5), follow these steps:

1. Create the WIT file

Copy the latest version and modify:

cp -r ext/app_bridge/wit/v4 ext/app_bridge/wit/v5

Edit ext/app_bridge/wit/v5/world.wit:

  • Update the package version: package standout:[email protected];
  • Add new interfaces or modify existing ones

2. Add the bindgen module

In ext/app_bridge/src/component.rs, add after the existing modules:

pub mod v5 {
    wasmtime::component::bindgen!({
        path: "./wit/v5",
        world: "bridge",
    });
}

3. Generate type conversions

Add the conversion macro call:

impl_conversions!(v5);

4. Add BridgeWrapper variant

Update the enum:

pub enum BridgeWrapper {
    V3(v3::Bridge),
    V4(v4::Bridge),
    V5(v5::Bridge),  // <-- add this
}

Add an arm to each bridge_method! macro expansion. In the macro definitions, add:

BridgeWrapper::V5(b) => {
    let r = b.$interface().$method(store, ...)?;
    Ok(r.map(Into::into).map_err(Into::into))
}

5. Register interfaces in the linker

In build_linker():

// v5: http + environment + file + new_feature
v5::standout::app::http::add_to_linker(&mut linker, |s| s)?;
v5::standout::app::environment::add_to_linker(&mut linker, |s| s)?;
v5::standout::app::file::add_to_linker(&mut linker, |s| s)?;
v5::standout::app::new_feature::add_to_linker(&mut linker, |s| s)?;  // if applicable

6. Update the instantiation chain

In app(), add v5 at the top (newest first):

// v5 (newest)
if let Ok(instance) = v5::Bridge::instantiate(&mut *store, &component, &linker) {
    return Ok(BridgeWrapper::V5(instance));
}

// v4
if let Ok(instance) = v4::Bridge::instantiate(&mut *store, &component, &linker) {
    return Ok(BridgeWrapper::V4(instance));
}

// v3 (oldest)
// ...

7. Register Host implementations

In app_state.rs:

impl_host_for_version!(v5);

In request_builder.rs:

impl_host_request_builder!(v5);
impl_http_type_conversions!(v5);

8. If the version has the file interface

In file_ops.rs (only if v5 includes the file interface):

impl_file_host!(v5);

9. If adding new host functions

If the new version introduces entirely new interfaces (not just file), implement the Host trait:

impl v5::standout::app::new_feature::Host for AppState {
    fn some_function(&mut self, ...) -> ... {
        // implementation
    }
}

Benefits

  • No forced rebuilds: Existing connectors continue to work after gem updates
  • Gradual migration: Update connectors to new WIT versions at your own pace
  • Type safety: Each version's types are converted to the latest version internally

Development

To contribute or modify this gem, ensure you have the following dependencies installed:

  • Ruby 3.3.0 (or later)
  • Rust 1.84.0 (or later)

Setting Up the Development Environment

Run the following command to setup and install additional dependencies:

bin/setup

Then, to compile example applications, run tests, and perform syntax checks, execute:

rake

Useful Commands

  • Interactive Console: Run bin/console to interactively test the code.
  • Full Test Suite & Linting: Run rake to compile, execute tests, and perform syntax checks.
  • Run Tests Only: Execute rake spec to run only the test suite.
  • Linting: Run rake rubocop to check code style and formatting.
  • Compile Example Applications: Use rake fixtures to build the example apps.

To install this gem locally for testing purposes, run:

bundle exec rake install

Release & Distribution

To release a new version of the gem, update the version number in lib/app_bridge/version.rb and in ext/app_bridge/Cargo.toml. They should be the same.

Then push the changes to the repository and create a new release on GitHub. The gem will be automatically built and published to RubyGems.

About

Bridge between integration apps and our integration plattform.

Topics

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors 4

  •  
  •  
  •  
  •