Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: API review #8

Merged
merged 21 commits into from
Oct 16, 2023
Merged

feat: API review #8

merged 21 commits into from
Oct 16, 2023

Conversation

mhmd-azeez
Copy link
Collaborator

@mhmd-azeez mhmd-azeez commented Oct 5, 2023

This PR fixes #4 and addresses some of the points of #6

  • Export createPlugin instead of ExtismPlugin.new to be more idiomatic for JS developers.
  • Implement CurrentPlugin
  • Sync README with Ruby SDK. I decided to include this in the PR since it depends on the new APIs
  • Stream incoming wasm plugin data if possible, especially if we're in the browser
  • Accept a URL for both ManifestWasmFile and ManifestWasmUrl

@mhmd-azeez
Copy link
Collaborator Author

mhmd-azeez commented Oct 5, 2023

@chrisdickinson regarding:

add async createPlugin(plugin, options): Plugin as the default export

I changed it to be a named export because as far as I know, you can't import both a named export and a default export in the same line in NodeJS:

const createPlugin, { ExtismPluginOptions } = require("../dist/node/index")

examples/deno.js Outdated Show resolved Hide resolved
@mhmd-azeez
Copy link
Collaborator Author

@bhelx @chrisdickinson because of the way I had written the tests, I didn't realize that I hadn't implemented CurrentPlugin. It was a deep rabbit hole, but I considered these approaches:

  1. Partial application of the host functions:
const options = new ExtismPluginOptions()
    .withFunction("env", "do_stuff", function (cp: CurrentPlugin, offs: bigint) {
        
    })

I couldn't get this to work because when initializing the imports, the number of parameters have to be known. And all of the partial application approaches assume the caller knows how many params the resulting function has.
2. Bind this to CurrentPlugin:

const options = new ExtismPluginOptions()
    .withFunction("env", "do_stuff", function (this: CurrentPlugin, offs: bigint) {
        
    })

This works well (note that this: CurrentPlugin is optional), the only issue is that it doesn't work with arrow functions.

I ended up going with approach 2 because I couldn't find any way to make approach 1 work

README.md Outdated Show resolved Hide resolved
README.md Outdated
Plug-in code can come from a file on disk, object storage or any number of places. Since you may not have one handy let's load a demo plug-in from the web:

