{"id":19696119,"url":"https://github.com/volijs/nestedtypes","last_synced_at":"2026-02-10T08:33:12.534Z","repository":{"id":15531729,"uuid":"18266337","full_name":"VoliJS/NestedTypes","owner":"VoliJS","description":"BackboneJS compatibility layer for Type-R data framework.","archived":false,"fork":false,"pushed_at":"2022-12-01T23:42:56.000Z","size":5416,"stargazers_count":94,"open_issues_count":20,"forks_count":17,"subscribers_count":23,"default_branch":"master","last_synced_at":"2025-08-19T15:07:17.635Z","etag":null,"topics":["backbone","data-management","models","nestedtypes","observable","reactive-programming","schema-validation","serialization","type-system"],"latest_commit_sha":null,"homepage":"https://volicon.github.io/Type-R/","language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/VoliJS.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-03-30T14:59:17.000Z","updated_at":"2023-09-08T16:46:38.000Z","dependencies_parsed_at":"2022-08-27T00:12:00.647Z","dependency_job_id":null,"html_url":"https://github.com/VoliJS/NestedTypes","commit_stats":null,"previous_names":["volicon/nestedtypes","volicon/backbone.nestedtypes"],"tags_count":19,"template":false,"template_full_name":null,"purl":"pkg:github/VoliJS/NestedTypes","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VoliJS%2FNestedTypes","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VoliJS%2FNestedTypes/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VoliJS%2FNestedTypes/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VoliJS%2FNestedTypes/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/VoliJS","download_url":"https://codeload.github.com/VoliJS/NestedTypes/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VoliJS%2FNestedTypes/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273222083,"owners_count":25066695,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-02T02:00:09.530Z","response_time":77,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["backbone","data-management","models","nestedtypes","observable","reactive-programming","schema-validation","serialization","type-system"],"created_at":"2024-11-11T19:33:50.439Z","updated_at":"2025-12-12T03:50:30.081Z","avatar_url":"https://github.com/VoliJS.png","language":"HTML","funding_links":[],"categories":[],"sub_categories":[],"readme":"NestedTypes 2.0 is BackboneJS compatibility layer for the [Type-R](https://volicon.github.io/Type-R/Getting_Started.html) data framework. Type-R is Model/Collection core written from the scratch with the TypeScript and has no side dependencies.\n\nNestedTypes adds support for REST (standard BackboneJS API), Underscore methods, and Backbone 1.1 classes.\n\n\u003e If you're upgrading from the version 1.3, *[there are compatibility issues](/docs/compatibility.md)*.\n\u003e Mostly due to the fact that the Type-R and NestedTypes 2.0 is built around the concept of [aggregation trees](https://volicon.github.io/Type-R/API_by_feature/Aggregation_tree.html).\n\u003e NestedTypes 1.3 code won't work without refactoring.\n\n# Important Notice\n\nStaring with v2.0 Type-R includes generic I/O abstraction which is far superior to the legacy BackboneJS I/O. \nNestedTypes \u0026 NestedReact will be maintained as the BackboneJS compatibility layer as long as Verizon/Volicon systems have legacy Backbone code. Therefore:\n\n- NestedTypes docs won't be updated. Use [Type-R](https://volicon.github.io/Type-R) documentation as you primary source of documentation.\n- Functional-wise, there's no reason to prefer NestedTypes over the Type-R any more. If you don't need BackboneJS backward compatibility, move to the [Type-R](https://volicon.github.io/Type-R) which doesn't have any legacy dependencies like jQuery and underscore.\n\n# Features\n\nPost-backbone data framework. 10 times faster, first-class support for nested models and collections and relations by id. \n\n- ES6 classes support.\n- Deeply observable changes.\n- First-class support for [aggregation](https://volicon.github.io/Type-R/API_by_feature/Aggregation_tree.html) and [relations by id](https://volicon.github.io/Type-R/API_by_feature/id-references_and_Stores.html).\n- Attribute type annotations and dynamic type safety.\n- More than 10 times faster than BackboneJS and 2-4 times faster than NestedTypes 1.3 in all browsers.\n\n# Installation \u0026 Requirements\n\nAll modern JS engines are supported (IE10+, Safari, Firefox, Edge, Chrome, nodejs). May work in IE9 but not tested.\n\n`npm install nestedtypes`\n\n`underscore` and `jquery` are hard dependencies.\n\nFor lighter framework version without dependencies and Backbone compatibility shim check out [Type-R](https://github.com/Volicon/Type-R).   \n\n# Quick API Reference\n\nCentral concept in NestedTypes is `Record` type, which is the JS class with following capabilities:\n\n- Class members are deeply observable.\n- It is serializable to JSON by default.\n- Class members are typed and their changes are guardered with run-time type checks.\n\n`Model` is the `Record` subclass representing REST API endpoint. Models, records, and their collections\nare used as building blocks to describe both application's UI state and its data layer.\n\n`Record` definition looks like normal ES6 class definition, but it's mandatory to declare\nattributes. It looks like this:\n\n```javascript\nimport { define, Record } from 'nestedtypes'\n\n@define // \u003c- decorator to perform class transformation\nclass User extends Model {\n    urlRoot : '/api/users',\n\n    static attributes = { // \u003c- attributes declaration\n        name : '', // \u003c- can be either default value\n        email : String, // \u003c- or any JS type constructor\n        isActive : true,\n        lastLogin : Date.value( null ) // \u003c- or both\n    }\n}\n\nconst user = new User({ id : 5 }); // \u003c- constructor takes optional attributes hash as an argument\nuser.fetch().done( () =\u003e { // GET /api/users/5\n    user.name = 'John';\n    user.save(); // PUT /api/users/5\n});\n```\n\n## Record's Attributes Type Annotations Basics\n\nAll record's attributes must be declared with `static attributes = { [ attrName ] : TypeAnnotation }` member.\nType annotation can be one of the following:\n\n- `attrName : Constructor`. Such as `attrName : Date`.\n- `attrName : Constructor.value( defaultValue )`. Such as `lastLogin : Date.value( null )`. \n- `attrName : defaultValue`. In this case, attribute type will be inferred from the value, so `isActive : true` has the same effect as `isActive : Boolean.value( true )`.\n\nRecord attributes can be accessed directly, like `user.name = x`. When attribute is assigned, the type of the the value is\nchecked and being converted to the declared type with its constructor invocation if it's necessary.\n\nFor the assignments like `user.isActive = x`, where `isActive` is declared as `Boolean` \n\n- it is assigned as is if `x` is `null` or boolean. \n- for primitive types, it's converted with plain constructor invokation like `Boolean( x )`.\n- For non-primitives convertion will invoke constructor with `new`, like `new Date( x )`.\n\nIf it's impossible to convert the value it may be assigned with `NaN` or `Invalid Date` (or depending on the type update will be rejected),\nand there will be an error in the console.\n\nTherefore, *it's guaranteed* that Record attributes always have declared type.\n\n## Collections\n\nEvery model has corresponding `Collection` type declared implicitly. Collections\nimplements [Backbone Collection API](http://backbonejs.org/#Collection).\n\n```javascript\nvar users = new User.Collection();\n\nusers.fetch().done( () =\u003e {\n    console.log( users.length );\n});\n```\n\nWhen Record is extended, its collection is extended too. The creation of implicit Colleciton type is equivalent to this:\n\n```javascript\n@define\nclass Users extends Record.Collection {} \n\n@define\nclass User extends Record {\n    static Collection = Users;\n    // ...\n}\n```\n\nYou can use this pattern when you need to add custom members to the record's collection.\n\n## Nested Records and Collections\n\nNested records and collections are declared with mentioning constructor in attribute type annotation.\nAll changes in nested objects are deeply observable; any change in children will cause change events in a parent.\n\nRecords and collections emiting the [standard set of Backbone events](http://backbonejs.org/#Events-catalog), with following differences:\n\n- Collections does not bubble `change:[attribute]` event from the model by default (`change` event is bubbled);\n     event bubbling needs to be enabled for every particular event with `static itemEvents = { 'change:attr1' : true, ... }` declaration.\n- Collections have `changes` event which is semantically similar to the model's `change`.\n\n### Transactions\n\nRecord's `change` event (and collection's `changes` event) are _transactional_. Whatever some changes \nare made as the reaction on any of change event, it won't cause additional `change` event for the owner.\n\nAlso, you can explicitly group the sequence of changes to the single transaction:\n\n```javascript\n    some.record.transaction( record =\u003e {\n        record.a = 1;\n        record.b = 2;\n        ...\n    }); // some.record will emit single 'change' event if there was any changes.\n\n    // Execute collection.each in the scope of transaction.\n    todoCollection.updateEach( item =\u003e item.done = true ); // One 'changes' event will be emitted. \n```\n\n### Aggregation\n\nRecord can aggregate other records and collections in its attributes. \n\n```javascript\n@define\nclass Team extends Record {\n    static attributes = {\n        members : User.Collection\n        leader : User\n    }\n}\n\nconst team = new Team();\nteam.members.add( new User({ name : 'John' }) );\n```\n\nAggregated members are:\n\n- serialized as nested JSON.\n- following operations recursively when the operation happens to its owner.\n\nAggregated members forms the tree of exclusive ownership. The same record or collection instance cannot be aggregated in two places at the same time,\nand this rule is checked and enforced. \n\n### Shared nested objects\n\nRecords may have nested members which belongs to different ownership trees.\nSpecial type annotations are required to mark attribute as shared.\n\n- `RecordType.shared` or `CollectionType.shared`. Reference to collection or record which may be aggregated somewhere else. `null` by default.   \n- `CollectionType.Refs` constructor. Collection of records which may be aggregated somewhere else. Defaults to empty collection.\n\n`CollectionType.Refs` is an equivalent to `CollectionType.shared.value( [] )` when used as attribute type annotation.\n\nShared types are:\n\n- not a part of record's ownership tree.\n- not serialized.\n- Not a subject of recursive operations. They are empty by default, not cloned, not validated, and not disposed when the corresponding operation applied to the parent. \n\nIn all other aspects, they are indistinguishable from aggregated records and collections.\n\n```javascript\n@define\nclass Team extends Record {\n    static attributes = {\n        members : User.Collection.Refs\n        leader : User.shared\n    }\n}\n```\n\n### Relationship by `id`\n\nIt's possible to create serializable reference to the shared object which is represented in JSON as a record's id (or array of ids).\nSpecial type annotation is required to point out the master collection which will be used to resolve ids to the records. \n\n- `RecordType.from( masterCollection )` represents an id reference to the model.\n- `CollectionType.subsetOf( masterCollection )` represents the collection of models id references.\n\nid-reference types behaves as shared types, but:\n\n- they are serializable as an object id (or array of ids for collections).\n- they are not observable (internal changes do not trigger change events on the record).\n\n`masterCollection` reference may be either:\n\n- direct reference to the globally available collection.\n- function returning the reference to the collection.\n- string, which is the *symbolic reference* to collection (dot-separated path to the collection taken relative to the record's `this`).\n\n```javascript\nclass Team extends Record {\n    static attributes = {\n        members : User.Collection,\n        leader : User.from( 'members' ) // \u003c- leader is serializable reference to the record from members collection.  \n    }\n}\n``` \n\n#### Owner-references\n\n`^` symbol in symbolic reference represents `getOwner()` call and returns the record owner.\nCollections are skipped.\n\nFollowing example expects that `Team` record will be aggregated (alone or in a collection) together\nwith `users` collection.\n\n```javascript\nclass Team extends Record {\n    static attributes = {\n        members : User.Collection.subsetOf( '^users' ),\n        leader : User.from( 'members' )  \n    }\n}\n``` \n\n#### Tilda-References and Stores\n\nSymbolic reference staring with `~` is resolved relative to the record called _store_,\nwhich is located with `record.getStore()` method.\nFor instance, reference `~users` will be resolved as `this.getStore().users`.\n\n`getStore()` uses following store location algorithm:\n\n1. It traverse an ownership tree upwards and return the first `Store` model it has found.\n2. If none of the record's owners is the Store, it returns global store from `Nested.store`. \n\n`Store` is the subclass of the `Record` and behaves as a regular Record.\nTherefore, resolution of id references depends on the context and you may have as many stores as you like.\n\nFollowing example expects that there's `users` collection in some upper record which is inherited from Store,\nor (if there are none) in the global store: \n\n```javascript\nclass Team extends Record {\n    static attributes = {\n        members : User.Collection.subsetOf( '~users' ),\n        leader : User.from( 'members' )  \n    }\n}\n```\n\n## Attribute has-annotations\n\nIt's possible to control different aspects of record's attribute behavior through additional metadata.\nAll of them starts with a keyword `.has` added to the constructor type.\n\nObject describing an attribute is called *metatype*. Operations on metatypes are immutable (returns new metatype),\nand can be chained.\n\n```javascript\n// Declare Month metatype.\nconst Month = Number.value( 1 ).has.check( x =\u003e x \u003e 0 \u0026\u0026 x \u003c= 12 );\n``` \n\n#### attribute : Type.has.toJSON( false | ( x, name ) =\u003e json )\n\nOverride default serializer for the attribute. `false` option will exclude attribute from serialization.\n\n#### attribute : Type.has.parse( ( json, name ) =\u003e data )\n\nOverride default JSON parser for the attribute.\n\n#### attribute : Type.has.get( ( value, name ) =\u003e value )\n\nGet hook which may transform attribute value on read. Get hooks can be chained.\n\n#### attribute : Type.has.set( ( value, name ) =\u003e value )\n\nSet hook which may transform attribute value before it's assigned. Set hooks can be chained.\n\n#### attribute : RecordOrCollectionType.has.changeEvents( false )\n\nWhen nested attribute is changed, don't mark the owner as changed.\n\n#### attribute : Type.has.events({ [ event ] : handler | handlerName })\n\nListen to the specified events from the attribute. handler can be either function or\nthe name of the record's method.\n\n#### attribute : Type.has.check( x =\u003e boolean, errorMsg? : any )\n\nAttach check to the attribute. Checks can be chained. Attribute is valid whenever check function returns truthy value.\n\nerrorMessage is optional.\n\n#### attribute : Type.isRequired\n\nSimilar to `Type.has.check( x =\u003e x, 'Required' )`.\n\n## Validation\n\nValidation is performed recursively on ownership tree. Record and collection shares the same validation API.\n\n#### record.validate()\n\nOverride it to add custom record-level validation. Method shoudl return truthy value in case of validation error.\n\nFor attribute level checks see `Type.has.check` annotation.\n\n#### record.isValid() : boolean\n\nChecks whenever record is valid. \n\n#### record.validationError\n\nReturn validation error object or null if there are no errors.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvolijs%2Fnestedtypes","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvolijs%2Fnestedtypes","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvolijs%2Fnestedtypes/lists"}