Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@devzen/zendash

Package Overview
Dependencies
Maintainers
0
Versions
4
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@devzen/zendash

A small set of utilities around types checking, exact or like comparisons, extracting props & deep mutation of objects

  • 1.0.0-alpha.5
  • unpublished
  • latest
  • Source
  • npm
  • Socket score

Version published
Weekly downloads
0
Maintainers
0
Weekly downloads
 
Created
Source

ZenDash 1.0.0

ZenDash is so much more than a collection of useful utils for JavaScript/TypeScript, that are missing from JS, lodash & other libraries!

It features a Runtime Type System to check the real-world types, backed by a Static TypeScript typings system, and a JS Native Iteration on Anything called loop(), that is powered by Generators & extended by a Project & Filter on Anything engine with Keys & Props Included (optionally), all features are optional cause Options are King. It's Well typed with TypeScript, Extensively Tested with 1000's of tests (including generated ones) and more!

Runtime Type System

ZenDash dares to propose an extended, very logical and only a bit radical Runtime Type System for JS/TS, to fix the UTTERLY BROKEN runtime typing system of JS in 2024, 29 years after its conception. That's why The Bad Parts is a must read before even The Good Parts, for anyone touching this otherwise great language!

In short, ZenDash's Runtime Type System:

  • Is "complying" when it's OK, its "extending" when it should, it is "fixing" when breaking is a must and is "improving" when it's wise. The Bad Parts is a good background!

  • You can forget and never use again the myriad inconsistencies of the existing typeof, instanceof & other type-related absurdities of JS & the status-quo. My favorite is NaN !== NaN ;-)

  • Also, you can bypass lodash's shortcomings around types (and more below):

Can you believe it's 2024 and native / status quo JS is :-(

- `typeof prom === 'object'`
- `typeof Promise === 'function'`
- `Promise.isPromise === undefined` 
- `_.isPromise === undefined`      

We can only rely on aiPromise instanceof Promise, for a limited true or false check.

Compare with the consistency of ZenDash's Type System:

  - `_z.isPromise(prom) === true`
  - `_z.type(prom) = 'Promise'`
  - `_z.type(Promise) === 'class'`      
  - `_z.classType(Promise) === 'systemClass'`
  - `_z.constructor(prom) === Promise`

And this is just 1 example, just for Promise, but almost ALL types have similar or even worse issues! For example, typeof null is 'object' and typeof NaN is 'number'!

With ZenDash / ZenType you'll get a consistent, logical, and rich type system, that is also well-typed with TypeScript.

Consider a system Class/constructor & instance info:

// vanilla JS
console.log(typeof Set)         // function
console.log(typeof new Set())   // object    

Really, is Set just a function? Can we really call Set([])? No! It is a class/constructor and we have to new Set([])!

And the set instance, is it just an object? You should know that everything (except some primitives) is a typeof object in JS!

JS is lying to us relentlessly and typeof is utterly broken!

Compare with ZenDash:

console.log(type(Set))                // class
console.log(type(new Set()))          // Set

// And more: 
console.log(classType(Set))           // systemClass
console.log(instanceType(new Set()))  // systemInstance

What about user classes and instances?

class Aclass {a = 1}
const aInstance = new Aclass()

// Vanilla JS
console.log(typeof Aclass)              // function
console.log(typeof aInstance)           // object

Sucks like above! With ZenDash:

// ZenDash `type()` basics
console.log(type(Aclass))               // class
console.log(type(aInstance))            // realObject

// ZenDash Extras
console.log(realObjectType(aInstance))  // instance
console.log(realType(aInstance))           // instance
console.log(classType(Aclass))          // userClass
console.log(instanceType(aInstance))    // userInstance

Perhaps the epitome of JS madness is NaN !== NaN. But test your self, do you know the typeof null? You'll be surprised, I still am ;-)

Let 2024 be the year we stopped using typeof, instanceof, Object.isObject and the rest of the gang, and started using a proper Type System, with _z.typeand _z.isXxx!

The Type System comes packed with:

  • _z.type & many other xxxType() functions, with many natural-occuring & synthetic types

  • A plethora of individual type inference checkers like ( _z.isSingle, _z.isMany, _z.isPrimitive, _z.isClass and tens more

  • You also get useful types like Tany (so you can Exclude<Tany, TPrimitive>), along with TPrimitive, TSingle & TMany and many more, to help you with your TypeScript types.

  • You even get TPrimitiveNames and TsingleNames and similarly for all, to help you with your TypeScript types handling!

  • To be complete, you also get a runtime PRIMITIVE_TYPES, SINGLE_TYPES and similarly for all, to help you with your runtime type checks!

  • Equality & similarity checkers like _z.isEqual, _z.isLike and many more, and all isXxx type checks you'll ever need ( _z.isBigInt, _z.isGenerator etc) & few set-theory utils (_z.isSetEqual). All accepting options, to cover many different use cases.

  • All typesafe with TypeScript ;-)

