Migrating from metarhia
@alexify/metaschema 1.0 is a fork of metaschema 2.2. The schema language and the validation rules are the same. What changed is the package around them, plus the fixes listed below: more precise messages and paths, definition errors that say where they are, and definitions that used to pass silently and now throw.
2.0 goes further: the shape of a result, the signature of check and a few rules of the language change on top of this page, and the outputs below are the 2.0 ones. Read this page, then Migrating from 1.x.
Install
pnpm remove metaschema
pnpm add @alexify/metaschema// before
const { Schema, Model } = require('metaschema');
// after
const { Schema, Model } = require('@alexify/metaschema');Removed: loading schemas from files and strings
createSchema, loadSchema, readDirectory and loadModel are gone, together with the metavm sandbox they used. metaschema no longer reads files or evaluates source code. Schemas are plain values, so load them the way you load any other module:
| Before | After |
|---|---|
createSchema(name, src) | new Schema(name, definition) |
await loadSchema('./schemas/User.js') | new Schema('User', require('./schemas/User.js')) |
await loadModel('./schemas', types) | new Model(types, new Map([...]), database) |
Schema files written as a bare expression, ({ ... }), need to become modules:
// schemas/User.js, before
({
Registry: {},
login: { type: 'string', unique: true },
});
// schemas/User.js, after
module.exports = {
Registry: {},
login: { type: 'string', unique: true },
};loadModel treated .types.js and .database.js in the directory as the custom types and the database metadata. Pass them to Model directly:
const types = { ...systemTypes, ...require('./schemas/types.js') };
const database = require('./schemas/database.js');
const entities = new Map([
['Company', require('./schemas/Company.js')],
['User', require('./schemas/User.js')],
]);
const model = new Model(types, entities, database);saveTypes stays, in the @alexify/metaschema/generators subpath since 3.0 (with toTypeScript and toJSONSchema).
No runtime dependencies
metautil, metavm and metaskills are no longer installed. The few metautil helpers metaschema used are copied into the package.
Changed
Optional nested structs as collection elements.
{ array: { type: 'schema', schema, required: false } }now acceptsnullelements; upstream ignoredrequired: falsethere.Messages and paths. Typos are fixed and paths are more precise; the old text is not kept. Update any test that matches it:
Before After Filed "x" is requiredField "x" is requiredFiled "x" is not a objectField "x" not of expected type: objectFiled "x" is not a mapField "x" not of expected type: mapValue of "x" must be an objectField "x" not of expected type: objectvalue length is more then expected in tupleexceeds the maximum length(1.x:value length is more than expected in tuple)Field "field2" is not expected(insidenested)Field "nested" has unexpected keys: field2(1.x:Field "nested.field2" is not expected)Field "Person.companies.name" ...(amanyrecord)Field "Person.companies[1].name" ...Field "o" In object "o": type of key must be a stringField "o" keys must be of type stringField "o" validation failed TypeError: ...fornullField "o" not of expected type: objectRecursive dependency: A.B(a model warning)Warning [recursive-reference]: "A" depends on "B" recursivelyEvery element of a tuple and every record of a
manyreference is reported, not only the first; a circular value is reported asis a circular referenceinstead of overflowing the stack; and key names taken from the input are truncated to 100 characters in messages.Definition errors. A broken definition throws
SchemaDefinitionError(aTypeErrorwithcode,schemaandfield) instead of a bareError, and the message ends with the location:Unknown type strngis nowUnknown type "strng" in "Order.total". Definitions that upstream accepted and that could never validate correctly now throw when the schema is built:Definition Now { type: 'enum' }without values,enum: []ERR_INVALID_ENUMlength: 'abc',length: { min: 'a' }ERR_INVALID_LENGTH(andlength: { max: 0 }works){ many: 5 },{ one: '' }ERR_INVALID_REFERENCEa projection naming a field its parent does not have ERR_PROJECTIONa field definition key named like a method of the field ( check,constructor, ...) or__proto__ERR_RESERVED_KEYa type table entry that redefines a registered name, such as json: { js: 'schema' }from a metasql-style system table (jsonis a built-in)ERR_TYPE_REGISTERED; writejson: { metadata: { pg: 'jsonb' } }{ js: 'strng' }ERR_UNKNOWN_JS_TYPE(jsmay name a custom type registered earlier)Tuples in the short form are required.
point: ['number', 'number']was never required upstream; it is now, like every other field, and'point?': [...]makes it optional.result.issues. Besideserrors, a result carries{ code, path, message, params }per problem, withpathas an array of keys, andcheck(value, { maxErrors })limits how many are collected. The root path of 1.x and upstream (check(value, path)) ischeck(value, { root }). See Validation for the result and its options.new Schema(name, instance). ASchemainstance is still returned as is, but the namespaces are attached to it and a differentnameis a definition error.Model#preprocessandModel#reorderEntityare constructor internals and no longer public.Schema#detach. The misspeltdetouchis renamed todetach. There is no alias: rename the calls.Browser entry.
dist.jsis nowbrowser.jsand exports the same names as the main entry, andsaveTypes(in the generators subpath since 3.0) rejects in the browser. Bundlers pick it automatically.Exports map. The package has an
exportsfield, so deep imports such asmetaschema/lib/types.jsare no longer reachable.Typings.
index.d.tsnow matches the runtime: the staticSchema.KIND-style fields and the'system'scope are gone,validate,findReferenceandModel#databasecan benull,Cardinalityhas the two values the runtime produces, and there is a type for everything public (Fields,FieldType,TypeEntry,ValidationIssue,DefinitionErrorCode, ...).