Giter VIP home page Giter VIP logo

dbjs's Introduction

dbjs

In-Memory Database Engine for JavaScript

Concept

dbjs is database of events, each atomic update is represented as an event which is added on top of log.

In contrary to popular CRUD model, each creation, update and deletion is just another atomic event that occurs and affects state of things.

Please see great presentation by Greg Young, which while unrelated to this project, describes well one of the main ideas behind dbjs.

Important: dbjs already powers sophisticated projects, but it's still under heavy development. It's API is not yet in stable state and is subject to changes

If you need help with dbjs, please don't hesitate to ask on dedicated mailing list: [email protected]

Installation

NPM

In your project path:

$ npm install medikoo/dbjs
Browser

You can easily bundle NPM packages for browser with modules-webmake

Introduction

Data modeling

In common application we define models in database engine that persists our data, and then we try to resemble that model (in manual or more less automatic way) in a language that we program our application. We connect both worlds and work like that.

In dbjs we define models directly in JavaScript, using most of things that language has to offer, its types, functions, prototypal inheritance etc. and we work with it natural way. On the other side dbjs provides all means to observe the changes in reasonable manner. Persistent layer can be easily connected to low-end point which expresses data with graph/key-value representations, that remains transparent to our work.

Let's start step by step, by writing example model setup:

var Database = require('dbjs');
var db = new Database();

db is our database, it exposes basic types, that correspond directly to JavaScript types

Basic types
  • db.Boolean
  • db.Number
  • db.String
  • db.DateTime
  • db.RegExp
  • db.Function
  • db.Object

Types are actually constructors that work in similar way as native JavaScript constructors:

db.Number('343'); // 343
db.String(343); // '343'
db.Boolean('foo'); // true

but they're more strict:

db.Number('foo'); // TypeError: foo is invalid Number

Any type can be extended into other:

db.String.extend('ShortString', { max: { value: 5 } });

Type name must be upper-case and follow camelCase convention, also name must be unique. After type is created it can be accessed directly on Database object:

db.ShortString('foo'); // 'foo'

When deriving from String type, we can define additional characteristics via options:

  • min: Minimum length
  • max: Maximum length
  • pattern: RegExp pattern

We set ShortString type to handle strings that are no longer than 3 characters

db.ShortString('foobar'); // TypeError: foobar is too long

Similar options can be provided when extending Number type (min, max and step) or DateTime type (min, max and step).

Mind that, while this is the only programmed-in options, you still can create your very own custom types programmatically, by creating custom constructors and providing other logic.

Within dbjs following types: Boolean, Number, String, DateTime, RegExp and Function are all considered as primitive and are expressed with one end value (even though in JavaScript language some of them are expressed with objects).

Object type

Base type for object types is Db.Object, Instance of Db.Object is (as in plain JavaScript) a plain object (a set of properties). Each property can have value that can be of any defined dbjs type

var obj = db.Object({ foo: 'bar', bar: 34 });

Object.keys(obj); // ['foo', 'bar']
obj.__id__;         // '158nineyo28' Unique internal id of an object

When type for property is not defined, then property is of Db.Base type. Base is representation of undefined type and shouldn't be used when defining model. Note: all basic types inherit from Base.

Object.getPrototypeOf(db.Boolean);     // db.Base
Object.getPrototypeOf(db.String);      // db.Base
Object.getPrototypeOf(db.ShortString); // db.String

We can access descriptor object of a property via following means:

  • obj.getDescriptor(propertyName): Returns descriptor of a property on definition or value level, that means that if e.g. we do user.getDescriptor('firstName'), it's possible we will receive an object for User.prototype.firstName property. This variant should be used when we want to just read the characterictics.
  • obj.getOwnDescriptor(propertyName)): Returns descriptor of a property on context object level, so for user.getDescriptor('firstName'), we will receive a descriptor for user.firstName. This variant should be used when we want to alter property characteristics.
  • obj.${propertyName}: (deprecated) - an alias for obj.getDescriptor('propertyName')
obj.foo; // 'bar'
obj.getDescriptor('foo'); // {}, descriptor of a property

We can read property's characteristics from its descriptor object