Native Iteration on anything

The mighty _z.loop allows the iteration on ALL collections/nested values types (a.k.a _z.isMany like Array, realObject, Set, Map, Iterator, AsyncIterator etc) in the exact same way, with native JS only:

You can just

for const [val, key] of _z.loop(anything) {...}

and iterate on anything that has nested values on it, in the same way, with the same code, without any special cases or special libraries or weird callbacks and APIs. In plain JS.

Forget iteration (and projections) with Array.forEach/map/filter(), lodash limited _.each & _.map etc and the same with Ramda, IxJs, Set.entries() and the myriads other ways for iteration & projections. They are suboptimal, some don't support async/await, others have steep APIs, others don't support Set and Map etc.

Just a for...of _z.loop(anything) {} and you can loop over anything has nested values / props (and also map, filter, take etc while doing so, declaratively!)

Project & Filter Anything

A collection of tools like _z.map() / _z.filter() / _z.take() / _z.clone() / _z.keys() and more like _z.reduce()] (all based on the _z.loop) that return the same type, as the input value type!

  • You pass an Array, you get an Array (with mapped/filtered/etc elements - i.e _.map / _.filter).

  • You pass an Object, you get an Object with (with mapped/filtered/etc values - i.e _.mapValues / _.pickBy).

  • Similar for Map, Set, Iterator, Generator etc!

  • You can also optionally pass a single value (experimental) and get the mapped/filtered etc single value back! Booxed, if it was Boxed, primitive if it was primitive.

You can forget _.filter, _.map, _.mapValues, _.pickBy, _.clone, keys() and many others, that are limited and stringent and working only on narrow cases, (eg only for objects or only for Arrays etc) and none for Maps & Sets ;-(. _z. works with Zenything, in the same way!

Note: FP flavour coming soon ;-)

Keys & Props Included

All methods differentiate between normal keys/Indices, elements & values of a nested value (like an Array, Set etc) and the props all _.isObject values have. You can choose what to include/exclude in any iteration / projection (eg own, inherited, non-enumerable, symbols, props as well or props only etc) in the options!

Options are King

Almost all functions are accepting a plethora of options, that allow customisable behaviour in meaningful ways. Many of the options are common among functions (eg ImapOptions extends IloopOptions and in turn IloopOptions extends IkeysOptions) hence synergies emerge. All projections like map(), filter(), take() etc share the same implementation, just with different types and tiny extra checks.

Lib & Type System is Typed

The Type System and whole library are typed with TypeScript (workable, not perfect in v1).

For example, passing an AsyncIterator to c will return an AsyncGenerator type and not a Generator type, respecting the types of the original type. Similarly, passing an Array to _z.map() will return an Array, passing a realObject will return an Record and not something different.

Lib & Type System is Tested

Extensively tested, with 1000's of exhaustive & interpolated edge test cases ;-)

Typings are also tested, mostly via ts-expect, check the xxx-typing-tests.ts

Highlights

Nested / Collections & Iteration

  • _z.loop returns an Iterator of tuples [item, idxOrKey, count] that work the same way with any kind of nested values (i.e collection) such as Array, Object, Map, Set, Iterator, class and more). But even _z.isSingle (i.e non-nested) values are iterated once, yielding the value itself (but you can opt to be strict)! Currently, in the JS world there was no way to iterate on anything in the same way, but now you can for (const [keyOrIdx, item, count] of _z.loop(value)) {...} and it will work as expected. At the same time you filter, map, take etc while doing so, declaratively!

  • _z.each is a more powerful _.each() (which improved Array.forEach by allowing objects as well), that based is based on _z.loop and works with any kind of nested values (i.e collection) such as Array, Object, Map & Set). It accepts a callback (item, keyOrIdx, count) => {} and since its built on _z.loop, it accepts the same options so you can also map, filter, take while looping, and more

  • _z.keys optionally returns ALL possible natural keys or indexes of any object (Real object, Array, Function, Map & Set entries etc) with many twists: it can also bring props of the underlying JS object (instead of its normal keys/indexes), is supports both string & Symbol props, it can filter own & inherited keys, enumerable & non-enumerable and top-level keys (eg toString). Naturally, you can choose which keys/indexes/props to include/exclude.

  • _z.map() / _z.filter() / _z.reduce()] / _z.take() / _z.clone() / _z.keys() and more are in place, more coming soon. Most are based on the _z.loop and accept the same options. They all return the same type, as the input value type (eg you pass an Array, you get an Array, with filtered/mapped/etc elements).

Check docs for the full list of functions & usage.

Objects & Arrays

  • _z.getProp gets a property from a nested Property Bag (i.e objects, classes) or Array, using a custom separated string or array path, with many twists (e.g. separator, defaultKey, inherited etc.)

  • _z.setProp sets a property to a nested Property Bag (i.e objects, classes) or Array, using a string or array path, with many twists (such as separator, create, overwrite).

  • _z.mutate mutates values of an object/array, using a mutator function, if agreements are met.

Equality & Similarity

  • _z.isEqual checks if two values are deep equal, similar to _.isEqual but with many optional twists (inherited, like, path, exclude etc.)

  • _z.isLike checks if two values are deep equal, but the first value only having a subset of props being equal. Shortcut to _z.isEqual.

  • _z.isExact checks if two values are deep equal, then all refs must point to the same objects, not lookalike clones! Shortcut to _z.isEqual.

  • _z.isDisjoint Checks if there are no common value between the two objects/arrays (i.e. their intersection is empty)

  • _z.isRefDisjoint Given two Property Bags (i.e objects, classes) or Arrays, it returns true if there are NO common/shared references in their properties.

  • _z.isSetEqual Checks if 2 Sets or 2 arrays are equal, without caring about the order of their items, with custom equality functions (on one, or either side).

Type Checks - Runtime Type System

  • _z.type returns the type of the value, as a superset of typeof (with a compatible naming format when these co-exist), but in a much richer & non-Bad Parts manner, recognising & many more distinct "real world" types. For example 'object' is considered only and for all real {} objects, in all object forms (i.e plain {} object, instance etc.) but does NOT include Arrays, Functions etc unlike lodash & typeof. Naturally null's type is well... 'null' and not 'object', unlike JS's dummy type. Also, functions are recognised as 'function' but ES6 Classes as 'class'. And NaNs as just a 'NaN', not a number as the name stipulates! Finally, it recognises many other types, like Set, Map, Iterator etc.

  • _z.functionType returns the specific type of the function, eg 'class', 'Function', 'AsyncFunction', 'GeneratorFunction', 'AsyncGeneratorFunction' & 'ArrowFunction'.

  • _z.objectType returns the specific type of the real object, either 'pojso' or 'instance'.

More in the docs

isXxx Type Checks

A plethora of missing type isXxx(value) checks (more than 25), that are not provided by lodash and most other libraries or are scattered around stackoverflow and other libs. In ZenDash they are all & tested in one place. For example:

  • _z.isClass checks if a value is an ES2015 class

  • _z.isIterator checks if a value is any kind of Iterator // @todo: add tests

  • _z.isPromise checks if a value is a (native) Promise

  • _z.isRealObject checks if a value is a Hash (a.k.a. an {} object in any form, such as object literal, class instance created with new MyClass, or object created by Object.create(parent), with or without a prototype constructor etc.). There is no other way to check for this, as _.isObject & _.isPlainObject don't do the trick!

  • _z.isSingle checks if the value's data type is "plain" in terms of NOT naturally/normally having nested items (eg props, array items etc.) inside it. For example number, string etc. are single.

  • _z.isMany the opposite of isSingle: checks if the value's data type is "nested" in terms of naturally/normally having nested items (eg props, array items etc.) inside it. For example object, array, Map etc. are nested.

  • _z.isPrimitive according to the definition of "primitive" in JavaScript

plus many many more...

Numbers: From strict to any

JavaScript has a lot of numbers, and it sucks when it comes to checking which is which. Trying to smooth it a bit, we have 3 levels of checks:

  • _z.isStrictNumber checks if value is a strict number, excluding NaNs, boxed new Number('123'), BigInt & Infinity.

  • _z.isNumber checks if value is a strict number OR BigInt OR Infinity, excluding only the invalid NaNs & boxed Numbers.

  • _z.isAnyNumber Checks and returns true, if value is any kind of a "real number", a good candidate for Number(): boxed new Number(), BigInt, Infinity or even a string that represents a "real number" (i.e. any value that is resulting to a non-NaN via Number(value)).

More...

More exotic ones exist - check the typedoc Docs for the full list (npm run docs!).

Various type utils

