diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index d802fc6..db89e54 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -16,9 +16,7 @@ jobs: steps: - name: Clone repo - uses: actions/checkout@v4 - with: - submodules: recursive + uses: actions/checkout@v7 - name: Setup Rust uses: dtolnay/rust-toolchain@stable diff --git a/.github/workflows/create_release_pr.yaml b/.github/workflows/create_release_pr.yaml index 43c8cb1..4cf5e51 100644 --- a/.github/workflows/create_release_pr.yaml +++ b/.github/workflows/create_release_pr.yaml @@ -13,9 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Clone repo - uses: actions/checkout@v4 - with: - submodules: recursive + uses: actions/checkout@v7 - name: Create GitHub PR for release branch env: diff --git a/.github/workflows/fossa.yaml b/.github/workflows/fossa.yaml index 2654a4f..0da5dc0 100644 --- a/.github/workflows/fossa.yaml +++ b/.github/workflows/fossa.yaml @@ -13,7 +13,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v7 - name: Install FOSSA CLI run: | diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 12b3d8c..7d0fcbe 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -46,9 +46,7 @@ jobs: steps: - name: Clone repo - uses: actions/checkout@v4 - with: - submodules: recursive + uses: actions/checkout@v7 - name: Get the release version from the tag shell: bash diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index d231796..0000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "crates/reactor/wit"] - path = crates/reactor/wit - url = https://github.com/G-Core/FastEdge-wit.git diff --git a/CLAUDE.md b/CLAUDE.md index 6ec9406..0b7bbbb 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -25,7 +25,7 @@ cargo run --bin fastedge-run -- http --port 8080 --wasm ./my_app.wasm ## Repository setup gotchas -- **WIT is a git submodule.** `crates/reactor/wit/` is the `FastEdge-wit` repo. After clone or branch switches that touch it, run `git submodule update --init --recursive -f`. Without this, `crates/reactor` (and everything that depends on it) fails to build because `bindgen!` can't find the `.wit` files. +- **WIT is vendored via git subtree.** `crates/reactor/wit/` contains the `FastEdge-wit` sources directly, so no `git submodule update` step is required after clone or branch switches. To sync it with upstream, run `git subtree pull --prefix=crates/reactor/wit https://github.com/G-Core/FastEdge-wit.git main --squash`. - **Custom Wasmtime fork.** All `wasmtime-*` deps point at `github.com/G-Core/wasmtime.git#release-36.0.0`. `.cargo/config.toml` contains a commented-out `[patch]` block that redirects those to a local sibling `../wasmtime` checkout — uncomment when developing against a local Wasmtime tree. - **Wasm components vs core modules.** `componentize_if_necessary` (in `crates/runtime/src/lib.rs`) auto-wraps core modules using the bundled Preview1 adapter (`crates/runtime/src/adapters/wasi_snapshot_preview1.reactor.wasm`). Don't replace that adapter casually — it's pinned to the Wasmtime fork. diff --git a/README.md b/README.md index c225fb4..f4b4e72 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,15 @@ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh source "$HOME/.cargo/env" ``` -## Pull submodules +## WIT subtree -Run `git submodule update --init --recursive -f` +`crates/reactor/wit/` is vendored directly in this repository, so cloning the repo is enough to build. + +To refresh the WIT definitions from the upstream `FastEdge-wit` repository, run: + +```bash +git subtree pull --prefix=crates/reactor/wit https://github.com/G-Core/FastEdge-wit.git main --squash +``` ## Building diff --git a/crates/reactor/wit b/crates/reactor/wit deleted file mode 160000 index 217ec2f..0000000 --- a/crates/reactor/wit +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 217ec2fcf6f3dca5c96702a5afb705ef94aa655f diff --git a/crates/reactor/wit/LICENSE b/crates/reactor/wit/LICENSE new file mode 100644 index 0000000..97f2655 --- /dev/null +++ b/crates/reactor/wit/LICENSE @@ -0,0 +1,161 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, and distribution as defined + by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is + granting the License. + + "Legal Entity" shall mean the union of the acting entity and all other entities that control, + are controlled by, or are under common control with that entity. For the purposes of this + definition, "control" means (i) the power, direct or indirect, to cause the direction or + management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent + (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by + this License. + + "Source" form shall mean the preferred form for making modifications, including but not limited + to software source code, documentation source, and configuration files. + + "Object" form shall mean any form resulting from mechanical transformation or translation of a + Source form, including but not limited to compiled object code, generated documentation, and + conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or Object form, made available under + the License, as indicated by a copyright notice that is included in or attached to the work (an + example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or + derived from) the Work and for which the editorial revisions, annotations, elaborations, or + other modifications represent, as a whole, an original work of authorship. For the purposes of + this License, Derivative Works shall not include works that remain separable from, or merely + link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including the original version of the Work and + any modifications or additions to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or + Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this + definition, "submitted" means any form of electronic, verbal, or written communication sent to + the Licensor or its representatives, including but not limited to communication on electronic + mailing lists, source code control systems, and issue tracking systems that are managed by, or + on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding + communication that is conspicuously marked or otherwise designated in writing by the copyright + owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a + Contribution has been received by Licensor and subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this License, each + Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, + irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, + publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or + Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor + hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, use, offer to sell, sell, + import, and otherwise transfer the Work, where such license applies only to those patent claims + licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or + by combination of their Contribution(s) with the Work to which such Contribution(s) was + submitted. If You institute patent litigation against any entity (including a cross-claim or + counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work + constitutes direct or contributory patent infringement, then any patent licenses granted to You + under this License for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof + in any medium, with or without modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; + and + + (b) You must cause any modified files to carry prominent notices stating that You changed the + files; and + + (c) You must retain, in the Source form of any Derivative Works that You distribute, all + copyright, patent, trademark, and attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative + Works that You distribute must include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not pertain to any part of the + Derivative Works, in at least one of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or documentation, if provided along with + the Derivative Works; or, within a display generated by the Derivative Works, if and wherever + such third-party notices normally appear. The contents of the NOTICE file are for informational + purposes only and do not modify the License. You may add Your own attribution notices within + Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the + Work, provided that such additional attribution notices cannot be construed as modifying the + License. + + You may add Your own copyright statement to Your modifications and may provide additional or + different license terms and conditions for use, reproduction, or distribution of Your + modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and + distribution of the Work otherwise complies with the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution + intentionally submitted for inclusion in the Work by You to the Licensor shall be under the + terms and conditions of this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate + license agreement you may have executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, trademarks, service + marks, or product names of the Licensor, except as required for reasonable and customary use in + describing the origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor + provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT + WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, + any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or + redistributing the Work and assume any risks associated with Your exercise of permissions under + this License. + +8. Limitation of Liability. In no event and under no legal theory, whether in tort (including + negligence), contract, or otherwise, unless required by applicable law (such as deliberate and + grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for + damages, including any direct, indirect, special, incidental, or consequential damages of any + character arising as a result of this License or out of the use or inability to use the Work + (including but not limited to damages for loss of goodwill, work stoppage, computer failure or + malfunction, or any and all other commercial damages or losses), even if such Contributor has + been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works + thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, + indemnity, or other liability obligations and/or rights consistent with this License. However, + in accepting such obligations, You may act only on Your own behalf and on Your sole + responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability incurred by, or claims asserted + against, such Contributor by reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright 2024 G-Core Innovations SARL + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in +compliance with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is +distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied. See the License for the specific language governing permissions and limitations under the +License. diff --git a/crates/reactor/wit/README.md b/crates/reactor/wit/README.md new file mode 100644 index 0000000..51243b1 --- /dev/null +++ b/crates/reactor/wit/README.md @@ -0,0 +1,15 @@ +# Wasm Interface Type (WIT) + +This directory is synced from the upstream `FastEdge-wit` repository with [git subtree](https://www.atlassian.com/git/tutorials/git-subtree). That keeps the WIT files available directly in this repository while still allowing updates from the upstream source. + +To add the WIT definitions to another repository as a subtree, run: + +```sh +git subtree add --prefix= https://github.com/G-Core/FastEdge-wit.git main --squash +``` + +To refresh an existing subtree from upstream, run: + +```sh +git subtree pull --prefix= https://github.com/G-Core/FastEdge-wit.git main --squash +``` diff --git a/crates/reactor/wit/cache-sync.wit b/crates/reactor/wit/cache-sync.wit new file mode 100644 index 0000000..0f6cc49 --- /dev/null +++ b/crates/reactor/wit/cache-sync.wit @@ -0,0 +1,72 @@ +/// FastEdge cache interface (synchronous variant). +interface cache-sync { + use cache-types.{payload, error}; + + /// Get the value associated with `key`. + /// + /// Returns: + /// - `ok(some(value))` if the key exists. + /// - `ok(none)` if the key does not exist. + /// - `err(error)` if the operation fails. + get: func(key: string) -> result, error>; + + /// Set the value for `key` with an optional expiry. + /// + /// If the key already exists, its current value is overwritten. + /// If the key does not exist, a new key-value pair is created. + /// + /// `ttl-ms` is the time-to-live in milliseconds. Pass `none` for no expiry. + /// + /// Returns `err(error)` if the operation fails. + set: func(key: string, value: payload, ttl-ms: option) -> result<_, error>; + + /// Delete the key-value pair associated with `key`. + /// + /// If the key does not exist, this operation is a no-op. + /// + /// Returns `err(error)` if the operation fails. + delete: func(key: string) -> result<_, error>; + + /// Check whether `key` exists in the cache. + /// + /// Returns: + /// - `ok(true)` if the key exists. + /// - `ok(false)` if the key does not exist. + /// - `err(error)` if the operation fails. + exists: func(key: string) -> result; + + /// Increment the integer value stored at `key` by `delta`. + /// + /// If the key does not exist, it is initialised to `0` before incrementing. + /// The operation is atomic. `delta` may be negative to decrement. + /// + /// Returns the new value after the increment, or `err(error)` if the + /// operation fails (for example, if the stored value is not an integer). + incr: func(key: string, delta: s64) -> result; + + /// Set or update the expiry of `key` to `ttl-ms` milliseconds from now. + /// + /// If the key does not exist, returns `ok(false)`. + /// If the expiry was updated successfully, returns `ok(true)`. + /// Returns `err(error)` if the operation fails. + expire: func(key: string, ttl-ms: u64) -> result; + + /// Purge all cache entries owned by the calling application. + /// + /// The host scans the application's key index, deletes every cached key, + /// and then removes the index itself. + /// + /// Returns the number of keys that were deleted, or `err(error)` if the + /// operation fails. + purge: func() -> result; + + /// Purge all cache entries whose keys begin with `prefix`. + /// + /// The host scans the application's key index for keys that begin with the + /// given prefix, deletes every matched key, and removes the matched entries + /// from the index (the index itself is kept for any remaining keys). + /// + /// Returns the number of keys that were deleted, or `err(error)` if the + /// operation fails. + purge-prefix: func(prefix: string) -> result; +} diff --git a/crates/reactor/wit/cache-types.wit b/crates/reactor/wit/cache-types.wit new file mode 100644 index 0000000..ea98bf8 --- /dev/null +++ b/crates/reactor/wit/cache-types.wit @@ -0,0 +1,15 @@ +/// Shared cache types for cache interfaces. +interface cache-types { + type payload = list; + + /// The set of errors that may be returned by cache operations. + variant error { + /// The requesting component does not have access to the specified cache + /// (which may or may not exist). + access-denied, + /// An unexpected internal error occurred. + internal-error, + /// An implementation-specific error occurred (for example, I/O). + other(string) + } +} diff --git a/crates/reactor/wit/dictionary.wit b/crates/reactor/wit/dictionary.wit new file mode 100644 index 0000000..6a0bbff --- /dev/null +++ b/crates/reactor/wit/dictionary.wit @@ -0,0 +1,6 @@ +interface dictionary { + /// Get the value associated with the specified `key` + /// + /// Returns `ok(none)` if the key does not exist. + get: func(key: string) -> option; +} \ No newline at end of file diff --git a/crates/reactor/wit/http-client.wit b/crates/reactor/wit/http-client.wit new file mode 100644 index 0000000..8e58295 --- /dev/null +++ b/crates/reactor/wit/http-client.wit @@ -0,0 +1,5 @@ +interface http-client { + use http.{request, response, error}; + + send-request: func(req: request) -> result; +} diff --git a/crates/reactor/wit/http-handler.wit b/crates/reactor/wit/http-handler.wit new file mode 100644 index 0000000..e66a319 --- /dev/null +++ b/crates/reactor/wit/http-handler.wit @@ -0,0 +1,4 @@ +interface http-handler { + use http.{request, response}; + process: func(req: request) -> response; +} diff --git a/crates/reactor/wit/http.wit b/crates/reactor/wit/http.wit new file mode 100644 index 0000000..de8f11e --- /dev/null +++ b/crates/reactor/wit/http.wit @@ -0,0 +1,38 @@ +interface http { + type http-status = u16; + type body = list; + type headers = list>; + type uri = string; + + enum method { + get, + post, + put, + delete, + head, + patch, + options + } + + record request { + method: method, + uri: uri, + headers: headers, + body: option, + } + + record response { + status: http-status, + headers: option, + body: option, + } + + enum error { + success, + destination-not-allowed, + invalid-url, + request-error, + runtime-error, + too-many-requests, + } +} diff --git a/crates/reactor/wit/key-value.wit b/crates/reactor/wit/key-value.wit new file mode 100644 index 0000000..72ad52c --- /dev/null +++ b/crates/reactor/wit/key-value.wit @@ -0,0 +1,54 @@ +interface key-value { + type value = list; + + /// FastEdge key-value persistent store resource + resource store { + /// Open the store with the specified name. + /// + /// `error::no-such-store` will be raised if the `name` is not recognized. + open: static func(name: string) -> result; + + /// Get the value associated with the specified `key` + /// + /// Returns `ok(none)` if the key does not exist. + get: func(key: string) -> result, error>; + + /// Interface to scan over keys in the store. + /// It matches glob-style pattern filter on each element from the retrieved collection. + /// + /// Returns an array of elements as a list of keys. + scan: func(pattern: string) -> result, error>; + + /// Get all the elements with score from the sorted set at `key` with a f64 score between min and max + /// (including elements with score equal to min or max). The elements are considered to be ordered from low to high + /// scores. + /// + /// Returns an empty list if the key does not exist or if no elements have scores between min and max. + zrange-by-score: func(key: string, min: f64, max: f64) -> result>, error>; + + /// Interface to scan through a sorted set by key + /// It matches glob-style pattern filter on each elements from the retrieved collection. + /// + /// Returns an array of elements as a list of value of the Sorted Set. + zscan: func(key: string, pattern: string) -> result>, error>; + + /// Determines whether a given item was added to a Bloom filter. + /// + /// Returns one of these replies: 'true' means that, with high probability, item was already added to the filter, + /// and 'false' means that key does not exist or that item had not been added to the filter. + bf-exists: func(key: string, item: string) -> result; + } + + /// The set of errors which may be raised by functions in this interface + variant error { + /// The host does not recognize the store label requested. + no-such-store, + /// The requesting component does not have access to the specified store + /// (which may or may not exist). + access-denied, + /// Some unexpected internal error has occurred. + internal-error, + /// Some implementation-specific error has occurred (e.g. I/O) + other(string) + } +} diff --git a/crates/reactor/wit/secret.wit b/crates/reactor/wit/secret.wit new file mode 100644 index 0000000..ab03b8d --- /dev/null +++ b/crates/reactor/wit/secret.wit @@ -0,0 +1,20 @@ +interface secret { + /// Get the secret associated with the specified `key` effective at current timestamp. + /// Returns `ok(none)` if the key does not exist. + get: func(key: string) -> result, error>; + + /// Get the secret associated with the specified `key` effective `at` given timestamp in seconds. + /// Returns `ok(none)` if the key does not exist. + get-effective-at: func(key: string, at: u32) -> result, error>; + + /// The set of errors which may be raised by functions in this interface + variant error { + /// The requesting component does not have access to the specified key + /// (which may or may not exist). + access-denied, + /// Decryption error. + decrypt-error, + /// Some implementation-specific error has occurred (e.g. I/O) + other(string) + } +} diff --git a/crates/reactor/wit/utils.wit b/crates/reactor/wit/utils.wit new file mode 100644 index 0000000..9fbbac1 --- /dev/null +++ b/crates/reactor/wit/utils.wit @@ -0,0 +1,4 @@ +interface utils { + /// Set a user diagnostic context name to be associated with the current request (and included in call statistics). + set-user-diag: func(name: string); +} \ No newline at end of file diff --git a/crates/reactor/wit/world.wit b/crates/reactor/wit/world.wit new file mode 100644 index 0000000..f319593 --- /dev/null +++ b/crates/reactor/wit/world.wit @@ -0,0 +1,13 @@ +package gcore:fastedge; + +world reactor { + import http; + import http-client; + import dictionary; + import secret; + import key-value; + import utils; + import cache-sync; + + export http-handler; +} \ No newline at end of file