var fooDesc = obj.getDescriptor('foo');
fooDesc.type;        // db.Base, type of property
fooDesc.__id__;      // '158nineyo28/$foo', id of a desciptor object
fooDesc.__valueId__; // '158nineyo28/foo', id of a value that object describes
fooDesc.lastModified // 1373553256564482,  microtime stamp of last modification
fooDesc.required;    // false, whether property is required

We can override property characteristics:

var barDesc = obj.getOwnDescriptor('bar');
barDesc.type = db.String;
obj.bar; // '34'
barDesc.type = db.Number;
obj.bar; // 34
barDesc.type = db.Boolean;
obj.bar; // true

barDesc.required = true;
obj.bar = null; // TypeError: Property is required
barDesc.required = false;
obj.bar = null; // Ok
Defining object model

Let's define some custom object types.

We're going to create Patient and Doctor types for simple patient registry system:

Each dbjs type provides rel function, which generates property descriptor, through which we can define custom property of given type:

db.Object.extend('Patient', {
  firstName: { type: db.String, required: true }, 
  lastName: { type: db.String, required: true },
  birthDate: { type: db.DateTime, required: true }
});

db.Object.extend('Doctor', {
  firstName: { type: db.String, required: true },
  lastName: { type: db.String, required: true },
  patients: { type: db.Patient, multiple: true, reverse: 'doctor', unique: true }
});

Following descriptor properties, have special semantics defined in dbjs internals:

  • required boolean - Property will be required
  • multiple boolean - Property will be multiple (set of multiple values)
  • reverse any - Valid only for object types, will expose reverse property on a values, e.g. In Case of doctor.patients and reverse set to doctor, we would be able to access patient's doctor on patient's object, via patient.doctor property.
  • unique boolean - Whether values should be unique.
  • order number - Order number, used in ordered lists of properties
  • value - Default value, that will be set on prototype.

Any other option which may be provided will be set in it's direct form on meta-data object and it will not be used in internally by dbjs engine. That way you can define your custom meta properties and use them later in your custom way.

Let's build some objects for given schema:

var drHouse = new db.Doctor({ firstName: "Gregory", lastName: "House" });

drHouse.firstName; // 'Gregory'
drHouse.lastName; // 'House'
drHouse.patients; // {}, set of patients

var john = new db.Patient({ firstName: "John", lastName: "Smith", birthDate: new Date(1977, 0, 3) });

john.firstName; // 'John';
john.doctor; // null, we access reverse doctor value out of doctor.patients property.

Let's assign patient to our doctor:

drHouse.patients.add(john);
drHouse.patients.has(john); // true
drHouse.patients // { john }

john.doctor; // drHouse

Events

dbjs is highly evented, and provides observer functionalities for each object and property

var johnLastNameObservable = john._lastName;
john.lastName = 'House'; // 'change' emitted on johnLastNameObservable
drHouse.patients.delete(john); // 'delete' emitted on drHouse.patients
drHouse.patients.add(john); // 'add' emitted on drHouse.patients

There are more event types dedicated for persistent layer, they'll be documented in near future.

Computed properties

With dbjs we can also define computed (getter) properties:

Any function value, where length of function signature is 0 is considered and handled as a getter:

db.Doctor.prototype.define('fullName', {
	type: db.String,
	value: function () { return this.firstName + " " + this.lastName; }
});

drHouse.fullName; // "Gregory House"

In above case value is recalculated on each access. However whenever we decide to listen for changes on drHouse object or on its fullName observer, value will be automatically recalculated whenever firstName or lastName of drHouse changes:

drHouse._fullName.on('change', function () { ... });

drHouse.firstName = "John" // fullName recalculated and 'change' emitted
drHouse.fullName; // "John House"

Binding with persistent layer

See Engine agnostic example of persistent layer binding

dbjs-ext Other types (extensions)

dbjs on its own provides just basic types (which correspond to native JavaScript types), you can extend them into more custom on your own, but there's also dedicated dbjs-EXT project which defines all other common types that you may be after.

dbjs-dom DOM bindings

dbjs-DOM is dedicated project which provides two-way DOM bindings for any dbjs objects (each type is handled in dedicated way). If you build website with dbjs models, it will definitely you a lot of time.