Various / Experimental / Abandoned

  • _z.isAgree checks if value is in agreement with one or more agreement(s) (functions or values).

  • _z.arrayize converts a value to an array with [ value ], if value is not already an array. Only undefined yields an empty array.

History & Codebase

Some of the functions & tests setup originated from outdated uberscore. Parts of the library has originated from transpiled CoffeeScript v1, so some code (mainly testing) is a bit unreadable, as redundant code is present (excessive let, ref variables, obsolete results.push() in loops, not needed returns etc.

Versions & Generations

ZenDash versions follows Semantic Versioning: Only when it breaks functionality (hopefully rarely) eg with current version 1.x.x, it goes from 1.x.x to 2.0.0 etc. Not when features are added. Read more.

What's more interesting, is Generation, which is quite independent of Version.

  • Version 1.0.0 & Gen 1.0.0 is with initial functionality, with many Not Implemented exotic features, but still immensely useful ;-) Tested enough, but not battle tested yet. Version 2.0.0, 3.0.0 etc will be just iterations, possibly breaking of those! Think of v1.0.0 G1 as 0.1.0 & then v2.0.0 G1 as 0.2.0, but with semantic versioning!

  • Gen 2.0.0, Version XX.0.0 (unknown yet) will start dealing with some future features stuff and a more stable release train!

Future Features - G2/G3 & Beyond

  • Synergy with ValidZen, to augment with the Runtime Type Checks and Static & Runtime Args Extraction & Function Signature Validation for Functions with one or more overloaded signatures. Imagine Joi / yup / zod on steroids, via ValidZen's & ZenDash's magic sauce.

  • ZenType to the next level: enclose your code or function in a ZenType block/decorators and get:

    • Better TypeScript types, leveraging the vast amount of Type helpers that emerge in ZenDash, type-fest, ValidZen and others (eg imagine TnumberString[])!
    • Runtime type checks, declaratively/automagically in function calls (see ValidZen above). Your code should never need to check or choke on types no more!
  • Functional Programming flavour zendash/fp (like lodash/fp / Ramda), while supporting options optionally - for example:

    • _zf.filter(filterCb)(value) -_zf.filter(filterCb, options)(value)
  • Assess loop() & project()/map()/filter()/reduce() support/interoperability for other Pull Iterables (like IxJs, nodejs streams API etc) and more.

  • Wild thinking about Push Iterators like RxJs/Signals/Push Streams? Can these API's ever converge via loop(), with a mediator buffer in the middle (backed by memory or a KeyValue store)?

  • More functions like findKey, filterKeys & more from lodash

  • Refine Type System based on feedback & battle tests

  • Refactoring, more synergies, more options

  • Improve Docs - separate stuff & more examples upfront

  • Fix Bugs, improve performance

  • More/better tests via SpecZen

Installation & Usage

npm i @devzen/zendash

Then in your code (ES Modules or TypeScript):

import * as _z from '@devzen/zendash'

and then

_z.getProp(obj, 'a/b/c')

OR

import { getProp, setProp } as _z from '@devzen/zendash'

and then

setProp(obj, 'a.b.c', 123, {seperator: '.'} )

Similarly, in CommonJS:

const _z = require('@devzen/zendash')

OR

const { getProp, setProp } = require('@devzen/zendash')

Developing & Testing: CliZen

CliZen

ZenDash uses CliZen, a collection of conventions around npm scripts, that makes it easier to develop & test projects, by providing a simple & consistent interface. CliZen allows you to build, test, watch for changes and re-run, generate documentation etc via a simple & consistent CLI.

CliZen Conventions

You only need these 2 conventions to understand the scripts:

  • ~ (Post-fix) means it opens one or more neoTerm consoles and returns control, perhaps after some sleep. It typically opens multiple neoTerm consoles, some might be a one-off command that closes, but typically they are xxx! commands, that just stay open and execute on changes etc

  • ! (Post-fix) eg test! means it's a test:watch, so control is NOT returned once it runs. These are tasks like test:watch or build:watch (we now write them as build! & test!) which re-run when we have some signal, like code changes etc. When you can run in your current Terminal, it blocks your input, and you need to open a new terminal manually. So they will probably be invoked to run inside their own neoTerm, by some other task~ that groups them together logically.

For example dev~: 't -npr build! test! serve!' will call them serially, and each will open in it own neoTerm. These commands typically have a plain equivalent, that runs once & finishes (eg the straight test without watch, used in ci etc). These 2 should behave roughly the same (and the 'task!' could call into the plain 'task' via extra --watch flags or nodemon etc).

In rare cases, the simple xxx! can't run in one terminal (eg having 2 different kind of tests running at the same time, say jest! and assert!), then we only have test~ which calls t jest! assert! to open two separate test in different neoTerm consoles. In these cases, we only note the tilda~, as the watch inside is assumed.

neoTerm in CliZen

All npm scripts that are post-fixed with a tilde (i.e ~), for example npm run dev~, rely on neoTerm that opens new neoTerm Terminals / Consoles where it is required, to separate the building, testing etc processes and thus make development & testing easier to follow. You can mimic what neoTerm does, by executing each npm script contained in it at a separate terminal window manually ;-)

Development Installation

ZenDash is part of the devzen-tools lerna monorepo.

To start development installation, clone the repo locally, cd to the repo's root and execute:

$ npm install

to install the repo's root dependencies.

You'll also need npm-run-all globally (as local npx npm-run-all fails for some reason):

$ npm install -g npm-run-all

Finally execute:

$ npm run boot

This will run a boostrap pipeline that installs all dependencies inside ./packages, builds all packages, and installs locally all sibling deps via DistZen.

Note: DO NOT run a normal npm install inside each package, as this will fail (since it can not find sibling dependencies).

You can issue an:

$ npm run test

inside devzen-tools root, to execute all tests in all packages & verify everything's OK.

NOTE: Tests in src/code/loopzen/tests/all-typings are generated and are literally HUGE! Nodejs crashes with default RAM, so you need to run them with export NODE_OPTIONS="--max-old-space-size=8192" on your node (might work with less). Also note they will take a long time to run (up to 5 or 10 minutes, depending on your machine), so you might want to run them separately:

  • This will run ONLY those long tests

    $ npm run test:long!

  • And this will run the rest of the tests

    $ npm run test:short!

You can also configure other "workbench" tests when developing, examples in package.json

Development

For the quickest and fullest development cycle for ZenDash, you can issue:

$ cd packages/zendash

$ npm run dev~

(mind the tilde ~) This starts the development environment, which watches for changes and re-builds & re-tests the project (along with the assert-based integration light test suite) and also watch-builds and serves the documentation. Each of these tasks is running in a separate terminal window (4 in total) which relies on neoTerm (preview only - not fully released).

You can also run the following commands in 3 separate terminal windows to mimic it:

$ npm run build:watch
$ npm run mocha:watch    
$ npm run docs:watch

or you can pick which ones you need each time ;-)

Testing

Assuming the project has being built, execute

$ npm test

which runs Mocha based tests and then generates the assert-based integration test suite and executes it. It runs only once and stops.

Similarly, you can also run $ npm run test:coverage to see the test coverage.

WARNING: Tests will fail with nodejs < 20 & you need NODE_OPTIONS=--max_old_space_size=8096 in your environment. Make sure $ node -e 'const ram=v8.getHeapStatistics().heap_size_limit/(1024*1024);const low=ram<8000;console[low ? error:log](${ low ? "WARNING" : "NOTE"} nodejs heap_size_limit RAM =, ram)' returns at least 8144 Mbytes

NOTE: tests (& build) take a huge time to run (5-15 minutes, depending on machine), cause a lot of combinations are generated (if generation is on, see src/test-utils/generate.ts)!

Testing with watch

Execute

$ npm test!~

(mind the tilde ~) which (assuming the project has been build) runs the tests in 2 separate terminal windows (via neoTerm) and watches for changes to re-run them.

So for now you run this in a separate terminal window:

$ npm run mocha:watch

Testing on multiple node engines via Docker (*Nix/WSL only)

ZenDash is tested against the node version contained in .docker-node-versions file, which is currently

  • 22.11.0
  • 20.18.0

To test against all .docker-node-versions, first execute $ chmod +x z/* to make the scripts executable. Then issue:

$ npm run test:all_node_versions

To test a specific node version, issue:

$ npm run test:node_version 20.18.0

TypeScript versions

The ZenDash library is best used with TS version > 5.0, ideally 5.6.3 that is used to compile and run all the tests. It has been tested that it compiles with projects on 5.0.2 & 4.9.5 but its advised that 5.x is used.

Documentation

To generate the documentation, execute:

$ npm run docs:build

To generate the documentation while watching for changes and regenerating and also serve via a local-web-server, execute:

$ npm run docs:watch

These generate docs inside ./dist/docs-html directory, in HTML format using TypeDoc. You can serve via a local web server, by executing:

$ npm run docs:serve

and then browse to http://localhost:8091

License

MIT

FAQs

Package last updated on 16 Nov 2024

Did you know?

Socket

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.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc