{"_id":"obj_diff","_rev":"9-73f61f8d12bee6d031c9caeccd97c170","name":"obj_diff","description":"Identify differences between objects; assert permitted and mandatory differences","dist-tags":{"latest":"0.3.0"},"versions":{"0.1.0":{"name":"obj_diff","version":"0.1.0","author":{"name":"Jason Smith","email":"jhs@iriscouch.com"},"description":"Find all differences between Javascript objects","tags":["couchdb"],"homepage":"http://github.com/iriscouch/obj_diff","repository":{"type":"git","url":"git://github.com/iriscouch/obj_diff.git"},"engines":["node"],"devDependencies":{},"main":"./api","dependencies":{},"_id":"obj_diff@0.1.0","_engineSupported":true,"_npmVersion":"1.0.6","_nodeVersion":"v0.4.8","_defaultsLoaded":true,"dist":{"shasum":"13a1e95ec7a4fbace3c3545de086ecb3f4f62132","tarball":"https://registry.npmjs.org/obj_diff/-/obj_diff-0.1.0.tgz","integrity":"sha512-A+k5tsyXIHvjNGXJv5vH/j903eruwGAAHMIY17BSQBhNc3QBDe1GOmLxJsQ1R/Po6qk3aMpAf4NMVCvQwFCbXg==","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQCi9KI0prv0Z3tCPlFigR482VUv8gnbK+b8f7Z24uaXZQIhAKwGebhMZsLXdaSTlvR6RUAKUTQISO2qLleh859L1OMg"}]},"scripts":{}},"0.2.0":{"name":"obj_diff","version":"0.2.0","author":{"name":"Jason Smith","email":"jhs@iriscouch.com"},"description":"Find all differences between Javascript objects","tags":["couchdb"],"homepage":"http://github.com/iriscouch/obj_diff","repository":{"type":"git","url":"git://github.com/iriscouch/obj_diff.git"},"engines":["node"],"devDependencies":{},"main":"./api","_npmUser":{"name":"jhs","email":"jhs@iriscouch.com"},"_id":"obj_diff@0.2.0","dependencies":{},"_engineSupported":true,"_npmVersion":"1.0.90","_nodeVersion":"v0.4.8","_defaultsLoaded":true,"dist":{"shasum":"6a80cd45465194a347f4f0ec8a36b6950b6049ba","tarball":"https://registry.npmjs.org/obj_diff/-/obj_diff-0.2.0.tgz","integrity":"sha512-sxebaOTUgNFzHZ3hbo3hpe3OZwAFS6ETTHFDAf2PQmhfLIUeiqqusiqCIFhqUxI96JNUdw5OmjwyNZi/kIyTxQ==","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCICYUstk+JPsV1fN20qvHH4UZPb1J50fB6WLxxj8t/dBcAiEAipgo68dKim/qcaY5V8tEUT663Xwdi8DtQ7MXYPXleiY="}]},"maintainers":[{"name":"jhs","email":"jhs@couchone.com"}]},"0.3.0":{"name":"obj_diff","description":"Identify differences between objects; assert permitted and mandatory differences","version":"0.3.0","author":{"name":"Jason Smith","email":"jhs@iriscouch.com","url":"http://www.iriscouch.com/"},"tags":["couchdb","security","diff","couchapp"],"homepage":"http://github.com/iriscouch/obj_diff","repository":{"type":"git","url":"git://github.com/iriscouch/obj_diff.git"},"engines":["node"],"dependencies":{"defaultable":"0.7.2","burrito":"0.2.11"},"devDependencies":{"tap":"0.0.13","couchapp":"0.8.1"},"main":"./api","_npmUser":{"name":"jhs","email":"jhs@iriscouch.com"},"_id":"obj_diff@0.3.0","_engineSupported":true,"_npmVersion":"1.1.0-beta-4","_nodeVersion":"v0.6.6","_defaultsLoaded":true,"dist":{"shasum":"fcd9986b1faa6e6505cbc71ce4396a95a0d70d6d","tarball":"https://registry.npmjs.org/obj_diff/-/obj_diff-0.3.0.tgz","integrity":"sha512-t5ihOt2O0dq9OWzqv5lo09/SKKSi5OjN6jvXaD4HgIX3TibBksmF+uksGpUaG1oaEWEV5epDr9EkdEfNoThdig==","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIAKJuknIY8+WfZBN4/NxKqU+mMTGMQM8PMvbrRnaFg2WAiEAmg57JZ+iHAbqQhR8p/sVtcZ5CX1MOV74lacfl2+x0lU="}]},"readme":"# Identify and assert differences betwen objects\n\nobj_diff is for examining changes between Javascript and JSON objects. Use it to **see how data has changed** and to **decide whether that change is good or bad**. Thus obj_diff is useful for security and validation.\n\nobj_diff comes from an internal [Iris Couch][ic] application used in production for two years. It works in the browser, in CouchDB, and as an NPM module.\n\n    npm install obj_diff\n\n## Is it any good?\n\nYes.\n\n## Usage\n\nDiff two objects. Then use helper functions to see what's changed.\n\n```javascript\nvar obj_diff = require(\"obj_diff\");\n\nvar original = { hello:\"world\"     , note: {\"nice\":\"shoes\"} };\nvar modified = { hello:\"underworld\", note: {\"nice\":\"hat\"  } };\n\nvar diff = obj_diff(original, modified);\n\n// Mandatory changes\nif(diff.atleast(\"hello\", \"world\", \"underworld\"))    // true\n  console.log(\"That's kind of dark\");\n\n\n// Approved changes\nif(diff.atmost(\"hello\", \"world\", \"underworld\"))     // false (.hello.note.nice also changed)\n  console.log(\"That's kind of dark\");\n\n\nif(diff.atmost(\"hello\"          , \"world\", /world/, // true\n               \"hello.note.nice\", \"shoes\", String))\n  console.log(\"Hooray!\");\n```\n\n## Design\n\nTo work well with databases, obj_diff has these design goals:\n\n* **Declarative**. Data validation is crucial. It must be correct. Validation rules must be easy to express clearly and easy to reason about.\n* **JSON compatible**. Diffs and validation rules (containing regexes, functions, etc.) can be encoded and decoded as JSON, without losing functionality. You can store changes and validation policies as plain JSON.\n\n## Mandatory vs. Approved changes\n\nThere is a symbiotic relationship between *atleast* and *atmost*:\n\n* atleast() returns `true` only if **every rule matches a change**.\n* atmost() returns `true` only if **every change matches a rule**.\n\n```javascript\n// Give a key name, an expected old value, and expected new value.\ndiff.atleast(\"some_key\", \"old_value\", \"new_value\");\n\n// Specify multiple rules simultaneously.\ndiff.atleast(\n\n  // Nested objects: just type them out in the string.\n  \"options.production.log.level\", \"debug\", \"info\",\n\n  // Regular expressions, e.g. first letter must change from \"J\" to \"S\".\n  \"name\", /^J/, /^S/,\n\n  // ANY matches any value.\n  \"state\", obj_diff.ANY, \"run\", // State must become \"run\".\n  \"owner\", null, obj_diff.ANY,  // Owner must become non-null.\n\n  // GONE implies a missing value.\n  \"error\", \"locked\", obj_diff.GONE, // Error must be deleted.\n  \"child\", obj_diff.GONE, \"Bob\",    // Child must be created.\n\n  // FALSY matches false, null, undefined, the empty string, 0, NaN, and a missing value.\n  \"is_new\", obj_diff.ANY, obj_diff.FALSY,\n\n  // \"TRUTHY\" matches anything not falsy.\n  \"changed\", obj_diff.GONE, obj_diff.TRUTHY,\n\n  // Javascript types\n  \"ratio\"  , undefined    , Number , // Numeric ratio, note undefined is not GONE\n  \"age\"    , obj_diff.ANY , Number , // Age must change to something numeric.\n  \"name\"   , obj_diff.GONE, String , // Must create a name string.\n  \"deleted\", obj_diff.ANY , Boolean, // Deleted flag must be true/false.\n  \"config\" , obj_diff.GONE, Object , // Must create a config object.\n  \"backups\", null         , Array  , // Null backups must become an array.\n\n  // TIMESTAMP matches ISO-8601 strings (what JSON.stringify makes from a Date)\n  \"created_at\", GONE, TIMESTAMP, // e.g. \"2011-11-10T04:21:45.046Z\"\n\n  // GREATER and LESSER compare a value to its counterpart.\n  \"age\", Number, GREATER, // Age must increase in number\n  \"age\", LESSER, Number,  // (same as the previous test)\n\n  \"weight\", GREATER, LESSER , // Mandatory weight loss\n  \"age\"   , 21 , GREATER,     // Must increase from 21\n\n  \"WRONG\", GREATER, GREATER, // This always fails.\n  \"WRONG\", LESSER , LESSER , // This always fails.\n\n  // Use functions (predicates) for arbitrary data validation\n  \"weapon\", obj_diff.ANY, good_weapon\n);\n\ndiff.atmost(\n  // Changing my weapon is fine.\n  \"weapon\", obj_diff.ANY, good_weapon,\n\n  // Changing my first name to something readable is fine.\n  \"name.first\", obj_diff.ANY, /^\\w+$/,\n\n  // People named \"Smith\" may change their last name.\n  \"name.last\", \"Smith\", /^\\w+$/,\n\n  // Middle must be just an initial.\n  \"name.middle\", obj_diff.ANY, /^\\w$/\n);\n\n// Or as an assertion, with an extra \"reason\" argument.\ntry {\n  diff.assert_atleast(\n    \"some_key\"         , \"must become new new\" , \"old_value\" , \"new_value\",\n    \"options.log.level\", \"must upgrade to info\", \"debug\"     , \"info\",\n    \"name\"             , \"must start with 'S'\" , obj_diff.ANY, /^S/,\n    \"weapon\"           , \"cannot be sharp\"     , obj_diff.ANY, good_weapon\n  );\n} catch (er) {\n  if(!er.diff)\n    throw er; // Unknown error, not a policy failure, e.g. bad parameters, or a predicate error.\n\n  console.error(\"Hey! \" + er.message); // e.g. Hey! options.log.level must upgrade to info\n}\n\ntry {\n  diff.assert_atmost(\n    \"weapon\"     , \"cannot be sharp\"       , obj_diff.ANY, good_weapon,\n    \"name.first\" , \"must be readable\"      , obj_diff.ANY, /^\\w+$/,\n    \"name.last\"  , \"may no longer be Smith\", \"Smith\"     , /^\\w+$/,\n    \"name.middle\", \"must be one letter\"    , obj_diff.ANY, /^\\w$/\n  );\n} catch (er) {\n  if(!er.diff)\n    throw er; // Unknown error\n\n  // .reason, .key, .from, .to are available.\n  console.error(er.key + \" is wrong because it \" + er.reason); // detailed\n}\n\nfunction good_weapon(weapon) {\n  return weapon != process.env.sharp_weapon;\n}\n```\n\nA useful trick with *atmost()* is to assert no changes.\n\n```javascript\ntry {\n  diff2.assert_atmost();   // No rules given, i.e. \"zero changes, at most\"\n  diff2.assert_nochange(); // Same as atmost() but more readable.\n} catch (er) {\n  console.error(\"Sorry, no changes allowed\");\n}\n```\n\n<a name=\"couchdb\"></a>\n## CouchDB validation\n\nobj_diff excels (and was designed for) [Apache CouchDB][couchdb] `validate_doc_update()` functions. Combine *atleast()* and *atmost()* to make a sieve and sift out good and bad changes. obj_diff cannot replace all validation code, but it augments it well.\n\n* *atleast()* confirms **required** changes.\n* *atmost()* confirms **allowed** changes.\n\nFirst of all, CouchDB changes document metadata under the hood, and you don't want that triggering false alarms. So the first thing is to set obj_diff's [defaults][def] for CouchDB mode, which modifies *atmost()* to allow normal document changes:\n\n1. `null` is treated as an empty object, `{}`. This always works: `doc_diff(oldDoc, newDoc)`\n2. *atmost()* allows normal changes:\n  * `_id` for document creation\n  * `_rev` may change appropriately.\n  * `_revisions.ids` and `_revisions.start` may change appropriately.\n3. *assert_atleast()* and *assert_atmost()* throw `{\"forbidden\": <reason>}` objects that Couch likes.\n\nThus, this is your typical `validate_doc_update` function:\n\n```javascript\nfunction(newDoc, oldDoc, userCtx, secObj) {\n  var doc_diff = require(\"obj_diff\").defaults({\"couchdb\":true}) // Relaxed diff.\n    , ANY      = doc_diff.ANY\n    , GONE     = doc_diff.GONE\n    ;\n\n  var diff = doc_diff(oldDoc, newDoc);\n  // Start validating!\n}\n```\n\n### Valid data vs. valid changes\n\nobj_diff validates *changes*, not *data*. What happens if you GET a document and PUT it back unmodified? There will be zero changes in the diff. Any *atleast()* checks will necessarily fail. Therefore, the best practice is to check the data and then apply certain policies based on that.\n\nOf course, sometimes you *want* changes in every update, such as timestamp validation:\n\n```javascript\nif(!oldDoc)\n  // Creation, require the timestamp fields.\n  diff.assert_atleast(\n    'created_at', 'timestamp required', GONE, TIMESTAMP,\n    'updated_at', 'timestamp required', GONE, newDoc.created_at // Must match created_at\n  );\nelse\n  // Update, exact() will reject changes to .created_at (and all other fields)\n  diff.assert_exactly(\n    'updated_at', 'Must be a timestamp'  , TIMESTAMP, TIMESTAMP,\n    'updated_at', 'Must be later in time', TIMESTAMP, GREATER\n  );\n```\n\n### Example: User Documents\n\nTODO\n\n## JSON Support\n\nobj_diff supports regular expressions and function callbacks in its rules. Yet it can be nice to store them as JSON, and to load them later. For example, you could store a few rules in a CouchDB `_security` object, and do database-specific data validation with an identical `validate_doc_update()` function.\n\nBoth Diff and Rule obejcts behave the same after a JSON round-trip. They have a `.toJSON` function to handle things, so just `JSON.stringify()` them and store them in a file or database. Later, `JSON.parse()` them and pass the object to the constructors.\n\n```javascript\nvar obj_diff = require(\"obj_diff\");\n\nfunction good_guy(guy) { return guy.good || guy.awesome }\n\nvar diffs =\n  [ obj_diff({some_key: \"old_value\"}, {some_key: \"new_value\"})\n  , obj_diff({log: {level: \"Anything!\"}}, {log: {level: \"info\"}})\n  , obj_diff({guy: {\"good\":true}}, {guy:\"Fawkes\"})\n  ];\n\nvar rules =\n  [ new obj_diff.Rule(\"some_key\", \"old_value\", \"new_value\")\n  , new obj_diff.Rule(\"log.level\", obj_diff.ANY, /^(debug|info|error)$/)\n  , new obj_diff.Rule(\"guy\", good_guy, obj_diff.ANY)\n  ];\n\nconsole.log(\"Diffs: \" + JSON.stringify(diffs));\nconsole.log(\"Rules: \" + JSON.stringify(rules));\n```\n\nNote, functions are stored using their source code, so be careful about global or closed variables they depend on.\n\n## Development\n\nobj_diff uses [node-tap][tap] unit tests. Install it globally (`npm -g install node-tap`) and run `tap t`. Or for a more robust local install:\n\n    $ npm install --dev\n    tap@0.0.10 ./node_modules/tap\n    └── tap-runner@0.0.7\n\n    $ ./node_modules/.bin/tap t\n    ok api.js ......................... 82/82\n    ok diffs.js ....................... 60/60\n    ok policy.js .................... 123/123\n    ok rules.js ..................... 774/774\n    total ......................... 1043/1043\n\n    ok\n\nFinally, you can use the diff object yourself. Here's what it looks like:\n\n    > obj_diff({x:\"hi\"}, {x:\"bye\"})\n    { x: { from: 'hi', to: 'bye' } }\n\n    > obj_diff({name:\"Joe\", word:\"hi\"},\n    ...        {name:\"Joe\", word:\"bye\"})\n    { word: { from: 'hi', to: 'bye' } }\n\n    > obj_diff({name:\"Joe\", contact: {email:\"doe@example.com\"}},\n    ...        {name:\"Joe\", contact: {email:\"doe@example.com\", cell:\"555-1212\"}})\n    { contact: { cell: { from: ['gone'], to: '555-1212' } } }\n\n    > obj_diff({name:\"Joe\", contact: {email:\"doe@example.com\", cell:null      }},\n    ...        {name:\"Joe\", contact: {email:\"doe@example.com\", cell:\"555-1212\"}})\n    { contact: { cell: { from: null, to: '555-1212' } } }\n\n## License\n\nobj_diff is licensed under the Apache License, version 2.0\n\n[def]: https://github.com/iriscouch/defaultable\n[ic]: http://www.iriscouch.com/\n[couchdb]: http://couchdb.apache.org/\n[tap]: https://github.com/isaacs/node-tap\n","maintainers":[{"name":"jhs","email":"jhs@couchone.com"}]}},"maintainers":[{"name":"jhs","email":"jhs@couchone.com"}],"time":{"modified":"2022-06-22T16:17:57.250Z","created":"2011-06-20T04:06:14.016Z","0.1.0":"2011-06-20T04:06:16.582Z","0.2.0":"2011-10-13T23:26:16.163Z","0.3.0":"2012-05-12T08:49:49.755Z"},"author":{"name":"Jason Smith","email":"jhs@iriscouch.com","url":"http://www.iriscouch.com/"},"repository":{"type":"git","url":"git://github.com/iriscouch/obj_diff.git"}}