```js
const options = new ExtismPluginOptions().withWasi();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an interesting one – I missed the config builder on my first read-through, apologies!

Generally in JS we pass plain objects for options & validate on the receiving end, with a typescript interface as the receiving function signature (e.g., the RequestInit interface for the fetch function.)

Moving to a plain object would allow for the default export, too.

Copy link
Collaborator Author

@mhmd-azeez mhmd-azeez Oct 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, I have turned ExtismPluginOptions into an interface, and now you can use the library like so:

// CommonJS
const { createPlugin } = require("../dist/node/index")

// ES Modules
import createPlugin from '../src/deno/mod.ts'

Note: for CJS, if I had used a default export, the user would have to write something like:

const createPlugin = require("../dist/node/index").default

Please let me know if there is a better way to go about this

src/plugin.ts Outdated Show resolved Hide resolved
@mhmd-azeez
Copy link
Collaborator Author

mhmd-azeez commented Oct 8, 2023

@chrisdickinson

Stream incoming wasm plugin data if possible, especially if we're in the browser

I have implemented this using WebAssembly.instantiateStreaming. However there are two caveats:

  1. If the user specifies a hash for the wasm source, then we have to download the content anyway and WebAssembly.instantiate will be used (since we already have the data in memory)
  2. WebAssembly.instantiateStreaming expects the Content-Type of the response to be application/wasm. So I have written some code to make sure the content type is always application/wasm. I have done this because we haven't given the user any way to opt-out of WebAssembly.instantiateStreaming. And it was a blocker for one of the tests that was downloading a plugin from github (this is also used in the readme as an example)

Accept a URL for both ManifestWasmFile and ManifestWasmUrl

I took a look at this too, I faced an issue with NodeJS. While the newer versions have support for fetch, on older versions we are falling back to node-fetch which doesn't support local files (file://). So I decided to leave it as as. Let me know if there is any workaround for that. One workaround would be to check the path scheme, what do you think?

@chrisdickinson
Copy link
Contributor

I took a look at this too, I faced an issue with NodeJS. While the newer versions have support for fetch, on older versions we are falling back to node-fetch which doesn't support local files (file://). So I decided to leave it as as. Let me know if there is any workaround for that. One workaround would be to check the path scheme, what do you think?

It looks like v18 (which is about to enter Maintenance according to the release plan) supports fetch() without flags; v16 went into end-of-life as of this last September. Usually I've seen libraries support the LTS + current versions – it might be okay to drop the node-fetch polyfill / Node 16 support now?

Copy link
Contributor

@chrisdickinson chrisdickinson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking really good! Re: the .default – yeah, I just don't see many ways around that. The good news is that it should be invisible to Babel/Typescript users thanks to the __esModuleInterop property. ESM is definitely a sore spot in Node still, though.

src/plugin.ts Outdated Show resolved Hide resolved
src/plugin.ts Outdated
@@ -648,21 +534,68 @@ export type HttpRequest = {
};

export const embeddedRuntime =
'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EHJBAEGQARCCgICAABoMAgsgAUEUakIANwIAIAFBATYCDCABQaCAwIAANgIIIAFBkIDAgAA2AhAgAUEIakG0gMCAABCEgICAAAALQQAoAvSAwIAAQRB0IQMLIACnQXRqIgRFDQAgAykDCCADQRBqrXwgAFgNACAENQIIIQILIAFBIGokgICAgAAgAgsIACAApy0AAAsIACAApykDAAsSAEEAKQPIgMCAACAAfKctAAALEgBBACkDyIDAgAAgAHynKQMACwoAIACnIAE6AAALCgAgAKcgATcDAAsYAEEAIAE3A9CAwIAAQQAgADcDyIDAgAALGABBACABNwPggMCAAEEAIAA3A9iAwIAACwsAQQApA9CAwIAACwsAQQApA8iAwIAACwsAQQApA+CAwIAACwsAQQApA9iAwIAAC/ABAQJ/I4CAgIAAQSBrIgAkgICAgABBAEIANwPogMCAAEEAQQAtAPCAwIAAIgFBASABGzoA8IDAgAACQAJAIAENAEEAQQFAACIBNgL0gMCAAAJAIAFBf0YNACABQRB0IgFCADcDACABQvD/AzcDCCABQRByQQBBkAEQgoCAgAAaDAILIABBFGpCADcCACAAQQE2AgwgAEGggMCAADYCCCAAQZCAwIAANgIQIABBCGpBtIDAgAAQhICAgAAAC0EAKAL0gMCAAEEQdCEBCyABQRBqQQAgASgCCBCCgICAABogAUIANwMAIABBIGokgICAgAALDQBBACAANwPogMCAAAsLAEEAKQPogMCAAAvWAQICfwF+I4CAgIAAQSBrIgAkgICAgABBAEEALQDwgMCAACIBQQEgARs6APCAwIAAAkACQCABDQBBAEEBQAAiATYC9IDAgAACQCABQX9GDQAgAUEQdCIBQgA3AwAgAULw/wM3AwggAUEQckEAQZABEIKAgIAAGgwCCyAAQRRqQgA3AgAgAEEBNgIMIABBoIDAgAA2AgggAEGQgMCAADYCECAAQQhqQbSAwIAAEISAgIAAAAtBACgC9IDAgABBEHQhAQsgASkDACECIABBIGokgICAgAAgAgsLTQEAQYCAwAALRAEAAAAAAAAAAQAAAAIAAABPdXQgb2YgbWVtb3J5AAAAEAAQAA0AAABzcmMvbGliLnJzAAAoABAACgAAAJsAAAANAAAA';
'AGFzbQEAAAABMApgAX8AYAN/f38Bf2ACf38AYAF+AX5gAX4AYAF+AX9gAn5/AGACfn4AYAABfmAAAAMaGQABAQACAgMEAwUDBQMGBwcHCAgICAkECAgEBQFwAQMDBQMBABEGGQN/AUGAgMAAC38AQfiAwAALfwBBgIHAAAsHlQMWBm1lbW9yeQIADGV4dGlzbV9hbGxvYwAGC2V4dGlzbV9mcmVlAAcNZXh0aXNtX2xlbmd0aAAIDmV4dGlzbV9sb2FkX3U4AAkPZXh0aXNtX2xvYWRfdTY0AAoUZXh0aXNtX2lucHV0X2xvYWRfdTgACxVleHRpc21faW5wdXRfbG9hZF91NjQADA9leHRpc21fc3RvcmVfdTgADRBleHRpc21fc3RvcmVfdTY0AA4QZXh0aXNtX2lucHV0X3NldAAPEWV4dGlzbV9vdXRwdXRfc2V0ABATZXh0aXNtX2lucHV0X2xlbmd0aAARE2V4dGlzbV9pbnB1dF9vZmZzZXQAEhRleHRpc21fb3V0cHV0X2xlbmd0aAATFGV4dGlzbV9vdXRwdXRfb2Zmc2V0ABQMZXh0aXNtX3Jlc2V0ABUQZXh0aXNtX2Vycm9yX3NldAAWEGV4dGlzbV9lcnJvcl9nZXQAFxNleHRpc21fbWVtb3J5X2J5dGVzABgKX19kYXRhX2VuZAMBC19faGVhcF9iYXNlAwIJCAEAQQELAgMFCoYQGQQAAAALtQEBA38CQAJAIAJBD0sNACAAIQMMAQsgAEEAIABrQQNxIgRqIQUCQCAERQ0AIAAhAwNAIAMgAToAACADQQFqIgMgBUkNAAsLIAUgAiAEayIEQXxxIgJqIQMCQCACQQFIDQAgAUH/AXFBgYKECGwhAgNAIAUgAjYCACAFQQRqIgUgA0kNAAsLIARBA3EhAgsCQCACRQ0AIAMgAmohBQNAIAMgAToAACADQQFqIgMgBUkNAAsLIAALDgAgACABIAIQgYCAgAALAgALTAEBfyOAgICAAEEgayICJICAgIAAIAIgADYCFCACQYCAwIAANgIMIAJBgIDAgAA2AgggAkEBOgAYIAIgATYCECACQQhqEICAgIAAAAsiACAAQpTpyfD234+bmX83AwggAEKbyMGq6ey7kcgANwMAC7cEBwF/AX4CfwJ+AX8BfgJ/I4CAgIAAQSBrIgEkgICAgAACQAJAIABQRQ0AQgAhAgwBC0EAQQAtAPCAwIAAIgNBASADGzoA8IDAgAACQAJAIAMNAEEAQQFAACIDNgL0gMCAAAJAIANBf0YNACADQRB0IgRCADcDACAEQvD/AzcDCCAEQRByQQBBkAEQgoCAgAAaDAILIAFBFGpCADcCACABQQE2AgwgAUGggMCAADYCCCABQZCAwIAANgIQIAFBCGpBtIDAgAAQhICAgAAAC0EAKAL0gMCAAEEQdCEECyAEKQMIIQUCQAJAAkACQAJAAkAgBCkDACIGIARBEGoiB60iCHwiAiAIWA0AIACnIQkgByEDA0ACQAJAAkAgAy0AAA4DBgABAAsgAygCBCEKDAELIAMoAgQiCiAJTw0DCyACIAogA2pBGGoiA61WDQALCyAFIAZ9QnB8IgIgAFgNAgwDCyAKIAlrIgpBgAFJDQAgA0EANgIIIAMgCjYCBCADIApqIgNBFGpBADYCACADQRBqIAk2AgAgA0EMaiIDQQI6AAALIANBAToAACADIAk2AggMAgsCQCAAIAJ9IgJC//8Dg0IAUiACQhCIp2oiA0AAQX9HDQBBACEDDAILIAQgBCkDCCADrUIQhnw3AwgLIAQgACAEKQMAfEIMfDcDACAGpyAHaiIDIACnIgo2AgggAyAKNgIEIANBAToAAAsgA0EMaq1CACADGyECCyABQSBqJICAgIAAIAIL9gEBA38jgICAgABBIGsiASSAgICAAAJAIABQDQBBAEEALQDwgMCAACICQQEgAhs6APCAwIAAAkACQCACDQBBAEEBQAAiAjYC9IDAgAACQCACQX9GDQAgAkEQdCICQgA3AwAgAkLw/wM3AwggAkEQckEAQZABEIKAgIAAGgwCCyABQRRqQgA3AgAgAUEBNgIMIAFBoIDAgAA2AgggAUGQgMCAADYCECABQQhqQbSAwIAAEISAgIAAAAtBACgC9IDAgABBEHQhAgsgAKdBdGoiA0UNACACKQMIIAJBEGqtfCAAWA0AIANBAjoAAAsgAUEgaiSAgICAAAuAAgMBfwF+An8jgICAgABBIGsiASSAgICAAEIAIQICQCAAUA0AQQBBAC0A8IDAgAAiA0EBIAMbOgDwgMCAAAJAAkAgAw0AQQBBAUAAIgM2AvSAwIAAAkAgA0F/Rg0AIANBEHQiA0IANwMAIANC8P8DNwMIIANBEHJBAEGQARCCgICAABoMAgsgAUEUakIANwIAIAFBATYCDCABQaCAwIAANgIIIAFBkIDAgAA2AhAgAUEIakG0gMCAABCEgICAAAALQQAoAvSAwIAAQRB0IQMLIACnQXRqIgRFDQAgAykDCCADQRBqrXwgAFgNACAENQIIIQILIAFBIGokgICAgAAgAgsIACAApy0AAAsIACAApykDAAsSAEEAKQPIgMCAACAAfKctAAALEgBBACkDyIDAgAAgAHynKQMACwoAIACnIAE6AAALCgAgAKcgATcDAAsYAEEAIAE3A9CAwIAAQQAgADcDyIDAgAALGABBACABNwPggMCAAEEAIAA3A9iAwIAACwsAQQApA9CAwIAACwsAQQApA8iAwIAACwsAQQApA+CAwIAACwsAQQApA9iAwIAAC/ABAQJ/I4CAgIAAQSBrIgAkgICAgABBAEIANwPogMCAAEEAQQAtAPCAwIAAIgFBASABGzoA8IDAgAACQAJAIAENAEEAQQFAACIBNgL0gMCAAAJAIAFBf0YNACABQRB0IgFCADcDACABQvD/AzcDCCABQRByQQBBkAEQgoCAgAAaDAILIABBFGpCADcCACAAQQE2AgwgAEGggMCAADYCCCAAQZCAwIAANgIQIABBCGpBtIDAgAAQhICAgAAAC0EAKAL0gMCAAEEQdCEBCyABQRBqQQAgASgCCBCCgICAABogAUIANwMAIABBIGokgICAgAALDQBBACAANwPogMCAAAsLAEEAKQPogMCAAAvWAQICfwF+I4CAgIAAQSBrIgAkgICAgABBAEEALQDwgMCAACIBQQEgARs6APCAwIAAAkACQCABDQBBAEEBQAAiATYC9IDAgAACQCABQX9GDQAgAUEQdCIBQgA3AwAgAULw/wM3AwggAUEQckEAQZABEIKAgIAAGgwCCyAAQRRqQgA3AgAgAEEBNgIMIABBoIDAgAA2AgggAEGQgMCAADYCECAAQQhqQbSAwIAAEISAgIAAAAtBACgC9IDAgABBEHQhAQsgASkDACECIABBIGokgICAgAAgAgsLTQEAQYCAwAALRAEAAAAAAAAAAQAAAAIAAABPdXQgb2YgbWVtb3J5AAAAEAAQAA0AAABzcmMvbGliLnJzAAAoABAACgAAAJsAAAANAAAA';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be outside the scope of this PR, but could we move this to another standalone module? It would make updating it programmatically a little more straightforward.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i have written a little regex magic that updates it. you can run via: make update-kernel. I think it's okay for now

src/plugin.ts Outdated Show resolved Hide resolved
src/plugin.ts Outdated Show resolved Hide resolved
src/plugin.ts Outdated Show resolved Hide resolved
src/plugin.ts Outdated Show resolved Hide resolved
src/plugin.ts Outdated Show resolved Hide resolved
src/plugin.ts Show resolved Hide resolved
src/plugin.ts Show resolved Hide resolved
@mhmd-azeez
Copy link
Collaborator Author

@chrisdickinson great suggestions, I have applied them all.

  1. Regarding unifying ManifestWasmUrl and ManifestWasmFile:
    We now only have ManifestWasmUrl, for node we are still falling back to fs because even the official fetch doesn't support local files. Users can also create a plugin from a string:
const plugin = await createPlugin("wasm/code.wasm");
  1. Reverted node to default export too

examples/deno.ts Outdated Show resolved Hide resolved
First you should import `createPlugin` and `ExtismPluginOptions` from Extism:
```js
// CommonJS
const createPlugin = require("../dist/node/index")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we write this from the user's perspective? how do we import from the npm module? If i'm using typescript how do i get the types I need?

@mhmd-azeez mhmd-azeez merged commit 87f7815 into main Oct 16, 2023
3 checks passed
@mhmd-azeez mhmd-azeez deleted the feat/api-review branch October 16, 2023 14:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Write a new README and getting started guide
4 participants