MCPcopy Index your code
hub / github.com/adopted-ember-addons/validated-changeset

github.com/adopted-ember-addons/validated-changeset @v1.4.1-validated-changeset

Chat with this repo
repository ↗ · DeepWiki ↗ · release v1.4.1-validated-changeset ↗ · + Follow
288 symbols 609 edges 57 files 66 documented · 23% 1 cross-repo links
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

Download count all time TravisCI Build Status npm version

npm install validated-changeset --save

tl;dr

This library is the base class for functionality in ember-changeset but could be used with any front end framework. Example uses in template assume handlebars.

import { Changeset } from 'validated-changeset';

let changeset = Changeset(user, validatorFn);
user.firstName; // "Michael"
user.lastName; // "Bolton"

changeset.set('firstName', 'Jim');
changeset.set('lastName', 'B');
changeset.get('isInvalid'); // true
changeset.get('errors'); // [{ key: 'lastName', validation: 'too short', value: 'B' }]
changeset.set('lastName', 'Bob');
changeset.get('isValid'); // true

user.firstName; // "Michael"
user.lastName; // "Bolton"

changeset.save(); // sets and saves valid changes on the user
user.firstName; // "Jim"
user.lastName; // "Bob"

Usage

First, create a new Changeset through JavaScript:

import { Changeset } from 'validated-changeset';

export default class FormComponent {
  constructor(...args) {
    let validatorFn = this.validate;
    this.changeset = Changeset(this.model, validatorFn);
  }
}

The helper receives any Object and an optional validator action. If a validator is passed into the helper, the changeset will attempt to call that function when a value changes.

In the above example, when the input changes, only the changeset's internal values are updated. When the submit button is clicked, the changes are only executed if all changes are valid.

On rollback, all changes are dropped and the underlying Object is left untouched.

Full API

Changeset(model, lookupValidator(validationMap), validationMap, { skipValidate: boolean, initValidate: boolean, changesetKeys: string[] });
  • model (required)

  • validationFunc (optional) - see ember-changeset-validations for usage.

  • validationMap (optional) - see ember-changeset-validations for usage.

    note: validationMap might not be inclusive of all keys that can be set on an object.

  • changesetKeys (optional) - will ensure your changeset and related isDirty state is contained to a specific enum of keys. If a key that is not in changesetKeys is set on the changeset, it will not dirty the changeset.

  • initValidate (optional) - will run the validations and set the validation state when the changeset is created. This option does not support async validations.

Alternative Changeset

We now ship a ValidatedChangeset that is a proposed new API we would like to ship. The goal of this refactor is to remove confusing APIs and externalize validations.

  • ✂️ save
  • ✂️ cast
  • ✂️ merge
  • errors are required to be added to the Changeset manually after validate
  • validate takes a callback with the sum of changes. In user land you will call changeset.validate((changes) => yupSchema.validate(changes))
import { ValidationChangeset } from 'validated-changeset';
import { array, object, string, number, date } from 'yup';

const UserSchema = object({
  name: string().required(),
  age: number()
    .required()
    .positive()
    .integer(),
  email: string().email(),
  website: string()
    .url()
    .nullable(),
  createdOn: date().default(() => new Date())
});

export default class FormComponent {
  constructor(...args) {
    this.changeset = ValidationChangeset(this.model);
  }

  onSubmit() {
    try {
      await this.changeset.validate(changes => UserSchema.validate(changes));
      this.changeset.removeError()
    } catch (e) {
      dummyChangeset.addError(e.path, { value: changeset.get(e.path), validation: e.message });
    }
  }
}

Examples

API

error

Returns the error object.

{
  firstName: {
    value: 'Jim',
    validation: 'First name must be greater than 7 characters'
  }
}

Note that keys can be arbitrarily nested:

{
  address: {
    zipCode: {
      value: '123',
      validation: 'Zip code must have 5 digits'
    }
  }
}

If you have multiple validations, validation will be an array:

{
  address: {
    zipCode: {
      value: '123',
      validation: ['Zip code must have 5 digits', 'too short']
    }
  }
}

You can use this property to locate a single error:

{{#if changeset.error.firstName}}


{{changeset.error.firstName.validation}}


{{/if}}

{{#if changeset.error.address.zipCode}}


{{changeset.error.address.zipCode.validation}}


{{/if}}

⬆️ back to top

change

Returns the change object.

{
  firstName: 'Jim'
}

Note that keys can be arbitrarily nested:

{
  address: {
    zipCode: '10001'
  }
}

You can use this property to locate a single change:

{{changeset.change.firstName}}
{{changeset.change.address.zipCode}}

⬆️ back to top

errors

Returns an array of errors. If your validate function returns a non-boolean value, it is added here as the validation property.

[
  {
    key: 'firstName',
    value: 'Jim',
    validation: 'First name must be greater than 7 characters'
  },
  {
    key: 'address.zipCode',
    value: '123',
    validation: 'Zip code must have 5 digits'
  }
]

You can use this property to render a list of errors:

{{#if changeset.isInvalid}}


There were errors in your form:


  <ul>
    {{#each changeset.errors as |error|}}
      <li>{{error.key}}: {{error.validation}}</li>
    {{/each}}
  </ul>
{{/if}}

⬆️ back to top

changes

Returns an array of changes to be executed. Only valid changes will be stored on this property.

[
  {
    key: 'firstName',
    value: 'Jim'
  },
  {
    key: 'address.zipCode',
    value: 10001
  }
]

You can use this property to render a list of changes:

<ul>
  {{#each changeset.changes as |change|}}
    <li>{{change.key}}: {{change.value}}</li>
  {{/each}}
</ul>

⬆️ back to top

data

Returns the Object that was wrapped in the changeset.

let user = { name: 'Bobby', age: 21, address: { zipCode: '10001' } };
let changeset = Changeset(user);

changeset.get('data'); // user

⬆️ back to top

isValid

Returns a Boolean value of the changeset's validity.

changeset.get('isValid'); // true

You can use this property in the template:

{{#if changeset.isValid}}


Good job!


{{/if}}

⬆️ back to top

isInvalid

Returns a Boolean value of the changeset's (in)validity.

changeset.get('isInvalid'); // true

You can use this property in the template:

{{#if changeset.isInvalid}}


There were one or more errors in your form


{{/if}}

⬆️ back to top

isPristine

Returns a Boolean value of the changeset's state. A pristine changeset is one with no changes.

changeset.get('isPristine'); // true

If changes present on the changeset are equal to the content's, this will return true. However, note that key/value pairs in the list of changes must all be present and equal on the content, but not necessarily vice versa:

let user = { name: 'Bobby', age: 21, address: { zipCode: '10001' } };

changeset.set('name', 'Bobby');
changeset.get('isPristine'); // true

changeset.set('address.zipCode', '10001');
changeset.get('isPristine'); // true

changeset.set('foo', 'bar');
changeset.get('isPristine'); // false

⬆️ back to top

isDirty

Returns a Boolean value of the changeset's state. A dirty changeset is one with changes.

changeset.get('isDirty'); // true

⬆️ back to top

get

Exactly the same semantics as Ember.get. This proxies first to the error value, the changed value, and finally to the underlying Object.

changeset.get('firstName'); // "Jim"
changeset.set('firstName', 'Billy'); // "Billy"
changeset.get('firstName'); // "Billy"

changeset.get('address.zipCode'); // "10001"
changeset.set('address.zipCode', '94016'); // "94016"
changeset.get('address.zipCode'); // "94016"

You can use and bind this property in the template:

{{input value=changeset.firstName}}

Note that using Ember.get will not necessarily work if you're expecting an Object. On the other hand, using changeset.get will work just fine:

get(changeset, 'momentObj').format('dddd'); // will error, format is undefined
changeset.get('momentObj').format('dddd');  // => "Friday"

This is because Changeset wraps an Object with Ember.ObjectProxy internally, and overrides Ember.Object.get to hide this implementation detail.

Because an Object is wrapped with Ember.ObjectProxy, the following (although more verbose) will also work:

get(changeset, 'momentObj.content').format('dddd'); // => "Friday"

⬆️ back to top

set

Exactly the same semantics as Ember.set. This stores the change on the changeset. It is recommended to use changeset.set(...) instead of Ember.set(changeset, ...). Ember.set will set the property for nested keys on the underlying model.

changeset.set('firstName', 'Milton'); // "Milton"
changeset.set('address.zipCode', '10001'); // "10001"

You can use and bind this property in the template:

{{input value=changeset.firstName}}
{{input value=changeset.address.country}}

Any updates on this value will only store the change on the changeset, even with 2 way binding.

⬆️ back to top

prepare

Provides a function to run before emitting changes to the model. The callback function must return a hash in the same shape:

changeset.prepare((changes) => {
  // changes = { firstName: "Jim", lastName: "Bob", 'address.zipCode': "07030" };
  let modified = {};

  for (let key in changes) {
    let newKey = key.split('.').map(underscore).join('.')
    modified[newKey] = changes[key];
  }

  // don't forget to return, the original changes object is not mutated
  // modified = { first_name: "Jim", last_name: "Bob", 'address.zip_code': "07030" };
  return modified;
}); // returns changeset

The callback function is not validated – if you modify a value, it is your responsibility to ensure that it is valid.

Returns the changeset.

⬆️ back to top

execute

Applies the valid changes to the underlying Object.

changeset.execute(); // returns changeset

Note that executing the changeset will not remove the internal list of changes - instead, you should do so explicitly with rollback or save if that is desired.

Moreover, if you need to perform additional work on changeset.execute, you can register a callback with a key 'execute' and we will ensure it is carried out whenever changeset.execute is called.

function callback() {
  ...
}
changeset.on('execute', callback);


changeset.execute();

unexecute

Undo changes made to underlying Object for changeset. This is often useful if you want to remove changes from underlying Object if save fails.

changeset
  .save()
  .catch(() => {
    // save applies changes to the underlying Object via this.execute(). This may be undesired for your use case.
    dummyChangeset.unexecute();
  })

⬆️ back to top

save

Executes changes, then proxies to the underlying Object's save method, if one exists. If it does, the method can either return a Promise or a non-Promise value. Either way, the changeset's save method will return a promise.

changeset.save(); // returns Promise

The save method will also remove the internal list of changes if the save is successful.

⬆️ back to top

merge

Merges 2 changesets and returns a Changeset with the same underlying content and validator as the origin. Both changesets must point to the same underlying object. For example:

```js let changesetA = Changeset(user, validatorFn); let changesetB = Changeset(user, validatorFn);

changesetA.set('firstName', 'Jim'); changesetA.set('address.zipCode', '94016');

changesetB.set('firstName', 'Jimmy'); changesetB.set('lastName', 'Fallon'); changesetB.set('address.zipCode', '10112');

let changesetC = changesetA.merge(changesetB); changesetC.execute();

user.firstName; // "

Extension points exported contracts — how you extend this code

IEvented (Interface)
(no doc) [2 implementers]
src/types/index.ts
Options (Interface)
(no doc)
src/utils/merge-deep.ts
ProxyHandler (Interface)
(no doc) [1 implementers]
src/types/index.ts
Options (Interface)
(no doc)
src/utils/set-deep.ts
INotifier (Interface)
(no doc) [1 implementers]
src/types/index.ts
ChangesetDef (Interface)
(no doc) [1 implementers]
src/types/index.ts
IChangeset (Interface)
(no doc) [1 implementers]
src/types/index.ts

Core symbols most depended-on inside this repo

set
called by 312
src/index.ts
Changeset
called by 280
src/index.ts
get
called by 262
src/index.ts
lookupValidator
called by 78
src/utils/validator-lookup.ts
execute
called by 45
src/index.ts
setDeep
called by 39
src/utils/set-deep.ts
unwrap
called by 25
src/utils/object-tree-node.ts
addError
called by 24
src/index.ts

Shape

Method 123
Function 109
Class 44
Interface 12

Languages

TypeScript100%

Modules by API surface

src/index.ts67 symbols
src/validated.ts49 symbols
test/index.test.ts39 symbols
src/types/index.ts17 symbols
src/utils/object-tree-node.ts12 symbols
test/validated.test.ts11 symbols
src/utils/merge-deep.ts11 symbols
test/utils/merge-deep.test.ts7 symbols
test/utils/flatten-validations.test.ts6 symbols
src/-private/notifier.ts6 symbols
test/utils/set-deep.test.ts5 symbols
test/utils/assign.test.ts5 symbols

Used by 1 indexed graphs manifest dependencies, hub-wide

For agents

$ claude mcp add validated-changeset \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact