Skip to content

Serialize and restore compiled schema#115

Open
Ahmedmhmud wants to merge 5 commits intohyperjump-io:mainfrom
Ahmedmhmud:serialize-compiled-schema
Open

Serialize and restore compiled schema#115
Ahmedmhmud wants to merge 5 commits intohyperjump-io:mainfrom
Ahmedmhmud:serialize-compiled-schema

Conversation

@Ahmedmhmud
Copy link
Copy Markdown

This PR adds experimental helpers to serialize and deserialize compiledSchema so applications can persist and restore compiled validation logic more easily. It handles the non-JSON parts of a compiled schema by preserving RegExp values and restoring EvaluationPlugins by ID, first from built-in plugins and then from an optional pluginsById map for custom plugins, with a clear error when a plugin cannot be found. It also wires the helpers into the experimental API surface and includes focused tests and error cases.

Copy link
Copy Markdown
Collaborator

@jdesrosiers jdesrosiers left a comment

Choose a reason for hiding this comment

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

I think using replacer/reviver for plugins isn't the best approach. The pattern matching approach used by isPlugin feels clunky. Since those plugins are always in a known location, I think it makes more sense to convert them before serialization.

Converting RegEpx is different because they could be anywhere and determining if something is a RegExp is trivial.

Plugins isn't actually a Set, it's an array. That was a error in the type that has already been corrected. Rebase your branch to get the correct type.

The exported functions should be a the top of the file with helper functions below.

Using __type isn't going to work. If someone uses that as a property name in their schema, this approach could break. You'll need to pick a key that won't collide with anything that could end up in a schema. I suggest a UUID. That way it's effectively impossible for a user to accidentally create a schema with a conflict.

@Ahmedmhmud
Copy link
Copy Markdown
Author

Converted plugin objects in plugins to UUID-marked references before serialization to avoid shape-matching and property collisions. RegExp values are serialized and revived using a UUID marker. Deserializer accepts both the new UUID markers and legacy __type markers for compatibility. Updated also tests in compiled-schema-serialization.spec.ts.

const REGEXP_MARKER = "a6d8f3e1-9b2c-4f7a-8d5b-1c2e3f4a5b6c";

export const serialize = (compiledSchema) => {
const ast = compiledSchema?.ast ? { ...compiledSchema.ast } : undefined;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

ast isn't optional on compiledSchema.

Comment on lines +9 to +10
if (ast && (Array.isArray(ast.plugins) || ast.plugins instanceof Set)) {
ast.plugins = [...ast.plugins].map((plugin) => {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

ast.plugins will never be a Set.

if (!plugin?.id) {
throw Error("Cannot serialize plugin without id");
}
return { [PLUGIN_MARKER]: true, id: plugin.id };
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The PLUGIN_MARKER isn't necessary because we're not using replacer/reviver for this part. You should be able to just replace the plugin with its id.

Comment on lines +39 to +42
// Legacy support: old tests/serializations used a __type marker.
if (value.__type === "RegExp") {
return new RegExp(value.source, value.flags);
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

__type was introduced in this PR. No one is depending on it. This looks like a mistake an AI coding tool would make. If you use those tools make sure you check everything they do and make sure it makes sense.

Comment on lines +44 to +46
if (value[PLUGIN_MARKER]) {
return resolvePlugin(value.id, options);
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Don't restore plugins in the reviver. Do it as a separate step like in serialize.

Comment on lines +48 to +55
if (value.__type === "Plugin") {
return resolvePlugin(value.id, options);
}

if (value.__type === "PluginSet") {
// Legacy PluginSet: restore to an array of plugin objects
return (value.values || []).map((p) => resolvePlugin(p.id, options));
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Unnecessary

@Ahmedmhmud
Copy link
Copy Markdown
Author

Thanks for heads up, I will update it.

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.

2 participants