Tests

$ npm test

dbjs's People

Contributors

kamsi avatar medikoo avatar mtuchowski avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

dbjs's Issues

Allow optional nesteds

Currently when property is defined as nested, value (nested object ) becomes uncoditionally set. There's no way to introduce null alternative for such property.
There are many use cases where such approach becomes problematic.

Serialization of such state might go to 7 when nested should be returned.

Allow to provide nested paths for `reverse` setting

/cc @kamsi

Currently all we can provide is a key while sometimes it can be valuable to to pass it to nested property.

We would need to address it with another property, probably reverseKeyPath and maybe rename reverse to reverseKey which would also be more self explanatory

Rethink computables recalcuation resolution

/cc @kamsi

Currently on incoming data batch, following happens:

  1. Static (non-computable) records are put in place. If some record changes state from non computable to computable (may happen if some object got back from destroyed state to I'm here again state), it's also immediately recomputed.
  2. All computables that depend on statics are recomputed
  3. All computables that depend on computables are recomputed (and so on)
  4. Events for boths statics and computables are propagated

There's an issue with resolution of new computables in first step, there's a risk they may approach uneven state. Technically that should be put between step 1 and 2.

At this point I've introduced a fix that forces another re-computation of them in step 2.

Make DateTime type immutable

It's in primitives basket, having just this one type a methods that allow to change self value is out of convention

Introduce 'getByKeyPath'

/cc @kamsi

We should have easy means to get deep properties via keyPath. At this point there's obj.resolveSKeyPath method that can be used for that, but it's not as convenient and intuitive as it can be.

Improve validation of reverse definition

It must not be allowed to have it defined twice for same type with same reverse property name.

Lack of this validation produced serious bug in Salvador system

Allow `null` Type

Values for properties with no type will never be validated, and they won't be propagated to persistent layer.

Valid use case for that, is to have computables that do not necessarily return values that are applicable for persistent layer. Currently it's not possible

Assigning values to reverse properties doesn't work

As reported by @roxanadina:

when doing

john.doctor = drHouse

$ node test.js

DbjsError: Some values are invalid:
       firstName,John is not a Patient
lastName,Smith is not a Patient
birthDate,Mon Jan 03 1977 00:00:00 GMT+0200 (GTB Standard Time) is not a Patient

   at defineProperties._validateMultiple_ (d:\workspace\node_modules\dbjs\_setu
p\1.property\4.descriptor-validate-value.js:129:10)
   at defineProperties._validateSetValue_ (d:\workspace\node_modules\dbjs\_setu
p\1.property\4.descriptor-validate-value.js:53:35)
   at Self.defineProperties._validateSet_ (d:\workspace\node_modules\dbjs\_setu
p\1.property\7.set-property.js:190:16)
   at Object.create._validateSet_ (d:\workspace\node_modules\dbjs\_setup\1.prop
erty\reverse-map.js:155:9)
   at defineProperties._validateSetValue_ (d:\workspace\node_modules\dbjs\_setu
p\1.property\4.descriptor-validate-value.js:47:46)
   at Self.defineProperties._validateSet_ (d:\workspace\node_modules\dbjs\_setu
p\1.property\7.set-property.js:190:16)
   at Self.<anonymous> (d:\workspace\node_modules\dbjs\_setup\1.property\8.acce
ss-property.js:32:26)
   at Object.<anonymous> (d:\workspace\newGit\dbjs-new\test.js:28:13)
   at Module._compile (module.js:456:26)
   at Object.Module._extensions..js (module.js:474:10)


when doing

john._set_('doctor', drHouse)

d:\workspace\node_modules\dbjs\_setup\1.property\reverse-map.js:145
               if (set.has(value)) return;
                       ^
TypeError: Cannot call method 'has' of undefined
   at Object.create._set_ (d:\workspace\node_modules\dbjs\_setup\1.property\re
erse-map.js:145:11)
   at Self.defineProperties._set_ (d:\workspace\node_modules\dbjs\_setup\1.pro
erty\7.set-property.js:82:20)
   at Object.<anonymous> (d:\workspace\newGit\dbjs-new\test.js:29:6)
   at Module._compile (module.js:456:26)
   at Object.Module._extensions..js (module.js:474:10)
   at Module.load (module.js:356:32)
   at Function.Module._load (module.js:312:12)
   at Function.Module.runMain (module.js:497:10)
   at startup (node.js:119:16)
   at node.js:906:3

Improve normalisation/validation mean for String types

/cc @mtuchowski @kamsi

e.g. we'd like to have just upper case result. This case is normalizable, while in dbjs all we have now is pattern which is treated strict way (if string doesn't match it is assumed invalid and cannot be normalized), Still pattern would not be ideal for upper, lower case as it's not possible to easily and accurately describe such rule with regex.

