app_bridge 4.0.0-aarch64-linux → 4.1.1-aarch64-linux

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cab97976e4f0f1a702030c3299f960f7e55d93d1a3bbbab6f91f073923602fe8
4
- data.tar.gz: 3050ea6d00b9d7502add60d950a1e8b1a52ada913bbabb2a2a12042ad43a05db
3
+ metadata.gz: '0956b97581a6ec5f02c7f5a76f5f9f64572850dd150bb499df0cc4a832067801'
4
+ data.tar.gz: cdcbd2e154fe007150809540bd840f99e02fbe533f0bdfeb22faaa467457d5ec
5
5
  SHA512:
6
- metadata.gz: 7cf52a8112aa404d0397f54e38f74e8bdd2bb4d279036a712d54ad0dcd34dafd2aef5473eb023a4cbe7547f739c078cc6cde90e3c153df948091866c7b9aecfe
7
- data.tar.gz: a56d0198771e98b4888ff1e04692ffa5f171391474b53c946eff3f0f8c10286465ace276462fef1a95c4be5a928e11c191b3d8cad5686b5cd62f4b69b9da1ad2
6
+ metadata.gz: a165d3d086527d978bf2a51e4dcbcf54647e965d101f3e78e41e428f984d3367f883cefa9c2f35a8aa3caeee639b9b7d1f68a2cfdbaed28b6c6bd64c6dc4d7b8
7
+ data.tar.gz: d311bdf6107e7c87436bcf166b05b8b30ace8963e32c02f0022d720bf8e6a3feb34cf7ca7cfcd1f9d6c2d35a94f6ab9dafb859ea761c0357f8335453af2bcc85
data/README.md CHANGED
@@ -103,6 +103,67 @@ AppBridge.file_uploader = ->(file_data) {
103
103
 
104
104
  The gem automatically replaces file data with the return value (in this example blob IDs) before returning the action response.
105
105
 
106
+ ### Multipart Form Data
107
+
108
+ For `multipart/form-data`, you must build the body manually and set the `Content-Type` header with a boundary. In `standout:app@4.1.0` you can send raw bytes via `body-bytes`; earlier versions only support a string body.
109
+
110
+ #### In your WASM connector (Rust):
111
+
112
+ ```rust
113
+ let boundary = "----app-bridge-boundary";
114
+
115
+ let mut body = Vec::new();
116
+ body.extend_from_slice(format!(
117
+ "--{b}\r\n\
118
+ Content-Disposition: form-data; name=\"metadata\"\r\n\r\n\
119
+ {metadata}\r\n\
120
+ --{b}\r\n\
121
+ Content-Disposition: form-data; name=\"file\"; filename=\"invoice.pdf\"\r\n\
122
+ Content-Type: application/pdf\r\n\r\n",
123
+ b = boundary,
124
+ metadata = metadata_json,
125
+ ).as_bytes());
126
+ body.extend_from_slice(&file_bytes);
127
+ body.extend_from_slice(format!("\r\n--{b}--\r\n", b = boundary).as_bytes());
128
+
129
+ let response = RequestBuilder::new()
130
+ .method(Method::Post)
131
+ .url(upload_url)
132
+ .header("Content-Type", format!("multipart/form-data; boundary={}", boundary))
133
+ .body_bytes(body)
134
+ .send()?;
135
+ ```
136
+
137
+ If you are targeting `standout:app@4.0.0` 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`.
138
+
139
+ ## Retry With Reference
140
+
141
+ `standout:app@4.1.0` 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`.
142
+
143
+ ### In your WASM connector (Rust)
144
+
145
+ ```rust
146
+ return Err(AppError {
147
+ code: ErrorCode::RetryWithReference(ReferenceObject {
148
+ reference: request_id.to_string(),
149
+ status: status.to_string(),
150
+ }),
151
+ message: "Background request still processing".to_string(),
152
+ });
153
+ ```
154
+
155
+ ### In the Ruby platform
156
+
157
+ When the connector returns `retry-with-reference`, the bridge raises `AppBridge::RetryWithReferenceError`. You can access the structured fields:
158
+
159
+ ```ruby
160
+ rescue AppBridge::RetryWithReferenceError => e
161
+ e.reference # => "ref-123"
162
+ e.status # => "queued"
163
+ e.message # => "Background request still processing after 1 attempts."
164
+ end
165
+ ```
166
+
106
167
  ## Backward Compatibility
107
168
 
108
169
  The gem supports **multi-version WIT interfaces**, allowing connectors built against older WIT versions to continue working when the gem is updated.
@@ -0,0 +1,345 @@
1
+ package standout:app@4.1.0;
2
+
3
+ interface types {
4
+ // The trigger-store is a string that is used to store data between trigger
5
+ // invocations. It is unique per trigger instance and is persisted between
6
+ // invocations.
7
+ //
8
+ // You can store any string here. We suggest that you use a serialized
9
+ // JSON object or similar since that will give you some flexibility if you
10
+ // need to add more data to the store.
11
+ type trigger-store = string;
12
+
13
+ record connection {
14
+ id: string,
15
+ name: string,
16
+ // The connection data is a JSON object serialized into a string. The JSON root
17
+ // will always be an object.
18
+ serialized-data: string,
19
+ }
20
+
21
+ record trigger-context {
22
+ // Trigger ID is a unique identifier for the trigger that is requested to be
23
+ // invoked.
24
+ trigger-id: string,
25
+
26
+ // The connection that the trigger is invoked for.
27
+ // Connection is required for all trigger operations.
28
+ connection: connection,
29
+
30
+ // The store will contain the data that was stored in the trigger store the
31
+ // last time the trigger was invoked.
32
+ store: trigger-store,
33
+
34
+ // The input data for the trigger, serialized as a JSON object string.
35
+ // This contains the input data from the trigger configuration form.
36
+ serialized-input: string,
37
+ }
38
+
39
+ record action-context {
40
+ // Action ID is a unique identifier for the action that is requested to be
41
+ // invoked.
42
+ action-id: string,
43
+
44
+ // The connection that the action is invoked for.
45
+ // Connection is required for all action operations.
46
+ connection: connection,
47
+
48
+ // The input data for the action, serialized as a JSON object string.
49
+ // This contains the data passed from the previous step in the workflow.
50
+ serialized-input: string,
51
+
52
+ // Optional reference information when the platform retries an action.
53
+ reference-object: option<reference-object>,
54
+ }
55
+
56
+ record trigger-response {
57
+ // The trigger events, each event will be used to spawn a new workflow
58
+ // execution in Standouts integration platform.
59
+ events: list<trigger-event>,
60
+
61
+ // The updated store will be stored and used the next time the trigger is
62
+ // invoked.
63
+ store: trigger-store,
64
+ }
65
+
66
+ record action-response {
67
+ // The output data from the action, serialized as a JSON object string.
68
+ // This contains the data that will be passed to the next step in the workflow.
69
+ // The data must be a valid JSON object (not an array or primitive).
70
+ serialized-output: string
71
+ }
72
+
73
+ record trigger-event {
74
+ // The ID of the trigger event
75
+ //
76
+ // If the connection used for the given instance of the trigger is the same,
77
+ // as seen before. Then the event will be ignored.
78
+ //
79
+ // A scheduler could therefore use a timestamp as the ID, to ensure that
80
+ // the event is only triggered once per given time.
81
+ //
82
+ // A trigger that acts on created orders in a e-commerce system could use
83
+ // the order ID as the ID, to ensure that the event is only triggered once
84
+ // per order.
85
+ //
86
+ // A trigger that acts on updated orders in a e-commerce system could use
87
+ // the order ID in combination with an updated at timestamp as the ID, to
88
+ // ensure that the event is only triggered once per order update.
89
+ id: string,
90
+
91
+ // Serialized data must be a JSON object serialized into a string
92
+ // Note that it is important that the root is an object, not an array,
93
+ // or another primitive type.
94
+ serialized-data: string,
95
+ }
96
+
97
+ /// Retry reference payload returned with error-code.retry-with-reference.
98
+ record reference-object {
99
+ /// Reference ID provided for retrying this request later.
100
+ reference: string,
101
+
102
+ /// Status describing the retry state.
103
+ status: string,
104
+ }
105
+
106
+ record app-error {
107
+ /// The error code identifying the type of failure.
108
+ code: error-code,
109
+
110
+ /// A human-readable message describing the error in more detail.
111
+ message: string,
112
+ }
113
+
114
+ /// An enumeration of error codes that can be returned by a trigger implementation.
115
+ /// These codes help the platform and plugin developers distinguish between different types of failures.
116
+ variant error-code {
117
+ /// Authentication failed. Typically due to an invalid or expired API key or token.
118
+ unauthenticated,
119
+
120
+ /// Authorization failed. The connection is valid but does not have the necessary permissions.
121
+ forbidden,
122
+
123
+ /// The trigger is misconfigured. For example, a required setting is missing or invalid.
124
+ misconfigured,
125
+
126
+ /// The target system does not support a required feature or endpoint.
127
+ unsupported,
128
+
129
+ /// The target system is rate-limiting requests. Try again later.
130
+ rate-limit,
131
+
132
+ /// The request timed out. The target system did not respond in time.
133
+ timeout,
134
+
135
+ /// The target system is currently unavailable or unreachable.
136
+ unavailable,
137
+
138
+ /// An unexpected internal error occurred in the plugin.
139
+ internal-error,
140
+
141
+ /// The response from the external system could not be parsed or was in an invalid format.
142
+ malformed-response,
143
+
144
+ /// A catch-all for all other types of errors. Should include a descriptive message.
145
+ other,
146
+
147
+ /// Retry the request using a reference identifier.
148
+ retry-with-reference(reference-object),
149
+
150
+ /// Complete the current workflow execution.
151
+ complete-workflow,
152
+
153
+ /// Complete the parent step execution.
154
+ complete-parent,
155
+ }
156
+ }
157
+
158
+
159
+ interface triggers {
160
+ use types.{trigger-context, trigger-event, trigger-response, app-error};
161
+
162
+ trigger-ids: func() -> result<list<string>, app-error>;
163
+
164
+ // Get the input schema for a specific trigger
165
+ // Returns a JSON Schema Draft 2020-12 schema as a string
166
+ // The schema may vary based on the connection in the context
167
+ // The trigger-id is extracted from the context
168
+ input-schema: func(context: trigger-context) -> result<string, app-error>;
169
+
170
+ // Get the output schema for a specific trigger
171
+ // Returns a JSON Schema Draft 2020-12 schema as a string
172
+ // The schema may vary based on the connection in the context
173
+ // The trigger-id is extracted from the context
174
+ output-schema: func(context: trigger-context) -> result<string, app-error>;
175
+
176
+ // Fetch events
177
+ //
178
+ // There are some limitations to the function:
179
+ // - It must return a `trigger-response` within 30 seconds
180
+ // - It must return less than or equal to 100 `trigger-response.events`
181
+ // - It must not return more than 64 kB of data in the `trigger-response.store`
182
+ //
183
+ // If you need to fetch more events, you can return up to 100 events and then
184
+ // store the data needed for you to remember where you left off in the store.
185
+ // The next time the trigger is invoked, you can use the store to continue
186
+ // where you left off.
187
+ //
188
+ // If you do not pass the limitations the return value will be ignored. We
189
+ // will not handle any events and we persist the store that was returned in
190
+ // the response.
191
+ //
192
+ // That also means that you should implement your fetch event function in a
193
+ // way that it can be called multiple times using the same context and return
194
+ // the same events. That will ensure that the user that is building an
195
+ // integration with your trigger will not miss any events if your system is
196
+ // down for a short period of time.
197
+ fetch-events: func(context: trigger-context) -> result<trigger-response, app-error>;
198
+ }
199
+
200
+ interface actions {
201
+ use types.{action-context, action-response, app-error};
202
+
203
+ action-ids: func() -> result<list<string>, app-error>;
204
+
205
+ // Get the input schema for a specific action
206
+ // Returns a JSON Schema Draft 2020-12 schema as a string
207
+ // The schema may vary based on the connection in the context
208
+ // The action-id is extracted from the context
209
+ input-schema: func(context: action-context) -> result<string, app-error>;
210
+
211
+ // Get the output schema for a specific action
212
+ // Returns a JSON Schema Draft 2020-12 schema as a string
213
+ // The schema may vary based on the connection in the context
214
+ // The action-id is extracted from the context
215
+ output-schema: func(context: action-context) -> result<string, app-error>;
216
+
217
+ // Execute an action
218
+ //
219
+ // There are some limitations to the function:
220
+ // - It must return an `action-response` within 30 seconds
221
+ // - The serialized-output must be a valid JSON object serialized as a string
222
+ //
223
+ // Actions can perform various operations such as:
224
+ // - Making HTTP requests to external APIs
225
+ // - Processing and transforming data
226
+ // - Storing data for future use
227
+ // - Triggering other systems or workflows
228
+ //
229
+ // The action receives input data from the previous step and can return
230
+ // serialized output data to be passed to the next step in the workflow.
231
+ execute: func(context: action-context) -> result<action-response, app-error>;
232
+ }
233
+
234
+ interface environment {
235
+ // Get all environment variables
236
+ env-vars: func() -> list<tuple<string, string>>;
237
+ // Get a specific environment variable by name
238
+ env-var: func(name: string) -> option<string>;
239
+ }
240
+
241
+ interface http {
242
+ record response {
243
+ status: u16,
244
+ headers: headers,
245
+ body: string,
246
+ /// Raw response payload for binary responses.
247
+ body-bytes: option<list<u8>>,
248
+ }
249
+
250
+ record request {
251
+ method: method,
252
+ url: string,
253
+ headers: headers,
254
+ body: string,
255
+ }
256
+
257
+ variant request-error {
258
+ other(string)
259
+ }
260
+
261
+ type headers = list<tuple<string, string>>;
262
+
263
+ resource request-builder {
264
+ constructor();
265
+
266
+ method: func(method: method) -> request-builder;
267
+ url: func(url: string) -> request-builder;
268
+
269
+ // Add a header to the request
270
+ header: func(key: string, value: string) -> request-builder;
271
+ headers: func(headers: list<tuple<string, string>>) -> request-builder;
272
+
273
+ // Add a body to the request
274
+ body: func(body: string) -> request-builder;
275
+ // Add a binary body to the request
276
+ body-bytes: func(body: list<u8>) -> request-builder;
277
+
278
+ object: func() -> request;
279
+
280
+ // Send the request
281
+ send: func() -> result<response, request-error>;
282
+ }
283
+
284
+ variant method {
285
+ get,
286
+ post,
287
+ put,
288
+ delete,
289
+ patch,
290
+ options,
291
+ head,
292
+ }
293
+ }
294
+
295
+ interface file {
296
+ // HTTP headers for file requests (same as http interface)
297
+ type headers = list<tuple<string, string>>;
298
+
299
+ // Normalized file data
300
+ record file-data {
301
+ // Base64-encoded file content
302
+ base64: string,
303
+ // MIME type (e.g., "application/pdf")
304
+ content-type: string,
305
+ // Filename
306
+ filename: string,
307
+ }
308
+
309
+ variant file-error {
310
+ // Failed to fetch file from URL
311
+ fetch-failed(string),
312
+ // Invalid input format (not a valid URL, data URI, or base64)
313
+ invalid-input(string),
314
+ // Request timed out
315
+ timeout(string),
316
+ // Any other error
317
+ other(string),
318
+ }
319
+
320
+ // Normalize any file source to FileData
321
+ //
322
+ // The source is automatically detected:
323
+ // - URL: "https://example.com/file.pdf" - fetched with optional headers
324
+ // - Data URI: "data:application/pdf;base64,JVBERi0..." - parsed and extracted
325
+ // - Base64: Any other string is treated as raw base64 - decoded to detect type
326
+ //
327
+ // Parameters:
328
+ // - source: URL, data URI, or base64-encoded content
329
+ // - headers: Optional HTTP headers for URL requests (e.g., Authorization)
330
+ // - filename: Optional filename override (auto-detected if not provided)
331
+ //
332
+ // Returns file-data which will be processed by the platform:
333
+ // 1. Fields with format: "file-output" in the output schema are identified
334
+ // 2. File data is uploaded using the configured file_uploader
335
+ // 3. The file-data is replaced with the blob ID in the response
336
+ normalize: func(source: string, headers: option<headers>, filename: option<string>) -> result<file-data, file-error>;
337
+ }
338
+
339
+ world bridge {
340
+ import http;
341
+ import environment;
342
+ import file;
343
+ export triggers;
344
+ export actions;
345
+ }
Binary file
Binary file
Binary file
@@ -4,5 +4,5 @@
4
4
  # ext/app_bridge/Cargo.toml file to keep them in sync.
5
5
 
6
6
  module AppBridge
7
- VERSION = "4.0.0"
7
+ VERSION = "4.1.1"
8
8
  end
data/lib/app_bridge.rb CHANGED
@@ -11,7 +11,6 @@ module AppBridge
11
11
  class TooManyEventsError < Error; end
12
12
  class StoreTooLargeError < Error; end
13
13
  class ActionResponseTooLargeError < Error; end
14
- class FileUploadError < Error; end
15
14
  class InternalError < Error; end
16
15
 
17
16
  class << self
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: app_bridge
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.0
4
+ version: 4.1.1
5
5
  platform: aarch64-linux
6
6
  authors:
7
7
  - Alexander Ross
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-01-27 00:00:00.000000000 Z
11
+ date: 2026-02-10 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: The app_bridge gem is designed to enable seamless interaction with WebAssembly
14
14
  components that adhere to the WIT specification `standout:app`. It is developed
@@ -28,8 +28,10 @@ files:
28
28
  - Rakefile
29
29
  - ext/app_bridge/wit/v3/world.wit
30
30
  - ext/app_bridge/wit/v4/world.wit
31
+ - ext/app_bridge/wit/v4_1/world.wit
31
32
  - lib/app_bridge.rb
32
33
  - lib/app_bridge/3.2/app_bridge.so
34
+ - lib/app_bridge/3.3/app_bridge.so
33
35
  - lib/app_bridge/3.4/app_bridge.so
34
36
  - lib/app_bridge/app.rb
35
37
  - lib/app_bridge/file_processor.rb