crystal
crystal copied to clipboard
Customize codec for base64JSON throws runtime error
Summary
The id: ID! values produced by the NodePlugin and the NodeIdCodecBase64JSONPlugin plugin are not URL-safe. This means, if I wanted to use these ID's as part of a URL, I'd have to do additional client-side logic to make them URL safe. I'd instead like to customize/override the codec to use a url-safe base64 encoding. I'm finding not able to do so successfully.
Steps to reproduce
When creating a plugin like this:
import "graphile-config";
import { NodePlugin } from "graphile-build";
function base64JSONEncode(value: any): string | null {
return Buffer.from(JSON.stringify(value), "utf8").toString("base64url");
}
base64JSONEncode.isSyncAndSafe = true; // Optimization
function base64JSONDecode(value: string): any {
return JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
}
base64JSONDecode.isSyncAndSafe = true; // Optimization
export const NodeIdCodecBase64URLJSONPlugin: GraphileConfig.Plugin = {
name: "NodeIdCodecBase64URLJSONPlugin",
version: "1.0.0",
description: `Adds a URL-safe 'base64JSON' codec for NodeIDs`,
after: [
NodePlugin.name,
],
schema: {
hooks: {
init(_, build) {
if (!build.registerNodeIdCodec) {
return _;
}
build.registerNodeIdCodec({
name: "base64JSON",
encode: base64JSONEncode,
decode: base64JSONDecode,
});
return _;
},
},
},
};
...configured with a plugins and presets configuration that ignores the stock codec:
{
extends: [
PostGraphileAmberPreset,
PostGraphileRelayPreset,
],
plugins: [
NodeIdCodecBase64URLJSONPlugin,
],
disablePlugins: [
// disable existing plugins
NodeIdCodecBase64JSONPlugin.name,
],
}
Expected results
I'd expect code that currently relies on this codec to work successfully.
Actual results
I get a runtime error thrown from inside the PgTableNodePlugin:
Error occurred during watch schema generation: Error: Could not find Node ID codec 'base64JSON'
at Object.getNodeIdCodec (/workdir/node_modules/.pnpm/[email protected][email protected][email protected][email protected]/node_modules/graphile-build/dist/plugins/NodePlugin.js:58:35)
at init (/workdir/node_modules/.pnpm/[email protected]_@[email protected][email protected]_graphile-build_u4y5pffkmjvd272uyyrjurxzfq/node_modules/graphile-build-pg/dist/plugins/PgTableNodePlugin.js:100:38)
at SchemaBuilder.applyHooks (/workdir/node_modules/.pnpm/[email protected][email protected][email protected][email protected]/node_modules/graphile-build/dist/SchemaBuilder.js:98:30)
at SchemaBuilder.createBuild (/workdir/node_modules/.pnpm/[email protected][email protected][email protected][email protected]/node_modules/graphile-build/dist/SchemaBuilder.js:148:14)
at SchemaBuilder.buildSchema (/workdir/node_modules/.pnpm/[email protected][email protected][email protected][email protected]/node_modules/graphile-build/dist/SchemaBuilder.js:157:28)
at buildSchema (/workdir/node_modules/.pnpm/[email protected][email protected][email protected][email protected]/node_modules/graphile-build/dist/index.js:322:28)
at /workdir/node_modules/.pnpm/[email protected][email protected][email protected][email protected]/node_modules/graphile-build/dist/index.js:467:56
at watch (/workdir/node_modules/.pnpm/[email protected][email protected][email protected][email protected]/node_modules/graphile-build/dist/index.js:246:13)
Possible Solution
I'd be equally happy to propose a change to the stock NodeIdCodecBase64JSONPlugin implementation to use the base64url buffer encoding as I'm doing above — but I'm unsure the extent to which this would be an undesirable or breaking change.
Another viable alternative would be having an explicit build function that plugins can call to redefine the codec scheme used for the NodePlugin. Right now this codec implicitly has to be called base64JSON, and decode has to return [string, string] — it might be nice to make this pluggable.
The codecs are indeed designed to be pluggable (that’s why they exist and there are more than one of them); but I probably haven’t hooked up the final stage of this to make it so you can ACTUALLY change the ones it uses by default. Even if I have hooked it up, I’ve not documented it. I’ll look into it at some point 👍