Summarising this is about two things:

  • Allow ease configuration of forced case (lower or upper) on string types and string property descriptors
  • Alllow custom normalisation for pattern validation, for feasible cases (e.g. entering - betweet NIP numbers), in such case even if input string doesn't match pattern it may be treated as normalisable, then valid.

Natural handling of JSON (input/output)

/cc @kamsi

After upcoming refactor of dbjs, it'll be great so it works naturally with typical REST server backed (assuming that we want to use engine just on client side).

For that it'll be good if dbjs database instance can be easily updated via incoming JSON objects, and that we have possibility to get JSON snapshots of chosen data from dbjs database.

Currently such setup, while possible, is not straightforward and demands custom configuration.

Do not allow future timestamps

/cc @kamsi

There's most likely no use case to use timestamps from future, and when they're accidentally injected, they introduce hard to investigate bugs.

It might be good to throw whenever such timestamp is proposed

Provide a way to observe stringified values

Currently the flow when we want to reactively display value in DOM is that we have some DOM handler which observes value directly and then on each change it stringifies value and updates DOM with it.

While it should rather be, that we observe already stringified version of that value, and on its change we update DOM.

So all goes to point that there should be an easy way to observe results of toString for given property

toJSON

it would be nice to be able to call toJSON() on an object in the database in order to debug, test, or send over a traditional HTTP API. is this friendly for in dbjs, or is dbjs general enough that this should be in es6-map and es6-set? i'd be happy to write the code.

cheers!

Introduce global read-only mode

Normally application should work with its database working in read-only mode.

Where data needs to be updated (e.g. after form submission), read-only should be postponed for a moment of update with special handler, e.g. it may work as:

db.write(function () {
  user.firstName = "John";
  ...
});

This will prevent developers from accidentally (or not accidentally) introducing updates to database.
Currently any update of db data anywhere in a code, will just work without a warning, that's not great.

It will also be a great stopper, from not wise ideas of introducing updates within property getters.

More natural configuration of object streams

/cc @kamsi

Currently when we want to configure some action on when given object receives certain state, we usually build collection as e.g.

db.BusinessProcess.filterByKey('isRevisionReady', true).on(function (event) { ... });

What's painful is that usually we rewrite similar event handler which works around add, delete, batch events. It might be nicer if we can achieve same via something as:

db.BusinessProcess.stream('isRevisionReady', true).on(function (businessProcess) { .. });

This stream will also at initialization stream all objects that already match expected state.

Additional functionality would be to create a streams that are also guarded by pre-triggers, so we observe specific state change from A to B, and not just fact that object landed at given state.
Very rough idea, on how it may look:

db.BusinessProcess.stream('isRevisionReady', true)
  .to('isRevisionApproved', true).on(function (businessProcess) { .. });

Allow to configure enumerability of property

/cc @kamsi

At this point all defined properties, are set as enumerable. There's no way to tweak that.

There are however use cases, when we may want non-enumerable property on an object. Good example is map of same type values, where we want to add some custom property, that should not be treated as part of map collection, and should not be iterable with forEach.

There's a specific use case for that in eRegistrations app, where we have map of sections, and we need a property that will calculate overall status of the sections. At this point we put it aside on an object as sectionsStatus, while more natural would be to have it as sections.status.

Consider better shortcut versions for getDescriptor and getObservable

/cc @kamsi @mtuchowski

Currently there's $ prefix for descriptors and _ for observables.

