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

Initial system plugins #645

Merged
merged 54 commits into from
Mar 28, 2024
Merged

Initial system plugins #645

merged 54 commits into from
Mar 28, 2024

Conversation

James-Mart
Copy link
Member

@James-Mart James-Mart commented Mar 5, 2024

This adds some initial system app plugins and the initial component runtime infrastructure necessary for them to function. The plugins are only partially completed, and primarily intended to show the ergonomics of writing plugins.

The DemoApp1 front-end shows how to make calls to those plugins.

@James-Mart James-Mart marked this pull request as draft March 5, 2024 03:34
Base automatically changed from dev/plugins to main March 8, 2024 21:06
@James-Mart James-Mart marked this pull request as ready for review March 25, 2024 21:16
[dependencies]
bitflags = "2.4.2"
wit-bindgen-rt = "0.22.0"
#psibase = { version = "0.7.0", path = "../../../../rust/psibase/" }
Copy link
Member

Choose a reason for hiding this comment

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

Cruft? or left in here for some future use?

Copy link
Member Author

Choose a reason for hiding this comment

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

Cruft, removed

result: T;
const getFunctionBody = (pluginFunc: PluginFunc, resultCache: ResultCache[]): string => {
let {service, plugin, intf, method} = pluginFunc;
const found = resultCache.find((f: ResultCache)=>{
Copy link
Contributor

Choose a reason for hiding this comment

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

Seems like prettier formatting is absent here

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed

message: `${toString(args)}: Failed to download ${service}:${plugin}.`
}
});
} else if ( e instanceof ParserDownloadFailed ) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Prettier missing

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed

return fetch_cache.get(url);
}

await sleep(200);
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need this 200ms wait?

Copy link
Member Author

Choose a reason for hiding this comment

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

This is a local copy of the https://github.com/DougAnderson444/rollup-plugin-wit-component repo. I added a local copy so we could control the version of JCO instead of having to wait for Doug to publish a new version of his plugin. Furthermore we will eventually cache the transpilation in this plugin for some additional time savings.

I've now added attribution to the tops of all .js files in this /lib/ directory.

Comment on lines 140 to 145
let inviter = match inviter {
Some(i) => i,
None => {
return Err(InviterLoggedIn.err(""));
}
};
Copy link
Contributor

Choose a reason for hiding this comment

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

We could use ok_or here hey?

Copy link
Member Author

Choose a reason for hiding this comment

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

Well... I was going to say no, because I'm returning Err from the function (not assigning it to inviter).
Buuuut, actually the answer is yes. I can just use ? ...clever :)

Actually, ok_or_else (ok_or is apparently eagerly evaluated?)

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed (and in general made all the error handling more idiomatic)

Copy link
Contributor

@brandonfancher brandonfancher 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 great. I just have some readability nits...but nothing worth blocking the PR over.

