WIP: keep iterating on the store API
This commit is contained in:
		
							parent
							
								
									8eec24c555
								
							
						
					
					
						commit
						e43c169257
					
				| @ -1,10 +1,99 @@ | |||||||
| 'use strict'; | 'use strict'; | ||||||
| 
 | 
 | ||||||
|  | var crypto = require('crypto'); | ||||||
|  | 
 | ||||||
| function jsonDeepClone(target) { | function jsonDeepClone(target) { | ||||||
|   return JSON.parse( |   return JSON.parse( | ||||||
|     JSON.stringify(target) |     JSON.stringify(target) | ||||||
|   ); |   ); | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | function mergeObjects() { | ||||||
|  |   // arguments should be an array of objects. We
 | ||||||
|  |   // reverse it because the last argument to set
 | ||||||
|  |   // a value wins.
 | ||||||
|  |   var args = [].slice.call(arguments).reverse(); | ||||||
|  |   var len = args.length; | ||||||
|  |   if (len === 1) { | ||||||
|  |     return args[0]; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // gather the set of keys from all arguments
 | ||||||
|  |   var keyLists = args.map(function (arg) { | ||||||
|  |     return Object.keys(arg); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   var keys = Object.keys(keyLists.reduce(function (all, list) { | ||||||
|  |     list.forEach(function (k) { | ||||||
|  |       all[k] = true; | ||||||
|  |     }); | ||||||
|  |     return all; | ||||||
|  |   }, {})); | ||||||
|  | 
 | ||||||
|  |   // for each key
 | ||||||
|  |   return keys.reduce(function (target, k) { | ||||||
|  |     // find the first argument (because of the reverse() above) with the key set
 | ||||||
|  |     var values = []; | ||||||
|  |     var isObject = false; | ||||||
|  |     for (var i = 0; i < len; i++) { | ||||||
|  |       var v = args[i]; | ||||||
|  |       var vType = typeof v; | ||||||
|  | 
 | ||||||
|  |       if (vType === 'object') { | ||||||
|  |         if (!v) { | ||||||
|  |           // typeof null is object. null is the only falsey object. null represents
 | ||||||
|  |           // a delete or the end of our argument list;
 | ||||||
|  |           break; | ||||||
|  |         } | ||||||
|  |         // we need to collect values until we get a non-object, so we can merge them
 | ||||||
|  |         values.push(v); | ||||||
|  |         isObject = true; | ||||||
|  |       } else if (!isObject) { | ||||||
|  |         if (vType === 'undefined') { | ||||||
|  |           // if the arg actually has the key set this is effectively a "delete"
 | ||||||
|  |           if (keyList[i].indexOf(k) != -1) { | ||||||
|  |             break; | ||||||
|  |           } | ||||||
|  |           // otherwise we need to check the next argument's value, so we don't break the loop
 | ||||||
|  |         } else { | ||||||
|  |           values.push(v); | ||||||
|  |           break; | ||||||
|  |         } | ||||||
|  |       } else { | ||||||
|  |         // a previous value was an object, this one isn't
 | ||||||
|  |         // That means we are done collecting values.
 | ||||||
|  |         break; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (values.length > 0) { | ||||||
|  |       target[k] = mergeObjects.apply(null, values); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return target; | ||||||
|  |   }, {}); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function prepareZone(zone, options) { | ||||||
|  |   var opts = options || {}; | ||||||
|  |   var timestamp = opts.timestamp || Date.now(); | ||||||
|  |   if (!zone.name) { | ||||||
|  |     zone.name = zone.id; | ||||||
|  |     zone.id = null; | ||||||
|  |   } | ||||||
|  |   if (!zone.id) { | ||||||
|  |     zone.id = crypto.randomBytes(16).toString('hex'); | ||||||
|  |   } | ||||||
|  |   if (!zone.createdAt) { zone.createdAt = timestamp; } | ||||||
|  |   if (!zone.updatedAt || opts.isUpdate) { zone.updatedAt = timestamp; } | ||||||
|  | 
 | ||||||
|  |   // create a names set for the zone, keyed by record name mapped to
 | ||||||
|  |   // an object for the various records with that name, by type (A, MX, TXT, etc.)
 | ||||||
|  |   zone.records = zone.records || {}; | ||||||
|  | 
 | ||||||
|  |   return zone; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| /* | /* | ||||||
| init() should return an object with: { | init() should return an object with: { | ||||||
|   save: function -> undefined - changes to in memory representation should be persisted |   save: function -> undefined - changes to in memory representation should be persisted | ||||||
| @ -14,14 +103,12 @@ init() should return an object with: { | |||||||
|   }, |   }, | ||||||
|   zones: { |   zones: { | ||||||
|     list: function -> list zones, |     list: function -> list zones, | ||||||
|     find: function -> read zone by ???, |  | ||||||
|     create: |     create: | ||||||
|     update: |     update: | ||||||
|     delete: |     delete: | ||||||
|   }, |   }, | ||||||
|   records: { |   records: { | ||||||
|     list: function -> list records, |     list: function -> list records, | ||||||
|     find: function -> read record by ???, |  | ||||||
|     create: |     create: | ||||||
|     update: |     update: | ||||||
|     delete: |     delete: | ||||||
| @ -35,51 +122,71 @@ module.exports = function init (opts) { | |||||||
|   // opts = { filepath };
 |   // opts = { filepath };
 | ||||||
| 
 | 
 | ||||||
|   var db = require(opts.filepath); |   var db = require(opts.filepath); | ||||||
|   var stat = require('fs').statSync(opts.filepath); |   var mtime = require('fs').statSync(opts.filepath).mtime.valueOf(); | ||||||
|   var crypto = require('crypto'); | 
 | ||||||
|   //
 |   //
 | ||||||
|   // Manual Migration
 |   // Migration from other formats
 | ||||||
|   //
 |   //
 | ||||||
| 
 | 
 | ||||||
|   // Convert the primary nameservers from strings to objects with names and IDs.
 |   // Convert the primary nameservers from an array of strings to objects with names and IDs.
 | ||||||
|   db.primaryNameservers.forEach(function (ns, i, arr) { |   // also switch to the 'peers' name, since we are really interested in the other FQDNs that
 | ||||||
|     if ('string' === typeof ns) { |   // use the same data store and are kept in sync.
 | ||||||
|       ns = { name: ns }; |   var peerList = (!db.peers || Array.isArray(db.peers))? db.peers : Object.keys(db.peers).map(function (p) { | ||||||
|       arr[i] = ns; |     return db.peers[p]; | ||||||
|     } |  | ||||||
|     if (!ns.id) { |  | ||||||
|       ns.id = crypto.randomBytes(16).toString('hex'); |  | ||||||
|     } |  | ||||||
|   }); |   }); | ||||||
|  |   db.peers = [].concat(db.primaryNameservers, peerList).filter(function (p) { | ||||||
|  |     // filer out empty strings, undefined, etc.
 | ||||||
|  |     return !!p; | ||||||
|  |   }).map(function (ns) { | ||||||
|  |     var peer = ('string' === typeof ns)? ns : { name: ns }; | ||||||
|  |     if (!ns.id) { | ||||||
|  |       peer.id = crypto.randomBytes(16).toString('hex'); | ||||||
|  |     } | ||||||
|  |     return peer; | ||||||
|  |   }).reduce(function (peers, p) { | ||||||
|  |     peers[p.name] = p; | ||||||
|  |     return peers; | ||||||
|  |   }, {}); | ||||||
|  |   delete db.primaryNameservers; | ||||||
| 
 | 
 | ||||||
|   // Convert domains to zones and ensure that they have proper IDs and timestamps
 |   // Convert domains to zones and ensure that they have proper IDs and timestamps
 | ||||||
|   db.zones = db.zones || []; |   // Organize zones as a set of zone names
 | ||||||
|   if (db.domains) { |   var zoneList = (!db.zones || Array.isArray(db.zones))? db.zones : Object.keys(db.zones).map(function (z) { | ||||||
|     db.zones = db.zones.concat(db.domains); |     return db.zones[z]; | ||||||
|   } |  | ||||||
|   db.zones.forEach(function (zone) { |  | ||||||
|     if (!zone.name) { |  | ||||||
|       zone.name = zone.id; |  | ||||||
|       zone.id = null; |  | ||||||
|     } |  | ||||||
|     if (!zone.id) { |  | ||||||
|       zone.id = crypto.randomBytes(16).toString('hex'); |  | ||||||
|     } |  | ||||||
|     if (!zone.createdAt) { zone.createdAt = stat.mtime.valueOf(); } |  | ||||||
|     if (!zone.updatedAt) { zone.updatedAt = stat.mtime.valueOf(); } |  | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   // Records belong to zones, but they (currently) refer to them by a zone property.
 |   db.zones = [].concat(db.domains, zoneList).filter(function (z) { | ||||||
|   // NOTE/TODO: This may pose problems where the whole list of records is not easily
 |     // filer out empty strings, undefined, etc.
 | ||||||
|  |     return !!z; | ||||||
|  |   }).map(function (zone) { | ||||||
|  |     return prepareZone(zone, { timestamp: mtime }); | ||||||
|  |   }).reduce(function (zones, z) { | ||||||
|  |     zones[z.name] = z; | ||||||
|  |     return zones; | ||||||
|  |   }, {}); | ||||||
|  |   delete db.domains; | ||||||
|  | 
 | ||||||
|  |   // NOTE: Records belong to zones, but they previously referred to them only by a
 | ||||||
|  |   // zone property. This may pose problems where the whole list of records is not easily
 | ||||||
|   // filtered / kept in memory / indexed and/or retrieved by zone. Traditionally,
 |   // filtered / kept in memory / indexed and/or retrieved by zone. Traditionally,
 | ||||||
|   // records are stored "within a zone" in a zone file. We may wish to have the
 |   // records are stored "within a zone" in a zone file. We want to have the store API
 | ||||||
|   // DB API behave more traditionally, even though some stores (like a SQL database
 |   // behave more traditionally, even though some stores (like a SQL database
 | ||||||
|   // table) might actually store the zone as a property of a record as we currently do.
 |   // table) might actually store the zone as a property of a record as we currently do.
 | ||||||
|   db.records.forEach(function (record) { |   // (This fits with the somewhat unexpected and confusing logic of wildcard records.)
 | ||||||
|  |   (db.records || []).forEach(function (record) { | ||||||
|  |     // make sure the record has an ID
 | ||||||
|     if (!record.id) { |     if (!record.id) { | ||||||
|       record.id = crypto.randomBytes(16).toString('hex'); |       record.id = crypto.randomBytes(16).toString('hex'); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     // put it in it's zone - synthesize one if needed
 | ||||||
|  |     db.zones[record.zone] = db.zones[record.zone] || prepareZone({ name: record.zone }); | ||||||
|  |     var zone = db.zones[record.zone]; | ||||||
|  |     zone.records[record.name] = zone.records[record.name] || []; | ||||||
|  |     var recordsForName = zone.records[record.name]; | ||||||
|  |     recordsForName.push(record); | ||||||
|   }); |   }); | ||||||
|  |   delete db.records; | ||||||
| 
 | 
 | ||||||
|   // Write the migrated data
 |   // Write the migrated data
 | ||||||
|   require('fs').writeFileSync(opts.filepath, JSON.stringify(db, null, 2)); |   require('fs').writeFileSync(opts.filepath, JSON.stringify(db, null, 2)); | ||||||
| @ -95,7 +202,6 @@ module.exports = function init (opts) { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     save._saving = true; |     save._saving = true; | ||||||
|     // TODO: replace with something not destructive to original non-json data
 |  | ||||||
|     require('fs').writeFile(opts.filepath, JSON.stringify(db, null, 2), function (err) { |     require('fs').writeFile(opts.filepath, JSON.stringify(db, null, 2), function (err) { | ||||||
|       console.log('done writing'); |       console.log('done writing'); | ||||||
|       var pending = save._pending.splice(0); |       var pending = save._pending.splice(0); | ||||||
| @ -112,54 +218,217 @@ module.exports = function init (opts) { | |||||||
|   }; |   }; | ||||||
|   save._pending = []; |   save._pending = []; | ||||||
| 
 | 
 | ||||||
|  |   function matchPredicate(predicate) { | ||||||
|  |     return function (toCheck) { | ||||||
|  |       // which items match the predicate?
 | ||||||
|  |       if (!toCheck) { | ||||||
|  |         return false; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       // check all the keys in the predicate - only supporting exact match
 | ||||||
|  |       // of at least one listed option for all keys right now
 | ||||||
|  |       if (Object.keys(predicate || {}).some(function (k) { | ||||||
|  |         return [].concat(predicate[k]).indexOf(toCheck[k]) === -1; | ||||||
|  |       })) { | ||||||
|  |         return false; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       // we have a match
 | ||||||
|  |       return true; | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function matchZone(predicate) { | ||||||
|  |     var zonenames = !!predicate.name ? [].concat(predicate.name) : Object.keys(db.zones); | ||||||
|  |     var check = matchPredicate(predicate); | ||||||
|  |     // TODO: swap the filter() for a functional style "loop" recursive function
 | ||||||
|  |     // that lets us return early if we have a limit, etc.
 | ||||||
|  |     var found = zonenames.filter(function (zonename) { | ||||||
|  |       /* | ||||||
|  |       if (predicate.id && predicate.id !== z.id) { return false; } | ||||||
|  |       if (predicate.name && predicate.name !== z.name) { return false; } | ||||||
|  |       */ | ||||||
|  |       return check(db.zones[zonename]); | ||||||
|  |     }).map(function (zonename) { | ||||||
|  |       return db.zones[zonename]; | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     return found; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   var dbApi = { |   var dbApi = { | ||||||
|     save: function () { |     save: function () { | ||||||
|       // hide _pending and _saving from callers
 |       // hide _pending and _saving from callers
 | ||||||
|       var args = [].slice.call(arguments); |       var args = [].slice.call(arguments); | ||||||
|       return save.apply(null, args); |       return save.apply(null, args); | ||||||
|     }, |     }, | ||||||
|     // primaryNameservers really isn't editable - it's literally the list of FQDN's
 |     // peers really isn't editable - it's literally the list of FQDN's
 | ||||||
|     // that this database is replicated to in a multi-master fashion.
 |     // that this database is replicated to in a multi-master fashion.
 | ||||||
|     //
 |     //
 | ||||||
|     // However, lib/store/index.js does plenty to update these records in support
 |     // However, lib/store/index.js does plenty to update these records in support
 | ||||||
|     // of the SOA records that are built from them (as does this file in the "migration"
 |     // of the SOA records that are built from them (as does this file in the "migration"
 | ||||||
|     // section). I'm toying with the idea of not storing them seperately or creating the
 |     // section). I'm toying with the idea of not storing them seperately or creating the
 | ||||||
|     // SOA records somewhat immediately.
 |     // SOA records somewhat immediately.
 | ||||||
|     primaryNameservers: { |     peers: function listPeers(cb) { | ||||||
|       list: function listNameservers() { |       // Most data stores are going to have an asynchronous storage API. If we need
 | ||||||
|         return jsonDeepClone(db.primaryNameservers); |       // synchronous access to the data it is going to have to be cached. If it is
 | ||||||
|       } |       // cached, there is still the issue the cache getting out of sync (a legitimate
 | ||||||
|  |       // issue anyway). If we explicitly make all of these operations async then we
 | ||||||
|  |       // have greater flexibility for store implmentations to address these issues.
 | ||||||
|  |       return setImmediate(cb, null, jsonDeepClone(db.peers)); | ||||||
|     }, |     }, | ||||||
|     zones: { |     zones: { | ||||||
|       list: function listZones() { |       /* | ||||||
|         return jsonDeepClone(db.zones); |       I'm fairly certan that zone names must be unique and therefore are legitimately | ||||||
|       }, |       IDs within the zones namespace. This is similarly true of record names within a zone. | ||||||
|       find: function getZone(predicate, cb) { |       I'm not certain that having a distinct ID adds value and it may add confusion / complexity. | ||||||
|         var found; |        */ | ||||||
|         db.zones.some(function (z) { |       // NOTE: `opts` exists so we can add options - like properties to read - easily in the future
 | ||||||
|           if (z.id && predicate.id === z.id) { found = z; return true; } |       // without modifying the function signature
 | ||||||
|           if (z.name && predicate.name === z.name) { found = z; return true; } |       list: function listZones(predicate, opts, cb) { | ||||||
|  |         // TODO: consider whether we should just return the zone names
 | ||||||
|  |         var found = jsonDeepClone(matchZone(predicate)).map(function (z) { | ||||||
|  |           // This is fairly inefficient!! Consider alternative storage
 | ||||||
|  |           // that does not require deleting the records like this.
 | ||||||
|  |           delete z.records; | ||||||
|  |           return z; | ||||||
|         }); |         }); | ||||||
|         if (!found) { |         return setImmediate(cb, null, found); | ||||||
|           cb(null, null); |  | ||||||
|           return; |  | ||||||
|         } |  | ||||||
|         cb(null, jsonDeepClone(found)); |  | ||||||
|         return; |  | ||||||
|       }, |       }, | ||||||
|       create: function() {}, |       // // NOTE: I'm not sure we need a distinct 'find()' operation in the API
 | ||||||
|       update: function() {}, |       // // unless we are going to limit the output of the
 | ||||||
|       delete: function() {} |       // // 'list()' operation in some incompatible way.
 | ||||||
|  |       // // NOTE: `opts` exists so we can add options - like properties to read - easily in the future
 | ||||||
|  |       // // without modifying the function signature
 | ||||||
|  |       // find: function getZone(predicate, opts, cb) {
 | ||||||
|  |       //   if (!predicate.name || predicate.id) {
 | ||||||
|  |       //     return setImmediate(cb, new Error('Finding a zone requires a `name` or `id`'));
 | ||||||
|  |       //   }
 | ||||||
|  |       //   // TODO: implement a limit / short circuit and possibly offset
 | ||||||
|  |       //   // to allow for paging of zone data.
 | ||||||
|  |       //   var found = matchZone(predicate);
 | ||||||
|  |       //   if (!found[0]) {
 | ||||||
|  |       //     // TODO: make error message more specific?
 | ||||||
|  |       //     return setImmediate(cb, new Error('Zone not found'));
 | ||||||
|  |       //   }
 | ||||||
|  | 
 | ||||||
|  |       //   var z = jsonDeepClone(found[0]);
 | ||||||
|  |       //   delete z.records;
 | ||||||
|  |       //   return setImmediate(cb, null, z);
 | ||||||
|  |       // },
 | ||||||
|  |       create: function createZone(zone, cb) { | ||||||
|  |         // We'll need a lock mechanism of some sort that works
 | ||||||
|  |         // for simultaneous requests and multiple processes.
 | ||||||
|  |         matchZone({ name: zone.name }, function (err, matched) { | ||||||
|  |           if (err) { | ||||||
|  |             return setImmediate(cb, err); | ||||||
|  |           } | ||||||
|  | 
 | ||||||
|  |           var found = matched[0]; | ||||||
|  |           if (found) { | ||||||
|  |             return setImmediate(cb, new Error('Zone ' + zone.name + ' already exists')); | ||||||
|  |           } | ||||||
|  |            | ||||||
|  |           db.zones[zone.name] = prepareZone(zone); | ||||||
|  |           return setImmediate(function () { | ||||||
|  |             cb(null, jsonDeepClone(db.zones[zone.name])); | ||||||
|  |             // release lock
 | ||||||
|  |           }); | ||||||
|  |         }); | ||||||
|  |       }, | ||||||
|  |       update: function updateZone(zone, cb) { | ||||||
|  |         // We'll need a lock mechanism of some sort that works
 | ||||||
|  |         // for simultaneous requests and multiple processes.
 | ||||||
|  |         matchZone({ name: zone.name }, function (err, matched) { | ||||||
|  |           if (err) { | ||||||
|  |             return setImmediate(cb, err); | ||||||
|  |           } | ||||||
|  |           var found = matched[0]; | ||||||
|  |           if (!found) { | ||||||
|  |             return setImmediate(cb, new Error('Zone not found')); | ||||||
|  |           } | ||||||
|  |           // make sure we are not writing records through this interface
 | ||||||
|  |           delete zone.records; | ||||||
|  | 
 | ||||||
|  |           var combined = mergeObjects(found, zone); | ||||||
|  |           db.zones[zone.name] = prepareZone(combined, { isUpdate: true }); | ||||||
|  |           return setImmediate(function () { | ||||||
|  |             cb(null, jsonDeepClone(db.zones[zone.name])); | ||||||
|  |             // release lock
 | ||||||
|  |           }); | ||||||
|  |         }); | ||||||
|  |       }, | ||||||
|  |       delete: function(zone, cb) { | ||||||
|  |         // We'll need a lock mechanism of some sort that works
 | ||||||
|  |         // for simultaneous requests and multiple processes.
 | ||||||
|  |         matchZone({ name: zone.name }, function (err, matched) { | ||||||
|  |           if (err) { | ||||||
|  |             return setImmediate(cb, err); | ||||||
|  |           } | ||||||
|  |           var found = matched[0]; | ||||||
|  |           if (!found) { | ||||||
|  |             return setImmediate(cb, new Error('Zone not found')); | ||||||
|  |           } | ||||||
|  | 
 | ||||||
|  |           delete db.zones[zone.name]; | ||||||
|  |           return setImmediate(function () { | ||||||
|  |             cb(); | ||||||
|  |             // release lock
 | ||||||
|  |           }); | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|     }, |     }, | ||||||
|     records: { |     records: { | ||||||
|       list: function listRecords() { |       list: function listRecords(rPredicate, cb) { | ||||||
|         return jsonDeepClone(db.records); |         var recordNames = [].concat(rPredicate.name); | ||||||
|  |         var check = matchPredicate(rPredicate); | ||||||
|  | 
 | ||||||
|  |         var found = matchZone({ name: rPredicate.zone }).reduce(function (records, zone) { | ||||||
|  |           // get the records from the zone that match the record predicate
 | ||||||
|  |           var zFound = recordNames.filter(function (name) { | ||||||
|  |             return !!zone.records[name]; | ||||||
|  |           }).map(function (name) { | ||||||
|  |             return zone.records[name].filter(check); | ||||||
|  |           }); | ||||||
|  |           return records.concat(zFound); | ||||||
|  |         }, []); | ||||||
|  | 
 | ||||||
|  |         return setImmediate(cb, null, jsonDeepClone(found)); | ||||||
|       }, |       }, | ||||||
|       find: function getRecord(predicate, cb) { |       // find: function getRecord(rPredicate, cb) {
 | ||||||
|  |       //   var recordNames = [].concat(rPredicate.name);
 | ||||||
|  |       //   var check = matchPredicate(rPredicate);
 | ||||||
|  | 
 | ||||||
|  |       //   // TODO: swap the `filter()` and `map()` for a functional style "loop"
 | ||||||
|  |       //   // recursive function that lets us return early if we have a limit, etc.
 | ||||||
|  |       //   var found = matchZone({ name: rPredicate.zone }).reduce(function (records, zone) {
 | ||||||
|  |       //     // get the records from the zone that match the record predicate
 | ||||||
|  |       //     var zFound = recordNames.filter(function (name) {
 | ||||||
|  |       //       return !!zone.records[name];
 | ||||||
|  |       //     }).map(function (name) {
 | ||||||
|  |       //       return zone.records[name].filter(check);
 | ||||||
|  |       //     });
 | ||||||
|  |       //     return records.concat(zFound);
 | ||||||
|  |       //   }, []);
 | ||||||
|  | 
 | ||||||
|  |       //   return setImmediate(cb, null, jsonDeepClone(found[0]));
 | ||||||
|  |       // },
 | ||||||
|  |       create: function(record, cb) { | ||||||
|  |         var zone = matchZone({ name: record.zone })[0]; | ||||||
|  |         if (!zone) { | ||||||
|  |           return setImmediate(cb, new Error('Unble to find zone ' + record.zone + ' to create record')); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         var records = zone.records[record.name] = zone.records[record.name] || []; | ||||||
|  |         var check = matchPredicate(record); | ||||||
|  |         if (records.filter(check)[0]) { | ||||||
|  |           return setImmediate(cb, new Error('Exact record already exists in zone ' + record.zone )); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return setImmediate(cb, null, jsonDeepClone(found)); | ||||||
|       }, |       }, | ||||||
|       create: function() {}, |       update: function(record, cb) {}, | ||||||
|       update: function() {}, |       delete: function(record, cb) {} | ||||||
|       delete: function() {} |  | ||||||
|     } |     } | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user