ember-changeset
Version:
297 lines (277 loc) • 7.65 kB
JavaScript
import { assert } from '@ember/debug';
import { dependentKeyCompat } from '@ember/object/compat';
import { BufferedChangeset } from 'validated-changeset';
export { Changeset as ValidatedChangeset } from './validated-changeset.js';
import ArrayProxy from '@ember/array/proxy';
import ObjectProxy from '@ember/object/proxy';
import { get, set, notifyPropertyChange } from '@ember/object';
import mergeDeep from './utils/merge-deep.js';
import isObject from './utils/is-object.js';
import { tracked } from '@glimmer/tracking';
import { macroCondition, dependencySatisfies, importSync } from '@embroider/macros';
import { g, i, n } from 'decorator-transforms/runtime';
export { default as changesetGet } from './helpers/changeset-get.js';
export { default as changesetSet } from './helpers/changeset-set.js';
const CHANGES = '_changes';
const PREVIOUS_CONTENT = '_previousContent';
const CONTENT = '_content';
const defaultValidatorFn = () => true;
function buildOldValues(content, changes, getDeep) {
const obj = Object.create(null);
for (let change of changes) {
obj[change.key] = getDeep(content, change.key);
}
return obj;
}
function isProxy(o) {
return !!(o && (o instanceof ObjectProxy || o instanceof ArrayProxy));
}
function maybeUnwrapProxy(o) {
return isProxy(o) ? maybeUnwrapProxy(get(o, 'content')) : o;
}
let Model;
if (macroCondition(dependencySatisfies('ember-data', '*'))) {
Model = importSync('@ember-data/model').default;
}
class EmberChangeset extends BufferedChangeset {
static {
g(this.prototype, "_changes", [tracked]);
}
#_changes = (i(this, "_changes"), void 0);
static {
g(this.prototype, "_errors", [tracked]);
}
#_errors = (i(this, "_errors"), void 0);
static {
g(this.prototype, "_content", [tracked]);
}
#_content = (i(this, "_content"), void 0);
isObject = isObject;
maybeUnwrapProxy = maybeUnwrapProxy;
// DO NOT override setDeep. Ember.set does not work wth empty hash and nested
// key Ember.set({}, 'user.name', 'foo');
// override base class
// DO NOT override setDeep. Ember.set does not work with Ember.set({}, 'user.name', 'foo');
getDeep = get;
mergeDeep = mergeDeep;
safeGet(obj, key) {
if (Model && obj.relationshipFor?.(key)?.meta?.kind == 'belongsTo') {
return obj.belongsTo(key).value();
}
return get(obj, key);
}
safeSet(obj, key, value) {
return set(obj, key, value);
}
/**
* @property isValid
* @type {Array}
*/
get isValid() {
return super.isValid;
}
/**
* @property isInvalid
* @type {Boolean}
*/
static {
n(this.prototype, "isValid", [dependentKeyCompat]);
}
get isInvalid() {
return super.isInvalid;
}
/**
* @property isPristine
* @type {Boolean}
*/
static {
n(this.prototype, "isInvalid", [dependentKeyCompat]);
}
get isPristine() {
return super.isPristine;
}
/**
* @property isDirty
* @type {Boolean}
*/
static {
n(this.prototype, "isPristine", [dependentKeyCompat]);
}
get isDirty() {
return super.isDirty;
}
static {
n(this.prototype, "isDirty", [dependentKeyCompat]);
}
get pendingData() {
let content = this[CONTENT];
let changes = this[CHANGES];
let pendingChanges = this.mergeDeep(Object.create(Object.getPrototypeOf(content)), content, {
safeGet: get,
safeSet: set
});
return this.mergeDeep(pendingChanges, changes, {
safeGet: get,
safeSet: set
});
}
/**
* Manually add an error to the changeset. If there is an existing
* error or change for `key`, it will be overwritten.
*
* @method addError
*/
addError(key, error) {
super.addError(key, error);
notifyPropertyChange(this, key);
// Return passed-in `error`.
return error;
}
/**
* Manually remove an error from the changeset.
*
* @method removeError
*/
removeError(key) {
super.removeError(key);
notifyPropertyChange(this, key);
return this;
}
/**
* Manually clears the errors from the changeset
*
* @method removeError
*/
removeErrors() {
super.removeErrors();
return this;
}
/**
* Manually push multiple errors to the changeset as an array.
*
* @method pushErrors
*/
pushErrors(key, ...newErrors) {
const {
value,
validation
} = super.pushErrors(key, ...newErrors);
notifyPropertyChange(this, key);
return {
value,
validation
};
}
/**
* Sets property or error on the changeset.
* Returns value or error
*/
_setProperty({
key,
value,
oldValue
}) {
super._setProperty({
key,
value,
oldValue
});
notifyPropertyChange(this, key);
}
/**
* Notifies virtual properties set on the changeset of a change.
* You can specify which keys are notified by passing in an array.
*
* @private
* @param {Array} keys
* @return {Void}
*/
_notifyVirtualProperties(keys) {
keys = super._notifyVirtualProperties(keys);
(keys || []).forEach(key => notifyPropertyChange(this, key));
return;
}
/**
* Deletes a key off an object and notifies observers.
*/
_deleteKey(objName, key = '') {
const result = super._deleteKey(objName, key);
notifyPropertyChange(this, key);
return result;
}
/**
* Executes the changeset if in a valid state.
*
* @method execute
*/
execute() {
let oldContent;
if (this.isValid && this.isDirty) {
let content = this[CONTENT];
let changes = this[CHANGES];
// keep old values in case of error and we want to rollback
oldContent = buildOldValues(content, this.changes, this.getDeep);
// we want mutation on original object
// @tracked
this[CONTENT] = this.mergeDeep(content, changes, {
safeGet: get,
safeSet: set
});
}
this[PREVIOUS_CONTENT] = oldContent;
return this;
}
}
/**
* Creates new changesets.
*/
function changeset(obj, validateFn = defaultValidatorFn, validationMap = {}, options = {}) {
assert('Underlying object for changeset is missing', Boolean(obj));
assert('Array is not a valid type to pass as the first argument to `changeset`', !Array.isArray(obj));
if (options.changeset) {
return new options.changeset(obj, validateFn, validationMap, options);
}
const c = new EmberChangeset(obj, validateFn, validationMap, options);
return c;
}
/**
* Creates new changesets.
* @function Changeset
*/
function Changeset(obj, validateFn = defaultValidatorFn, validationMap = {}, options = {}) {
const c = changeset(obj, validateFn, validationMap, options);
return new Proxy(c, {
get(targetBuffer, key /*, receiver*/) {
const res = targetBuffer.get(key.toString());
return res;
},
set(targetBuffer, key, value /*, receiver*/) {
targetBuffer.set(key.toString(), value);
return true;
}
});
}
class ChangesetKlass {
/**
* Changeset factory
* TODO: deprecate in favor of factory function
*
* @class ChangesetKlass
* @constructor
*/
constructor(obj, validateFn = defaultValidatorFn, validationMap = {}, options = {}) {
const c = changeset(obj, validateFn, validationMap, options);
return new Proxy(c, {
get(targetBuffer, key /*, receiver*/) {
const res = targetBuffer.get(key.toString());
return res;
},
set(targetBuffer, key, value /*, receiver*/) {
targetBuffer.set(key.toString(), value);
return true;
}
});
}
}
export { Changeset, EmberChangeset, buildOldValues, changeset, ChangesetKlass as default };
//# sourceMappingURL=index.js.map