Comparing version 3.0.0-beta.8 to 3.0.0-beta.10
@@ -0,1 +1,18 @@ | ||
##### 3.0.0-beta.10 - 02 July 2016 | ||
Forgot to bump version in package.json for previous release | ||
##### 3.0.0-beta.9 - 02 July 2016 | ||
###### Breaking changes | ||
- Usaved records can now be added to a Collection (added to the store) | ||
- Record#save now always updates the original record | ||
###### Backwards compatible changes | ||
- Added `Collection#unsaved`, `LinkedCollection#unsaved`, `DataStore#unsaved` | ||
- Added `Collection#prune`, `LinkedCollection#prune`, `DataStore#prune` | ||
- Added `Record#isNew` and `Record#changeHistory` | ||
- Added option `DataStore#usePendingFind`, which defaults to `true` | ||
- Added option `DataStore#usePendingFindAll`, which defaults to `true` | ||
##### 3.0.0-beta.8 - 22 June 2016 | ||
@@ -2,0 +19,0 @@ |
@@ -88,2 +88,3 @@ export const version: { | ||
beforeLoadRelations(): void | ||
changeHistory(): any[] | ||
changes(opts?: any): Diff | ||
@@ -93,2 +94,3 @@ commit(): this | ||
destroy(opts?: any): Promise<any> | ||
isNew(): boolean | ||
loadRelations(relations: any, opts?: any): Promise<this> | ||
@@ -195,9 +197,11 @@ 'get'(key: string): any | ||
mapCall(funcName: string, ...args: any[]): any[] | ||
prune(): any[] | ||
query(): Query | ||
recordId(record?: any): any | ||
query(): Query | ||
reduce(cb: Function, initialValue: any): any | ||
remove(id: string|number, opts?: any): any | ||
removeAll(query: any, opts?: any): void|any[] | ||
removeAll(query: any, opts?: any): any[] | ||
skip(num: number): any[] | ||
toJSON(opts?: any): any[] | ||
unsaved(): any[] | ||
updateIndex(record: any, opts?: any): void | ||
@@ -231,3 +235,4 @@ updateIndexes(record: any): void | ||
_completedQueries: Object | ||
linkRelations: boolean | ||
usePendingFind: boolean | ||
usePendingFindAll: boolean | ||
as(name: string): Mapper|LinkedCollection | ||
@@ -234,0 +239,0 @@ constructor(opts?: any) |
export function sort (a, b, hashCode) { | ||
// Short-curcuit comparison if a and b are strictly equal | ||
// Short-circuit comparison if a and b are strictly equal | ||
// This is absolutely necessary for indexed objects that | ||
@@ -12,11 +12,11 @@ // don't have the idAttribute field | ||
} | ||
if (a === null && b === null) { | ||
return 0 | ||
if ((a === null && b === null) || (a === undefined && b === undefined)) { | ||
return -1 | ||
} | ||
if (a === null) { | ||
if (a === null || a === undefined) { | ||
return -1 | ||
} | ||
if (b === null) { | ||
if (b === null || b === undefined) { | ||
return 1 | ||
@@ -23,0 +23,0 @@ } |
@@ -46,3 +46,3 @@ // Copyright (c) 2015, InternalFX. | ||
let key = keyList.shift() || null | ||
let key = keyList.shift() || undefined | ||
let pos = binarySearch(this.keys, key) | ||
@@ -77,3 +77,3 @@ | ||
let key = keyList.shift() || null | ||
let key = keyList.shift() || undefined | ||
let pos = binarySearch(this.keys, key) | ||
@@ -86,3 +86,3 @@ | ||
} else { | ||
return this.values[pos.index] | ||
return this.values[pos.index].slice() | ||
} | ||
@@ -255,5 +255,5 @@ } else { | ||
if (utils.isFunction(field)) { | ||
return field(data) || null | ||
return field(data) || undefined | ||
} else { | ||
return data[field] || null | ||
return data[field] || undefined | ||
} | ||
@@ -266,2 +266,3 @@ }) | ||
let removed | ||
const isUnique = this.hashCode(data) !== undefined | ||
this.values.forEach((value, i) => { | ||
@@ -278,3 +279,16 @@ if (value.isIndex) { | ||
} else { | ||
const dataLocation = binarySearch(value, data, this.hashCode) | ||
let dataLocation = {} | ||
if (this.keys[i] === undefined || !isUnique) { | ||
for (let j = value.length - 1; j >= 0; j--) { | ||
if (value[j] === data) { | ||
dataLocation = { | ||
found: true, | ||
index: j | ||
} | ||
break | ||
} | ||
} | ||
} else if (isUnique) { | ||
dataLocation = binarySearch(value, data, this.hashCode) | ||
} | ||
if (dataLocation.found) { | ||
@@ -295,5 +309,7 @@ removeAt(value, dataLocation.index) | ||
updateRecord (data) { | ||
this.removeRecord(data) | ||
this.insertRecord(data) | ||
const removed = this.removeRecord(data) | ||
if (removed !== undefined) { | ||
this.insertRecord(data) | ||
} | ||
} | ||
}) |
{ | ||
"name": "js-data", | ||
"description": "Robust, framework-agnostic in-memory data store.", | ||
"version": "3.0.0-beta.8", | ||
"version": "3.0.0-beta.10", | ||
"homepage": "http://www.js-data.io", | ||
@@ -74,14 +74,14 @@ "repository": { | ||
"babel-plugin-transform-regenerator": "6.9.0", | ||
"js-data-repo-tools": "0.5.4", | ||
"karma": "0.13.22", | ||
"js-data-repo-tools": "0.5.5", | ||
"karma": "1.1.0", | ||
"karma-babel-preprocessor": "6.0.1", | ||
"karma-chai": "0.1.0", | ||
"karma-chrome-launcher": "1.0.1", | ||
"karma-mocha": "1.0.1", | ||
"karma-phantomjs-launcher": "1.0.0", | ||
"karma-mocha": "1.1.1", | ||
"karma-phantomjs-launcher": "1.0.1", | ||
"karma-sauce-launcher": "1.0.0", | ||
"karma-sinon": "1.0.5", | ||
"phantomjs-prebuilt": "2.1.7", | ||
"uglify-js": "2.6.2" | ||
"uglify-js": "2.6.4" | ||
} | ||
} |
@@ -233,7 +233,4 @@ import utils from './utils' | ||
let id = this.recordId(record) | ||
if (!utils.isSorN(id)) { | ||
throw utils.err(`${DOMAIN}#add`, `record.${idAttribute}`)(400, 'string or number', id) | ||
} | ||
// Grab existing record if there is one | ||
const existing = this.get(id) | ||
const existing = id === undefined ? id : this.get(id) | ||
// If the currently visited record is just a reference to an existing | ||
@@ -253,4 +250,4 @@ // record, then there is nothing to be done. Exit early. | ||
utils.forOwn(existing, (value, key) => { | ||
if (key !== idAttribute && !record.hasOwnProperty(key)) { | ||
delete existing[key] | ||
if (key !== idAttribute && record[key] === undefined) { | ||
existing[key] = undefined | ||
} | ||
@@ -597,17 +594,11 @@ }) | ||
/** | ||
* Return the primary key of the given, or if no record is provided, return the | ||
* name of the field that holds the primary key of records in this Collection. | ||
* Return all "unsaved" (not uniquely identifiable) records in this colleciton. | ||
* | ||
* @method Collection#recordId | ||
* @method Collection#prune | ||
* @param {Object} [opts] Configuration options, passed to {@link Collection#removeAll}. | ||
* @since 3.0.0 | ||
* @param {(Object|Record)} [record] The record whose primary key is to be | ||
* returned. | ||
* @returns {(string|number)} Primary key or name of field that holds primary | ||
* key. | ||
* @returns {Array} The removed records, if any. | ||
*/ | ||
recordId (record) { | ||
if (record) { | ||
return utils.get(record, this.recordId()) | ||
} | ||
return this.mapper ? this.mapper.idAttribute : this.idAttribute | ||
prune (opts) { | ||
return this.removeAll(this.unsaved(), opts) | ||
}, | ||
@@ -637,2 +628,20 @@ | ||
/** | ||
* Return the primary key of the given, or if no record is provided, return the | ||
* name of the field that holds the primary key of records in this Collection. | ||
* | ||
* @method Collection#recordId | ||
* @since 3.0.0 | ||
* @param {(Object|Record)} [record] The record whose primary key is to be | ||
* returned. | ||
* @returns {(string|number)} Primary key or name of field that holds primary | ||
* key. | ||
*/ | ||
recordId (record) { | ||
if (record) { | ||
return utils.get(record, this.recordId()) | ||
} | ||
return this.mapper ? this.mapper.idAttribute : this.idAttribute | ||
}, | ||
/** | ||
* Reduce the data in the collection to a single value and return the result. | ||
@@ -661,46 +670,50 @@ * | ||
* @since 3.0.0 | ||
* @param {(string|number)} id The primary key of the record to be removed. | ||
* @param {(string|number|object|Record)} idOrRecord The primary key of the | ||
* record to be removed, or a reference to the record that is to be removed. | ||
* @param {Object} [opts] Configuration options. | ||
* @returns {Object|Record} The removed record, if any. | ||
*/ | ||
remove (id, opts) { | ||
remove (idOrRecord, opts) { | ||
// Default values for arguments | ||
opts || (opts = {}) | ||
this.beforeRemove(id, opts) | ||
const record = this.get(id) | ||
this.beforeRemove(idOrRecord, opts) | ||
let record = utils.isSorN(idOrRecord) ? this.get(idOrRecord) : idOrRecord | ||
// The record is in the collection, remove it | ||
if (record) { | ||
this.index.removeRecord(record) | ||
utils.forOwn(this.indexes, function (index, name) { | ||
index.removeRecord(record) | ||
}) | ||
if (record && utils.isFunction(record.off)) { | ||
record.off('all', this._onRecordEvent, this) | ||
if (!opts.silent) { | ||
this.emit('remove', record) | ||
if (utils.isObject(record)) { | ||
record = this.index.removeRecord(record) | ||
if (record) { | ||
utils.forOwn(this.indexes, function (index, name) { | ||
index.removeRecord(record) | ||
}) | ||
if (utils.isFunction(record.off)) { | ||
record.off('all', this._onRecordEvent, this) | ||
if (!opts.silent) { | ||
this.emit('remove', record) | ||
} | ||
} | ||
} | ||
} | ||
return this.afterRemove(id, opts, record) || record | ||
return this.afterRemove(idOrRecord, opts, record) || record | ||
}, | ||
/** | ||
* Remove the record selected by "query" from this collection. | ||
* Remove from this collection the given records or the records selected by | ||
* the given "query". | ||
* | ||
* @method Collection#removeAll | ||
* @since 3.0.0 | ||
* @param {Object} [query={}] Selection query. See {@link query}. | ||
* @param {Object} [query.where] See {@link query.where}. | ||
* @param {number} [query.offset] See {@link query.offset}. | ||
* @param {number} [query.limit] See {@link query.limit}. | ||
* @param {string|Array[]} [query.orderBy] See {@link query.orderBy}. | ||
* @param {Object|Object[]|Record[]} [queryOrRecords={}] Records to be removed or selection query. See {@link query}. | ||
* @param {Object} [queryOrRecords.where] See {@link query.where}. | ||
* @param {number} [queryOrRecords.offset] See {@link query.offset}. | ||
* @param {number} [queryOrRecords.limit] See {@link query.limit}. | ||
* @param {string|Array[]} [queryOrRecords.orderBy] See {@link query.orderBy}. | ||
* @param {Object} [opts] Configuration options. | ||
* @returns {(Object[]|Record[])} The removed records, if any. | ||
*/ | ||
removeAll (query, opts) { | ||
removeAll (queryOrRecords, opts) { | ||
// Default values for arguments | ||
opts || (opts = {}) | ||
this.beforeRemoveAll(query, opts) | ||
const records = this.filter(query) | ||
this.beforeRemoveAll(queryOrRecords, opts) | ||
let records = utils.isArray(queryOrRecords) ? queryOrRecords.slice() : this.filter(queryOrRecords) | ||
@@ -710,9 +723,9 @@ // Remove each selected record from the collection | ||
optsCopy.silent = true | ||
records.forEach((item) => { | ||
this.remove(this.recordId(item), optsCopy) | ||
}) | ||
records = records | ||
.map((record) => this.remove(record, optsCopy)) | ||
.filter((record) => record) | ||
if (!opts.silent) { | ||
this.emit('remove', records) | ||
} | ||
return this.afterRemoveAll(query, opts, records) || records | ||
return this.afterRemoveAll(queryOrRecords, opts, records) || records | ||
}, | ||
@@ -753,2 +766,13 @@ | ||
/** | ||
* Return all "unsaved" (not uniquely identifiable) records in this colleciton. | ||
* | ||
* @method Collection#unsaved | ||
* @since 3.0.0 | ||
* @returns {Array} The unsaved records, if any. | ||
*/ | ||
unsaved (opts) { | ||
return this.index.get() | ||
}, | ||
/** | ||
* Update a record's position in a single index of this collection. See | ||
@@ -755,0 +779,0 @@ * {@link Collection#updateIndexes} to update a record's position in all |
@@ -210,3 +210,3 @@ import utils from './utils' | ||
* const collection = new Collection() | ||
* collection.on('foo', (msg) => { | ||
* collection.on('foo', function (msg) { | ||
* console.log(msg) | ||
@@ -217,3 +217,3 @@ * }) | ||
* const store = new DataStore() | ||
* store.on('beep', (msg) => { | ||
* store.on('beep', function (msg) { | ||
* console.log(msg) | ||
@@ -220,0 +220,0 @@ * }) |
@@ -46,4 +46,20 @@ import utils from './utils' | ||
_addMeta (record, timestamp) { | ||
// Track when this record was added | ||
this._added[this.recordId(record)] = timestamp | ||
if (utils.isFunction(record._set)) { | ||
record._set('$', timestamp) | ||
} | ||
}, | ||
_clearMeta (record) { | ||
delete this._added[this.recordId(record)] | ||
if (utils.isFunction(record._set)) { | ||
record._set('$') // unset | ||
} | ||
}, | ||
_onRecordEvent (...args) { | ||
utils.getSuper(this).prototype._onRecordEvent.apply(this, args) | ||
Collection.prototype._onRecordEvent.apply(this, args) | ||
const event = args[0] | ||
@@ -65,3 +81,3 @@ // This is a very brute force method | ||
} | ||
records = utils.getSuper(this).prototype.add.call(this, records, opts) | ||
records = Collection.prototype.add.call(this, records, opts) | ||
@@ -72,3 +88,3 @@ if (mapper.relationList.length && records.length) { | ||
mapper.relationList.forEach(function (def) { | ||
def.linkRecords(mapper, records) | ||
def.addLinkedRecords(records) | ||
}) | ||
@@ -82,7 +98,17 @@ } | ||
remove (id, opts) { | ||
const record = utils.getSuper(this).prototype.remove.call(this, id, opts) | ||
remove (idOrRecord, opts) { | ||
const mapper = this.mapper | ||
const record = Collection.prototype.remove.call(this, idOrRecord, opts) | ||
if (record) { | ||
this._clearMeta(record) | ||
} | ||
if (mapper.relationList.length && record) { | ||
// Check the currently visited record for relations that need to be | ||
// inserted into their respective collections. | ||
mapper.relationList.forEach(function (def) { | ||
def.removeLinkedRecords(mapper, [record]) | ||
}) | ||
} | ||
return record | ||
@@ -92,21 +118,15 @@ }, | ||
removeAll (query, opts) { | ||
const records = utils.getSuper(this).prototype.removeAll.call(this, query, opts) | ||
const mapper = this.mapper | ||
const records = Collection.prototype.removeAll.call(this, query, opts) | ||
records.forEach(this._clearMeta, this) | ||
return records | ||
}, | ||
_clearMeta (record) { | ||
delete this._added[this.recordId(record)] | ||
if (this.mapper.recordClass) { | ||
record._set('$') // unset | ||
if (mapper.relationList.length && records.length) { | ||
// Check the currently visited record for relations that need to be | ||
// inserted into their respective collections. | ||
mapper.relationList.forEach(function (def) { | ||
def.removeLinkedRecords(mapper, records) | ||
}) | ||
} | ||
}, | ||
_addMeta (record, timestamp) { | ||
// Track when this record was added | ||
this._added[this.recordId(record)] = timestamp | ||
if (this.mapper.recordClass) { | ||
record._set('$', timestamp) | ||
} | ||
return records | ||
} | ||
@@ -113,0 +133,0 @@ }) |
@@ -764,3 +764,4 @@ import utils from './utils' | ||
* | ||
* @example Return the age of all users | ||
* @example | ||
* // Return the age of all users | ||
* const store = new JSData.DataStore() | ||
@@ -767,0 +768,0 @@ * store.defineMapper('user') |
@@ -126,2 +126,8 @@ import utils from './utils' | ||
} | ||
// Set the idAttribute value first, if it exists. | ||
const mapper = this.constructor.mapper | ||
const id = mapper ? utils.get(props, mapper.idAttribute) : undefined | ||
if (id !== undefined) { | ||
utils.set(this, mapper.idAttribute, id) | ||
} | ||
utils.fillIn(this, props) | ||
@@ -172,2 +178,13 @@ _set('creating', false) | ||
/** | ||
* Return the change history of this record since it was instantiated or | ||
* {@link Record#commit} was called. | ||
* | ||
* @method Record#changeHistory | ||
* @since 3.0.0 | ||
*/ | ||
changeHistory () { | ||
return (this._get('history') || []).slice() | ||
}, | ||
/** | ||
* Return changes to this record since it was instantiated or | ||
@@ -226,2 +243,3 @@ * {@link Record#commit} was called. | ||
this._set('changed') // unset | ||
this._set('history', []) // clear history | ||
this._set('previous', utils.plainCopy(this)) | ||
@@ -314,2 +332,28 @@ }, | ||
/** | ||
* Return whether the record is unsaved. Records that have primary keys are | ||
* considered "saved". Records without primary keys are considered "unsaved". | ||
* | ||
* @example <caption>Record#isNew</caption> | ||
* // Normally you would do: import {Container} from 'js-data' | ||
* const JSData = require('js-data@3.0.0-beta.7') | ||
* const {Container} = JSData | ||
* console.log('Using JSData v' + JSData.version.full) | ||
* const store = new Container() | ||
* store.defineMapper('user') | ||
* const user = store.createRecord('user', { | ||
* id: 1234 | ||
* }) | ||
* const user2 = store.createRecord('user') | ||
* console.log('user isNew: ' + user.isNew()) // false | ||
* console.log('user2 isNew: ' + user2.isNew()) // true | ||
* | ||
* @method Record#isNew | ||
* @returns {boolean} Whether the record is unsaved. | ||
* @since 3.0.0 | ||
*/ | ||
isNew (opts) { | ||
return utils.get(this, this._mapper().idAttribute) === undefined | ||
}, | ||
/** | ||
* Return whether the record in its current state passes validation. | ||
@@ -587,5 +631,15 @@ * | ||
let props = this | ||
if (utils.isUndefined(id)) { | ||
return superMethod(mapper, 'create')(props, opts) | ||
const postProcess = (result) => { | ||
const record = opts.raw ? result.data : result | ||
if (record) { | ||
utils.deepMixIn(this, record) | ||
this.commit() | ||
} | ||
return result | ||
} | ||
if (id === undefined) { | ||
return superMethod(mapper, 'create')(props, opts).then(postProcess) | ||
} | ||
if (opts.changesOnly) { | ||
@@ -597,10 +651,3 @@ const changes = this.changes(opts) | ||
} | ||
return superMethod(mapper, 'update')(id, props, opts).then((result) => { | ||
const record = opts.raw ? result.data : result | ||
if (record) { | ||
utils.deepMixIn(this, record) | ||
this.commit() | ||
} | ||
return result | ||
}) | ||
return superMethod(mapper, 'update')(id, props, opts).then(postProcess) | ||
}, | ||
@@ -607,0 +654,0 @@ |
@@ -28,3 +28,3 @@ import utils from './utils' | ||
get canAutoAddLinks () { | ||
return utils.isUndefined(this.add) || !!this.add | ||
return this.add === undefined || !!this.add | ||
}, | ||
@@ -72,3 +72,3 @@ | ||
canFindLinkFor () { | ||
return Boolean(this.foreignKey || this.localKey) | ||
return !!(this.foreignKey || this.localKey) | ||
}, | ||
@@ -92,11 +92,11 @@ | ||
_setForeignKey (record, relatedRecord) { | ||
_setForeignKey (record, relatedRecords) { | ||
const idAttribute = this.mapper.idAttribute | ||
if (!utils.isArray(relatedRecord)) { | ||
relatedRecord = [relatedRecord] | ||
if (!utils.isArray(relatedRecords)) { | ||
relatedRecords = [relatedRecords] | ||
} | ||
relatedRecord.forEach((relatedRecordItem) => { | ||
utils.set(relatedRecordItem, this.foreignKey, utils.get(record, idAttribute)) | ||
relatedRecords.forEach((relatedRecord) => { | ||
utils.set(relatedRecord, this.foreignKey, utils.get(record, idAttribute)) | ||
}) | ||
@@ -109,4 +109,4 @@ }, | ||
setLocalField (record, data) { | ||
return utils.set(record, this.localField, data) | ||
setLocalField (record, relatedData) { | ||
return utils.set(record, this.localField, relatedData) | ||
}, | ||
@@ -135,3 +135,3 @@ | ||
linkRecords (relatedMapper, records) { | ||
addLinkedRecords (records) { | ||
const datastore = this.mapper.datastore | ||
@@ -151,3 +151,3 @@ | ||
if (isEmptyLinks && this.canFindLinkFor(record)) { | ||
relatedData = this.findExistingLinksFor(relatedMapper, record) | ||
relatedData = this.findExistingLinksFor(record) | ||
} | ||
@@ -161,10 +161,35 @@ | ||
removeLinkedRecords (relatedMapper, records) { | ||
const localField = this.localField | ||
records.forEach((record) => { | ||
const relatedData = utils.get(record, localField) | ||
this.unlinkInverseRecords(relatedData) | ||
utils.set(record, localField, undefined) | ||
}) | ||
}, | ||
unlinkInverseRecords (record) { | ||
if (!record) { | ||
return | ||
} | ||
utils.set(record, this.getInverse(this.mapper).localField, undefined) | ||
}, | ||
linkRecord (record, relatedRecord) { | ||
const relatedId = utils.get(relatedRecord, this.mapper.idAttribute) | ||
if (relatedRecord !== this.relatedCollection.get(relatedId)) { | ||
this.setForeignKey(record, relatedRecord) | ||
if (relatedId === undefined) { | ||
const unsaved = this.relatedCollection.unsaved() | ||
if (unsaved.indexOf(relatedRecord) === -1) { | ||
if (this.canAutoAddLinks) { | ||
relatedRecord = this.relatedCollection.add(relatedRecord) | ||
} | ||
} | ||
} else { | ||
if (relatedRecord !== this.relatedCollection.get(relatedId)) { | ||
this.setForeignKey(record, relatedRecord) | ||
if (this.canAutoAddLinks) { | ||
relatedRecord = this.relatedCollection.add(relatedRecord) | ||
if (this.canAutoAddLinks) { | ||
relatedRecord = this.relatedCollection.add(relatedRecord) | ||
} | ||
} | ||
@@ -176,7 +201,8 @@ } | ||
findExistingLinksByForeignKey (foreignId) { | ||
// e.g. user hasMany post via "foreignKey", so find all posts of user | ||
findExistingLinksByForeignKey (id) { | ||
return this.relatedCollection.filter({ | ||
[this.foreignKey]: foreignId | ||
[this.foreignKey]: id | ||
}) | ||
} | ||
}) |
@@ -13,6 +13,9 @@ import utils from '../utils' | ||
findExistingLinksFor (relatedMapper, record) { | ||
findExistingLinksFor (record) { | ||
// console.log('\tBelongsTo#findExistingLinksFor', record) | ||
if (!record) { | ||
return | ||
} | ||
const relatedId = utils.get(record, this.foreignKey) | ||
if (!utils.isUndefined(relatedId)) { | ||
if (relatedId !== undefined) { | ||
return this.relatedCollection.get(relatedId) | ||
@@ -19,0 +22,0 @@ } |
@@ -17,4 +17,13 @@ import utils from '../utils' | ||
const hasForeignKeys = this.foreignKey || this.foreignKeys | ||
return !!(hasForeignKeys || (this.localKeys && utils.get(record, this.localKeys))) | ||
}, | ||
return Boolean(hasForeignKeys || this.localKeys && utils.get(record, this.localKeys)) | ||
unlinkInverseRecords (records) { | ||
if (!records) { | ||
return | ||
} | ||
const localField = this.getInverse(this.mapper).localField | ||
records.forEach(function (record) { | ||
utils.set(record, localField, undefined) | ||
}) | ||
}, | ||
@@ -24,32 +33,34 @@ | ||
const relatedCollection = this.relatedCollection | ||
const canAutoAddLinks = this.canAutoAddLinks | ||
const foreignKey = this.foreignKey | ||
const unsaved = this.relatedCollection.unsaved() | ||
return relatedRecords.map((toInsertItem) => { | ||
const relatedId = relatedCollection.recordId(toInsertItem) | ||
return relatedRecords.map((relatedRecord) => { | ||
const relatedId = relatedCollection.recordId(relatedRecord) | ||
if (toInsertItem !== relatedCollection.get(relatedId)) { | ||
if (this.foreignKey) { | ||
if ((relatedId === undefined && unsaved.indexOf(relatedRecord) === -1) || relatedRecord !== relatedCollection.get(relatedId)) { | ||
if (foreignKey) { | ||
// TODO: slow, could be optimized? But user loses hook | ||
this.setForeignKey(record, toInsertItem) | ||
this.setForeignKey(record, relatedRecord) | ||
} | ||
if (this.canAutoAddLinks) { | ||
toInsertItem = relatedCollection.add(toInsertItem) | ||
if (canAutoAddLinks) { | ||
relatedRecord = relatedCollection.add(relatedRecord) | ||
} | ||
} | ||
return toInsertItem | ||
return relatedRecord | ||
}) | ||
}, | ||
findExistingLinksFor (relatedMapper, record) { | ||
const recordId = utils.get(record, relatedMapper.idAttribute) | ||
const localKeysValue = this.localKeys ? utils.get(record, this.localKeys) : null | ||
findExistingLinksFor (record) { | ||
const id = utils.get(record, this.mapper.idAttribute) | ||
const ids = this.localKeys ? utils.get(record, this.localKeys) : null | ||
let records | ||
if (this.foreignKey) { | ||
records = this.findExistingLinksByForeignKey(recordId) | ||
} else if (this.localKeys && localKeysValue) { | ||
records = this.findExistingLinksByLocalKeys(localKeysValue) | ||
} else if (this.foreignKeys) { | ||
records = this.findExistingLinksByForeignKeys(recordId) | ||
if (id !== undefined && this.foreignKey) { | ||
records = this.findExistingLinksByForeignKey(id) | ||
} else if (this.localKeys && ids) { | ||
records = this.findExistingLinksByLocalKeys(ids) | ||
} else if (id !== undefined && this.foreignKeys) { | ||
records = this.findExistingLinksByForeignKeys(id) | ||
} | ||
@@ -62,7 +73,8 @@ | ||
findExistingLinksByLocalKeys (localKeysValue) { | ||
// e.g. user hasMany group via "foreignKeys", so find all users of a group | ||
findExistingLinksByLocalKeys (ids) { | ||
return this.relatedCollection.filter({ | ||
where: { | ||
[this.mapper.idAttribute]: { | ||
'in': localKeysValue | ||
'in': ids | ||
} | ||
@@ -73,7 +85,8 @@ } | ||
findExistingLinksByForeignKeys (foreignId) { | ||
// e.g. group hasMany user via "localKeys", so find all groups that own a user | ||
findExistingLinksByForeignKeys (id) { | ||
return this.relatedCollection.filter({ | ||
where: { | ||
[this.foreignKeys]: { | ||
'contains': foreignId | ||
'contains': id | ||
} | ||
@@ -80,0 +93,0 @@ } |
@@ -490,3 +490,3 @@ import utils from './utils' | ||
// functionally the same | ||
const additionalProperties = utils.isUndefined(schema.additionalProperties) ? true : schema.additionalProperties | ||
const additionalProperties = schema.additionalProperties === undefined ? true : schema.additionalProperties | ||
// "s": The property set of the instance to validate. | ||
@@ -508,3 +508,3 @@ const toValidate = {} | ||
utils.forOwn(properties || {}, function (_schema, prop) { | ||
if (utils.isUndefined(value[prop]) && !utils.isUndefined(_schema['default'])) { | ||
if (value[prop] === undefined && _schema['default'] !== undefined) { | ||
value[prop] = utils.copy(_schema['default']) | ||
@@ -561,3 +561,3 @@ } | ||
required.forEach(function (prop) { | ||
if (utils.isUndefined(utils.get(value, prop))) { | ||
if (utils.get(value, prop) === undefined) { | ||
const prevProp = opts.prop | ||
@@ -602,3 +602,3 @@ opts.prop = prop | ||
if (!validType) { | ||
return makeError(!utils.isUndefined(value) && value !== null ? typeof value : '' + value, `one of (${type.join(', ')})`, opts) | ||
return makeError(value !== undefined && value !== null ? typeof value : '' + value, `one of (${type.join(', ')})`, opts) | ||
} | ||
@@ -648,3 +648,3 @@ // Run keyword validators for matched type | ||
const validateKeyword = function (op, value, schema, opts) { | ||
return !utils.isUndefined(schema[op]) && validationKeywords[op](value, schema, opts) | ||
return schema[op] !== undefined && validationKeywords[op](value, schema, opts) | ||
} | ||
@@ -692,3 +692,3 @@ | ||
let prevProp = opts.prop | ||
if (utils.isUndefined(schema)) { | ||
if (schema === undefined) { | ||
return | ||
@@ -699,7 +699,7 @@ } | ||
} | ||
if (utils.isUndefined(opts.path)) { | ||
if (opts.path === undefined) { | ||
opts.path = [] | ||
} | ||
// Track our location as we recurse | ||
if (!utils.isUndefined(opts.prop)) { | ||
if (opts.prop !== undefined) { | ||
shouldPop = true | ||
@@ -719,3 +719,3 @@ opts.path.push(opts.prop) | ||
} | ||
if (utils.isUndefined(value)) { | ||
if (value === undefined) { | ||
// Check if property is required | ||
@@ -745,2 +745,4 @@ if (schema.required === true && !opts.existingOnly) { | ||
const changedPath = 'changed' | ||
// Object[] - History of change records | ||
const changeHistoryPath = 'history' | ||
// boolean - Whether a Record is currently being instantiated | ||
@@ -770,3 +772,3 @@ const creatingPath = 'creating' | ||
// enumerability, they won't be "own" properties of individual records | ||
enumerable: utils.isUndefined(schema.enumerable) ? true : !!schema.enumerable | ||
enumerable: schema.enumerable === undefined ? true : !!schema.enumerable | ||
} | ||
@@ -863,3 +865,9 @@ // Cache a few strings for optimal performance | ||
} | ||
this.emit('change', this, this.changes()) | ||
const changes = this.changes() | ||
const changeRecord = utils.plainCopy(changes) | ||
changeRecord.timestamp = new Date().getTime() | ||
const changeHistory = _get(changeHistoryPath) || [] | ||
_set(changeHistoryPath, changeHistory) | ||
changeHistory.push(changeRecord) | ||
this.emit('change', this, changes) | ||
} | ||
@@ -1007,3 +1015,3 @@ _unset(silentPath) | ||
* | ||
* @example Schema#constructor</caption> | ||
* @example <caption>Schema#constructor</caption> | ||
* // Normally you would do: import {Schema} from 'js-data' | ||
@@ -1010,0 +1018,0 @@ * const JSData = require('js-data@3.0.0-beta.7') |
@@ -99,3 +99,3 @@ /** | ||
utils.forOwn(src, function (value, key) { | ||
if (key && utils.isUndefined(dest[key]) && !utils.isFunction(value) && key.indexOf('_') !== 0) { | ||
if (key && dest[key] === undefined && !utils.isFunction(value) && key.indexOf('_') !== 0) { | ||
dest[key] = value | ||
@@ -390,3 +390,3 @@ } | ||
/** | ||
* Recursively shallow copy own enumerable properties from `source` to `dest`. | ||
* Recursively shallow copy enumerable properties from `source` to `dest`. | ||
* | ||
@@ -409,3 +409,4 @@ * @example | ||
if (source) { | ||
utils.forOwn(source, function (value, key) { | ||
for (var key in source) { | ||
const value = source[key] | ||
const existing = dest[key] | ||
@@ -417,3 +418,3 @@ if (isPlainObject(value) && isPlainObject(existing)) { | ||
} | ||
}) | ||
} | ||
} | ||
@@ -472,3 +473,3 @@ return dest | ||
} | ||
if (utils.isUndefined(oldValue)) { | ||
if (oldValue === undefined) { | ||
diff.added[key] = newValue | ||
@@ -484,3 +485,3 @@ } else { | ||
const newValue = newObject[key] | ||
if (utils.isUndefined(newValue) && !utils.isUndefined(oldValue)) { | ||
if (newValue === undefined && oldValue !== undefined) { | ||
diff.removed[key] = undefined | ||
@@ -1217,3 +1218,5 @@ } | ||
dbg (...args) { | ||
this.log('debug', ...args) | ||
if (utils.isFunction(this.log)) { | ||
this.log('debug', ...args) | ||
} | ||
}, | ||
@@ -1220,0 +1223,0 @@ log (level, ...args) { |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
2748358
38548