UNPKG

angular-meteor

Version:

Combining the simplicity and power of AngularJS and Meteor

413 lines (409 loc) 1.67 MB
(function () { /* Imports */ var Meteor = Package.meteor.Meteor; var global = Package.meteor.global; var meteorEnv = Package.meteor.meteorEnv; var _ = Package.underscore._; var Base64 = Package.base64.Base64; /* Package-scope variables */ var EJSON, EJSONTest; (function(){ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // // packages/ejson/ejson.js // // // /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // /** // 1 * @namespace // 2 * @summary Namespace for EJSON functions // 3 */ // 4 EJSON = {}; // 5 EJSONTest = {}; // 6 // 7 // 8 // 9 // Custom type interface definition // 10 /** // 11 * @class CustomType // 12 * @instanceName customType // 13 * @memberOf EJSON // 14 * @summary The interface that a class must satisfy to be able to become an // 15 * EJSON custom type via EJSON.addType. // 16 */ // 17 // 18 /** // 19 * @function typeName // 20 * @memberOf EJSON.CustomType // 21 * @summary Return the tag used to identify this type. This must match the tag used to register this type with [`EJSON.addType`](#ejson_add_type). * @locus Anywhere // 23 * @instance // 24 */ // 25 // 26 /** // 27 * @function toJSONValue // 28 * @memberOf EJSON.CustomType // 29 * @summary Serialize this instance into a JSON-compatible value. // 30 * @locus Anywhere // 31 * @instance // 32 */ // 33 // 34 /** // 35 * @function clone // 36 * @memberOf EJSON.CustomType // 37 * @summary Return a value `r` such that `this.equals(r)` is true, and modifications to `r` do not affect `this` and vice versa. * @locus Anywhere // 39 * @instance // 40 */ // 41 // 42 /** // 43 * @function equals // 44 * @memberOf EJSON.CustomType // 45 * @summary Return `true` if `other` has a value equal to `this`; `false` otherwise. // 46 * @locus Anywhere // 47 * @param {Object} other Another object to compare this to. // 48 * @instance // 49 */ // 50 // 51 // 52 var customTypes = {}; // 53 // Add a custom type, using a method of your choice to get to and // 54 // from a basic JSON-able representation. The factory argument // 55 // is a function of JSON-able --> your object // 56 // The type you add must have: // 57 // - A toJSONValue() method, so that Meteor can serialize it // 58 // - a typeName() method, to show how to look it up in our type table. // 59 // It is okay if these methods are monkey-patched on. // 60 // EJSON.clone will use toJSONValue and the given factory to produce // 61 // a clone, but you may specify a method clone() that will be // 62 // used instead. // 63 // Similarly, EJSON.equals will use toJSONValue to make comparisons, // 64 // but you may provide a method equals() instead. // 65 /** // 66 * @summary Add a custom datatype to EJSON. // 67 * @locus Anywhere // 68 * @param {String} name A tag for your custom type; must be unique among custom data types defined in your project, and must match the result of your type's `typeName` method. * @param {Function} factory A function that deserializes a JSON-compatible value into an instance of your type. This should match the serialization performed by your type's `toJSONValue` method. */ // 71 EJSON.addType = function (name, factory) { // 72 if (_.has(customTypes, name)) // 73 throw new Error("Type " + name + " already present"); // 74 customTypes[name] = factory; // 75 }; // 76 // 77 var isInfOrNan = function (obj) { // 78 return _.isNaN(obj) || obj === Infinity || obj === -Infinity; // 79 }; // 80 // 81 var builtinConverters = [ // 82 { // Date // 83 matchJSONValue: function (obj) { // 84 return _.has(obj, '$date') && _.size(obj) === 1; // 85 }, // 86 matchObject: function (obj) { // 87 return obj instanceof Date; // 88 }, // 89 toJSONValue: function (obj) { // 90 return {$date: obj.getTime()}; // 91 }, // 92 fromJSONValue: function (obj) { // 93 return new Date(obj.$date); // 94 } // 95 }, // 96 { // NaN, Inf, -Inf. (These are the only objects with typeof !== 'object' // 97 // which we match.) // 98 matchJSONValue: function (obj) { // 99 return _.has(obj, '$InfNaN') && _.size(obj) === 1; // 100 }, // 101 matchObject: isInfOrNan, // 102 toJSONValue: function (obj) { // 103 var sign; // 104 if (_.isNaN(obj)) // 105 sign = 0; // 106 else if (obj === Infinity) // 107 sign = 1; // 108 else // 109 sign = -1; // 110 return {$InfNaN: sign}; // 111 }, // 112 fromJSONValue: function (obj) { // 113 return obj.$InfNaN/0; // 114 } // 115 }, // 116 { // Binary // 117 matchJSONValue: function (obj) { // 118 return _.has(obj, '$binary') && _.size(obj) === 1; // 119 }, // 120 matchObject: function (obj) { // 121 return typeof Uint8Array !== 'undefined' && obj instanceof Uint8Array // 122 || (obj && _.has(obj, '$Uint8ArrayPolyfill')); // 123 }, // 124 toJSONValue: function (obj) { // 125 return {$binary: Base64.encode(obj)}; // 126 }, // 127 fromJSONValue: function (obj) { // 128 return Base64.decode(obj.$binary); // 129 } // 130 }, // 131 { // Escaping one level // 132 matchJSONValue: function (obj) { // 133 return _.has(obj, '$escape') && _.size(obj) === 1; // 134 }, // 135 matchObject: function (obj) { // 136 if (_.isEmpty(obj) || _.size(obj) > 2) { // 137 return false; // 138 } // 139 return _.any(builtinConverters, function (converter) { // 140 return converter.matchJSONValue(obj); // 141 }); // 142 }, // 143 toJSONValue: function (obj) { // 144 var newObj = {}; // 145 _.each(obj, function (value, key) { // 146 newObj[key] = EJSON.toJSONValue(value); // 147 }); // 148 return {$escape: newObj}; // 149 }, // 150 fromJSONValue: function (obj) { // 151 var newObj = {}; // 152 _.each(obj.$escape, function (value, key) { // 153 newObj[key] = EJSON.fromJSONValue(value); // 154 }); // 155 return newObj; // 156 } // 157 }, // 158 { // Custom // 159 matchJSONValue: function (obj) { // 160 return _.has(obj, '$type') && _.has(obj, '$value') && _.size(obj) === 2; // 161 }, // 162 matchObject: function (obj) { // 163 return EJSON._isCustomType(obj); // 164 }, // 165 toJSONValue: function (obj) { // 166 var jsonValue = Meteor._noYieldsAllowed(function () { // 167 return obj.toJSONValue(); // 168 }); // 169 return {$type: obj.typeName(), $value: jsonValue}; // 170 }, // 171 fromJSONValue: function (obj) { // 172 var typeName = obj.$type; // 173 if (!_.has(customTypes, typeName)) // 174 throw new Error("Custom EJSON type " + typeName + " is not defined"); // 175 var converter = customTypes[typeName]; // 176 return Meteor._noYieldsAllowed(function () { // 177 return converter(obj.$value); // 178 }); // 179 } // 180 } // 181 ]; // 182 // 183 EJSON._isCustomType = function (obj) { // 184 return obj && // 185 typeof obj.toJSONValue === 'function' && // 186 typeof obj.typeName === 'function' && // 187 _.has(customTypes, obj.typeName()); // 188 }; // 189 // 190 EJSON._getTypes = function () { // 191 return customTypes; // 192 }; // 193 // 194 EJSON._getConverters = function () { // 195 return builtinConverters; // 196 }; // 197 // 198 // for both arrays and objects, in-place modification. // 199 var adjustTypesToJSONValue = // 200 EJSON._adjustTypesToJSONValue = function (obj) { // 201 // Is it an atom that we need to adjust? // 202 if (obj === null) // 203 return null; // 204 var maybeChanged = toJSONValueHelper(obj); // 205 if (maybeChanged !== undefined) // 206 return maybeChanged; // 207 // 208 // Other atoms are unchanged. // 209 if (typeof obj !== 'object') // 210 return obj; // 211 // 212 // Iterate over array or object structure. // 213 _.each(obj, function (value, key) { // 214 if (typeof value !== 'object' && value !== undefined && // 215 !isInfOrNan(value)) // 216 return; // continue // 217 // 218 var changed = toJSONValueHelper(value); // 219 if (changed) { // 220 obj[key] = changed; // 221 return; // on to the next key // 222 } // 223 // if we get here, value is an object but not adjustable // 224 // at this level. recurse. // 225 adjustTypesToJSONValue(value); // 226 }); // 227 return obj; // 228 }; // 229 // 230 // Either return the JSON-compatible version of the argument, or undefined (if // 231 // the item isn't itself replaceable, but maybe some fields in it are) // 232 var toJSONValueHelper = function (item) { // 233 for (var i = 0; i < builtinConverters.length; i++) { // 234 var converter = builtinConverters[i]; // 235 if (converter.matchObject(item)) { // 236 return converter.toJSONValue(item); // 237 } // 238 } // 239 return undefined; // 240 }; // 241 // 242 /** // 243 * @summary Serialize an EJSON-compatible value into its plain JSON representation. // 244 * @locus Anywhere // 245 * @param {EJSON} val A value to serialize to plain JSON. // 246 */ // 247 EJSON.toJSONValue = function (item) { // 248 var changed = toJSONValueHelper(item); // 249 if (changed !== undefined) // 250 return changed; // 251 if (typeof item === 'object') { // 252 item = EJSON.clone(item); // 253 adjustTypesToJSONValue(item); // 254 } // 255 return item; // 256 }; // 257 // 258 // for both arrays and objects. Tries its best to just // 259 // use the object you hand it, but may return something // 260 // different if the object you hand it itself needs changing. // 261 // // 262 var adjustTypesFromJSONValue = // 263 EJSON._adjustTypesFromJSONValue = function (obj) { // 264 if (obj === null) // 265 return null; // 266 var maybeChanged = fromJSONValueHelper(obj); // 267 if (maybeChanged !== obj) // 268 return maybeChanged; // 269 // 270 // Other atoms are unchanged. // 271 if (typeof obj !== 'object') // 272 return obj; // 273 // 274 _.each(obj, function (value, key) { // 275 if (typeof value === 'object') { // 276 var changed = fromJSONValueHelper(value); // 277 if (value !== changed) { // 278 obj[key] = changed; // 279 return; // 280 } // 281 // if we get here, value is an object but not adjustable // 282 // at this level. recurse. // 283 adjustTypesFromJSONValue(value); // 284 } // 285 }); // 286 return obj; // 287 }; // 288 // 289 // Either return the argument changed to have the non-json // 290 // rep of itself (the Object version) or the argument itself. // 291 // 292 // DOES NOT RECURSE. For actually getting the fully-changed value, use // 293 // EJSON.fromJSONValue // 294 var fromJSONValueHelper = function (value) { // 295 if (typeof value === 'object' && value !== null) { // 296 if (_.size(value) <= 2 // 297 && _.all(value, function (v, k) { // 298 return typeof k === 'string' && k.substr(0, 1) === '$'; // 299 })) { // 300 for (var i = 0; i < builtinConverters.length; i++) { // 301 var converter = builtinConverters[i]; // 302 if (converter.matchJSONValue(value)) { // 303 return converter.fromJSONValue(value); // 304 } // 305 } // 306 } // 307 } // 308 return value; // 309 }; // 310 // 311 /** // 312 * @summary Deserialize an EJSON value from its plain JSON representation. // 313 * @locus Anywhere // 314 * @param {JSONCompatible} val A value to deserialize into EJSON. // 315 */ // 316 EJSON.fromJSONValue = function (item) { // 317 var changed = fromJSONValueHelper(item); // 318 if (changed === item && typeof item === 'object') { // 319 item = EJSON.clone(item); // 320 adjustTypesFromJSONValue(item); // 321 return item; // 322 } else { // 323 return changed; // 324 } // 325 }; // 326 // 327 /** // 328 * @summary Serialize a value to a string. // 329 // 330 For EJSON values, the serialization fully represents the value. For non-EJSON values, serializes the same way as `JSON.stringify`. * @locus Anywhere // 332 * @param {EJSON} val A value to stringify. // 333 * @param {Object} [options] // 334 * @param {Boolean | Integer | String} options.indent Indents objects and arrays for easy readability. When `true`, indents by 2 spaces; when an integer, indents by that number of spaces; and when a string, uses the string as the indentation pattern. * @param {Boolean} options.canonical When `true`, stringifies keys in an object in sorted order. // 336 */ // 337 EJSON.stringify = function (item, options) { // 338 var json = EJSON.toJSONValue(item); // 339 if (options && (options.canonical || options.indent)) { // 340 return EJSON._canonicalStringify(json, options); // 341 } else { // 342 return JSON.stringify(json); // 343 } // 344 }; // 345 // 346 /** // 347 * @summary Parse a string into an EJSON value. Throws an error if the string is not valid EJSON. // 348 * @locus Anywhere // 349 * @param {String} str A string to parse into an EJSON value. // 350 */ // 351 EJSON.parse = function (item) { // 352 if (typeof item !== 'string') // 353 throw new Error("EJSON.parse argument should be a string"); // 354 return EJSON.fromJSONValue(JSON.parse(item)); // 355 }; // 356 // 357 /** // 358 * @summary Returns true if `x` is a buffer of binary data, as returned from [`EJSON.newBinary`](#ejson_new_binary). * @param {Object} x The variable to check. // 360 * @locus Anywhere // 361 */ // 362 EJSON.isBinary = function (obj) { // 363 return !!((typeof Uint8Array !== 'undefined' && obj instanceof Uint8Array) || // 364 (obj && obj.$Uint8ArrayPolyfill)); // 365 }; // 366 // 367 /** // 368 * @summary Return true if `a` and `b` are equal to each other. Return false otherwise. Uses the `equals` method on `a` if present, otherwise performs a deep comparison. * @locus Anywhere // 370 * @param {EJSON} a // 371 * @param {EJSON} b // 372 * @param {Object} [options] // 373 * @param {Boolean} options.keyOrderSensitive Compare in key sensitive order, if supported by the JavaScript implementation. For example, `{a: 1, b: 2}` is equal to `{b: 2, a: 1}` only when `keyOrderSensitive` is `false`. The default is `false`. */ // 375 EJSON.equals = function (a, b, options) { // 376 var i; // 377 var keyOrderSensitive = !!(options && options.keyOrderSensitive); // 378 if (a === b) // 379 return true; // 380 if (_.isNaN(a) && _.isNaN(b)) // 381 return true; // This differs from the IEEE spec for NaN equality, b/c we don't want // 382 // anything ever with a NaN to be poisoned from becoming equal to anything. // 383 if (!a || !b) // if either one is falsy, they'd have to be === to be equal // 384 return false; // 385 if (!(typeof a === 'object' && typeof b === 'object')) // 386 return false; // 387 if (a instanceof Date && b instanceof Date) // 388 return a.valueOf() === b.valueOf(); // 389 if (EJSON.isBinary(a) && EJSON.isBinary(b)) { // 390 if (a.length !== b.length) // 391 return false; // 392 for (i = 0; i < a.length; i++) {