A JavaScript type checker with helpers to implement own types and do object shape validation.
The following basetypes are built-in and treated specially; they are always present and cannot be
overwritten or omitted. The definitions of their test methods reads like pseudo-code:
-
two ways to specify fields on objects
-
either in the 'nested style', by using the fields
entry of a type declaration; for example:
declarations:
quantity:
test: 'object'
fields:
q: 'float'
u: 'text'
-
or in the 'flat style', by using dot notation in the type name:
declarations:
quantity: 'object'
'quantity.q': 'float'
'quantity.u': 'text'
-
the two styles are identical and have the same result.
-
you can now call the following test methods:
types.isa.quantity x
: returns true
iff x
is an object with (at least) two properties q
and u
whose individual test methods both return true
as welltypes.isa.quantity.q x
: returns true
iff x
is a float
types.isa.quantity.u x
: returns true
iff x
is a text
-
at least for the time being,
a type T
with field declarations must have its test
entry set to object
, and,- when flat style is used, type
T
must be declared before any field is declared.
-
if there is an existing declaration for type T
, the only way to add fields to it is by using the flat
declaration style
-
field declarations constitute isolated namespaces, meaning that types.isa.text
—that is, type text
in
the root namespace—is entirely separate from, say, types.isa.product.rating.text
, which is type text
in the rating
namespace of type product
in the root namespace.
-
fields can be indefinitely nested, e.g.:
types.declare { 'person': 'object', }
types.declare { 'person.name': 'text', }
types.declare { 'person.address': 'object', }
types.declare { 'person.address.city': 'object', }
types.declare { 'person.address.city.name': 'text', }
types.declare { 'person.address.city.postcode': 'text', }
-
[+] hard-wire basic types anything
, nothing
, something
, null
, undefined
, unknown
-
[+] allow stand-alone methods ({ type_of } = new Intertype()
)
-
[+] ensure all methods have reasonable names
-
[+] use proper error types like Validation_error
-
[+] make it possible for Intertype methods to use an internal, private instance so type and arity
testing is possible for its own methods
-
[+] throw error with instructive message when a type testing or type_of()
is called with wrong arity
-
[+] throw error with instructive message when an undefined type is being accessed as in isa.quux x
-
[+] ensure that optional
cannot be used as a type name
-
[+] type-check declaration function (a.k.a. isa-test)
-
[+] given a declaration like this:
declarations =
float:
test: ( x ) -> Number.isFinite x
create: ( p ) -> parseFloat p
determine at what point(s) to insert a type validation; presumably, the return value of create()
(even
of one generated from a template
setting) should be validated
-
[+] validate that create
entries are sync functions
-
[+] validate nullarity of template methods when no create
entry is present
-
[+] implement a way to keep standard declarations and add own ones on top:
- by implementing a
declare()
method (which accepts an object with named declarations) by exporting (a copy of) default_declarations
by allowing or requiring a cfg
object with an appropriate setting (default_types: true
?)by implementing Intertype#declarations
as a class with an add()
method or similar
-
[+] allow overrides when so configured but not of built_ins
? the 'basetypes'
anything
, nothing
, something
, null
, undefined
, unknown
, or the 'meta type' optional
-
[+] what about declarations with missing test
? ensure an error is thrown when no test
method is present
-
[+] enable setting test
to the name of a declared type
-
[+] allow name-spacing a la isa.myproject.foobar()
? and use it to implement fields
-
[+] when fields
are implemented, also implement modified rules for test method
-
[+] in isa.foo.bar x
, foo
is implemented as a function with a bar
property; what about the
built-in properties of functions like name
and length
?
[–] can we use Function::call f, ...
instead of f.call ...
to avoid possible difficulty if
call
should get shadowed?
-
[+] allow declaration objects
-
[+] remove 'dogfeeding' (class _Intertype
), directly use test methods from catalog instead
-
[+] fix failure to call sub-tests for dotted type references
-
[+] fix failure to validate dotted type
-
[+] make get_isa()
&c private
-
[+] consider to replace override
with the (clearer?) replace
disallow
overrides
-
[+] remove indirection of declare()
, _declare()
keep indirection of declare()
to
avoid 'JavaScript Rip-Off' effect when detaching unbound method
-
[+] test whether correct error is thrown throw meaningful error when declare
is called with unsuitable arguments
-
[+] unify usage, orthography of 'built ins', 'builtins' (?), 'base type(s)', 'basetype(s)' ->
'basetype(s)'
-
[+] currently basetype
is declared as ( ( typeof x ) is 'string' ) and ( x is 'optional' or Reflect.has built_ins, x )
checking for string
is redundant checking for ( ( typeof x ) is 'string' )
is not
redundant as it prevents errors when isa.basetype()
is called with a non-object value- should
optional
be included? - [+] fix wrong usage of
Reflect.has()
in _isa.basetype()
(returns true
for toString
)
-
[+] to fix implementation failure connected to RHS optional
prefix:
- [+] commit current state, mistakes and all
- [+] identify and rip out all places concerned with
is_optional
and/or RHS optional
prefix - [+] reduce tests such that valuable tests are preserved but ones using RHS
optional
prefix are
skipped - [+] whatever the outcome, update docs
-
[+] test whether basic types are immutable with instances of Intertype_minimal
-
[+] in _compile_declaration_object()
, call recursively for each entry in declaration.fields
-
[+] find a way to avoid code duplication in handling of field sub_tests
across all four test methods
(isa
, isa.optional
, validate
, validate.optional
) ; can we bake those right into
declarations[ type ].test()
? But then what when more fields get declared?
this wouldn't pose a problem if we required that intertype
instances be closed for further
declarations before being used first; this could happen implicitly on first useif we didn't want that, we'd have to re-formulate the declaration's test method each time a field
is declared for a given type
-
[+] implement value creation for all the builtin types
-
[+] when fields are declared but no create()
method is given, generate a create()
method that
accepts any number of objects that, together with the template, will be condensed into one object using
Object.assign()
-
[+] test that template functions are called, even when used in template fields
-
[+] we should use a recursive merge()
method, call it deepmerge()
, instead of Object.assign()
when creating values from templates; this method should be exported for the benefit of users who want to
implement their own create()
method; conceivably, deepmerge()
could / should beimplemented in
webguy.props
-
[+] implement method evaluate.[typename] x
; like isa
and validate
methods, however does not
shortcut on failure but runs through all tests, returns object with named results so one can see e.g.
which fields did and which ones didn't conform
- [+] if an
evaluate
d value is null
, do we want the full complement of all the type's sub-fields
in the result or is it better to just return { [type]: false, }
?
-
[+] implement a generated field in declarations
that eumerates all fully qualified field names that
belong to the type in question; field generated by module-level method walk_transitive_field_names()
-
[+] would it be worth the effort to try and implement a 'permanent debugging' facility, one whose
calls are left in the code (maybe in the form of specially formatted comments) and can be activated when
needed? One could imagine those to produce a complete trace when activated that goes into an SQLite DB and
can then be inspected and filtered as needed. This would obviously be outside the scope of the present
package
-
[+] test that a declaration with fields defaults to { test: 'object', }