Security News
RubyGems.org Adds New Maintainer Role
RubyGems.org has added a new "maintainer" role that allows for publishing new versions of gems. This new permission type is aimed at improving security for gem owners and the service overall.
intertype
Advanced tools
A JavaScript type checker with helpers to implement own types and do object shape validation.
Table of Contents generated with DocToc
{ Intertype } = require 'intertype'
: instances of Intertype
will contain a catalog of pre-declared
types ('default types'){ Intertype_minimal } = require 'intertype'
: instances of Intertype_minimal
will not include the
default typesThe 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:
anything: ( x ) -> true
nothing: ( x ) -> not x?
something: ( x ) -> x?
null: ( x ) -> x is null
undefined: ( x ) -> x is undefined
unknown: ( x ) -> ( @type_of x ) is 'unknown'
anything
is the set of all JS values;nothing
is the set containing null
and undefined
,something
is anything
except nothing
(null
and undefined
).type_of x
will never test for and return anything
, nothing
or something
.null
is, unsurprisingly, the name of the value null
andundefined
is the name of the value undefined
.unknown
is the default type name returned by type_of x
when no other type test (except for anything
,
nothing
and something
) returns true
.In addition to the above, the 'metatype' or 'quasitype' optional
is also reserved. optional
is not a
type proper, rather, it is a type modifier to allow for optional null
s and undefined
s. It is used in
constructs like isa.optional.integer x
and validate.optional.integer x
to state succinctly that 'if x is
given (i.e. not null
or undefined
), it should be an integer'.
create.〈type〉()
Types declarations may include a create
and a template
entry:
create
nor a template
entry are not 'creatable'; trying to call
types.create.〈type〉()
will fail with an error.create
entry must be a (synchronous) function that may accept any number of arguments; if it
can make sense out of the values given, if any, it must return a value that passes its own test()
method; otherwise, it should return any non-validating value (maybe null
for all types except for
null
) to indicate failure. In the latter case, an Intertype_wrong_arguments_for_create
will be thrown,
assuming that the input arguments (not the create method) was at fault. Errors other than
Intertype_wrong_arguments_for_create
that are raised during calls to the create method should be
considered bugs.template
but no create
entry will become 'creatable' by being assigned an
auto-generated create method.template
, orrandom_integer
)
or time-dependent (date
) values.object
s as template values
should be copied (shallow or deep, as the case may be)declare()
Intertype#declare()
accepts any number of objects
it will iterate over all key, value pairs and interpret
the declaration will be rejected if the type name...
the declaration will be rejected if the declaration ...
test
entry is not a unary functioncreate
entry has been given but has the wrong arityType declarations are final, meaning that while you can use the types.declare()
method after the types
object has been instantiated, you cannot use it to re-declare a known type.
When instantiating Intertype
with a series of declaration objects, any duplicate names on the objects
passed in must be eliminated beforehand.
evaluate
methodsisa
and validate
methods, it can be difficult to see exactly what went wrong when a test
failsisa.employee_record x
fails you only know that either x
was not an object or that any nested
field such as person.address.city.postcode
was not satisfiedisa
or a validate
method; however, this was cumbersome and wasteful as
collecting the traces needs time and RAM for each single isa
and validate
method call, whether the
traces are used afterwards or, most of the time, silently discardedevaluate
methods let users obtain a succinct catalog of all the transitive fields of a given type
declaration and how they faredevaluate[type] x
will always return a flat object whose keys are fully qualified type names (like
person.address.city
); they will appear in order of their declaration with type
coming first, so the
object returned by evaluate.person x
will always have person
as its first key, and the one returned by
evaluate.person.address x
will always have person.address
as its first keyVariants can be defined with or without a qualifier, a syntactic element that precedes a type name
Variants without qualifiers ('unqualified variants') can be defined by using a logical disjunction
(or
, ||
) in the test method. For example, one could declare a type boolordeep
like this:
declarations:
boolordeep: ( x ) -> ( @isa.boolean x ) or ( x is 'deep' )
which will be satisfied by any one of the three values true
, false
, and (the string) 'deep'
, to the
exclusion of any other values.
'Qualified variants' do use an explicit qualifier that is meant to be used in conjunction with other types, for example:
declarations:
nonempty: { role: 'qualifier', }
'nonempty.list': ( x ) -> ( @isa.list x ) and ( x.length isnt 0 )
'nonempty.set': ( x ) -> ( @isa.set x ) and ( x.size isnt 0 )
nonempty
with its two branch types nonempty.list
and
nonempty.set
—we can now test for any of:
isa.nonempty.list x
: whether x
is a list
with non-zero x.length
isa.nonempty.set x
: whether x
is a set
with non-zero x.size
isa.nonempty x
: whether x
satisifes either isa.nonempty.list x
or isa.nonempty.set x
isa.nonempty.list
and isa.nonempty.set
are
ordinary tpes (though conceivably one could declare them as unqualified variants, as shown above)'Product types' or 'records', on the other hand, are types that mandate the presence not of alternatives, but of named fields each of which must satisfy its own test method for the record to be valid
Clarification of terminology:
foo: ( x ) -> ( @isa.a x ) or ( @isa.b x )
)empty
as a qualifier
and set up tests for both empty.text
and empty.list
, then the
explicit variant empty
can be tested for with isa.empty x
, which will return true
for exactly the
values ''
and []
.A valid declaration is either
test
: either a test method or the name of existing type (the latter will be compiled into the
former)template
fields
: keys are type names, values are declarationscreate()
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,
T
with field declarations must have its test
entry set to object
, and,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', }
S
as in T: 'some.test.here'
is
equivalent to using the same string S
to spell out a (possibly dotted, thus compound chain of) property
accessor(s) to @isa
inside a test method, as in T: ( x ) -> @isa.some.test.here x
isa
, isa.optional
, validate
and validate.optional
is equivalent to the bracket notation with a string literal (or a variable) on the
same base; thus, isa.some.accessor x
is equivalent to isa[ 'some.accessor' ] x
type_of()
method of Intertype_minimal
instances can only report the types of null
and
undefined
(as 'null'
and undefined'
); all other values are considered 'unknown'
. However, it is
possible to test for isa.anything x
, isa.nothing x
, isa.something x
, isa.unknown x
(and, of
course, isa.undefined x
and isa.null x
).partial / incomplete type system total type system
(may fail)
mulint: ( a, b = 1 ) ->
validate.integer a
validate.integer b
return validate.integer a * b
browserify --require intertype --debug -o public/browserified/intertype.js
_compile_declaration_object()
, add validation for return valueoptional
in a declarations, as in { foo: 'optional.text', }
optional.foo.bar
mean, is it potentially different from foo.optional.bar
(even
if we never want to implement the latter)? Observe that wile optional.foo.bar
might mean something
different than foo.optional.bar
, when testing for isa.optional.foo.bar x
we apparently still
understand foo.bar
as a (compound) fully qualified name of a type (bar
in namespace foo
) that in its
entirety may be present or absent
optional
except in front of a simple type name (without dots)create()
method for the recursive casefields
but no template
template
but no fields
template
but no fields
, and all fields in template
are constants (is it even worth
caring about?)template
but it's a functionevaluate.cardinal Infinity
returns { cardinal: false }
; evaluate.posnaught.integer Infinity
returns { 'posnaught.integer': false }
; in both cases, more details would be elucidating (Infinity
satisfies posnaught
but not integer
)evaluate
methods with length-limited rpr()
and type of values
encounteredexplain
or report
method that shows a table with all the tests and what the
actual values were that evaluate
enounterednonempty
etc. could be autogenerated: go through each enumerable property T
of
isa.nonempty
and add a test ( @isa[ T ] x ) and ( @isa.nonempty[ T ] x
isa.quantity()
has been set 'sub-methods' isa.quantity.q()
, isa.quantity.u()
will be set as
properties of their 'target' isa.quantity
; the current terminology is unfortunate and obfuscates more
than it elicitsoptional
, provide a type for the union
of bothbasetype
optional
usertype
qualifier
negation
(?)role
, allow users to set { test: '@qualifier' }
which then can be
shortened to empty: '@qualifier'
in dotted field enumerations (maybe really the preferred way to
differentiate qualifier trees from nested records)_isa
but missing from
default_declarations
testing
or similarforbidden
that, in contradistinction to established rules, does throw an error
from its test method when a record with a field thusly marked is encountered; this is to ease transition
when extransous fields are removed from record types[+] 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:
declare()
method (which accepts an object with named declarations)default_declarations
cfg
object with an appropriate setting (default_types: true
?)Intertype#declarations
as a class with an add()
method or similar[+] allow overrides when so configured but not of the 'basetypes'
built_ins
?anything
, nothing
, something
, null
, undefined
, unknown
, or the 'meta type' optional
[+] what about declarations with missing ensure an error is thrown when no test
method is presenttest
?
[+] 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
?
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 disallow
overridesoverride
with the (clearer?) replace
[+] remove indirection of keep indirection of declare()
, _declare()
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 )
string
is redundant( ( typeof x ) is 'string' )
is not
redundant as it prevents errors when isa.basetype()
is called with a non-object valueoptional
be included?Reflect.has()
in _isa.basetype()
(returns true
for toString
)[+] to fix implementation failure connected to RHS optional
prefix:
is_optional
and/or RHS optional
prefixoptional
prefix are
skipped[+] 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?
intertype
instances be closed for further
declarations before being used first; this could happen implicitly on first use[+] 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
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', }
[+] implement fields
[+] test that incorrect templates are rejected
[+] consider to implement nonempty.text()
, nonempty.list()
, empty.text()
, empty.list()
; here,
empty
and nonempty
are not types names of an object with fields, and the names after the dots are not
field names; also, isa.nonempty x
does not necessarily have to make sense so either it shouldn't be a
[+] implement 'qualifiers' (as in nonempty.text
) that look a lot like object fields but have a
different isa
method implementation
optional
which is and remains a prefix that can before
any other legal (known, declared) (fully quylified) type name, so isa.optional.nonempty.text x
may be
legal but isa.nonempty.optional.text x
won't
function or, if it is one, it should throw an error when called, something that has always been ruled out
so far[+] use prototypes of test methods throws()
&c for new version of guy-test
[+] use prototype of set equality for equals()
implementation in webguy
[+] allow undefined
, null
in create
methods that result in record
FAQs
A JavaScript typechecker
The npm package intertype receives a total of 0 weekly downloads. As such, intertype popularity was classified as not popular.
We found that intertype demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
RubyGems.org has added a new "maintainer" role that allows for publishing new versions of gems. This new permission type is aimed at improving security for gem owners and the service overall.
Security News
Node.js will be enforcing stricter semver-major PR policies a month before major releases to enhance stability and ensure reliable release candidates.
Security News
Research
Socket's threat research team has detected five malicious npm packages targeting Roblox developers, deploying malware to steal credentials and personal data.