A trimmed port of metosin/malli 0.20.1 to
Squint: ClojureScript syntax compiled to
plain JavaScript with plain JS data (strings for keywords, arrays, objects, Set).
It keeps malli's schema syntax, validation, explain and humanize, parse and
unparse, transformers, schema utilities and JSON Schema output. It drops the parts
that need a Clojure runtime or that make no sense once keywords are strings.
Status: experimental. The test suite passes (npm test). Not published to npm.
| Namespace | Contents |
|---|---|
malli.core |
schemas, validate, explain, parse/unparse, walk, decode/encode, registries |
malli.error |
humanize, error-value, spell-checking suggestions |
malli.transform |
string and JSON transformers, strip-extra-keys, default values, key transformers |
malli.json-schema |
schema to JSON Schema |
malli.util |
merge, union, select-keys, closed-schema, get-in, assoc-in, update-properties, ... |
malli.registry |
simple, composite, mutable, dynamic and lazy registries |
Schema types: :any :some :nil :string :int :float :double :boolean,
:> :>= :< :<= := :not=, :and :or :orn :not :maybe :enum :re :fn,
:map :map-of :vector :sequential :set :tuple :multi :ref :schema ::m/schema.
- Sequence (regex) schemas
:cat :alt :* :+ :? :repeat :catn :altnandmalli.impl.regex. - Function schemas
:=> :-> :function, instrumentation,malli.generator,malli.dev,malli.provider,malli.destructure,malli.experimental.*,malli.instrument. - The AST protocol (
m/ast,m/from-ast). - SCI evaluation.
:fnchildren,:multidispatch functions,:error/fn,:default/fnand:decode/*/:encode/*hooks must be real functions.:multi:dispatchmay also be a key, which is looked up withget. - Predicate schemas (
int?,string?, ...), bare regex literals as schemas,:andn,:seqable,:every, and the:merge/:union/:select-keysschema types (the functions inmalli.utilremain). - Types that cannot be distinguished from
:stringin Squint::keyword :symbol :qualified-keyword :qualified-symbol :uuid.
- Keywords are strings.
[:map [:id :int]]is["map", ["id", "int"]]from JS,(m/type s)returns"map", and error types are strings such as"malli.core/missing-key". - All map keys are strings. A
:map-ofwith non-string keys sees stringified keys. - Sets compare elements by reference, so
:enumonly works for primitives. :doubleand:floataccept any JS number.:intrequiresNumber.isInteger.- The invalid sentinel returned by
parseis the string"malli.core/invalid". - Schemas carry no metadata and do not print specially.
From Squint:
(ns app
(:require [malli.core :as m]
[malli.error :as me]
[malli.transform :as mt]))
(def User [:map [:id :int] [:tags [:vector :string]]])
(m/validate User {:id 1 :tags ["a"]}) ; => true
(-> (m/explain User {:id "x"}) me/humanize) ; => {:id ["should be an integer"]}
(m/decode User {:id "1" :tags []} (mt/string-transformer)) ; => {:id 1 :tags []}From JavaScript, after npm run compile:
import * as m from './dist/malli/core.mjs';
import * as me from './dist/malli/error.mjs';
import * as mt from './dist/malli/transform.mjs';
const User = ["map", ["id", "int"], ["tags", ["vector", "string"]]];
m.validate(User, {id: 1, tags: ["a"]}); // true
me.humanize(m.explain(User, {id: "x"})); // {id: ["should be an integer"]}
m.decode(User, {id: "1", tags: []}, mt.string_transformer()); // {id: 1, tags: []}Squint munges names the ClojureScript way: string-transformer becomes
string_transformer, map-of stays the string "map-of".
npm install
npm run compile # compiles src/ and test/ into dist/
npm test # compile, then run dist/malli/run_tests.mjs
npm run size # esbuild bundle sizes for three entry points
Bundle sizes (esbuild, minified, ES module), of which about 59 KB minified and 18 KB gzipped is Squint's own core library:
| Entry | Minified | Gzipped |
|---|---|---|
| validate only | 83.7 KB | 22.9 KB |
| validate + humanize | 98.1 KB | 26.0 KB |
| validate + humanize + transform + JSON Schema | 119.0 KB | 31.6 KB |
src/malli is generated from a malli jar by a mechanical script plus one patch of
hand edits, so a new malli release can be synced with npm run sync -- <version>.
The report says what changed upstream in the kept code and which hand edits no
longer apply. See SYNCING.md.
The sources come from the malli 0.20.1. Reader conditionals were resolved to
their :cljs branch, the namespaces listed above were removed with a rewrite-clj
script, and the remaining changes fall into a handful of mechanical categories:
- Maps, sets and keywords used as functions (
(@cache k),(m x),(ks k),:messageas a default function) becamegetandcontains?calls. implements?becamesatisfies?;MapEntry,keyword-identical?,goog-define,IPrintWithWriter,unchecked-*arithmetic andhash-combinewere replaced or removed.- Fully qualified protocol names such as
malli.core.Schemalost their prefix. - The
:cljmacro that registers predicate schemas became a runtime reduce, then was dropped with the predicate schemas. - Index-keyed maps in
:tuplebecame vectors, because every object key is a string. - A bare type name such as
:intis the string"int"and used to look like a registry reference.m/schemanow only treats a string as a reference when the registry entry is not a schema constructor.
Squint behaviors discovered while porting, which the code now works around:
- Destructuring with a local symbol as the map key,
{{:keys [..]} method}, looks up the literal string"method". - A local named
fnis compiled as a call to a core function. - A
defnamedevalis illegal in strict-mode ES modules. reduce-kvignoresreducedand yields string indices for vectors.malli.impl.util/-reduce-kvis used instead throughoutmalli.core.map?andcoll?return true forreifyinstances, so protocol checks must come first.- Calling a map, set or keyword as a function only works when the compiler can see a literal at the call site.
This is an experiment and feedback is welcome, especially on which dropped features you would miss, and on the Squint workarounds listed above. Please open an issue at https://github.com/kpassapk/squint-malli/issues.
Eclipse Public License 2.0, the same license as malli. The original work is
copyright Metosin Oy. See LICENSE.