diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index e534fcf..57e7735 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -33,6 +33,10 @@ jobs: exit 1 fi + fossa: + needs: [validate_branch_name] + uses: ./.github/workflows/fossa.yaml + code_validation: needs: [validate_branch_name] uses: ./.github/workflows/code-validation.yaml diff --git a/.github/workflows/fossa.yaml b/.github/workflows/fossa.yaml new file mode 100644 index 0000000..ada2984 --- /dev/null +++ b/.github/workflows/fossa.yaml @@ -0,0 +1,28 @@ +name: Fossa Compliance Check + +on: + workflow_call: + +jobs: + fossa: + runs-on: [self-hosted, ubuntu-22-04, regular] + + if: github.actor != 'dependabot[bot]' + + steps: + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Install FOSSA CLI + run: | + curl -H 'Cache-Control: no-cache' https://raw.githubusercontent.com/fossas/fossa-cli/master/install-latest.sh | bash + + - name: Run FOSSA Analysis + env: + FOSSA_API_KEY: ${{ secrets.FOSSA_PUB_API_KEY }} + run: fossa analyze + + - name: Run FOSSA Test + env: + FOSSA_API_KEY: ${{ secrets.FOSSA_PUB_API_KEY }} + run: fossa test diff --git a/.gitmodules b/.gitmodules index 2d7a6f1..69745e6 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,6 @@ -[submodule "fastedge-runtime/spidermonkey/gecko-dev"] - path = fastedge-runtime/spidermonkey/gecko-dev - url = https://github.com/bytecodealliance/gecko-dev.git -[submodule "fastedge-runtime/cbindings/wit-interface/wit"] - path = fastedge-runtime/cbindings/wit-interface/wit - url = https://github.com/G-Core/FastEdge-wit.git [submodule "runtime/StarlingMonkey"] path = runtime/StarlingMonkey url = https://github.com/bytecodealliance/StarlingMonkey.git +[submodule "runtime/FastEdge-wit"] + path = runtime/FastEdge-wit + url = https://github.com/G-Core/FastEdge-wit.git diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index 490122b..ac78366 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -8,6 +8,7 @@ export default defineConfig({ integrations: [ starlight({ title: '@gcoredev/fastedge-sdk-js', + customCss: ['./src/styles/custom.css'], social: { github: 'https://github.com/G-Core/FastEdge-sdk-js', }, @@ -47,7 +48,72 @@ export default defineConfig({ { label: 'Reference', collapsed: true, - autogenerate: { directory: 'reference' }, + items: [ + { + label: 'Overview', + link: `${import.meta.env.BASE_URL}reference/overview/`, + }, + { + label: 'FastEdge::env', + link: `${import.meta.env.BASE_URL}reference/fastedge/env/`, + }, + { + label: 'FastEdge::secret', + collapsed: true, + items: [ + { + label: 'getSecret', + link: `${import.meta.env.BASE_URL}reference/fastedge/secret/get-secret/`, + }, + { + label: 'getSecretEffectiveAt', + link: `${ + import.meta.env.BASE_URL + }reference/fastedge/secret/get-secret-effective-at/`, + }, + ], + }, + { + label: 'FastEdge::kv', + collapsed: true, + items: [ + { + label: 'KvStore.open()', + link: `${import.meta.env.BASE_URL}reference/fastedge/kv/open/`, + }, + { + label: 'KV Instance', + collapsed: true, + items: [ + { + label: 'get / scan', + link: `${import.meta.env.BASE_URL}reference/fastedge/kv/key-value/`, + }, + { + label: 'zrange / zscan', + link: `${import.meta.env.BASE_URL}reference/fastedge/kv/zset/`, + }, + { + label: 'bfExists', + link: `${import.meta.env.BASE_URL}reference/fastedge/kv/bloom-filter/`, + }, + ], + }, + ], + }, + { + label: 'Headers', + link: `${import.meta.env.BASE_URL}reference/headers/`, + }, + { + label: 'Request', + link: `${import.meta.env.BASE_URL}reference/request/`, + }, + { + label: 'Response', + link: `${import.meta.env.BASE_URL}reference/response/`, + }, + ], }, { label: 'Migrating', diff --git a/docs/examples/kv-store.js b/docs/examples/kv-store.js new file mode 100644 index 0000000..aff2b18 --- /dev/null +++ b/docs/examples/kv-store.js @@ -0,0 +1,15 @@ +import { KvStore } from 'fastedge::kv'; + +async function eventHandler(event) { + try { + const myStore = KvStore.open('kv-store-name-as-defined-on-app'); + const value = myStore.get('key'); + return new Response(`The KV Store responded with: ${value}`); + } catch (error) { + return Response.json({ error: error.message }, { status: 500 }); + } +} + +addEventListener('fetch', (event) => { + event.respondWith(eventHandler(event)); +}); diff --git a/docs/src/content/docs/examples/kv-store.mdx b/docs/src/content/docs/examples/kv-store.mdx new file mode 100644 index 0000000..8a01a8f --- /dev/null +++ b/docs/src/content/docs/examples/kv-store.mdx @@ -0,0 +1,12 @@ +--- +title: Key-value Store +description: An example using KV Store. +prev: + link: /FastEdge-sdk-js/examples/main-examples/ + label: Back to examples +--- + +import { Code } from '@astrojs/starlight/components'; +import importedCode from '/examples/kv-store.js?raw'; + + diff --git a/docs/src/content/docs/examples/main-examples.mdx b/docs/src/content/docs/examples/main-examples.mdx index f57a563..2883cdf 100644 --- a/docs/src/content/docs/examples/main-examples.mdx +++ b/docs/src/content/docs/examples/main-examples.mdx @@ -30,4 +30,8 @@ More examples will be added to the examples-repo as we build out more functional title='Environment variables and secrets' href='/FastEdge-sdk-js/examples/variables-and-secrets/' /> + diff --git a/docs/src/content/docs/reference/fastedge-env.md b/docs/src/content/docs/reference/fastedge/env/index.md similarity index 100% rename from docs/src/content/docs/reference/fastedge-env.md rename to docs/src/content/docs/reference/fastedge/env/index.md diff --git a/docs/src/content/docs/reference/fastedge/kv/bloom-filter.md b/docs/src/content/docs/reference/fastedge/kv/bloom-filter.md new file mode 100644 index 0000000..f3bd575 --- /dev/null +++ b/docs/src/content/docs/reference/fastedge/kv/bloom-filter.md @@ -0,0 +1,48 @@ +--- +title: Bloom Filter accessors +description: How to access Bloom Filter values from a FastEdge Kv Instance. +--- + +To access Bloom Filter values in a KV Store. First create a `KV Instance` using `KvStore.open()` + +This instance will then provide the `bfExists` method you can use to verify if a value exists. + +## zrangeByScore + +```js +import { KvStore } from 'fastedge::kv'; + +async function eventHandler(event) { + try { + const myStore = KvStore.open('kv-store-name-as-defined-on-app'); + const hasItem = myStore.bfExists('key', 'value'); + return new Response(`The KV Store responded with: ${hasItem}`); + } catch (error) { + return Response.json({ error: error.message }, { status: 500 }); + } +} + +addEventListener('fetch', (event) => { + event.respondWith(eventHandler(event)); +}); +``` + +```js title="SYNTAX" +storeInstance.bfExists(key, value); +``` + +##### Parameters + +- `key` (required) + + A string containing the key you want to retrieve the values from. + +- `value` (required) + + A string representing the value you want to check for existence. + + +##### Return Value + +`boolean`. It returns `true` if the Bloom Filter contains the value. + diff --git a/docs/src/content/docs/reference/fastedge/kv/key-value.md b/docs/src/content/docs/reference/fastedge/kv/key-value.md new file mode 100644 index 0000000..5421fa3 --- /dev/null +++ b/docs/src/content/docs/reference/fastedge/kv/key-value.md @@ -0,0 +1,80 @@ +--- +title: Key-value accessors +description: How to access Key-value pairs from a FastEdge Kv Instance. +--- + +To access key-value pairs in a KV Store. First create a `KV Instance` using `KvStore.open()` + +This instance will then provide the `get` and `scan` methods you can use to access key-value pairs. + +## get + +```js +import { KvStore } from 'fastedge::kv'; + +async function eventHandler(event) { + try { + const myStore = KvStore.open('kv-store-name-as-defined-on-app'); + const value = myStore.get('key'); + return new Response(`The KV Store responded with: ${value}`); + } catch (error) { + return Response.json({ error: error.message }, { status: 500 }); + } +} + +addEventListener('fetch', (event) => { + event.respondWith(eventHandler(event)); +}); +``` + +```js title="SYNTAX" +storeInstance.get(key); +``` + +##### Parameters + +- `key` (required) + + A string containing the key you want to retrieve the value of. + +##### Return Value + +An `ArrayBuffer` of the value for the given key. If the key does not exist, null is returned. + +## scan + +```js +import { KvStore } from 'fastedge::kv'; + +async function eventHandler(event) { + try { + const myStore = KvStore.open('kv-store-name-as-defined-on-app'); + const results = myStore.scan('pre*'); + return new Response(`The KV Store responded with: ${results.join(', ')}`); + } catch (error) { + return Response.json({ error: error.message }, { status: 500 }); + } +} + +addEventListener('fetch', (event) => { + event.respondWith(eventHandler(event)); +}); +``` + +```js title="SYNTAX" +storeInstance.scan(pattern); +``` + +##### Parameters + +- `pattern` (required) + + A string containing the prefix pattern match. + +**Note**: This is a prefix match, it must contain the wildcard `*`. In the given example it matches +all keys that start with `pre` + +##### Return Value + +An `Array` of all the keys that match the given pattern. If no `matches` are found it +returns an empty array. diff --git a/docs/src/content/docs/reference/fastedge/kv/open.md b/docs/src/content/docs/reference/fastedge/kv/open.md new file mode 100644 index 0000000..8fb3e90 --- /dev/null +++ b/docs/src/content/docs/reference/fastedge/kv/open.md @@ -0,0 +1,46 @@ +--- +title: KV Stores +description: How to use FastEdge Key-Value Stores. +--- + +### KV Store Open + +How to access Key-value Stores within FastEdge. + +```js +import { KvStore } from 'fastedge::kv'; + +async function eventHandler(event) { + try { + const myStore = KvStore.open('kv-store-name-as-defined-on-app'); + const value = myStore.get('key'); + return new Response(`The KV Store responded with: ${value}`); + } catch (error) { + return Response.json({ error: error.message }, { status: 500 }); + } +} + +addEventListener('fetch', (event) => { + event.respondWith(eventHandler(event)); +}); +``` + +```js title="SYNTAX" +KvStore.open(kvStoreName); +``` + +##### Parameters + +- `kvStoreName` (required) + + A string containing the name of the store you want to open. + +##### Return Value + +A `KV Instance` that lets you interact with the store. It provides: + +- get +- scan +- zrangeByScore +- zscan +- bfExists diff --git a/docs/src/content/docs/reference/fastedge/kv/zset.md b/docs/src/content/docs/reference/fastedge/kv/zset.md new file mode 100644 index 0000000..7a8ac00 --- /dev/null +++ b/docs/src/content/docs/reference/fastedge/kv/zset.md @@ -0,0 +1,94 @@ +--- +title: Sorted Set accessors +description: How to access Sorted Set values from a FastEdge Kv Instance. +--- + +To access Sorted Set values in a KV Store. First create a `KV Instance` using `KvStore.open()` + +This instance will then provide the `zrangeByScore` and `zscan` methods you can use to access Sorted +Set values. + +## zrangeByScore + +```js +import { KvStore } from 'fastedge::kv'; + +async function eventHandler(event) { + try { + const myStore = KvStore.open('kv-store-name-as-defined-on-app'); + const results = myStore.zrangeByScore('key', 0, 10); + return new Response(`The KV Store responded with: ${JSON.stringify(results)}`); + } catch (error) { + return Response.json({ error: error.message }, { status: 500 }); + } +} + +addEventListener('fetch', (event) => { + event.respondWith(eventHandler(event)); +}); +``` + +```js title="SYNTAX" +storeInstance.zrangeByScore(key, min, max); +``` + +##### Parameters + +- `key` (required) + + A string containing the key you want to retrieve the values from. + +- `min` (required) + + A number representing the min-score for which to return values for. + +- `max` (required) + + A number representing the max-score for which to return values for. + +##### Return Value + +An `Array<[ArrayBuffer, number]>`. It returns a list of tuples, containing the value in an +ArrayBuffer and the score as a number. + +## zscan + +```js +import { KvStore } from 'fastedge::kv'; + +async function eventHandler(event) { + try { + const myStore = KvStore.open('kv-store-name-as-defined-on-app'); + const results = myStore.zscan('pre*'); + return new Response(`The KV Store responded with: ${JSON.stringify(results)}`); + } catch (error) { + return Response.json({ error: error.message }, { status: 500 }); + } +} + +addEventListener('fetch', (event) => { + event.respondWith(eventHandler(event)); +}); +``` + +```js title="SYNTAX" +storeInstance.zscan(key, pattern); +``` + +##### Parameters + +- `key` (required) + + A string containing the key you want to retrieve the values from. + +- `pattern` (required) + + A string containing the prefix pattern match. + +**Note**: This is a prefix match, it must contain the wildcard `*`. In the given example it matches +all values that start with `pre` + +##### Return Value + +An `Array<[ArrayBuffer, number]>`. It returns a list of tuples, containing the value in an +ArrayBuffer and the score as a number. diff --git a/docs/src/content/docs/reference/fastedge::secret/getSecretEffectiveAt.md b/docs/src/content/docs/reference/fastedge/secret/get-secret-effective-at.md similarity index 100% rename from docs/src/content/docs/reference/fastedge::secret/getSecretEffectiveAt.md rename to docs/src/content/docs/reference/fastedge/secret/get-secret-effective-at.md diff --git a/docs/src/content/docs/reference/fastedge::secret/getSecret.md b/docs/src/content/docs/reference/fastedge/secret/get-secret.md similarity index 100% rename from docs/src/content/docs/reference/fastedge::secret/getSecret.md rename to docs/src/content/docs/reference/fastedge/secret/get-secret.md diff --git a/docs/src/styles/custom.css b/docs/src/styles/custom.css new file mode 100644 index 0000000..3ff056e --- /dev/null +++ b/docs/src/styles/custom.css @@ -0,0 +1,7 @@ +/* Custom styles for specific Starlight sidebar items */ + +/* Target only nested details (sub-sections) */ +.sidebar-content details details > summary > div > span { + font-weight: normal !important; + font-size: var(--sl-text-sm) !important; +} diff --git a/package-lock.json b/package-lock.json index d69e00d..3aa22ab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "Apache-2.0", "dependencies": { "@bytecodealliance/jco": "^1.2.4", - "@bytecodealliance/wizer": "^3.0.1", + "@bytecodealliance/wizer": "^10.0.0", "acorn": "^8.8.2", "acorn-walk": "^8.2.0", "arg": "^5.0.2", @@ -23,6 +23,7 @@ "regexpu-core": "^5.3.2" }, "bin": { + "fastedge-assets": "bin/fastedge-assets.js", "fastedge-build": "bin/fastedge-build.js", "fastedge-init": "bin/fastedge-init.js" }, @@ -1913,122 +1914,6 @@ "componentize-js": "src/cli.js" } }, - "node_modules/@bytecodealliance/componentize-js/node_modules/@bytecodealliance/wizer": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@bytecodealliance/wizer/-/wizer-10.0.0.tgz", - "integrity": "sha512-ziWmovyu1jQl9TsKlfC2bwuUZwxVPFHlX4fOqTzxhgS76jITIo45nzODEwPgU+jjmOr8F3YX2V2wAChC5NKujg==", - "license": "Apache-2.0", - "bin": { - "wizer": "wizer.js" - }, - "engines": { - "node": ">=16" - }, - "optionalDependencies": { - "@bytecodealliance/wizer-darwin-arm64": "10.0.0", - "@bytecodealliance/wizer-darwin-x64": "10.0.0", - "@bytecodealliance/wizer-linux-arm64": "10.0.0", - "@bytecodealliance/wizer-linux-s390x": "10.0.0", - "@bytecodealliance/wizer-linux-x64": "10.0.0", - "@bytecodealliance/wizer-win32-x64": "10.0.0" - } - }, - "node_modules/@bytecodealliance/componentize-js/node_modules/@bytecodealliance/wizer-darwin-arm64": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@bytecodealliance/wizer-darwin-arm64/-/wizer-darwin-arm64-10.0.0.tgz", - "integrity": "sha512-dhZTWel+xccGTKSJtI9A7oM4yyP20FWflsT+AoqkOqkCY7kCNrj4tmMtZ6GXZFRDkrPY5+EnOh62sfShEibAMA==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "bin": { - "wizer-darwin-arm64": "wizer" - } - }, - "node_modules/@bytecodealliance/componentize-js/node_modules/@bytecodealliance/wizer-darwin-x64": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@bytecodealliance/wizer-darwin-x64/-/wizer-darwin-x64-10.0.0.tgz", - "integrity": "sha512-r/LUIZw6Q3Hf4htd46mD+EBxfwjBkxVIrTM1r+B2pTCddoBYQnKVdVsI4UFyy7NoBxzEg8F8BwmTNoSLmFRjpw==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "bin": { - "wizer-darwin-x64": "wizer" - } - }, - "node_modules/@bytecodealliance/componentize-js/node_modules/@bytecodealliance/wizer-linux-arm64": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@bytecodealliance/wizer-linux-arm64/-/wizer-linux-arm64-10.0.0.tgz", - "integrity": "sha512-pGSfFWXzeTqHm6z1PtVaEn+7Fm3QGC8YnHrzBV4sQDVS3N1NwmuHZAc8kslmlFPNdu61ycEvdOsSgCny8JPQvg==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "bin": { - "wizer-linux-arm64": "wizer" - } - }, - "node_modules/@bytecodealliance/componentize-js/node_modules/@bytecodealliance/wizer-linux-s390x": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@bytecodealliance/wizer-linux-s390x/-/wizer-linux-s390x-10.0.0.tgz", - "integrity": "sha512-O8vHxRTAdb1lUnVXMIMTcp/9q4pq1D4iIKigJCipg2JN15taV9uFAWh0fO88wylXwuSlO7dOE1AwQl54fMKXQg==", - "cpu": [ - "s390x" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "bin": { - "wizer-linux-s390x": "wizer" - } - }, - "node_modules/@bytecodealliance/componentize-js/node_modules/@bytecodealliance/wizer-linux-x64": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@bytecodealliance/wizer-linux-x64/-/wizer-linux-x64-10.0.0.tgz", - "integrity": "sha512-fJtM1sy43FBMnp+xpapFX6U1YdTBKA/1T4CYfG/qeE8jn0SXk2EuiYoY/EnC2uyNy9hjTrvfdYO5n4MXW0EIdQ==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "bin": { - "wizer-linux-x64": "wizer" - } - }, - "node_modules/@bytecodealliance/componentize-js/node_modules/@bytecodealliance/wizer-win32-x64": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@bytecodealliance/wizer-win32-x64/-/wizer-win32-x64-10.0.0.tgz", - "integrity": "sha512-55BPLfGT7iT7gH5M69NpTM16QknJZ7OxJ0z73VOEoeGA9CT8QPKMRzFKsPIvLs+W8G28fdudFA94nElrdkp3Kg==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "win32" - ], - "bin": { - "wizer-win32-x64": "wizer" - } - }, "node_modules/@bytecodealliance/jco": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/@bytecodealliance/jco/-/jco-1.15.0.tgz", @@ -2069,9 +1954,9 @@ } }, "node_modules/@bytecodealliance/wizer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@bytecodealliance/wizer/-/wizer-3.0.1.tgz", - "integrity": "sha512-f0NBiBHCNBkbFHTPRbA7aKf/t4KyNhi2KvSqw3QzCgi8wFF/uLZ0dhejj93rbiKO/iwWbmU7v9K3SVkW81mcjQ==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@bytecodealliance/wizer/-/wizer-10.0.0.tgz", + "integrity": "sha512-ziWmovyu1jQl9TsKlfC2bwuUZwxVPFHlX4fOqTzxhgS76jITIo45nzODEwPgU+jjmOr8F3YX2V2wAChC5NKujg==", "license": "Apache-2.0", "bin": { "wizer": "wizer.js" @@ -2080,18 +1965,18 @@ "node": ">=16" }, "optionalDependencies": { - "@bytecodealliance/wizer-darwin-arm64": "3.0.1", - "@bytecodealliance/wizer-darwin-x64": "3.0.1", - "@bytecodealliance/wizer-linux-arm64": "3.0.1", - "@bytecodealliance/wizer-linux-s390x": "3.0.1", - "@bytecodealliance/wizer-linux-x64": "3.0.1", - "@bytecodealliance/wizer-win32-x64": "3.0.1" + "@bytecodealliance/wizer-darwin-arm64": "10.0.0", + "@bytecodealliance/wizer-darwin-x64": "10.0.0", + "@bytecodealliance/wizer-linux-arm64": "10.0.0", + "@bytecodealliance/wizer-linux-s390x": "10.0.0", + "@bytecodealliance/wizer-linux-x64": "10.0.0", + "@bytecodealliance/wizer-win32-x64": "10.0.0" } }, "node_modules/@bytecodealliance/wizer-darwin-arm64": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@bytecodealliance/wizer-darwin-arm64/-/wizer-darwin-arm64-3.0.1.tgz", - "integrity": "sha512-/8KYSajyhO9koAE3qQhYfC6belZheJw9X3XqW7hrizTpj6n4z4OJFhhqwJmiYFUUsPtC7OxcXMFFPbTuSQPBcw==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@bytecodealliance/wizer-darwin-arm64/-/wizer-darwin-arm64-10.0.0.tgz", + "integrity": "sha512-dhZTWel+xccGTKSJtI9A7oM4yyP20FWflsT+AoqkOqkCY7kCNrj4tmMtZ6GXZFRDkrPY5+EnOh62sfShEibAMA==", "cpu": [ "arm64" ], @@ -2105,9 +1990,9 @@ } }, "node_modules/@bytecodealliance/wizer-darwin-x64": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@bytecodealliance/wizer-darwin-x64/-/wizer-darwin-x64-3.0.1.tgz", - "integrity": "sha512-bMReultN/r+W/BRXV0F+28U5dZwbQT/ZO0k4icZlhUhrv5/wpQJix7Z/ZvBnVQ+/JHb0QDUpFk2/zCtgkRXP6Q==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@bytecodealliance/wizer-darwin-x64/-/wizer-darwin-x64-10.0.0.tgz", + "integrity": "sha512-r/LUIZw6Q3Hf4htd46mD+EBxfwjBkxVIrTM1r+B2pTCddoBYQnKVdVsI4UFyy7NoBxzEg8F8BwmTNoSLmFRjpw==", "cpu": [ "x64" ], @@ -2121,9 +2006,9 @@ } }, "node_modules/@bytecodealliance/wizer-linux-arm64": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@bytecodealliance/wizer-linux-arm64/-/wizer-linux-arm64-3.0.1.tgz", - "integrity": "sha512-35ZhAeYxWK3bTqqgwysbBWlGlrlMNKNng3ZITQV2PAtafpE7aCeqywl7VAS4lLRG5eTb7wxNgN7zf8d3wiIFTQ==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@bytecodealliance/wizer-linux-arm64/-/wizer-linux-arm64-10.0.0.tgz", + "integrity": "sha512-pGSfFWXzeTqHm6z1PtVaEn+7Fm3QGC8YnHrzBV4sQDVS3N1NwmuHZAc8kslmlFPNdu61ycEvdOsSgCny8JPQvg==", "cpu": [ "arm64" ], @@ -2137,9 +2022,9 @@ } }, "node_modules/@bytecodealliance/wizer-linux-s390x": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@bytecodealliance/wizer-linux-s390x/-/wizer-linux-s390x-3.0.1.tgz", - "integrity": "sha512-Smvy9mguEMtX0lupDLTPshXUzAHeOhgscr1bhGNjeCCLD1sd8rIjBvWV19Wtra0BL1zTuU2EPOHjR/4k8WoyDg==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@bytecodealliance/wizer-linux-s390x/-/wizer-linux-s390x-10.0.0.tgz", + "integrity": "sha512-O8vHxRTAdb1lUnVXMIMTcp/9q4pq1D4iIKigJCipg2JN15taV9uFAWh0fO88wylXwuSlO7dOE1AwQl54fMKXQg==", "cpu": [ "s390x" ], @@ -2153,9 +2038,9 @@ } }, "node_modules/@bytecodealliance/wizer-linux-x64": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@bytecodealliance/wizer-linux-x64/-/wizer-linux-x64-3.0.1.tgz", - "integrity": "sha512-uUue78xl7iwndsGgTsagHLTLyLBVHhwzuywiwHt1xw8y0X0O8REKRLBoB7+LdM+pttDPdFtKJgbTFL4UPAA7Yw==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@bytecodealliance/wizer-linux-x64/-/wizer-linux-x64-10.0.0.tgz", + "integrity": "sha512-fJtM1sy43FBMnp+xpapFX6U1YdTBKA/1T4CYfG/qeE8jn0SXk2EuiYoY/EnC2uyNy9hjTrvfdYO5n4MXW0EIdQ==", "cpu": [ "x64" ], @@ -2169,9 +2054,9 @@ } }, "node_modules/@bytecodealliance/wizer-win32-x64": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@bytecodealliance/wizer-win32-x64/-/wizer-win32-x64-3.0.1.tgz", - "integrity": "sha512-ycd38sx1UTZpHZwh8IfH/4N3n0OQUB8awxkUSLXf9PolEd088YbxoPB3noHy4E+L2oYN7KZMrg9517pX0z2RhQ==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@bytecodealliance/wizer-win32-x64/-/wizer-win32-x64-10.0.0.tgz", + "integrity": "sha512-55BPLfGT7iT7gH5M69NpTM16QknJZ7OxJ0z73VOEoeGA9CT8QPKMRzFKsPIvLs+W8G28fdudFA94nElrdkp3Kg==", "cpu": [ "x64" ], diff --git a/package.json b/package.json index 212985b..d75fb08 100644 --- a/package.json +++ b/package.json @@ -43,8 +43,8 @@ "build:types": "tsc -p ./tsconfig.build.json", "typecheck": "tsc -p ./tsconfig.typecheck.json ", "generate:wit-world": "npm-run-all -s wit:merge wit:bindings", - "wit:merge": "./runtime/fastedge/host-api/scripts/merge-wit-bindings.js", - "wit:bindings": "./runtime/fastedge/host-api/scripts/create-wit-bindings.sh", + "wit:merge": "./runtime/fastedge/scripts/merge-wit-bindings.js", + "wit:bindings": "./runtime/fastedge/scripts/create-wit-bindings.sh", "lint": "npx eslint -c ./config/eslint/repo/.eslintrc.cjs .", "semantic-release": "semantic-release", "test:solo": "NODE_ENV=test jest -c ./config/jest/jest.config.js --", @@ -85,7 +85,7 @@ }, "dependencies": { "@bytecodealliance/jco": "^1.2.4", - "@bytecodealliance/wizer": "^3.0.1", + "@bytecodealliance/wizer": "^10.0.0", "acorn": "^8.8.2", "acorn-walk": "^8.2.0", "arg": "^5.0.2", diff --git a/runtime/FastEdge-wit b/runtime/FastEdge-wit new file mode 160000 index 0000000..561aa99 --- /dev/null +++ b/runtime/FastEdge-wit @@ -0,0 +1 @@ +Subproject commit 561aa99135425fb2a7a01feb989614fcbd083a50 diff --git a/runtime/fastedge/CMakeLists.txt b/runtime/fastedge/CMakeLists.txt index 5a77d04..484ad36 100644 --- a/runtime/fastedge/CMakeLists.txt +++ b/runtime/fastedge/CMakeLists.txt @@ -4,6 +4,7 @@ include("../StarlingMonkey/cmake/add_as_subproject.cmake") # add_builtin(fastedge::runtime SRC handler.cpp) add_builtin(fastedge::fastedge SRC builtins/fastedge.cpp) +add_builtin(fastedge::kv_store SRC builtins/kv-store.cpp) project(fastedge) diff --git a/runtime/fastedge/builtins/fastedge.cpp b/runtime/fastedge/builtins/fastedge.cpp index eda1125..9ac67ff 100644 --- a/runtime/fastedge/builtins/fastedge.cpp +++ b/runtime/fastedge/builtins/fastedge.cpp @@ -194,7 +194,6 @@ bool install(api::Engine *engine) { return false; } - // Ensure that the fastedge objects are not garbage collected and modules are defined // fastedge:env RootedValue get_env_val(engine->cx()); diff --git a/runtime/fastedge/builtins/kv-store.cpp b/runtime/fastedge/builtins/kv-store.cpp new file mode 100644 index 0000000..b99ddf6 --- /dev/null +++ b/runtime/fastedge/builtins/kv-store.cpp @@ -0,0 +1,469 @@ +#include "kv-store.h" +#include + +using fastedge::kv_store::KvStore; + +namespace { +api::Engine *ENGINE; +} + +namespace fastedge::kv_store { + +// JSClassOps for KvStore instances +static const JSClassOps kv_store_class_ops = { + .finalize = KvStore::finalize, +}; + +// JSClass definition for KvStore instances +const JSClass KvStore::class_ = { + "KvStore", + JSCLASS_HAS_RESERVED_SLOTS(1) | JSCLASS_FOREGROUND_FINALIZE, + &kv_store_class_ops +}; + +// Static methods for the KvStore constructor +const JSFunctionSpec KvStore::static_methods[] = { + JS_FN("open", KvStore::open, 1, JSPROP_ENUMERATE), + JS_FS_END +}; + +// Instance methods for KvStore objects +const JSFunctionSpec KvStore::methods[] = { + JS_FN("get", KvStore::get, 1, JSPROP_ENUMERATE), + JS_FN("scan", KvStore::scan, 1, JSPROP_ENUMERATE), + JS_FN("zrangeByScore", KvStore::zrange_by_score, 3, JSPROP_ENUMERATE), + JS_FN("zscan", KvStore::zscan, 2, JSPROP_ENUMERATE), + JS_FN("bfExists", KvStore::bf_exists, 2, JSPROP_ENUMERATE), + JS_FS_END +}; + +KvStore* KvStore::get_instance(JSContext *cx, JSObject *obj) { + return static_cast(JS::GetReservedSlot(obj, 0).toPrivate()); +} + +void KvStore::finalize(JS::GCContext *gcx, JSObject *obj) { + KvStore* store = static_cast(JS::GetReservedSlot(obj, 0).toPrivate()); + if (store) { + delete store; + } +} + +bool KvStore::open(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + if (!args.requireAtLeast(cx, "KvStore.open", 1)) { + return false; + } + + // Convert the store name to string + JS::RootedString store_name_str(cx, JS::ToString(cx, args[0])); + if (!store_name_str) { + return false; + } + + JS::UniqueChars store_name = JS_EncodeStringToUTF8(cx, store_name_str); + if (!store_name) { + return false; + } + + // Call the host API to open the store + auto result = host_api::kv_store_open(store_name.get()); + + // THROW ERRORS... + if (!result.is_ok()) { + // Handle error cases + auto error = result.unwrap_err(); + switch (error.tag) { + case host_api::KvStoreErrorTag::NO_SUCH_STORE: + JS_ReportErrorUTF8(cx, "No such store: %s", store_name.get()); + break; + case host_api::KvStoreErrorTag::ACCESS_DENIED: + JS_ReportErrorUTF8(cx, "Access denied to store: %s", store_name.get()); + break; + case host_api::KvStoreErrorTag::INTERNAL_ERROR: + JS_ReportErrorUTF8(cx, "Internal error opening store: %s", store_name.get()); + break; + case host_api::KvStoreErrorTag::OTHER: + JS_ReportErrorUTF8(cx, "Error opening store %s: %s", store_name.get(), error.val.other.ptr); + break; + } + return false; + } + + int32_t store_handle = result.unwrap(); + + // Create a new KvStore instance + JS::RootedObject store_obj(cx, JS_NewObjectWithGivenProto(cx, &KvStore::class_, nullptr)); + if (!store_obj) { + return false; + } + + // Create the C++ instance and store it in the JS object + KvStore* store_instance = new KvStore(store_handle); + JS::SetReservedSlot(store_obj, 0, JS::PrivateValue(store_instance)); + + // Define the instance methods + if (!JS_DefineFunctions(cx, store_obj, KvStore::methods)) { + delete store_instance; + return false; + } + + args.rval().setObject(*store_obj); + return true; +} + +bool KvStore::get(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + if (!args.requireAtLeast(cx, "get", 1)) { + return false; + } + + // Get the KvStore instance + JS::RootedObject this_obj(cx, &args.thisv().toObject()); + KvStore* store = get_instance(cx, this_obj); + if (!store) { + JS_ReportErrorUTF8(cx, "Invalid KvStore instance"); + return false; + } + + // Convert the key to string + JS::RootedString key_str(cx, JS::ToString(cx, args[0])); + if (!key_str) { + return false; + } + + JS::UniqueChars key = JS_EncodeStringToUTF8(cx, key_str); + if (!key) { + return false; + } + + // Call the host API + auto result = host_api::kv_store_get(store->store_handle_, key.get()); + + if (!result.is_ok()) { + // Handle error + JS_ReportErrorUTF8(cx, "Error getting key: %s", key.get()); + return false; + } + + auto value_option = result.unwrap(); + if (!value_option.is_some()) { + args.rval().setNull(); + return true; + } + + auto value = value_option.unwrap(); + + // Convert the value (list) to a Uint8Array + JS::RootedObject byte_array(cx, JS_NewUint8Array(cx, value.len)); + if (!byte_array) { + return false; + } + + { + JS::AutoCheckCannotGC noGC(cx); + bool is_shared; + void *array_buffer = JS_GetArrayBufferViewData(byte_array, &is_shared, noGC); + memcpy(array_buffer, value.ptr, value.len); + } + + args.rval().setObject(*byte_array); + return true; +} + +bool KvStore::scan(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + if (!args.requireAtLeast(cx, "scan", 1)) { + return false; + } + + JS::RootedObject this_obj(cx, &args.thisv().toObject()); + KvStore* store = get_instance(cx, this_obj); + if (!store) { + JS_ReportErrorUTF8(cx, "Invalid KvStore instance"); + return false; + } + + JS::RootedString pattern_str(cx, JS::ToString(cx, args[0])); + if (!pattern_str) { + return false; + } + + JS::UniqueChars pattern = JS_EncodeStringToUTF8(cx, pattern_str); + if (!pattern) { + return false; + } + + auto result = host_api::kv_store_scan(store->store_handle_, pattern.get()); + + if (!result.is_ok()) { + JS_ReportErrorUTF8(cx, "Error scanning with pattern: %s (Only prefix matching is supported. e.g. 'foo*')", pattern.get()); + return false; + } + + auto keys = result.unwrap(); + + // Create a JavaScript array + JS::RootedObject keys_array(cx, JS::NewArrayObject(cx, keys.len)); + if (!keys_array) { + return false; + } + + for (size_t i = 0; i < keys.len; i++) { + JS::RootedString key_str(cx, JS_NewStringCopyUTF8N(cx, + JS::UTF8Chars(keys.ptr[i].begin(), keys.ptr[i].size()))); + if (!key_str) { + return false; + } + + JS::RootedValue key_val(cx, JS::StringValue(key_str)); + if (!JS_SetElement(cx, keys_array, i, key_val)) { + return false; + } + } + + args.rval().setObject(*keys_array); + return true; +} + +bool KvStore::zrange_by_score(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + if (!args.requireAtLeast(cx, "zrangeByScore", 3)) { + return false; + } + + JS::RootedObject this_obj(cx, &args.thisv().toObject()); + KvStore* store = get_instance(cx, this_obj); + if (!store) { + JS_ReportErrorUTF8(cx, "Invalid KvStore instance"); + return false; + } + + // Convert arguments + JS::RootedString key_str(cx, JS::ToString(cx, args[0])); + if (!key_str) { + return false; + } + + JS::UniqueChars key = JS_EncodeStringToUTF8(cx, key_str); + if (!key) { + return false; + } + + double min, max; + if (!JS::ToNumber(cx, args[1], &min) || !JS::ToNumber(cx, args[2], &max)) { + return false; + } + + auto result = host_api::kv_store_zrange_by_score(store->store_handle_, key.get(), min, max); + + if (!result.is_ok()) { + JS_ReportErrorUTF8(cx, "Error in zrangeByScore for key: %s", key.get()); + return false; + } + + auto tuples = result.unwrap(); + + // Create array of [value, score] tuples + JS::RootedObject tuples_array(cx, JS::NewArrayObject(cx, tuples.len)); + if (!tuples_array) { + return false; + } + + for (size_t i = 0; i < tuples.len; i++) { + // Create the value Uint8Array + JS::RootedObject byte_array(cx, JS_NewUint8Array(cx, tuples.ptr[i].f0.len)); + if (!byte_array) { + return false; + } + + { + JS::AutoCheckCannotGC noGC(cx); + bool is_shared; + void *array_buffer = JS_GetArrayBufferViewData(byte_array, &is_shared, noGC); + memcpy(array_buffer, tuples.ptr[i].f0.ptr, tuples.ptr[i].f0.len); + } + + // Create tuple [value, score] + JS::RootedObject tuple(cx, JS::NewArrayObject(cx, 2)); + if (!tuple) { + return false; + } + + JS::RootedValue value_val(cx, JS::ObjectValue(*byte_array)); + JS::RootedValue score_val(cx, JS::DoubleValue(tuples.ptr[i].f1)); + + if (!JS_SetElement(cx, tuple, 0, value_val) || + !JS_SetElement(cx, tuple, 1, score_val)) { + return false; + } + + JS::RootedValue tuple_val(cx, JS::ObjectValue(*tuple)); + if (!JS_SetElement(cx, tuples_array, i, tuple_val)) { + return false; + } + } + + args.rval().setObject(*tuples_array); + return true; +} + +bool KvStore::zscan(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + if (!args.requireAtLeast(cx, "zscan", 2)) { + return false; + } + + JS::RootedObject this_obj(cx, &args.thisv().toObject()); + KvStore* store = get_instance(cx, this_obj); + if (!store) { + JS_ReportErrorUTF8(cx, "Invalid KvStore instance"); + return false; + } + + JS::RootedString key_str(cx, JS::ToString(cx, args[0])); + if (!key_str) { + return false; + } + + JS::UniqueChars key = JS_EncodeStringToUTF8(cx, key_str); + if (!key) { + return false; + } + + JS::RootedString pattern_str(cx, JS::ToString(cx, args[1])); + if (!pattern_str) { + return false; + } + + JS::UniqueChars pattern = JS_EncodeStringToUTF8(cx, pattern_str); + if (!pattern) { + return false; + } + + auto result = host_api::kv_store_zscan(store->store_handle_, key.get(), pattern.get()); + + if (!result.is_ok()) { + JS_ReportErrorUTF8(cx, "Error in zscan for key: %s", key.get()); + return false; + } + + auto tuples = result.unwrap(); + + // Create array of [value, score] tuples + JS::RootedObject tuples_array(cx, JS::NewArrayObject(cx, tuples.len)); + if (!tuples_array) { + return false; + } + + for (size_t i = 0; i < tuples.len; i++) { + // Create the value Uint8Array + JS::RootedObject byte_array(cx, JS_NewUint8Array(cx, tuples.ptr[i].f0.len)); + if (!byte_array) { + return false; + } + + { + JS::AutoCheckCannotGC noGC(cx); + bool is_shared; + void *array_buffer = JS_GetArrayBufferViewData(byte_array, &is_shared, noGC); + memcpy(array_buffer, tuples.ptr[i].f0.ptr, tuples.ptr[i].f0.len); + } + + // Create tuple [value, score] + JS::RootedObject tuple(cx, JS::NewArrayObject(cx, 2)); + if (!tuple) { + return false; + } + + JS::RootedValue value_val(cx, JS::ObjectValue(*byte_array)); + JS::RootedValue score_val(cx, JS::DoubleValue(tuples.ptr[i].f1)); + + if (!JS_SetElement(cx, tuple, 0, value_val) || + !JS_SetElement(cx, tuple, 1, score_val)) { + return false; + } + + JS::RootedValue tuple_val(cx, JS::ObjectValue(*tuple)); + if (!JS_SetElement(cx, tuples_array, i, tuple_val)) { + return false; + } + } + + args.rval().setObject(*tuples_array); + return true; +} + +bool KvStore::bf_exists(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + if (!args.requireAtLeast(cx, "bfExists", 2)) { + return false; + } + + JS::RootedObject this_obj(cx, &args.thisv().toObject()); + KvStore* store = get_instance(cx, this_obj); + if (!store) { + JS_ReportErrorUTF8(cx, "Invalid KvStore instance"); + return false; + } + + JS::RootedString key_str(cx, JS::ToString(cx, args[0])); + if (!key_str) { + return false; + } + + JS::UniqueChars key = JS_EncodeStringToUTF8(cx, key_str); + if (!key) { + return false; + } + + JS::RootedString item_str(cx, JS::ToString(cx, args[1])); + if (!item_str) { + return false; + } + + JS::UniqueChars item = JS_EncodeStringToUTF8(cx, item_str); + if (!item) { + return false; + } + + auto result = host_api::kv_store_bf_exists(store->store_handle_, key.get(), item.get()); + + if (!result.is_ok()) { + JS_ReportErrorUTF8(cx, "Error checking bloom filter for key: %s", key.get()); + return false; + } + + args.rval().setBoolean(result.unwrap()); + return true; +} + +bool install(api::Engine *engine) { + ENGINE = engine; + + // Create the KvStore constructor function + JS::RootedObject kv_store_ctor(engine->cx(), + JS_NewObject(engine->cx(), &KvStore::class_)); + if (!kv_store_ctor) { + return false; + } + + // Add static methods to the constructor + if (!JS_DefineFunctions(engine->cx(), kv_store_ctor, KvStore::static_methods)) { + return false; + } + + // Define KvStore on the global object + if (!JS_DefineProperty(engine->cx(), engine->global(), "KvStore", kv_store_ctor, 0)) { + return false; + } + + return true; +} + +} // namespace fastedge::kv_store diff --git a/runtime/fastedge/builtins/kv-store.h b/runtime/fastedge/builtins/kv-store.h new file mode 100644 index 0000000..586a776 --- /dev/null +++ b/runtime/fastedge/builtins/kv-store.h @@ -0,0 +1,33 @@ +#pragma once + +#include "builtin.h" +#include "../host-api/include/fastedge_host_api.h" + +namespace fastedge::kv_store { + +class KvStore : public builtins::BuiltinNoConstructor { +public: + static constexpr const char *class_name = "KvStore"; + + static bool open(JSContext *cx, unsigned argc, JS::Value *vp); + static bool get(JSContext *cx, unsigned argc, JS::Value *vp); + static bool scan(JSContext *cx, unsigned argc, JS::Value *vp); + static bool zrange_by_score(JSContext *cx, unsigned argc, JS::Value *vp); + static bool zscan(JSContext *cx, unsigned argc, JS::Value *vp); + static bool bf_exists(JSContext *cx, unsigned argc, JS::Value *vp); + + static void finalize(JS::GCContext *gcx, JSObject *obj); + + static const JSClass class_; + static const JSFunctionSpec static_methods[]; + static const JSFunctionSpec methods[]; + +private: + int32_t store_handle_; + + explicit KvStore(int32_t handle) : store_handle_(handle) {} + + static KvStore* get_instance(JSContext *cx, JSObject *obj); +}; + +} // namespace fastedge::kv_store diff --git a/runtime/fastedge/host-api/bindings/bindings.c b/runtime/fastedge/host-api/bindings/bindings.c index 306b45c..1113ad0 100644 --- a/runtime/fastedge/host-api/bindings/bindings.c +++ b/runtime/fastedge/host-api/bindings/bindings.c @@ -16,6 +16,26 @@ extern void __wasm_import_gcore_fastedge_secret_get(uint8_t *, size_t, uint8_t * __attribute__((__import_module__("gcore:fastedge/secret"), __import_name__("get-effective-at"))) extern void __wasm_import_gcore_fastedge_secret_get_effective_at(uint8_t *, size_t, int32_t, uint8_t *); +// Imported Functions from `gcore:fastedge/key-value` + +__attribute__((__import_module__("gcore:fastedge/key-value"), __import_name__("[static]store.open"))) +extern void __wasm_import_gcore_fastedge_key_value_static_store_open(uint8_t *, size_t, uint8_t *); + +__attribute__((__import_module__("gcore:fastedge/key-value"), __import_name__("[method]store.get"))) +extern void __wasm_import_gcore_fastedge_key_value_method_store_get(int32_t, uint8_t *, size_t, uint8_t *); + +__attribute__((__import_module__("gcore:fastedge/key-value"), __import_name__("[method]store.scan"))) +extern void __wasm_import_gcore_fastedge_key_value_method_store_scan(int32_t, uint8_t *, size_t, uint8_t *); + +__attribute__((__import_module__("gcore:fastedge/key-value"), __import_name__("[method]store.zrange-by-score"))) +extern void __wasm_import_gcore_fastedge_key_value_method_store_zrange_by_score(int32_t, uint8_t *, size_t, double, double, uint8_t *); + +__attribute__((__import_module__("gcore:fastedge/key-value"), __import_name__("[method]store.zscan"))) +extern void __wasm_import_gcore_fastedge_key_value_method_store_zscan(int32_t, uint8_t *, size_t, uint8_t *, size_t, uint8_t *); + +__attribute__((__import_module__("gcore:fastedge/key-value"), __import_name__("[method]store.bf-exists"))) +extern void __wasm_import_gcore_fastedge_key_value_method_store_bf_exists(int32_t, uint8_t *, size_t, uint8_t *, size_t, uint8_t *); + // Imported Functions from `wasi:cli/environment@0.2.3` __attribute__((__import_module__("wasi:cli/environment@0.2.3"), __import_name__("get-environment"))) @@ -632,22 +652,57 @@ void gcore_fastedge_secret_result_option_string_error_free(gcore_fastedge_secret } } -void bindings_tuple2_string_string_free(bindings_tuple2_string_string_t *ptr) { - bindings_string_free(&ptr->f0); - bindings_string_free(&ptr->f1); -} - -void bindings_list_tuple2_string_string_free(bindings_list_tuple2_string_string_t *ptr) { +void gcore_fastedge_key_value_value_free(gcore_fastedge_key_value_value_t *ptr) { size_t list_len = ptr->len; if (list_len > 0) { - bindings_tuple2_string_string_t *list_ptr = ptr->ptr; + uint8_t *list_ptr = ptr->ptr; for (size_t i = 0; i < list_len; i++) { - bindings_tuple2_string_string_free(&list_ptr[i]); } free(list_ptr); } } +__attribute__((__import_module__("gcore:fastedge/key-value"), __import_name__("[resource-drop]store"))) +extern void __wasm_import_gcore_fastedge_key_value_store_drop(int32_t handle); + +void gcore_fastedge_key_value_store_drop_own(gcore_fastedge_key_value_own_store_t handle) { + __wasm_import_gcore_fastedge_key_value_store_drop(handle.__handle); +} + +gcore_fastedge_key_value_borrow_store_t gcore_fastedge_key_value_borrow_store(gcore_fastedge_key_value_own_store_t arg) { + return (gcore_fastedge_key_value_borrow_store_t) { arg.__handle }; +} + +void gcore_fastedge_key_value_error_free(gcore_fastedge_key_value_error_t *ptr) { + switch ((int32_t) ptr->tag) { + case 3: { + bindings_string_free(&ptr->val.other); + break; + } + } +} + +void gcore_fastedge_key_value_result_own_store_error_free(gcore_fastedge_key_value_result_own_store_error_t *ptr) { + if (!ptr->is_err) { + } else { + gcore_fastedge_key_value_error_free(&ptr->val.err); + } +} + +void bindings_option_value_free(bindings_option_value_t *ptr) { + if (ptr->is_some) { + gcore_fastedge_key_value_value_free(&ptr->val); + } +} + +void gcore_fastedge_key_value_result_option_value_error_free(gcore_fastedge_key_value_result_option_value_error_t *ptr) { + if (!ptr->is_err) { + bindings_option_value_free(&ptr->val.ok); + } else { + gcore_fastedge_key_value_error_free(&ptr->val.err); + } +} + void bindings_list_string_free(bindings_list_string_t *ptr) { size_t list_len = ptr->len; if (list_len > 0) { @@ -659,6 +714,60 @@ void bindings_list_string_free(bindings_list_string_t *ptr) { } } +void gcore_fastedge_key_value_result_list_string_error_free(gcore_fastedge_key_value_result_list_string_error_t *ptr) { + if (!ptr->is_err) { + bindings_list_string_free(&ptr->val.ok); + } else { + gcore_fastedge_key_value_error_free(&ptr->val.err); + } +} + +void bindings_tuple2_value_f64_free(bindings_tuple2_value_f64_t *ptr) { + gcore_fastedge_key_value_value_free(&ptr->f0); +} + +void bindings_list_tuple2_value_f64_free(bindings_list_tuple2_value_f64_t *ptr) { + size_t list_len = ptr->len; + if (list_len > 0) { + bindings_tuple2_value_f64_t *list_ptr = ptr->ptr; + for (size_t i = 0; i < list_len; i++) { + bindings_tuple2_value_f64_free(&list_ptr[i]); + } + free(list_ptr); + } +} + +void gcore_fastedge_key_value_result_list_tuple2_value_f64_error_free(gcore_fastedge_key_value_result_list_tuple2_value_f64_error_t *ptr) { + if (!ptr->is_err) { + bindings_list_tuple2_value_f64_free(&ptr->val.ok); + } else { + gcore_fastedge_key_value_error_free(&ptr->val.err); + } +} + +void gcore_fastedge_key_value_result_bool_error_free(gcore_fastedge_key_value_result_bool_error_t *ptr) { + if (!ptr->is_err) { + } else { + gcore_fastedge_key_value_error_free(&ptr->val.err); + } +} + +void bindings_tuple2_string_string_free(bindings_tuple2_string_string_t *ptr) { + bindings_string_free(&ptr->f0); + bindings_string_free(&ptr->f1); +} + +void bindings_list_tuple2_string_string_free(bindings_list_tuple2_string_string_t *ptr) { + size_t list_len = ptr->len; + if (list_len > 0) { + bindings_tuple2_string_string_t *list_ptr = ptr->ptr; + for (size_t i = 0; i < list_len; i++) { + bindings_tuple2_string_string_free(&list_ptr[i]); + } + free(list_ptr); + } +} + void wasi_cli_exit_result_void_void_free(wasi_cli_exit_result_void_void_t *ptr) { if (!ptr->is_err) { } @@ -1793,6 +1902,289 @@ bool gcore_fastedge_secret_get_effective_at(bindings_string_t *key, uint32_t at, } } +bool gcore_fastedge_key_value_static_store_open(bindings_string_t *name, gcore_fastedge_key_value_own_store_t *ret, gcore_fastedge_key_value_error_t *err) { + __attribute__((__aligned__(4))) + uint8_t ret_area[16]; + uint8_t *ptr = (uint8_t *) &ret_area; + __wasm_import_gcore_fastedge_key_value_static_store_open((uint8_t *) (*name).ptr, (*name).len, ptr); + gcore_fastedge_key_value_result_own_store_error_t result; + switch ((int32_t) *((uint8_t*) (ptr + 0))) { + case 0: { + result.is_err = false; + result.val.ok = (gcore_fastedge_key_value_own_store_t) { *((int32_t*) (ptr + 4)) }; + break; + } + case 1: { + result.is_err = true; + gcore_fastedge_key_value_error_t variant; + variant.tag = (int32_t) *((uint8_t*) (ptr + 4)); + switch ((int32_t) variant.tag) { + case 0: { + break; + } + case 1: { + break; + } + case 2: { + break; + } + case 3: { + variant.val.other = (bindings_string_t) { (uint8_t*)(*((uint8_t **) (ptr + 8))), (*((size_t*) (ptr + 12))) }; + break; + } + } + + result.val.err = variant; + break; + } + } + if (!result.is_err) { + *ret = result.val.ok; + return 1; + } else { + *err = result.val.err; + return 0; + } +} + +bool gcore_fastedge_key_value_method_store_get(gcore_fastedge_key_value_borrow_store_t self, bindings_string_t *key, bindings_option_value_t *ret, gcore_fastedge_key_value_error_t *err) { + __attribute__((__aligned__(4))) + uint8_t ret_area[16]; + uint8_t *ptr = (uint8_t *) &ret_area; + __wasm_import_gcore_fastedge_key_value_method_store_get((self).__handle, (uint8_t *) (*key).ptr, (*key).len, ptr); + gcore_fastedge_key_value_result_option_value_error_t result; + switch ((int32_t) *((uint8_t*) (ptr + 0))) { + case 0: { + result.is_err = false; + bindings_option_value_t option; + switch ((int32_t) *((uint8_t*) (ptr + 4))) { + case 0: { + option.is_some = false; + break; + } + case 1: { + option.is_some = true; + option.val = (gcore_fastedge_key_value_value_t) { (uint8_t*)(*((uint8_t **) (ptr + 8))), (*((size_t*) (ptr + 12))) }; + break; + } + } + + result.val.ok = option; + break; + } + case 1: { + result.is_err = true; + gcore_fastedge_key_value_error_t variant; + variant.tag = (int32_t) *((uint8_t*) (ptr + 4)); + switch ((int32_t) variant.tag) { + case 0: { + break; + } + case 1: { + break; + } + case 2: { + break; + } + case 3: { + variant.val.other = (bindings_string_t) { (uint8_t*)(*((uint8_t **) (ptr + 8))), (*((size_t*) (ptr + 12))) }; + break; + } + } + + result.val.err = variant; + break; + } + } + if (!result.is_err) { + *ret = result.val.ok; + return 1; + } else { + *err = result.val.err; + return 0; + } +} + +bool gcore_fastedge_key_value_method_store_scan(gcore_fastedge_key_value_borrow_store_t self, bindings_string_t *pattern, bindings_list_string_t *ret, gcore_fastedge_key_value_error_t *err) { + __attribute__((__aligned__(4))) + uint8_t ret_area[16]; + uint8_t *ptr = (uint8_t *) &ret_area; + __wasm_import_gcore_fastedge_key_value_method_store_scan((self).__handle, (uint8_t *) (*pattern).ptr, (*pattern).len, ptr); + gcore_fastedge_key_value_result_list_string_error_t result; + switch ((int32_t) *((uint8_t*) (ptr + 0))) { + case 0: { + result.is_err = false; + result.val.ok = (bindings_list_string_t) { (bindings_string_t*)(*((uint8_t **) (ptr + 4))), (*((size_t*) (ptr + 8))) }; + break; + } + case 1: { + result.is_err = true; + gcore_fastedge_key_value_error_t variant; + variant.tag = (int32_t) *((uint8_t*) (ptr + 4)); + switch ((int32_t) variant.tag) { + case 0: { + break; + } + case 1: { + break; + } + case 2: { + break; + } + case 3: { + variant.val.other = (bindings_string_t) { (uint8_t*)(*((uint8_t **) (ptr + 8))), (*((size_t*) (ptr + 12))) }; + break; + } + } + + result.val.err = variant; + break; + } + } + if (!result.is_err) { + *ret = result.val.ok; + return 1; + } else { + *err = result.val.err; + return 0; + } +} + +bool gcore_fastedge_key_value_method_store_zrange_by_score(gcore_fastedge_key_value_borrow_store_t self, bindings_string_t *key, double min, double max, bindings_list_tuple2_value_f64_t *ret, gcore_fastedge_key_value_error_t *err) { + __attribute__((__aligned__(4))) + uint8_t ret_area[16]; + uint8_t *ptr = (uint8_t *) &ret_area; + __wasm_import_gcore_fastedge_key_value_method_store_zrange_by_score((self).__handle, (uint8_t *) (*key).ptr, (*key).len, min, max, ptr); + gcore_fastedge_key_value_result_list_tuple2_value_f64_error_t result; + switch ((int32_t) *((uint8_t*) (ptr + 0))) { + case 0: { + result.is_err = false; + result.val.ok = (bindings_list_tuple2_value_f64_t) { (bindings_tuple2_value_f64_t*)(*((uint8_t **) (ptr + 4))), (*((size_t*) (ptr + 8))) }; + break; + } + case 1: { + result.is_err = true; + gcore_fastedge_key_value_error_t variant; + variant.tag = (int32_t) *((uint8_t*) (ptr + 4)); + switch ((int32_t) variant.tag) { + case 0: { + break; + } + case 1: { + break; + } + case 2: { + break; + } + case 3: { + variant.val.other = (bindings_string_t) { (uint8_t*)(*((uint8_t **) (ptr + 8))), (*((size_t*) (ptr + 12))) }; + break; + } + } + + result.val.err = variant; + break; + } + } + if (!result.is_err) { + *ret = result.val.ok; + return 1; + } else { + *err = result.val.err; + return 0; + } +} + +bool gcore_fastedge_key_value_method_store_zscan(gcore_fastedge_key_value_borrow_store_t self, bindings_string_t *key, bindings_string_t *pattern, bindings_list_tuple2_value_f64_t *ret, gcore_fastedge_key_value_error_t *err) { + __attribute__((__aligned__(4))) + uint8_t ret_area[16]; + uint8_t *ptr = (uint8_t *) &ret_area; + __wasm_import_gcore_fastedge_key_value_method_store_zscan((self).__handle, (uint8_t *) (*key).ptr, (*key).len, (uint8_t *) (*pattern).ptr, (*pattern).len, ptr); + gcore_fastedge_key_value_result_list_tuple2_value_f64_error_t result; + switch ((int32_t) *((uint8_t*) (ptr + 0))) { + case 0: { + result.is_err = false; + result.val.ok = (bindings_list_tuple2_value_f64_t) { (bindings_tuple2_value_f64_t*)(*((uint8_t **) (ptr + 4))), (*((size_t*) (ptr + 8))) }; + break; + } + case 1: { + result.is_err = true; + gcore_fastedge_key_value_error_t variant; + variant.tag = (int32_t) *((uint8_t*) (ptr + 4)); + switch ((int32_t) variant.tag) { + case 0: { + break; + } + case 1: { + break; + } + case 2: { + break; + } + case 3: { + variant.val.other = (bindings_string_t) { (uint8_t*)(*((uint8_t **) (ptr + 8))), (*((size_t*) (ptr + 12))) }; + break; + } + } + + result.val.err = variant; + break; + } + } + if (!result.is_err) { + *ret = result.val.ok; + return 1; + } else { + *err = result.val.err; + return 0; + } +} + +bool gcore_fastedge_key_value_method_store_bf_exists(gcore_fastedge_key_value_borrow_store_t self, bindings_string_t *key, bindings_string_t *item, bool *ret, gcore_fastedge_key_value_error_t *err) { + __attribute__((__aligned__(4))) + uint8_t ret_area[16]; + uint8_t *ptr = (uint8_t *) &ret_area; + __wasm_import_gcore_fastedge_key_value_method_store_bf_exists((self).__handle, (uint8_t *) (*key).ptr, (*key).len, (uint8_t *) (*item).ptr, (*item).len, ptr); + gcore_fastedge_key_value_result_bool_error_t result; + switch ((int32_t) *((uint8_t*) (ptr + 0))) { + case 0: { + result.is_err = false; + result.val.ok = (int32_t) *((uint8_t*) (ptr + 4)); + break; + } + case 1: { + result.is_err = true; + gcore_fastedge_key_value_error_t variant; + variant.tag = (int32_t) *((uint8_t*) (ptr + 4)); + switch ((int32_t) variant.tag) { + case 0: { + break; + } + case 1: { + break; + } + case 2: { + break; + } + case 3: { + variant.val.other = (bindings_string_t) { (uint8_t*)(*((uint8_t **) (ptr + 8))), (*((size_t*) (ptr + 12))) }; + break; + } + } + + result.val.err = variant; + break; + } + } + if (!result.is_err) { + *ret = result.val.ok; + return 1; + } else { + *err = result.val.err; + return 0; + } +} + void wasi_cli_environment_get_environment(bindings_list_tuple2_string_string_t *ret) { __attribute__((__aligned__(4))) uint8_t ret_area[8]; diff --git a/runtime/fastedge/host-api/bindings/bindings.h b/runtime/fastedge/host-api/bindings/bindings.h index 4250bc5..fc430f8 100644 --- a/runtime/fastedge/host-api/bindings/bindings.h +++ b/runtime/fastedge/host-api/bindings/bindings.h @@ -42,6 +42,97 @@ typedef struct { } val; } gcore_fastedge_secret_result_option_string_error_t; +typedef struct gcore_fastedge_key_value_value_t { + uint8_t *ptr; + size_t len; +} gcore_fastedge_key_value_value_t; + +typedef struct gcore_fastedge_key_value_own_store_t { + int32_t __handle; +} gcore_fastedge_key_value_own_store_t; + +typedef struct gcore_fastedge_key_value_borrow_store_t { + int32_t __handle; +} gcore_fastedge_key_value_borrow_store_t; + +// The set of errors which may be raised by functions in this interface +typedef struct gcore_fastedge_key_value_error_t { + uint8_t tag; + union { + bindings_string_t other; + } val; +} gcore_fastedge_key_value_error_t; + +// The host does not recognize the store label requested. +#define GCORE_FASTEDGE_KEY_VALUE_ERROR_NO_SUCH_STORE 0 +// The requesting component does not have access to the specified store +// (which may or may not exist). +#define GCORE_FASTEDGE_KEY_VALUE_ERROR_ACCESS_DENIED 1 +// Some unexpected internal error has occurred. +#define GCORE_FASTEDGE_KEY_VALUE_ERROR_INTERNAL_ERROR 2 +// Some implementation-specific error has occurred (e.g. I/O) +#define GCORE_FASTEDGE_KEY_VALUE_ERROR_OTHER 3 + +typedef struct { + bool is_err; + union { + gcore_fastedge_key_value_own_store_t ok; + gcore_fastedge_key_value_error_t err; + } val; +} gcore_fastedge_key_value_result_own_store_error_t; + +typedef struct { + bool is_some; + gcore_fastedge_key_value_value_t val; +} bindings_option_value_t; + +typedef struct { + bool is_err; + union { + bindings_option_value_t ok; + gcore_fastedge_key_value_error_t err; + } val; +} gcore_fastedge_key_value_result_option_value_error_t; + +typedef struct { + bindings_string_t *ptr; + size_t len; +} bindings_list_string_t; + +typedef struct { + bool is_err; + union { + bindings_list_string_t ok; + gcore_fastedge_key_value_error_t err; + } val; +} gcore_fastedge_key_value_result_list_string_error_t; + +typedef struct { + gcore_fastedge_key_value_value_t f0; + double f1; +} bindings_tuple2_value_f64_t; + +typedef struct { + bindings_tuple2_value_f64_t *ptr; + size_t len; +} bindings_list_tuple2_value_f64_t; + +typedef struct { + bool is_err; + union { + bindings_list_tuple2_value_f64_t ok; + gcore_fastedge_key_value_error_t err; + } val; +} gcore_fastedge_key_value_result_list_tuple2_value_f64_error_t; + +typedef struct { + bool is_err; + union { + bool ok; + gcore_fastedge_key_value_error_t err; + } val; +} gcore_fastedge_key_value_result_bool_error_t; + typedef struct { bindings_string_t f0; bindings_string_t f1; @@ -52,11 +143,6 @@ typedef struct { size_t len; } bindings_list_tuple2_string_string_t; -typedef struct { - bindings_string_t *ptr; - size_t len; -} bindings_list_string_t; - typedef struct { bool is_err; } wasi_cli_exit_result_void_void_t; @@ -1317,13 +1403,44 @@ typedef wasi_http_types_own_response_outparam_t exports_wasi_http_incoming_handl extern bool gcore_fastedge_dictionary_get(bindings_string_t *name, bindings_string_t *ret); // Imported Functions from `gcore:fastedge/secret` -// Get the secret associated with the specified `key` efective at current timestamp. +// Get the secret associated with the specified `key` effective at current timestamp. // Returns `ok(none)` if the key does not exist. extern bool gcore_fastedge_secret_get(bindings_string_t *key, bindings_option_string_t *ret, gcore_fastedge_secret_error_t *err); // Get the secret associated with the specified `key` effective `at` given timestamp in seconds. // Returns `ok(none)` if the key does not exist. extern bool gcore_fastedge_secret_get_effective_at(bindings_string_t *key, uint32_t at, bindings_option_string_t *ret, gcore_fastedge_secret_error_t *err); +// Imported Functions from `gcore:fastedge/key-value` +// Open the store with the specified name. +// +// `error::no-such-store` will be raised if the `name` is not recognized. +extern bool gcore_fastedge_key_value_static_store_open(bindings_string_t *name, gcore_fastedge_key_value_own_store_t *ret, gcore_fastedge_key_value_error_t *err); +// Get the value associated with the specified `key` +// +// Returns `ok(none)` if the key does not exist. +extern bool gcore_fastedge_key_value_method_store_get(gcore_fastedge_key_value_borrow_store_t self, bindings_string_t *key, bindings_option_value_t *ret, gcore_fastedge_key_value_error_t *err); +// 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. +extern bool gcore_fastedge_key_value_method_store_scan(gcore_fastedge_key_value_borrow_store_t self, bindings_string_t *pattern, bindings_list_string_t *ret, gcore_fastedge_key_value_error_t *err); +// 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 empty `Vec` if the key does not exist or if no elements have scores between min and max. +extern bool gcore_fastedge_key_value_method_store_zrange_by_score(gcore_fastedge_key_value_borrow_store_t self, bindings_string_t *key, double min, double max, bindings_list_tuple2_value_f64_t *ret, gcore_fastedge_key_value_error_t *err); +// 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. +extern bool gcore_fastedge_key_value_method_store_zscan(gcore_fastedge_key_value_borrow_store_t self, bindings_string_t *key, bindings_string_t *pattern, bindings_list_tuple2_value_f64_t *ret, gcore_fastedge_key_value_error_t *err); +// 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. +extern bool gcore_fastedge_key_value_method_store_bf_exists(gcore_fastedge_key_value_borrow_store_t self, bindings_string_t *key, bindings_string_t *item, bool *ret, gcore_fastedge_key_value_error_t *err); + // Imported Functions from `wasi:cli/environment@0.2.3` // Get the POSIX-style environment variables. // @@ -1842,12 +1959,36 @@ void gcore_fastedge_secret_error_free(gcore_fastedge_secret_error_t *ptr); void gcore_fastedge_secret_result_option_string_error_free(gcore_fastedge_secret_result_option_string_error_t *ptr); -void bindings_tuple2_string_string_free(bindings_tuple2_string_string_t *ptr); +void gcore_fastedge_key_value_value_free(gcore_fastedge_key_value_value_t *ptr); -void bindings_list_tuple2_string_string_free(bindings_list_tuple2_string_string_t *ptr); +extern void gcore_fastedge_key_value_store_drop_own(gcore_fastedge_key_value_own_store_t handle); + +extern gcore_fastedge_key_value_borrow_store_t gcore_fastedge_key_value_borrow_store(gcore_fastedge_key_value_own_store_t handle); + +void gcore_fastedge_key_value_error_free(gcore_fastedge_key_value_error_t *ptr); + +void gcore_fastedge_key_value_result_own_store_error_free(gcore_fastedge_key_value_result_own_store_error_t *ptr); + +void bindings_option_value_free(bindings_option_value_t *ptr); + +void gcore_fastedge_key_value_result_option_value_error_free(gcore_fastedge_key_value_result_option_value_error_t *ptr); void bindings_list_string_free(bindings_list_string_t *ptr); +void gcore_fastedge_key_value_result_list_string_error_free(gcore_fastedge_key_value_result_list_string_error_t *ptr); + +void bindings_tuple2_value_f64_free(bindings_tuple2_value_f64_t *ptr); + +void bindings_list_tuple2_value_f64_free(bindings_list_tuple2_value_f64_t *ptr); + +void gcore_fastedge_key_value_result_list_tuple2_value_f64_error_free(gcore_fastedge_key_value_result_list_tuple2_value_f64_error_t *ptr); + +void gcore_fastedge_key_value_result_bool_error_free(gcore_fastedge_key_value_result_bool_error_t *ptr); + +void bindings_tuple2_string_string_free(bindings_tuple2_string_string_t *ptr); + +void bindings_list_tuple2_string_string_free(bindings_list_tuple2_string_string_t *ptr); + void wasi_cli_exit_result_void_void_free(wasi_cli_exit_result_void_void_t *ptr); extern void wasi_io_error_error_drop_own(wasi_io_error_own_error_t handle); diff --git a/runtime/fastedge/host-api/bindings/bindings_component_type.o b/runtime/fastedge/host-api/bindings/bindings_component_type.o index 3217439..ff505ff 100644 Binary files a/runtime/fastedge/host-api/bindings/bindings_component_type.o and b/runtime/fastedge/host-api/bindings/bindings_component_type.o differ diff --git a/runtime/fastedge/host-api/deps/fastedge/key-value.wit b/runtime/fastedge/host-api/deps/fastedge/key-value.wit new file mode 100644 index 0000000..6d94782 --- /dev/null +++ b/runtime/fastedge/host-api/deps/fastedge/key-value.wit @@ -0,0 +1,54 @@ +interface key-value { + type value = list; + type list-result = list; + type zlist-result = 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 the values associated with the specified `key` stored in sorted set ordered by f64 score + /// + /// Returns empty `Vec` if the key does not exist or min and max are out of index. + zrange: func(key: string, min: f64, max: f64) -> result; + + /// 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; + + /// 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/runtime/fastedge/host-api/deps/fastedge/world.wit b/runtime/fastedge/host-api/deps/fastedge/world.wit index 29d741f..0ff9b23 100644 --- a/runtime/fastedge/host-api/deps/fastedge/world.wit +++ b/runtime/fastedge/host-api/deps/fastedge/world.wit @@ -3,4 +3,5 @@ package gcore:fastedge; world imports { import dictionary; import secret; + import key-value; } diff --git a/runtime/fastedge/host-api/fastedge_host_api.cpp b/runtime/fastedge/host-api/fastedge_host_api.cpp index c47d549..089b7fa 100644 --- a/runtime/fastedge/host-api/fastedge_host_api.cpp +++ b/runtime/fastedge/host-api/fastedge_host_api.cpp @@ -1,6 +1,7 @@ -#include "host_api.h" +#include "fastedge_host_api.h" #include "bindings/bindings.h" +#include namespace host_api { @@ -69,5 +70,154 @@ HostString get_secret_vars_effective_at(std::string_view name, uint32_t effectiv return nullptr; } +// KV Store implementations + +KvStoreResult kv_store_open(std::string_view name) { + auto name_str = string_view_to_world_string(name); + gcore_fastedge_key_value_own_store_t store{}; + gcore_fastedge_key_value_error_t err{}; + + // Initialize error struct to zero + memset(&err, 0, sizeof(err)); + memset(&store, 0, sizeof(store)); + + bool success = gcore_fastedge_key_value_static_store_open(&name_str, &store, &err); + + if (success) { + return KvStoreResult::ok(store.__handle); + } else { + KvStoreError error; + error.tag = static_cast(err.tag); + if (err.tag == GCORE_FASTEDGE_KEY_VALUE_ERROR_OTHER) { + error.val.other.ptr = (char*)err.val.other.ptr; + error.val.other.len = err.val.other.len; + } + return KvStoreResult::err(error); + } +} + +KvStoreResult> kv_store_get(int32_t store_handle, std::string_view key) { + auto key_str = string_view_to_world_string(key); + gcore_fastedge_key_value_borrow_store_t store = {store_handle}; + bindings_option_value_t ret{}; + gcore_fastedge_key_value_error_t err{}; + + bool success = gcore_fastedge_key_value_method_store_get(store, &key_str, &ret, &err); + + if (success) { + if (ret.is_some) { + KvStoreValue value; + value.ptr = ret.val.ptr; + value.len = ret.val.len; + return KvStoreResult>::ok(KvStoreOption::some(value)); + } else { + return KvStoreResult>::ok(KvStoreOption::none()); + } + } else { + KvStoreError error; + error.tag = static_cast(err.tag); + if (err.tag == GCORE_FASTEDGE_KEY_VALUE_ERROR_OTHER) { + error.val.other.ptr = (char*)err.val.other.ptr; + error.val.other.len = err.val.other.len; + } + return KvStoreResult>::err(error); + } +} + +KvStoreResult kv_store_scan(int32_t store_handle, std::string_view pattern) { + auto pattern_str = string_view_to_world_string(pattern); + gcore_fastedge_key_value_borrow_store_t store = {store_handle}; + bindings_list_string_t ret{}; + gcore_fastedge_key_value_error_t err{}; + + bool success = gcore_fastedge_key_value_method_store_scan(store, &pattern_str, &ret, &err); + + if (success) { + KvStoreStringList result; + result.ptr = reinterpret_cast(ret.ptr); + result.len = ret.len; + return KvStoreResult::ok(result); + } else { + KvStoreError error; + error.tag = static_cast(err.tag); + if (err.tag == GCORE_FASTEDGE_KEY_VALUE_ERROR_OTHER) { + error.val.other.ptr = (char*)err.val.other.ptr; + error.val.other.len = err.val.other.len; + } + return KvStoreResult::err(error); + } +} + +KvStoreResult kv_store_zrange_by_score(int32_t store_handle, std::string_view key, double min, double max) { + auto key_str = string_view_to_world_string(key); + gcore_fastedge_key_value_borrow_store_t store = {store_handle}; + bindings_list_tuple2_value_f64_t ret{}; + gcore_fastedge_key_value_error_t err{}; + + bool success = gcore_fastedge_key_value_method_store_zrange_by_score(store, &key_str, min, max, &ret, &err); + + if (success) { + KvStoreZList result; + result.ptr = reinterpret_cast(ret.ptr); + result.len = ret.len; + return KvStoreResult::ok(result); + } else { + KvStoreError error; + error.tag = static_cast(err.tag); + if (err.tag == GCORE_FASTEDGE_KEY_VALUE_ERROR_OTHER) { + error.val.other.ptr = (char*)err.val.other.ptr; + error.val.other.len = err.val.other.len; + } + return KvStoreResult::err(error); + } +} + +KvStoreResult kv_store_zscan(int32_t store_handle, std::string_view key, std::string_view pattern) { + auto key_str = string_view_to_world_string(key); + auto pattern_str = string_view_to_world_string(pattern); + gcore_fastedge_key_value_borrow_store_t store = {store_handle}; + bindings_list_tuple2_value_f64_t ret{}; + gcore_fastedge_key_value_error_t err{}; + + bool success = gcore_fastedge_key_value_method_store_zscan(store, &key_str, &pattern_str, &ret, &err); + + if (success) { + KvStoreZList result; + result.ptr = reinterpret_cast(ret.ptr); + result.len = ret.len; + return KvStoreResult::ok(result); + } else { + KvStoreError error; + error.tag = static_cast(err.tag); + if (err.tag == GCORE_FASTEDGE_KEY_VALUE_ERROR_OTHER) { + error.val.other.ptr = (char*)err.val.other.ptr; + error.val.other.len = err.val.other.len; + } + return KvStoreResult::err(error); + } +} + +KvStoreResult kv_store_bf_exists(int32_t store_handle, std::string_view key, std::string_view item) { + auto key_str = string_view_to_world_string(key); + auto item_str = string_view_to_world_string(item); + gcore_fastedge_key_value_borrow_store_t store = {store_handle}; + bool ret = false; + gcore_fastedge_key_value_error_t err{}; + + bool success = gcore_fastedge_key_value_method_store_bf_exists(store, &key_str, &item_str, &ret, &err); + + if (success) { + return KvStoreResult::ok(ret); + } else { + KvStoreError error; + error.tag = static_cast(err.tag); + if (err.tag == GCORE_FASTEDGE_KEY_VALUE_ERROR_OTHER) { + error.val.other.ptr = (char*)err.val.other.ptr; + error.val.other.len = err.val.other.len; + } + return KvStoreResult::err(error); + } +} + } // namespace host_api diff --git a/runtime/fastedge/host-api/include/fastedge_host_api.h b/runtime/fastedge/host-api/include/fastedge_host_api.h index 61b8868..87ec03a 100644 --- a/runtime/fastedge/host-api/include/fastedge_host_api.h +++ b/runtime/fastedge/host-api/include/fastedge_host_api.h @@ -8,10 +8,113 @@ struct JSErrorFormatString; namespace host_api { +// Environment and secrets HostString get_env_vars(std::string_view name); HostString get_secret_vars(std::string_view name); HostString get_secret_vars_effective_at(std::string_view name, uint32_t effective_at); +// KV Store types and enums +enum class KvStoreErrorTag : uint8_t { + NO_SUCH_STORE = 0, + ACCESS_DENIED = 1, + INTERNAL_ERROR = 2, + OTHER = 3 +}; + +struct KvStoreError { + KvStoreErrorTag tag; + union { + struct { + char* ptr; + size_t len; + } other; + } val; +}; + +template +class KvStoreResult { +public: + bool is_ok() const { return ok_; } + const T& unwrap() const { return value_; } + const KvStoreError& unwrap_err() const { return error_; } + + static KvStoreResult ok(T value) { + KvStoreResult result; + result.ok_ = true; + result.value_ = std::move(value); + return result; + } + + static KvStoreResult err(KvStoreError error) { + KvStoreResult result; + result.ok_ = false; + result.error_ = error; + return result; + } + +private: + bool ok_; + T value_; + KvStoreError error_; +}; + +struct KvStoreValue { + uint8_t* ptr; + size_t len; +}; + +template +class KvStoreOption { +public: + bool is_some() const { return has_value_; } + const T& unwrap() const { return value_; } + + static KvStoreOption some(T value) { + KvStoreOption option; + option.has_value_ = true; + option.value_ = std::move(value); + return option; + } + + static KvStoreOption none() { + KvStoreOption option; + option.has_value_ = false; + return option; + } + +private: + bool has_value_ = false; + T value_; +}; + +struct KvStoreList { + KvStoreValue* ptr; + size_t len; +}; + +struct KvStoreTuple { + KvStoreValue f0; + double f1; +}; + +struct KvStoreZList { + KvStoreTuple* ptr; + size_t len; +}; + +struct KvStoreStringList { + HostString* ptr; + size_t len; +}; + +// KV Store functions +KvStoreResult kv_store_open(std::string_view name); +KvStoreResult> kv_store_get(int32_t store_handle, std::string_view key); +KvStoreResult kv_store_scan(int32_t store_handle, std::string_view pattern); +KvStoreResult kv_store_zrange_by_score(int32_t store_handle, std::string_view key, double min, double max); +KvStoreResult kv_store_zscan(int32_t store_handle, std::string_view key, std::string_view pattern); +KvStoreResult kv_store_bf_exists(int32_t store_handle, std::string_view key, std::string_view item); + } // namespace host_api -#endif +#endif // FASTEDGE_HOST_API_H diff --git a/runtime/fastedge/host-api/wit/deps/fastedge/dictionary.wit b/runtime/fastedge/host-api/wit/deps/fastedge/dictionary.wit index d14c2d7..be0eb3b 100644 --- a/runtime/fastedge/host-api/wit/deps/fastedge/dictionary.wit +++ b/runtime/fastedge/host-api/wit/deps/fastedge/dictionary.wit @@ -3,4 +3,4 @@ interface dictionary { /// /// Returns `ok(none)` if the key does not exist. get: func(name: string) -> option; -} +} \ No newline at end of file diff --git a/runtime/fastedge/host-api/wit/deps/fastedge/key-value.wit b/runtime/fastedge/host-api/wit/deps/fastedge/key-value.wit new file mode 100644 index 0000000..18058cb --- /dev/null +++ b/runtime/fastedge/host-api/wit/deps/fastedge/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 empty `Vec` 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/runtime/fastedge/host-api/wit/deps/fastedge/secret.wit b/runtime/fastedge/host-api/wit/deps/fastedge/secret.wit index dc07a11..ab03b8d 100644 --- a/runtime/fastedge/host-api/wit/deps/fastedge/secret.wit +++ b/runtime/fastedge/host-api/wit/deps/fastedge/secret.wit @@ -1,5 +1,5 @@ interface secret { - /// Get the secret associated with the specified `key` efective at current timestamp. + /// 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>; diff --git a/runtime/fastedge/host-api/wit/deps/fastedge/world.wit b/runtime/fastedge/host-api/wit/deps/fastedge/world.wit index 29d741f..7c318cd 100644 --- a/runtime/fastedge/host-api/wit/deps/fastedge/world.wit +++ b/runtime/fastedge/host-api/wit/deps/fastedge/world.wit @@ -1,6 +1,9 @@ package gcore:fastedge; -world imports { +world reactor { + import dictionary; import secret; -} + import key-value; + +} \ No newline at end of file diff --git a/runtime/fastedge/host-api/wit/main.wit b/runtime/fastedge/host-api/wit/main.wit index 0540260..66e9285 100644 --- a/runtime/fastedge/host-api/wit/main.wit +++ b/runtime/fastedge/host-api/wit/main.wit @@ -1,7 +1,7 @@ package local:bindings; world bindings { - include gcore:fastedge/imports; + include gcore:fastedge/reactor; include wasi:cli/command@0.2.3; include wasi:http/proxy@0.2.3; } diff --git a/runtime/fastedge/host-api/scripts/create-wit-bindings.sh b/runtime/fastedge/scripts/create-wit-bindings.sh similarity index 100% rename from runtime/fastedge/host-api/scripts/create-wit-bindings.sh rename to runtime/fastedge/scripts/create-wit-bindings.sh diff --git a/runtime/fastedge/host-api/scripts/merge-wit-bindings.js b/runtime/fastedge/scripts/merge-wit-bindings.js similarity index 57% rename from runtime/fastedge/host-api/scripts/merge-wit-bindings.js rename to runtime/fastedge/scripts/merge-wit-bindings.js index e857b6b..fc5f96a 100755 --- a/runtime/fastedge/host-api/scripts/merge-wit-bindings.js +++ b/runtime/fastedge/scripts/merge-wit-bindings.js @@ -7,8 +7,10 @@ import { fileURLToPath } from 'node:url'; const HOST_VERSIONS = ['0.2.0', '0.2.2', '0.2.3']; const dirname = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../'); -const witDir = path.join(dirname, 'wit'); -const starlingHostApisDir = path.resolve(dirname, '../../StarlingMonkey/host-apis'); +const witDir = path.join(dirname, 'host-api/wit'); +const starlingHostApisDir = path.resolve(dirname, '../StarlingMonkey/host-apis'); +const fastedgeWitDir = path.resolve(dirname, '../FastEdge-wit'); +const fastedgeDepsToRemove = ['http-client', 'http-handler', 'http']; // These are Rust specific. we use wasi-http function clearExistingWitFiles() { // Remove all files from the ./wit directory @@ -40,17 +42,41 @@ function copyWitFilesFromHostApi(wasiHostVersion) { copyFilesRecursively(hostApiWitDir, witDir); } +function removeFastEdgeDepsWeDontUse(fastedgeDepsDir) { + const fastedgeWorldWitFile = path.join(fastedgeDepsDir, 'world.wit'); + const fastedgeWorldWitContent = fs.readFileSync(fastedgeWorldWitFile, 'utf-8'); + + let worldWitContentLines = fastedgeWorldWitContent.split('\n'); + + for (const dep of fastedgeDepsToRemove) { + // Remove the file for the given dependency. + fs.unlinkSync(path.join(fastedgeDepsDir, `${dep}.wit`)); + //Remove its import/export inclusion in the world. + worldWitContentLines = worldWitContentLines.filter((line) => { + return !line.includes(` ${dep};`); + }); + } + fs.writeFileSync(fastedgeWorldWitFile, worldWitContentLines.join('\n'), 'utf-8'); +} + function mergeFastEdgeWitFiles() { - // Copy files from wit-interface/fastedge to ./wit/deps - const fastedgeDepsDir = path.join(dirname, 'deps'); - copyFilesRecursively(fastedgeDepsDir, path.join(witDir, 'deps')); + // Copy files from FastEdge-wit submodule to deps/fastedge + const fastedgeDepsDir = path.join(witDir, 'deps/fastedge'); + fs.mkdirSync(fastedgeDepsDir, { recursive: true }); + copyFilesRecursively(fastedgeWitDir, fastedgeDepsDir); + // Remove the files we dont use in FastEdge-sdk-js + removeFastEdgeDepsWeDontUse(fastedgeDepsDir); + // Remove extra files from submodule + fs.unlinkSync(path.join(fastedgeDepsDir, 'LICENSE')); + fs.unlinkSync(path.join(fastedgeDepsDir, 'README.md')); + // Edit ./wit/main.wit to include the fastedge deps const mainWitFile = path.join(witDir, 'main.wit'); const mainWitContent = fs.readFileSync(mainWitFile, 'utf-8'); const updatedMainWitContent = mainWitContent.replace( 'world bindings {\n', - `world bindings {\n include gcore:fastedge/imports;\n`, + `world bindings {\n include gcore:fastedge/reactor;\n`, ); fs.writeFileSync(mainWitFile, updatedMainWitContent, 'utf-8'); console.log(`WIT files successfully created in ${witDir}`); diff --git a/src/componentize/__tests__/componentize.test.ts b/src/componentize/__tests__/componentize.test.ts index 284322c..8712a05 100644 --- a/src/componentize/__tests__/componentize.test.ts +++ b/src/componentize/__tests__/componentize.test.ts @@ -68,6 +68,7 @@ describe('componentize', () => { [ '--allow-wasi', '--wasm-bulk-memory=true', + '--wasm-reference-types=true', '--inherit-env=true', '--dir=.', '--dir=temp_root', diff --git a/src/componentize/componentize.ts b/src/componentize/componentize.ts index 9b9f97f..e76e759 100644 --- a/src/componentize/componentize.ts +++ b/src/componentize/componentize.ts @@ -73,6 +73,7 @@ async function componentize( [ '--allow-wasi', '--wasm-bulk-memory=true', + '--wasm-reference-types=true', '--inherit-env=true', '--dir=.', // '--dir=../', // Farq: NEED to iterate config file and add these paths for static building... @@ -109,6 +110,7 @@ async function componentize( cleanup(); } + // Core component creation code (commented out for now) const coreComponent = await readFile(output); const adapter = npxPackagePath('./lib/preview1-adapter.wasm'); diff --git a/src/componentize/es-bundle.ts b/src/componentize/es-bundle.ts index e638da4..cfa001a 100644 --- a/src/componentize/es-bundle.ts +++ b/src/componentize/es-bundle.ts @@ -28,6 +28,13 @@ const fastedgePackagePlugin: Plugin = { `, }; } + case 'kv': { + return { + contents: ` + export const KvStore = globalThis.KvStore; + `, + }; + } default: { return { contents: '' }; } diff --git a/src/server/static-assets/asset-manifest/__tests__/create-manifest.test.ts b/src/server/static-assets/asset-manifest/__tests__/create-manifest.test.ts index 3a9b0ca..0f40df1 100644 --- a/src/server/static-assets/asset-manifest/__tests__/create-manifest.test.ts +++ b/src/server/static-assets/asset-manifest/__tests__/create-manifest.test.ts @@ -303,7 +303,7 @@ describe('createStaticAssetsManifest', () => { ignoreWellKnown: 'booleanFalsy', ignorePaths: 'pathsArray', publicDir: 'path', - contentTypes: 'string', + contentTypes: 'stringArray', assetManifestPath: 'path', }), ); diff --git a/src/server/static-assets/asset-manifest/create-manifest.ts b/src/server/static-assets/asset-manifest/create-manifest.ts index 842f6c9..03fb45a 100644 --- a/src/server/static-assets/asset-manifest/create-manifest.ts +++ b/src/server/static-assets/asset-manifest/create-manifest.ts @@ -80,7 +80,7 @@ function normalizeAssetCacheConfig(config: Partial): AssetCach return normalizeConfig(config, { publicDir: 'path', assetManifestPath: 'path', - contentTypes: 'string', + contentTypes: 'stringArray', ignoreDotFiles: 'booleanTruthy', ignorePaths: 'pathsArray', ignoreWellKnown: 'booleanFalsy', diff --git a/types/fastedge-kv.d.ts b/types/fastedge-kv.d.ts new file mode 100644 index 0000000..2ec2b12 --- /dev/null +++ b/types/fastedge-kv.d.ts @@ -0,0 +1,92 @@ +declare module 'fastedge::kv' { + /** + * KvStore class to interact with the FastEdge Key-Value store. + * + * @example + * ```js + * /// + * + * import { KvStore } from "fastedge::kv"; + * + * async function app(event) { + * try { + * const kv = KvStore.open("my-kv-store"); + * const value = kv.get("key"); + * + * return new Response(value, { + * status: 200 + * }); + * + * } catch (error) { + * return new Response("Error opening store", { + * status: 500 + * }); + * } + * } + * + * addEventListener("fetch", event => event.respondWith(app(event))); + * ``` + */ + + export class KvStore { + /** + * Static method to open a store and return an instance + * + * @param {string} name The name of the KV store as defined on your application. + * + * @returns {KvStoreInstance} The KvStore instance for the opened store. + */ + static open(name: string): KvStoreInstance; + } + + export interface KvStoreInstance { + /** + * Retrieves the value associated with the given key from the KV store. + * + * @param {string} key The key to retrieve the value for. + * + * @returns {ArrayBuffer | null} The value associated with the key, or null if not found. + */ + get(key: string): ArrayBuffer | null; + + /** + * Retrieves all key prefix matches from the KV store. + * + * @param {string} pattern The prefix pattern to match keys against. e.g. 'foo*' ( Must include wildcard ) + * + * @returns {Array} The keys matching the pattern, or empty array if none found. + */ + scan(pattern: string): Array; + + /** + * Retrieves all the values from ZSet with scores between the given range. + * + * @param {string} key The key for the Sorted Set. + * @param {number} min The minimum score for the range. + * @param {number} max The maximum score for the range. + * + * @returns {Array<[ArrayBuffer, number]>} Array of [value, score] tuples within range for the key, or an empty array if none found. + */ + zrangeByScore(key: string, min: number, max: number): Array<[ArrayBuffer, number]>; + + /** + * Retrieves all value prefix matches from the KV ZSet. + * + * @param {string} key The key for the Sorted Set. + * @param {string} pattern The prefix pattern to match values against. e.g. 'foo*' ( Must include wildcard ) + * + * @returns {Array<[ArrayBuffer, number]>} Array of [value, score] tuples which match the prefix pattern, or an empty array if none found. + */ + zscan(key: string, pattern: string): Array<[ArrayBuffer, number]>; + + /** + * Checks if a given value exists within the KV stores Bloom Filter. + * + * @param {string} key The key for the Bloom Filter. + * @param {string} value The value to check for existence. + * + * @returns {boolean} True if the value exists, false otherwise. + */ + bfExists(key: string, value: string): boolean; + } +} diff --git a/types/index.d.ts b/types/index.d.ts index 4eeaec1..6d5f977 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -1,6 +1,7 @@ /// /// /// +/// /// export * from './server/static-assets/index.d.ts';