json-api-models
Advanced tools
Comparing version
@@ -1,1 +0,1 @@ | ||
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});class t{constructor(t,e){this.store=e,this.attributes={},this.relationships={},this.meta={},this.links={},this.casts={},this.merge(t)}getAttribute(t){const e=this.attributes[t],s=this.casts[t];return s&&null!=e?s!==String&&s!==Number&&s!==Boolean&&((i=s).prototype&&i.prototype.constructor.name)?new s(e):s(e):e;var i}getRelationship(t){return this.store.find(this.relationships[t].data)}identifier(){return{id:this.id,type:this.type}}merge(t){if("type"in t&&(this.type=t.type),"id"in t&&(this.id=t.id),"attributes"in t&&(Object.assign(this.attributes,t.attributes),Object.keys(t.attributes).forEach((t=>{Object.getOwnPropertyDescriptor(Object.getPrototypeOf(this),t)||Object.defineProperty(this,t,{configurable:!0,get:()=>this.getAttribute(t)})}))),"relationships"in t)for(const[e,s]of Object.entries(t.relationships))this.relationships[e]=this.relationships[e]||{},Object.assign(this.relationships[e],s),Object.getOwnPropertyDescriptor(Object.getPrototypeOf(this),e)||Object.defineProperty(this,e,{configurable:!0,get:()=>this.getRelationship(e)});"links"in t&&(this.links=t.links),"meta"in t&&(this.meta=t.meta)}}function e(t){return encodeURIComponent(t).replace(/[!'()*]/g,(function(t){return"%"+t.charCodeAt(0).toString(16).toUpperCase()}))}exports.Model=t,exports.Query=class{constructor(t={}){this.query=Object.assign({},t)}append(t,e){return"object"==typeof t?Object.entries(t).map((t=>this.append.apply(this,t))):this.query[t]=(this.query[t]?this.query[t]+",":"")+e,this}set(t,e){return"object"==typeof t?Object.entries(t).map((t=>this.set.apply(this,t))):this.query[t]=e,this}delete(t){return Array.isArray(t)?t.forEach((t=>this.delete(t))):delete this.query[t],this}toString(){return Object.entries(this.query).sort(((t,e)=>t[0].localeCompare(e[0]))).map((([t,s])=>e(t)+"="+e(s))).join("&")}},exports.Store=class{constructor(t={}){this.models=t,this.graph={}}model(t,e){this.models[t]=e}find(t,e){return null===t?null:Array.isArray(t)?t.map((t=>this.find(t))):"object"==typeof t?this.find(t.type,t.id):this.graph[t]&&this.graph[t][e]||null}findAll(t){return this.graph[t]?Object.keys(this.graph[t]).map((e=>this.graph[t][e])):[]}sync(t){const e=this.syncResource.bind(this);return"included"in t&&t.included.map(e),Array.isArray(t.data)?t.data.map(e):e(t.data)}syncResource(t){const{type:e,id:s}=t;return this.graph[e]=this.graph[e]||{},this.graph[e][s]?this.graph[e][s].merge(t):this.graph[e][s]=this.createModel(t),this.graph[e][s]}createModel(e){return new(this.models[e.type]||this.models["*"]||t)(e,this)}forget(t){delete this.graph[t.type][t.id]}reset(){this.graph={}}}; | ||
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});class t{constructor(t,e){this.store=e,this.attributes={},this.relationships={},this.meta={},this.links={},this.casts={},this.merge(t)}getAttribute(t){const e=this.attributes[t],s=this.casts[t];return s&&null!=e?s!==String&&s!==Number&&s!==Boolean&&((r=s).prototype&&r.prototype.constructor.name)?new s(e):s(e):e;var r}getRelationship(t){const e=this.relationships[t].data;return Array.isArray(e),this.store.find(e)}identifier(){return{id:this.id,type:this.type}}merge(t){"type"in t&&(this.type=t.type),"id"in t&&(this.id=t.id),"attributes"in t&&(Object.assign(this.attributes,t.attributes),Object.keys(t.attributes).forEach((t=>{Object.getOwnPropertyDescriptor(Object.getPrototypeOf(this),t)||Object.getOwnPropertyDescriptor(this,t)||(this[t]=null,Object.defineProperty(this,t,{get:()=>this.getAttribute(t),configurable:!0,enumerable:!0}))}))),"relationships"in t&&Object.entries(t.relationships).forEach((([t,e])=>{this.relationships[t]=this.relationships[t]||{},Object.assign(this.relationships[t],e),Object.getOwnPropertyDescriptor(Object.getPrototypeOf(this),t)||Object.getOwnPropertyDescriptor(this,t)||(this[t]=null,Object.defineProperty(this,t,{get:()=>this.getRelationship(t),configurable:!0,enumerable:!0}))})),"links"in t&&(this.links=t.links),"meta"in t&&(this.meta=t.meta)}}function e(t){return encodeURIComponent(t).replace(/[!'()*]/g,(function(t){return"%"+t.charCodeAt(0).toString(16).toUpperCase()}))}exports.Model=t,exports.Query=class{constructor(t={}){this.query=Object.assign({},t)}append(t,e){return"object"==typeof t?Object.entries(t).map((t=>this.append.apply(this,t))):this.query[t]=(this.query[t]?this.query[t]+",":"")+e,this}set(t,e){return"object"==typeof t?Object.entries(t).map((t=>this.set.apply(this,t))):this.query[t]=e,this}delete(t){return Array.isArray(t)?t.forEach((t=>this.delete(t))):delete this.query[t],this}toString(){return Object.entries(this.query).sort(((t,e)=>t[0].localeCompare(e[0]))).map((([t,s])=>e(t)+"="+e(s))).join("&")}},exports.Store=class{constructor(t={}){this.models=t,this.graph={}}find(t,e){return null===t?null:Array.isArray(t)?t.map((t=>this.find(t))):"object"==typeof t?this.find(t.type,t.id):this.graph[t]&&this.graph[t][e]||null}findAll(t){return this.graph[t]?Object.keys(this.graph[t]).map((e=>this.graph[t][e])):[]}sync(t){const e=this.syncResource.bind(this);return"included"in t&&t.included.map(e),Array.isArray(t.data)?t.data.map(e):e(t.data)}syncResource(t){const{type:e,id:s}=t;return this.graph[e]=this.graph[e]||{},this.graph[e][s]?this.graph[e][s].merge(t):this.graph[e][s]=this.createModel(t),this.graph[e][s]}createModel(e){return new(this.models[e.type]||this.models["*"]||t)(e,this)}forget(t){delete this.graph[t.type][t.id]}reset(){this.graph={}}}; |
export * from './types'; | ||
export { Store } from './store'; | ||
export { Model } from './model'; | ||
export { Query } from './query'; | ||
export * from './store'; | ||
export * from './model'; | ||
export * from './query'; |
@@ -1,1 +0,1 @@ | ||
class t{constructor(t,e){this.store=e,this.attributes={},this.relationships={},this.meta={},this.links={},this.casts={},this.merge(t)}getAttribute(t){const e=this.attributes[t],i=this.casts[t];return i&&null!=e?i!==String&&i!==Number&&i!==Boolean&&((s=i).prototype&&s.prototype.constructor.name)?new i(e):i(e):e;var s}getRelationship(t){return this.store.find(this.relationships[t].data)}identifier(){return{id:this.id,type:this.type}}merge(t){if("type"in t&&(this.type=t.type),"id"in t&&(this.id=t.id),"attributes"in t&&(Object.assign(this.attributes,t.attributes),Object.keys(t.attributes).forEach((t=>{Object.getOwnPropertyDescriptor(Object.getPrototypeOf(this),t)||Object.defineProperty(this,t,{configurable:!0,get:()=>this.getAttribute(t)})}))),"relationships"in t)for(const[e,i]of Object.entries(t.relationships))this.relationships[e]=this.relationships[e]||{},Object.assign(this.relationships[e],i),Object.getOwnPropertyDescriptor(Object.getPrototypeOf(this),e)||Object.defineProperty(this,e,{configurable:!0,get:()=>this.getRelationship(e)});"links"in t&&(this.links=t.links),"meta"in t&&(this.meta=t.meta)}}class e{constructor(t={}){this.models=t,this.graph={}}model(t,e){this.models[t]=e}find(t,e){return null===t?null:Array.isArray(t)?t.map((t=>this.find(t))):"object"==typeof t?this.find(t.type,t.id):this.graph[t]&&this.graph[t][e]||null}findAll(t){return this.graph[t]?Object.keys(this.graph[t]).map((e=>this.graph[t][e])):[]}sync(t){const e=this.syncResource.bind(this);return"included"in t&&t.included.map(e),Array.isArray(t.data)?t.data.map(e):e(t.data)}syncResource(t){const{type:e,id:i}=t;return this.graph[e]=this.graph[e]||{},this.graph[e][i]?this.graph[e][i].merge(t):this.graph[e][i]=this.createModel(t),this.graph[e][i]}createModel(e){return new(this.models[e.type]||this.models["*"]||t)(e,this)}forget(t){delete this.graph[t.type][t.id]}reset(){this.graph={}}}function i(t){return encodeURIComponent(t).replace(/[!'()*]/g,(function(t){return"%"+t.charCodeAt(0).toString(16).toUpperCase()}))}class s{constructor(t={}){this.query=Object.assign({},t)}append(t,e){return"object"==typeof t?Object.entries(t).map((t=>this.append.apply(this,t))):this.query[t]=(this.query[t]?this.query[t]+",":"")+e,this}set(t,e){return"object"==typeof t?Object.entries(t).map((t=>this.set.apply(this,t))):this.query[t]=e,this}delete(t){return Array.isArray(t)?t.forEach((t=>this.delete(t))):delete this.query[t],this}toString(){return Object.entries(this.query).sort(((t,e)=>t[0].localeCompare(e[0]))).map((([t,e])=>i(t)+"="+i(e))).join("&")}}export{t as Model,s as Query,e as Store}; | ||
class t{constructor(t,e){this.store=e,this.attributes={},this.relationships={},this.meta={},this.links={},this.casts={},this.merge(t)}getAttribute(t){const e=this.attributes[t],i=this.casts[t];return i&&null!=e?i!==String&&i!==Number&&i!==Boolean&&((s=i).prototype&&s.prototype.constructor.name)?new i(e):i(e):e;var s}getRelationship(t){const e=this.relationships[t].data;return Array.isArray(e),this.store.find(e)}identifier(){return{id:this.id,type:this.type}}merge(t){"type"in t&&(this.type=t.type),"id"in t&&(this.id=t.id),"attributes"in t&&(Object.assign(this.attributes,t.attributes),Object.keys(t.attributes).forEach((t=>{Object.getOwnPropertyDescriptor(Object.getPrototypeOf(this),t)||Object.getOwnPropertyDescriptor(this,t)||(this[t]=null,Object.defineProperty(this,t,{get:()=>this.getAttribute(t),configurable:!0,enumerable:!0}))}))),"relationships"in t&&Object.entries(t.relationships).forEach((([t,e])=>{this.relationships[t]=this.relationships[t]||{},Object.assign(this.relationships[t],e),Object.getOwnPropertyDescriptor(Object.getPrototypeOf(this),t)||Object.getOwnPropertyDescriptor(this,t)||(this[t]=null,Object.defineProperty(this,t,{get:()=>this.getRelationship(t),configurable:!0,enumerable:!0}))})),"links"in t&&(this.links=t.links),"meta"in t&&(this.meta=t.meta)}}class e{constructor(t={}){this.models=t,this.graph={}}find(t,e){return null===t?null:Array.isArray(t)?t.map((t=>this.find(t))):"object"==typeof t?this.find(t.type,t.id):this.graph[t]&&this.graph[t][e]||null}findAll(t){return this.graph[t]?Object.keys(this.graph[t]).map((e=>this.graph[t][e])):[]}sync(t){const e=this.syncResource.bind(this);return"included"in t&&t.included.map(e),Array.isArray(t.data)?t.data.map(e):e(t.data)}syncResource(t){const{type:e,id:i}=t;return this.graph[e]=this.graph[e]||{},this.graph[e][i]?this.graph[e][i].merge(t):this.graph[e][i]=this.createModel(t),this.graph[e][i]}createModel(e){return new(this.models[e.type]||this.models["*"]||t)(e,this)}forget(t){delete this.graph[t.type][t.id]}reset(){this.graph={}}}function i(t){return encodeURIComponent(t).replace(/[!'()*]/g,(function(t){return"%"+t.charCodeAt(0).toString(16).toUpperCase()}))}class s{constructor(t={}){this.query=Object.assign({},t)}append(t,e){return"object"==typeof t?Object.entries(t).map((t=>this.append.apply(this,t))):this.query[t]=(this.query[t]?this.query[t]+",":"")+e,this}set(t,e){return"object"==typeof t?Object.entries(t).map((t=>this.set.apply(this,t))):this.query[t]=e,this}delete(t){return Array.isArray(t)?t.forEach((t=>this.delete(t))):delete this.query[t],this}toString(){return Object.entries(this.query).sort(((t,e)=>t[0].localeCompare(e[0]))).map((([t,e])=>i(t)+"="+i(e))).join("&")}}export{t as Model,s as Query,e as Store}; |
@@ -1,1 +0,1 @@ | ||
var JsonApiModels=function(t){"use strict";class e{constructor(t,e){this.store=e,this.attributes={},this.relationships={},this.meta={},this.links={},this.casts={},this.merge(t)}getAttribute(t){const e=this.attributes[t],i=this.casts[t];return i&&null!=e?i!==String&&i!==Number&&i!==Boolean&&((s=i).prototype&&s.prototype.constructor.name)?new i(e):i(e):e;var s}getRelationship(t){return this.store.find(this.relationships[t].data)}identifier(){return{id:this.id,type:this.type}}merge(t){if("type"in t&&(this.type=t.type),"id"in t&&(this.id=t.id),"attributes"in t&&(Object.assign(this.attributes,t.attributes),Object.keys(t.attributes).forEach((t=>{Object.getOwnPropertyDescriptor(Object.getPrototypeOf(this),t)||Object.defineProperty(this,t,{configurable:!0,get:()=>this.getAttribute(t)})}))),"relationships"in t)for(const[e,i]of Object.entries(t.relationships))this.relationships[e]=this.relationships[e]||{},Object.assign(this.relationships[e],i),Object.getOwnPropertyDescriptor(Object.getPrototypeOf(this),e)||Object.defineProperty(this,e,{configurable:!0,get:()=>this.getRelationship(e)});"links"in t&&(this.links=t.links),"meta"in t&&(this.meta=t.meta)}}function i(t){return encodeURIComponent(t).replace(/[!'()*]/g,(function(t){return"%"+t.charCodeAt(0).toString(16).toUpperCase()}))}return t.Model=e,t.Query=class{constructor(t={}){this.query=Object.assign({},t)}append(t,e){return"object"==typeof t?Object.entries(t).map((t=>this.append.apply(this,t))):this.query[t]=(this.query[t]?this.query[t]+",":"")+e,this}set(t,e){return"object"==typeof t?Object.entries(t).map((t=>this.set.apply(this,t))):this.query[t]=e,this}delete(t){return Array.isArray(t)?t.forEach((t=>this.delete(t))):delete this.query[t],this}toString(){return Object.entries(this.query).sort(((t,e)=>t[0].localeCompare(e[0]))).map((([t,e])=>i(t)+"="+i(e))).join("&")}},t.Store=class{constructor(t={}){this.models=t,this.graph={}}model(t,e){this.models[t]=e}find(t,e){return null===t?null:Array.isArray(t)?t.map((t=>this.find(t))):"object"==typeof t?this.find(t.type,t.id):this.graph[t]&&this.graph[t][e]||null}findAll(t){return this.graph[t]?Object.keys(this.graph[t]).map((e=>this.graph[t][e])):[]}sync(t){const e=this.syncResource.bind(this);return"included"in t&&t.included.map(e),Array.isArray(t.data)?t.data.map(e):e(t.data)}syncResource(t){const{type:e,id:i}=t;return this.graph[e]=this.graph[e]||{},this.graph[e][i]?this.graph[e][i].merge(t):this.graph[e][i]=this.createModel(t),this.graph[e][i]}createModel(t){return new(this.models[t.type]||this.models["*"]||e)(t,this)}forget(t){delete this.graph[t.type][t.id]}reset(){this.graph={}}},Object.defineProperty(t,"__esModule",{value:!0}),t}({}); | ||
var JsonApiModels=function(t){"use strict";class e{constructor(t,e){this.store=e,this.attributes={},this.relationships={},this.meta={},this.links={},this.casts={},this.merge(t)}getAttribute(t){const e=this.attributes[t],i=this.casts[t];return i&&null!=e?i!==String&&i!==Number&&i!==Boolean&&((r=i).prototype&&r.prototype.constructor.name)?new i(e):i(e):e;var r}getRelationship(t){const e=this.relationships[t].data;return Array.isArray(e),this.store.find(e)}identifier(){return{id:this.id,type:this.type}}merge(t){"type"in t&&(this.type=t.type),"id"in t&&(this.id=t.id),"attributes"in t&&(Object.assign(this.attributes,t.attributes),Object.keys(t.attributes).forEach((t=>{Object.getOwnPropertyDescriptor(Object.getPrototypeOf(this),t)||Object.getOwnPropertyDescriptor(this,t)||(this[t]=null,Object.defineProperty(this,t,{get:()=>this.getAttribute(t),configurable:!0,enumerable:!0}))}))),"relationships"in t&&Object.entries(t.relationships).forEach((([t,e])=>{this.relationships[t]=this.relationships[t]||{},Object.assign(this.relationships[t],e),Object.getOwnPropertyDescriptor(Object.getPrototypeOf(this),t)||Object.getOwnPropertyDescriptor(this,t)||(this[t]=null,Object.defineProperty(this,t,{get:()=>this.getRelationship(t),configurable:!0,enumerable:!0}))})),"links"in t&&(this.links=t.links),"meta"in t&&(this.meta=t.meta)}}function i(t){return encodeURIComponent(t).replace(/[!'()*]/g,(function(t){return"%"+t.charCodeAt(0).toString(16).toUpperCase()}))}return t.Model=e,t.Query=class{constructor(t={}){this.query=Object.assign({},t)}append(t,e){return"object"==typeof t?Object.entries(t).map((t=>this.append.apply(this,t))):this.query[t]=(this.query[t]?this.query[t]+",":"")+e,this}set(t,e){return"object"==typeof t?Object.entries(t).map((t=>this.set.apply(this,t))):this.query[t]=e,this}delete(t){return Array.isArray(t)?t.forEach((t=>this.delete(t))):delete this.query[t],this}toString(){return Object.entries(this.query).sort(((t,e)=>t[0].localeCompare(e[0]))).map((([t,e])=>i(t)+"="+i(e))).join("&")}},t.Store=class{constructor(t={}){this.models=t,this.graph={}}find(t,e){return null===t?null:Array.isArray(t)?t.map((t=>this.find(t))):"object"==typeof t?this.find(t.type,t.id):this.graph[t]&&this.graph[t][e]||null}findAll(t){return this.graph[t]?Object.keys(this.graph[t]).map((e=>this.graph[t][e])):[]}sync(t){const e=this.syncResource.bind(this);return"included"in t&&t.included.map(e),Array.isArray(t.data)?t.data.map(e):e(t.data)}syncResource(t){const{type:e,id:i}=t;return this.graph[e]=this.graph[e]||{},this.graph[e][i]?this.graph[e][i].merge(t):this.graph[e][i]=this.createModel(t),this.graph[e][i]}createModel(t){return new(this.models[t.type]||this.models["*"]||e)(t,this)}forget(t){delete this.graph[t.type][t.id]}reset(){this.graph={}}},Object.defineProperty(t,"__esModule",{value:!0}),t}({}); |
@@ -6,5 +6,5 @@ import { Store } from './store'; | ||
}; | ||
export declare class Model implements JsonApiResource { | ||
export declare class Model<Type extends string = any> implements JsonApiResource<Type> { | ||
protected store: Store; | ||
type: string; | ||
type: Type; | ||
id: string; | ||
@@ -17,3 +17,3 @@ attributes: KeyValueObject; | ||
[field: string]: any; | ||
constructor(data: JsonApiResource, store: Store); | ||
constructor(data: JsonApiResource<Type>, store: Store); | ||
getAttribute(name: string): any; | ||
@@ -24,7 +24,7 @@ getRelationship(name: string): any; | ||
*/ | ||
identifier(): JsonApiIdentifier; | ||
identifier(): JsonApiIdentifier<Type>; | ||
/** | ||
* Merge new JSON:API resource data into the model. | ||
*/ | ||
merge(data: JsonApiResource): void; | ||
merge(data: Partial<JsonApiResource<Type>>): void; | ||
} |
import { Model } from './model'; | ||
import { JsonApiDocument, JsonApiIdentifier, JsonApiResource } from './types'; | ||
interface Graph { | ||
declare type Graph = { | ||
[type: string]: { | ||
[id: string]: Model; | ||
[id: string]: any; | ||
}; | ||
} | ||
declare type ModelConstructor = { | ||
new (data: JsonApiResource, store?: Store): Model; | ||
}; | ||
interface ModelCollection { | ||
[type: string]: ModelConstructor; | ||
} | ||
export declare class Store { | ||
models: ModelCollection; | ||
export declare type ModelConstructor<Type extends string> = { | ||
new (data: JsonApiResource<Type>, store: Store): Model<Type>; | ||
}; | ||
export declare type ModelCollection<Models> = { | ||
[Type in keyof Models & string]: ModelConstructor<Type>; | ||
} & { | ||
'*'?: ModelConstructor<any>; | ||
}; | ||
export declare type ModelForType<Type extends string, Models extends ModelCollection<Models>> = Type extends keyof Models ? InstanceType<Models[Type]> : Model; | ||
export declare class Store<Models extends ModelCollection<Models> = any> { | ||
models: Models; | ||
protected graph: Graph; | ||
constructor(models?: ModelCollection); | ||
model(type: string, model: ModelConstructor): void; | ||
find(identifier: JsonApiIdentifier): Model; | ||
find(identifiers: JsonApiIdentifier[]): Model[]; | ||
find(type: string, id: string): Model; | ||
findAll(type: string): Model[]; | ||
sync(document: JsonApiDocument): Model | Model[]; | ||
syncResource(data: JsonApiIdentifier): Model; | ||
private createModel; | ||
constructor(models?: Models); | ||
find<Type extends string>(identifier: JsonApiIdentifier<Type>): ModelForType<Type, Models> | null; | ||
find<Type extends string>(identifiers: JsonApiIdentifier<Type>[]): ModelForType<Type, Models>[]; | ||
find<Type extends string>(type: Type, id: string): ModelForType<Type, Models> | null; | ||
findAll<Type extends string>(type: Type): ModelForType<Type, Models>[]; | ||
sync<Type extends string>(document: JsonApiDocument<Type>): ModelForType<Type, Models> | ModelForType<Type, Models>[] | null; | ||
syncResource<Type extends string>(data: JsonApiResource<Type>): ModelForType<Type, Models>; | ||
protected createModel<Type extends string>(data: JsonApiResource<Type>): ModelForType<Type, Models>; | ||
forget(data: JsonApiIdentifier): void; | ||
@@ -27,0 +29,0 @@ reset(): void; |
export declare type KeyValueObject = { | ||
[key: string]: any; | ||
}; | ||
export interface JsonApiDocument { | ||
data: JsonApiResource | JsonApiResource[]; | ||
export interface JsonApiDocument<Type extends string = string> { | ||
data: JsonApiResource<Type> | JsonApiResource<Type>[]; | ||
included?: JsonApiResource[]; | ||
@@ -10,7 +10,7 @@ meta?: KeyValueObject; | ||
} | ||
export interface JsonApiIdentifier { | ||
type: string; | ||
export interface JsonApiIdentifier<Type extends string = string> { | ||
type: Type; | ||
id: string; | ||
} | ||
export interface JsonApiResource extends JsonApiIdentifier { | ||
export interface JsonApiResource<Type extends string = string> extends JsonApiIdentifier<Type> { | ||
attributes?: KeyValueObject; | ||
@@ -24,6 +24,12 @@ relationships?: JsonApiRelationships; | ||
} | ||
export interface JsonApiRelationship { | ||
data?: JsonApiIdentifier | JsonApiIdentifier[]; | ||
export interface JsonApiRelationship<Type extends string = string> { | ||
data?: JsonApiIdentifier<Type> | JsonApiIdentifier<Type>[] | null; | ||
meta?: KeyValueObject; | ||
links?: KeyValueObject; | ||
} | ||
export interface JsonApiRelationshipToOne<Type extends string = string> extends JsonApiRelationship<Type> { | ||
data?: JsonApiIdentifier<Type> | null; | ||
} | ||
export interface JsonApiRelationshipToMany<Type extends string = string> extends JsonApiRelationship<Type> { | ||
data?: JsonApiIdentifier<Type>[]; | ||
} |
{ | ||
"name": "json-api-models", | ||
"description": "A lightweight layer for working with JSON:API data.", | ||
"version": "0.1.0-beta.7", | ||
"version": "0.1.0-beta.8", | ||
"author": "Toby Zerner", | ||
@@ -33,3 +33,3 @@ "license": "MIT", | ||
"tslib": "^2.3.1", | ||
"typescript": "4.3.5" | ||
"typescript": "^4.6.3" | ||
}, | ||
@@ -36,0 +36,0 @@ "release-it": { |
@@ -112,5 +112,5 @@ # json-api-models | ||
class User extends Model { | ||
public name: string; | ||
public age: number; | ||
class User extends Model<'users'> { | ||
public declare name: string; | ||
public declare age: number; | ||
@@ -123,11 +123,8 @@ get firstName() { | ||
Register your custom models with the store either during construction, or with the `model` method: | ||
Register your custom models with the store during construction: | ||
```ts | ||
const models = new Store({ | ||
'users': User | ||
users: User, | ||
}); | ||
// or | ||
models.model('users', User); | ||
``` | ||
@@ -140,8 +137,8 @@ | ||
```ts | ||
class User extends Model { | ||
public name: string; | ||
public createdAt: Date; | ||
class User extends Model<'users'> { | ||
declare public name: string; | ||
declare public createdAt: Date; | ||
protected casts = { | ||
createdAt: Date | ||
createdAt: Date, | ||
}; | ||
@@ -168,3 +165,3 @@ } | ||
return fetch('http://example.org/api/' + url, options) | ||
.then(response => { | ||
.then(async response => { | ||
if (response.status === 204) { | ||
@@ -171,0 +168,0 @@ return { response }; |
export * from './types'; | ||
export { Store } from './store'; | ||
export { Model } from './model'; | ||
export { Query } from './query'; | ||
export * from './store'; | ||
export * from './model'; | ||
export * from './query'; |
import { Store } from './store'; | ||
import { | ||
JsonApiIdentifier, | ||
JsonApiRelationships, | ||
JsonApiResource, KeyValueObject | ||
} from './types'; | ||
import { JsonApiIdentifier, JsonApiRelationships, JsonApiResource, KeyValueObject } from './types'; | ||
@@ -16,4 +12,4 @@ export type CastAttributes = { | ||
export class Model implements JsonApiResource { | ||
public type: string; | ||
export class Model<Type extends string = any> implements JsonApiResource<Type> { | ||
public type: Type; | ||
public id: string; | ||
@@ -29,3 +25,3 @@ public attributes: KeyValueObject = {}; | ||
constructor(data: JsonApiResource, protected store: Store) { | ||
constructor(data: JsonApiResource<Type>, protected store: Store) { | ||
this.merge(data); | ||
@@ -50,3 +46,5 @@ } | ||
public getRelationship(name: string): any { | ||
return this.store.find(this.relationships[name].data as JsonApiIdentifier); | ||
const data = this.relationships[name].data; | ||
// https://github.com/microsoft/TypeScript/issues/14107 | ||
return Array.isArray(data) ? this.store.find(data) : this.store.find(data); | ||
} | ||
@@ -57,3 +55,3 @@ | ||
*/ | ||
identifier(): JsonApiIdentifier { | ||
identifier(): JsonApiIdentifier<Type> { | ||
return { | ||
@@ -68,5 +66,5 @@ id: this.id, | ||
*/ | ||
merge(data: JsonApiResource): void { | ||
merge(data: Partial<JsonApiResource<Type>>): void { | ||
if ('type' in data) { | ||
this.type = data.type; | ||
this.type = data.type as Type; | ||
} | ||
@@ -82,8 +80,14 @@ | ||
Object.keys(data.attributes).forEach(name => { | ||
if (! Object.getOwnPropertyDescriptor(Object.getPrototypeOf(this), name)) { | ||
Object.defineProperty(this, name, { | ||
configurable: true, | ||
get: () => this.getAttribute(name), | ||
}); | ||
} | ||
if ( | ||
Object.getOwnPropertyDescriptor(Object.getPrototypeOf(this), name) | ||
|| Object.getOwnPropertyDescriptor(this, name) | ||
) return; | ||
this[name] = null; | ||
Object.defineProperty(this, name, { | ||
get: () => this.getAttribute(name), | ||
configurable: true, | ||
enumerable: true, | ||
}); | ||
}); | ||
@@ -93,3 +97,3 @@ } | ||
if ('relationships' in data) { | ||
for (const [name, relationship] of Object.entries(data.relationships)) { | ||
Object.entries(data.relationships).forEach(([name, relationship]) => { | ||
this.relationships[name] = this.relationships[name] || {}; | ||
@@ -99,9 +103,15 @@ | ||
if (! Object.getOwnPropertyDescriptor(Object.getPrototypeOf(this), name)) { | ||
Object.defineProperty(this, name, { | ||
configurable: true, | ||
get: () => this.getRelationship(name), | ||
}); | ||
} | ||
} | ||
if ( | ||
Object.getOwnPropertyDescriptor(Object.getPrototypeOf(this), name) | ||
|| Object.getOwnPropertyDescriptor(this, name) | ||
) return; | ||
this[name] = null; | ||
Object.defineProperty(this, name, { | ||
get: () => this.getRelationship(name), | ||
configurable: true, | ||
enumerable: true, | ||
}); | ||
}); | ||
} | ||
@@ -108,0 +118,0 @@ |
import { Model } from './model'; | ||
import { | ||
JsonApiDocument, | ||
JsonApiIdentifier, | ||
JsonApiResource, | ||
} from './types'; | ||
import { JsonApiDocument, JsonApiIdentifier, JsonApiResource } from './types'; | ||
interface Graph { | ||
type Graph = { | ||
[type: string]: { | ||
[id: string]: Model; | ||
[id: string]: any; | ||
}; | ||
} | ||
}; | ||
type ModelConstructor = { new(data: JsonApiResource, store?: Store): Model }; | ||
export type ModelConstructor<Type extends string> = { | ||
new(data: JsonApiResource<Type>, store: Store): Model<Type>; | ||
}; | ||
interface ModelCollection { | ||
[type: string]: ModelConstructor; | ||
} | ||
export type ModelCollection<Models> = { [Type in keyof Models & string]: ModelConstructor<Type> } & { '*'?: ModelConstructor<any> }; | ||
export class Store { | ||
export type ModelForType<Type extends string, Models extends ModelCollection<Models>> = Type extends keyof Models ? InstanceType<Models[Type]> : Model; | ||
export class Store<Models extends ModelCollection<Models> = any> { | ||
protected graph: Graph = {}; | ||
public constructor(public models: ModelCollection = {}) {} | ||
public constructor(public models: Models = {} as Models) {} | ||
public model(type: string, model: ModelConstructor): void { | ||
this.models[type] = model; | ||
} | ||
public find(identifier: JsonApiIdentifier): Model; | ||
public find(identifiers: JsonApiIdentifier[]): Model[]; | ||
public find(type: string, id: string): Model; | ||
public find(a: JsonApiIdentifier | JsonApiIdentifier[] | string, b?: string) { | ||
public find<Type extends string>(identifier: JsonApiIdentifier<Type>): ModelForType<Type, Models> | null; | ||
public find<Type extends string>(identifiers: JsonApiIdentifier<Type>[]): ModelForType<Type, Models>[]; | ||
public find<Type extends string>(type: Type, id: string): ModelForType<Type, Models> | null; | ||
public find<Type extends string>(a: JsonApiIdentifier<Type> | JsonApiIdentifier<Type>[] | string, b?: string) { | ||
if (a === null) { | ||
@@ -38,3 +32,3 @@ return null; | ||
if (Array.isArray(a)) { | ||
return a.map((identifier: JsonApiIdentifier) => this.find(identifier)); | ||
return a.map((identifier: JsonApiIdentifier<Type>) => this.find(identifier)); | ||
} | ||
@@ -49,3 +43,3 @@ | ||
public findAll(type: string): Model[] { | ||
public findAll<Type extends string>(type: Type): ModelForType<Type, Models>[] { | ||
if (! this.graph[type]) { | ||
@@ -59,3 +53,3 @@ return []; | ||
public sync(document: JsonApiDocument): Model | Model[] { | ||
public sync<Type extends string>(document: JsonApiDocument<Type>): ModelForType<Type, Models> | ModelForType<Type, Models>[] | null { | ||
const syncResource = this.syncResource.bind(this); | ||
@@ -67,6 +61,8 @@ | ||
return Array.isArray(document.data) ? document.data.map(syncResource) : syncResource(document.data); | ||
return Array.isArray(document.data) | ||
? document.data.map(syncResource) | ||
: syncResource(document.data); | ||
} | ||
public syncResource(data: JsonApiIdentifier): Model { | ||
public syncResource<Type extends string>(data: JsonApiResource<Type>): ModelForType<Type, Models> { | ||
const { type, id } = data; | ||
@@ -85,6 +81,6 @@ | ||
private createModel(data: JsonApiIdentifier): Model { | ||
const ModelClass = this.models[data.type] || this.models['*'] || Model; | ||
protected createModel<Type extends string>(data: JsonApiResource<Type>): ModelForType<Type, Models> { | ||
const ModelClass = this.models[data.type as keyof Models] || this.models['*'] || Model; | ||
return new ModelClass(data, this); | ||
return new ModelClass(data, this) as ModelForType<Type, Models>; | ||
} | ||
@@ -91,0 +87,0 @@ |
@@ -5,4 +5,4 @@ export type KeyValueObject = { | ||
export interface JsonApiDocument { | ||
data: JsonApiResource | JsonApiResource[]; | ||
export interface JsonApiDocument<Type extends string = string> { | ||
data: JsonApiResource<Type> | JsonApiResource<Type>[]; | ||
included?: JsonApiResource[]; | ||
@@ -13,8 +13,8 @@ meta?: KeyValueObject; | ||
export interface JsonApiIdentifier { | ||
type: string; | ||
export interface JsonApiIdentifier<Type extends string = string> { | ||
type: Type; | ||
id: string; | ||
} | ||
export interface JsonApiResource extends JsonApiIdentifier { | ||
export interface JsonApiResource<Type extends string = string> extends JsonApiIdentifier<Type> { | ||
attributes?: KeyValueObject; | ||
@@ -30,6 +30,14 @@ relationships?: JsonApiRelationships; | ||
export interface JsonApiRelationship { | ||
data?: JsonApiIdentifier | JsonApiIdentifier[]; | ||
export interface JsonApiRelationship<Type extends string = string> { | ||
data?: JsonApiIdentifier<Type> | JsonApiIdentifier<Type>[] | null; | ||
meta?: KeyValueObject; | ||
links?: KeyValueObject; | ||
} | ||
export interface JsonApiRelationshipToOne<Type extends string = string> extends JsonApiRelationship<Type> { | ||
data?: JsonApiIdentifier<Type> | null; | ||
} | ||
export interface JsonApiRelationshipToMany<Type extends string = string> extends JsonApiRelationship<Type> { | ||
data?: JsonApiIdentifier<Type>[]; | ||
} |
30707
15.78%16
6.67%384
4.63%224
-1.32%