Comment on lines 83 to 100
try {
if (messageEvent.origin === supervisorOrigin) {
if (isIFrameInitialized(messageEvent.data)) {
this.onSupervisorInitialized();
} else if (isFunctionCallResponse(messageEvent.data)) {
this.onFunctionCallResponse(messageEvent.data);
}
} else if (messageEvent.origin !== myOrigin) {
console.log("Received unauthorized message. Ignoring.");
}
} catch (e) {
if (e instanceof Error) {
console.error(e.message);
} else {
console.error(
`Unrecognized error: ${JSON.stringify(e, null, 2)}`
);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Generally in js/ts, to avoid nested if statements and make this more readable, we'd use early returns:

if (messageEvent.origin !== myOrigin) {
    console.log("Received unauthorized message. Ignoring.");
    return;
}

try {
    if (isIFrameInitialized(messageEvent.data)) {
        this.onSupervisorInitialized();
    } else if (isFunctionCallResponse(messageEvent.data)) {
        this.onFunctionCallResponse(messageEvent.data);
    }
} catch (e) {
    if (e instanceof Error) {
        console.error(e.message);
        return;
    }
    console.error(`Unrecognized error: ${JSON.stringify(e, null, 2)}`);
}

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed

Comment on lines 115 to 122
const expected = this.pendingRequest!.call;
const received = response.call;
const unexpected: boolean =
expected.method != received.method ||
expected.service != received.service;

if (isErrorResponse(response)) {
this.pendingRequest!.reject(response.result.val);
Copy link
Contributor

Choose a reason for hiding this comment

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

I would think the ! in this.pendingRequest! wouldn't be required in these cases...is TS complaining? It should know it exists because of your check on line 112 above. 🤔 Also, line 117...does it not infer the type?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah I was wondering that too.

Copy link
Member Author

Choose a reason for hiding this comment

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

Nah ts wasn't complaining, just lack of familiarity with ts type deduction.
Fixed

Comment on lines 53 to 63
} else {
if (found.result !== undefined) {
if (isErrorResult(found.result)) {
return `throw ${JSON.stringify(found.result.val)};`;
} else {
return `return ${JSON.stringify(found.result)};`;
}
} else {
return ""; // No-op if the sync call has no return value.
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I recommend dropping the else here on line 53. It's not needed because of the return in the preceding if condition on line 42. Then, to avoid further nesting:

if (found.result === undefined) {
    return ""; // No-op...
}

if (isErrorResult(found.result)) {
    return `throw ${JSON.stringify(found.result.val)};`;
}

return `return ${JSON.stringify(found.result)};`;

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed

Comment on lines 78 to 84
if (dupIndex !== -1) {
if (args !== actions[dupIndex].args) {
throw new Error("Mismatched args");
}
return true;
}
return false;
Copy link
Contributor

Choose a reason for hiding this comment

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

const dupIndex = actions.findIndex((a) => a.action === action);
if (dupIndex === -1) {
    return false;
}

if (args !== actions[dupIndex].args) {
    throw new Error("Mismatched args");
}

return true;

Or even:

const targetAction = actions.find((a) => a.action === action);
if (!targetAction) {
    return false;
}

if (args !== targetAction.args) {
    throw new Error("Mismatched args");
}

return true;

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed

export const server = {
// add-action-to-transaction: func(service: string, action: string, args: list<u8>) -> result<_, string>;
addActionToTransaction(action, args) {
if (myService === UNINITIALIZED)
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd add the brackets here for consistency. And there are a couple opportunities in this function to drop some else's too...but I'm getting pretty nitpicky at this point haha. Same goes for the rest of this file.

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed

Comment on lines 59 to 75
if (!cache.exists(parserCacheKey)) {
let parserBytes: Uint8Array;
try {
parserBytes = await wasmUrlToIntArray(
"/common/component_parser.wasm"
);
} catch (e) {
console.error("Loader: component_parser download failed");
throw new ParserDownloadFailed();
}
// Performance improvement: Target wasm32-unknown-unknown when compiling
// parser instead of wasm32-wasi, then loading can be simplified without wasi
// shims.
cache.cacheData(parserCacheKey, await load(parserBytes));
return cache.getCachedData(parserCacheKey);
} else return cache.getCachedData(parserCacheKey);
};
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 flip this around to avoid more nesting?

if (cache.exists(parserCacheKey)) {
    return cache.getCachedData(parserCacheKey);
}

let parserBytes: Uint8Array;
...

Copy link
Contributor

Choose a reason for hiding this comment

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

Same for the next couple functions.

Copy link
Member Author

Choose a reason for hiding this comment

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

I guess I don't need to think about tail call optimization in js

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed

Copy link
Member

Choose a reason for hiding this comment

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

Changes to this file are only formatting changes, AND our default .prettier config for JS/TS files includes the trailing comma. Please checkout this file and restore it to pre-edit status.

Copy link
Member Author

Choose a reason for hiding this comment

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

I just formatted according to the .prettierrc.json that already existed in common-lib. Where is this "default .prettier config" of which you speak? I'll update this prettier config to match.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah, I see in another comment - I'll use the one in NftSys

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed - I corrected the prettier configs and ran the updated config on each project: supervisor ui, wasm-loader, and common-lib

Copy link
Member

Choose a reason for hiding this comment

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

Similar note here; please configure .prettier to match, for example, the NftSys .prettierrc.json, which is just
{
"tabWidth": 4,
"plugins": ["prettier-plugin-tailwindcss"]
}

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed

};
} catch (err) {
throw new Error(err.message);
}
Copy link
Member

Choose a reason for hiding this comment

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

Any reason not to leverage something like the fetch api? Doing AJAX like this is old school :)
const resp = fetch(req.uri, {
method: req.method.toString(),
headers: new Headers(req.headers)
})

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, the reason is that this magic allows it to be a synchronous import with no groundhog day!

export default plugin;
import { plugin } from './plugin.js';
import { load } from './loader.js';
export { plugin, load };
Copy link
Member

Choose a reason for hiding this comment

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

You can do this as follows (which is the standard)

export { plugin } from './plugin.js';
export { load } from './loader.js';

Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed

@James-Mart
Copy link
Member Author

Closes #541, #630

This was linked to issues Mar 28, 2024
@James-Mart James-Mart mentioned this pull request Mar 28, 2024
@James-Mart James-Mart merged commit fe967e1 into main Mar 28, 2024
3 checks passed
@James-Mart James-Mart deleted the initial-system-plugins branch March 28, 2024 15:39
@James-Mart James-Mart added Plugin Infra Related to the infrastructure used by front-end app or plugin developers and removed Plugin Infra Related to the infrastructure used by front-end app or plugin developers labels Mar 28, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
System app Related to system services and their apps/plugins
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Implement addToTx plugin function Add actions importable
4 participants