Both seem controversial, and to uninitiated developer they provide just wrong clues on what's behind (e.g. $ may suggest some jQuery call, while _ a private property).

Initial proposal is to introduce d and o methods, So instead of obj.$foo or obj._foo, developer may use obj.d('foo') and obj.o('foo').

Better proposals are welcome

Improve validation of definition calls

/cc @kamsi

Definitions accept strictly descriptors, when by mistake sometimes we try to pass value directly.

It would be good to throw on obviously non descriptor objects, whether we should accept just plain objects is questionable, but maybe it's the way it should be done.

Allow overide of nesteds

Currently when property is defined as nested, it's not possible to override with remote object.

It should be possible, there are cases when we may want property to be either nested or remote object.
Good use case is an image file, with preview property. In some cases preview may link self image file. Currently if we define preview as nested it can't work properly

Consider namespaced (nested) classes (types)

/cc @kamsi @mtuchowski

Now each class is set directly on dbjs, so they're names need to be quite specific (as they may collide with other not related class names), e.g. we may have db.Attorney and db.AttorneyFormSection classes.

However it might be to good to have possiblity to narrow class name to some namespace. so it's e.g. db.Attorney, db.FormSection and db.FormSection.Attorney.

In such cases we may also require that nested class extends parent class (so e.g. in above case it's clear that db.FormSection.Attorney extends db.FormSection)

Improve validation of some object primitives

If for property of type Function, function instance is passed, and it's neither direct instance of this Function type, nor plain JavaScript function, validator should throw.

In case of Dates or RegExp's validator instead of throwing may create copies, but in case of functions there's no straightforward way to create perfect copy.

It will prevent errors, as one we approached with @kamsi, where following bogus code:

db2.Type.extend('SomeType', db1.OtherType);

didn't report any issue, but have broken state of db1.OtherType (its prototype was turned)

Reorganize Set, Map and descriptorPrototype types handling

/cc @kamsi @mtuchowski

Currently, Set is defined via multiple: true, and Map (when we think of object with each property of same type) via _desciptorPrototype_.setProperties({ type: Object, nested: true }).

It doesn't seem intuitive, and probably can be improved, by removing both multiple: true and descriptorPrototype functions, and instead introducing aside of type meta property, an itemType property

So sets can be defined as:

Class.prototype.defineProperty('someSet', { type: db.Set, itemType: db.String })

and maps as e.g.:

Class.prototype.defineProperty('someMap', { type: db.Object, itemType: db.String })

Additionally for object kind of itemType's we may introduce (optional) itemPrototype property, which will allow to customise type provided for itemType without a need to create such type as standalone one. That thing is not possible in current implementation of dbjs (all we have is _descriptorPrototype_)

Allow override of reverse resolved properties

/cc @kamsi

Currently reverse resolution has priority, even if on extended class we want to shadow property resolved by reverse with a getter, it is not allowed.

It's plainly because validation of reverse gets priority, and crashes definition stating it received unexpected value.

Additionally it would be good to provide separation between definition of value, and set of value.
Currenlty if value is set via define, it is put through normal set validation, where source of action cannot be distinguished.

Improve type validation messaging

/cc @nix1

Currently when we try to define a property using type from other database instance, we get very basic error message: "X is not a valid type". It'll be nicer to indicate to developer that we can't use foreign type.

Customisable iteration order of maps and sets

/cc @kamsi @nix1

Currenlty by default sets and maps are iterated by last modification order. For most cases it plays well, still it might be wise to expose compare method which can be overriden for certain use cases

e.g. we may have list of countries, that we want to be iterated alphabetically, we can define them that way in model, but what if want to assure alphabetical order in any language (where labels will be different for each language), with custom compare method, it'll be possible to assure universal alphabetical order for such type.

Provide convenient access to 'super' method or getter

Currently it needs to be manually retrieved, and due to specific observables resolution requires additional specific hacks as:

// some getter
value: function (_observe) {
  var superGetter = this.database.SuperClass.getDescriptor('property')._value_;
  superGetter = this.database.resolveGetterObservables(superGetter);
  var superResult = superGetter.call(this, _observe);
}

While it possibly might be as easy as:

// some getter
value: function (_super) {
  var superResult = _super();
}

Prevent defintion of restricted property names

/cc @kamsi

There are some of them, which can't be defined successfully like owner, master, id

Currently dbjs allows definition of them, but resolution of them of course doesn't work as expected.
Best would be to throw at definition moment

Restrict definitions only to 'define' and 'extend'

/cc @kamsi

Currently it is allowed to tweak descriptor characteristics directly on descriptor objects.
It is error prone, as in some cases we may do it not being aware that descriptor presents not desired context e.g. following looks innocent

obj.getDescriptor('foo').type = db.X;

However as getDescriptor was used and not getOwnDescriptor, it may appear that we extend definition for way broader range than obj (which looks as intended context). This may lead to difficult to track bugs.

I think the only solid solution to prevent such issues, is to restrict definitions so at all times context needs to be indicated.
So definition will be allowed only via define (defineProperties) or defineProperties, and then we may introduce extendProperty or extendProperties when we want to extend definition of already defined property.

how to use as a data store

if i wanted to implement something like a feathers service or dstore/Store with find / get / create / update / remove functions for each data type, what is the most idiomatic way to do this?

  • find(params) maybe uses db.objects.filterByKey(key, value) for each keyvalue pair, any other pointers?
  • get(id, params) is db.objects.getById(id).
  • create(data, params) is db.Type(data). what about nested objects?
  • update(id, data, params) is a get(id) then obj[key] = value for each keyvalue pair. nested objects seem fine here.
  • remove(id, params) is db.objects.destroy(id). what about nested objects?

then to forward observe events, we listen with db.objects.on('change', ..., but how do we access the Type of the changed object?

cheers!

Provide a way to link values

/cc @mtuchowski

e.g. say that foo.bar should return same thing as foo.lorem return.

Currently it's achievable via getters, but if we deal with sets (multiple values) then always a reactive copy of resolved set is returned, and not set itself, which is unnecessary overload for some scenarios

Rethink automatic descriptor resolution

/cc @kamsi

Currently when doing obj.getDescriptor('x') we'll always get some descriptor. If property was never defined, we'll get base descriptor that is ancestor for each descriptor in a database (a base descriptor prototype)

It's dangerous, as when we try to do obj.getDescriptor('x').someSetting = value we may accidentally set this value for every property in a system.

Currently as an alternative there's obj.getOwnDescriptor('x') which by all means will return descriptor for given object (if it didn't exist, it's created and returned). Still it's dirty, as it creates objects we may not need. It's also usually not used, as in most cases we call getDescriptor on instances to get descriptor of prototype properties.

I see three possible solutions at the moment.

  1. Make getOwnDescriptor (or create alternative method) so it returns own descriptor only if it exists, and returns null otherwise. It will quickly expose eventual bugs.
  2. Do not return base descriptor (of $ id), from getDescriptor call, so it's accessible via public API.
  3. Freeze base descriptor (of $ id) (via Object.freeze) so any changes on it are forbidden.

Validation of set property

/cc @kamsi @mtuchowski

Sometimes we try to set properties directly in dbjs via obj.foo = 'bar', but if property of given name was never defined in dbjs (on any object), then it doesn't reach dbjs getter but becomes natural ECMAScript property set.

This behavior leads to issues, as our settings may get lost, when we do not expect it.

It might be good to try to find possibly dev only solution that would warn us about such usage (or even throw). It might be possible in ES6 with usage of proxies, or with Object.observe if V8 implements it.

Introduce real Type type

/cc @kamsi

There are cases, when we want to define property, to which other type may be assigned.
Currently at definition step to make it work properly we need to leave db.Base type as type, which technically allows all supported dbjs values to be set, so there's no constraint for Type objects, as it should be.

Additionally allow to restrict given type property to receive only type extensions of specific type

Provide enum functionality natively

/cc @kamsi

Currently it's provided with dbjs-ext, however it's very popular functionality, and additionally it'll be great to have possibility to define enum characteristics directly in property definitions (so on descriptors)

Improve string representation of a Type

Currently generic function string is genereted, and doesn't bring any meaningful information. It should be similar to db object instances, e.g. [dbjs String]

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.