////////////////////////////////////////////////////////////////////////// // // // This is a generated file. You can view the original // // source in your browser if your browser supports source maps. // // // // If you are using Chrome, open the Developer Tools and click the gear // // icon in its lower right corner. In the General Settings panel, turn // // on 'Enable source maps'. // // // // If you are using Firefox 23, go to `about:config` and set the // // `devtools.debugger.source-maps-enabled` preference to true. // // (The preference should be on by default in Firefox 24; versions // // older than 23 do not support source maps.) // // // ////////////////////////////////////////////////////////////////////////// (function () { /* Imports */ var Meteor = Package.meteor.Meteor; var JSON = Package.json.JSON; 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 // 191 // for both arrays and objects, in-place modification. // 192 var adjustTypesToJSONValue = // 193 EJSON._adjustTypesToJSONValue = function (obj) { // 194 // Is it an atom that we need to adjust? // 195 if (obj === null) // 196 return null; // 197 var maybeChanged = toJSONValueHelper(obj); // 198 if (maybeChanged !== undefined) // 199 return maybeChanged; // 200 // 201 // Other atoms are unchanged. // 202 if (typeof obj !== 'object') // 203 return obj; // 204 // 205 // Iterate over array or object structure. // 206 _.each(obj, function (value, key) { // 207 if (typeof value !== 'object' && value !== undefined && // 208 !isInfOrNan(value)) // 209 return; // continue // 210 // 211 var changed = toJSONValueHelper(value); // 212 if (changed) { // 213 obj[key] = changed; // 214 return; // on to the next key // 215 } // 216 // if we get here, value is an object but not adjustable // 217 // at this level. recurse. // 218 adjustTypesToJSONValue(value); // 219 }); // 220 return obj; // 221 }; // 222 // 223 // Either return the JSON-compatible version of the argument, or undefined (if // 224 // the item isn't itself replaceable, but maybe some fields in it are) // 225 var toJSONValueHelper = function (item) { // 226 for (var i = 0; i < builtinConverters.length; i++) { // 227 var converter = builtinConverters[i]; // 228 if (converter.matchObject(item)) { // 229 return converter.toJSONValue(item); // 230 } // 231 } // 232 return undefined; // 233 }; // 234 // 235 /** // 236 * @summary Serialize an EJSON-compatible value into its plain JSON representation. // 237 * @locus Anywhere // 238 * @param {EJSON} val A value to serialize to plain JSON. // 239 */ // 240 EJSON.toJSONValue = function (item) { // 241 var changed = toJSONValueHelper(item); // 242 if (changed !== undefined) // 243 return changed; // 244 if (typeof item === 'object') { // 245 item = EJSON.clone(item); // 246 adjustTypesToJSONValue(item); // 247 } // 248 return item; // 249 }; // 250 // 251 // for both arrays and objects. Tries its best to just // 252 // use the object you hand it, but may return something // 253 // different if the object you hand it itself needs changing. // 254 // // 255 var adjustTypesFromJSONValue = // 256 EJSON._adjustTypesFromJSONValue = function (obj) { // 257 if (obj === null) // 258 return null; // 259 var maybeChanged = fromJSONValueHelper(obj); // 260 if (maybeChanged !== obj) // 261 return maybeChanged; // 262 // 263 // Other atoms are unchanged. // 264 if (typeof obj !== 'object') // 265 return obj; // 266 // 267 _.each(obj, function (value, key) { // 268 if (typeof value === 'object') { // 269 var changed = fromJSONValueHelper(value); // 270 if (value !== changed) { // 271 obj[key] = changed; // 272 return; // 273 } // 274 // if we get here, value is an object but not adjustable // 275 // at this level. recurse. // 276 adjustTypesFromJSONValue(value); // 277 } // 278 }); // 279 return obj; // 280 }; // 281 // 282 // Either return the argument changed to have the non-json // 283 // rep of itself (the Object version) or the argument itself. // 284 // 285 // DOES NOT RECURSE. For actually getting the fully-changed value, use // 286 // EJSON.fromJSONValue // 287 var fromJSONValueHelper = function (value) { // 288 if (typeof value === 'object' && value !== null) { // 289 if (_.size(value) <= 2 // 290 && _.all(value, function (v, k) { // 291 return typeof k === 'string' && k.substr(0, 1) === '$'; // 292 })) { // 293 for (var i = 0; i < builtinConverters.length; i++) { // 294 var converter = builtinConverters[i]; // 295 if (converter.matchJSONValue(value)) { // 296 return converter.fromJSONValue(value); // 297 } // 298 } // 299 } // 300 } // 301 return value; // 302 }; // 303 // 304 /** // 305 * @summary Deserialize an EJSON value from its plain JSON representation. // 306 * @locus Anywhere // 307 * @param {JSONCompatible} val A value to deserialize into EJSON. // 308 */ // 309 EJSON.fromJSONValue = function (item) { // 310 var changed = fromJSONValueHelper(item); // 311 if (changed === item && typeof item === 'object') { // 312 item = EJSON.clone(item); // 313 adjustTypesFromJSONValue(item); // 314 return item; // 315 } else { // 316 return changed; // 317 } // 318 }; // 319 // 320 /** // 321 * @summary Serialize a value to a string. // 322 // 323 For EJSON values, the serialization fully represents the value. For non-EJSON values, serializes the same way as `JSON.stringify`. * @locus Anywhere // 325 * @param {EJSON} val A value to stringify. // 326 * @param {Object} [options] // 327 * @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. // 329 */ // 330 EJSON.stringify = function (item, options) { // 331 var json = EJSON.toJSONValue(item); // 332 if (options && (options.canonical || options.indent)) { // 333 return EJSON._canonicalStringify(json, options); // 334 } else { // 335 return JSON.stringify(json); // 336 } // 337 }; // 338 // 339 /** // 340 * @summary Parse a string into an EJSON value. Throws an error if the string is not valid EJSON. // 341 * @locus Anywhere // 342 * @param {String} str A string to parse into an EJSON value. // 343 */ // 344 EJSON.parse = function (item) { // 345 if (typeof item !== 'string') // 346 throw new Error("EJSON.parse argument should be a string"); // 347 return EJSON.fromJSONValue(JSON.parse(item)); // 348 }; // 349 // 350 /** // 351 * @summary Returns true if `x` is a buffer of binary data, as returned from [`EJSON.newBinary`](#ejson_new_binary). // 352 * @param {Object} x The variable to check. // 353 * @locus Anywhere // 354 */ // 355 EJSON.isBinary = function (obj) { // 356 return !!((typeof Uint8Array !== 'undefined' && obj instanceof Uint8Array) || // 357 (obj && obj.$Uint8ArrayPolyfill)); // 358 }; // 359 // 360 /** // 361 * @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 // 363 * @param {EJSON} a // 364 * @param {EJSON} b // 365 * @param {Object} [options] // 366 * @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`. */ // 368 EJSON.equals = function (a, b, options) { // 369 var i; // 370 var keyOrderSensitive = !!(options && options.keyOrderSensitive); // 371 if (a === b) // 372 return true; // 373 if (_.isNaN(a) && _.isNaN(b)) // 374 return true; // This differs from the IEEE spec for NaN equality, b/c we don't want // 375 // anything ever with a NaN to be poisoned from becoming equal to anything. // 376 if (!a || !b) // if either one is falsy, they'd have to be === to be equal // 377 return false; // 378 if (!(typeof a === 'object' && typeof b === 'object')) // 379 return false; // 380 if (a instanceof Date && b instanceof Date) // 381 return a.valueOf() === b.valueOf(); // 382 if (EJSON.isBinary(a) && EJSON.isBinary(b)) { // 383 if (a.length !== b.length) // 384 return false; // 385 for (i = 0; i < a.length; i++) { // 386 if (a[i] !== b[i]) // 387 return false; // 388 } // 389 return true; // 390 } // 391 if (typeof (a.equals) === 'function') // 392 return a.equals(b, options); // 393 if (typeof (b.equals) === 'function') // 394 return b.equals(a, options); // 395 if (a instanceof Array) { // 396 if (!(b instanceof Array)) // 397 return false; // 398 if (a.length !== b.length) // 399 return false; // 400 for (i = 0; i < a.length; i++) { // 401 if (!EJSON.equals(a[i], b[i], options)) // 402 return false; // 403 } // 404 return true; // 405 } // 406 // fallback for custom types that don't implement their own equals // 407 switch (EJSON._isCustomType(a) + EJSON._isCustomType(b)) { // 408 case 1: return false; // 409 case 2: return EJSON.equals(EJSON.toJSONValue(a), EJSON.toJSONValue(b)); // 410 } // 411 // fall back to structural equality of objects // 412 var ret; // 413 if (keyOrderSensitive) { // 414 var bKeys = []; // 415 _.each(b, function (val, x) { // 416 bKeys.push(x); // 417 }); // 418 i = 0; // 419 ret = _.all(a, function (val, x) { // 420 if (i >= bKeys.length) { // 421 return false; // 422 } // 423 if (x !== bKeys[i]) { // 424 return false; // 425 } // 426 if (!EJSON.equals(val, b[bKeys[i]], options)) { // 427 return false; // 428 } // 429 i++; // 430 return true; // 431 }); // 432 return ret && i === bKeys.length; // 433 } else { // 434 i = 0; // 435 ret = _.all(a, function (val, key) { // 436 if (!_.has(b, key)) { // 437 return false; // 438 } // 439 if (!EJSON.equals(val, b[key], options)) { // 440 return false; // 441 } // 442 i++; // 443 return true; // 444 }); // 445 return ret && _.size(b) === i; // 446 } // 447 }; // 448 // 449 /** // 450 * @summary Return a deep copy of `val`. // 451 * @locus Anywhere // 452 * @param {EJSON} val A value to copy. // 453 */ // 454 EJSON.clone = function (v) { // 455 var ret; // 456 if (typeof v !== "object") // 457 return v; // 458 if (v === null) // 459 return null; // null has typeof "object" // 460 if (v instanceof Date) // 461 return new Date(v.getTime()); // 462 // RegExps are not really EJSON elements (eg we don't define a serialization // 463 // for them), but they're immutable anyway, so we can support them in clone. // 464 if (v instanceof RegExp) // 465 return v; // 466 if (EJSON.isBinary(v)) { // 467 ret = EJSON.newBinary(v.length); // 468 for (var i = 0; i < v.length; i++) { // 469 ret[i] = v[i]; // 470 } // 471 return ret; // 472 } // 473 // XXX: Use something better than underscore's isArray // 474 if (_.isArray(v) || _.isArguments(v)) { // 475 // For some reason, _.map doesn't work in this context on Opera (weird test // 476 // failures). // 477 ret = []; // 478 for (i = 0; i < v.length; i++) // 479 ret[i] = EJSON.clone(v[i]); // 480 return ret; // 481 } // 482 // handle general user-defined typed Objects if they have a clone method // 483 if (typeof v.clone === 'function') { // 484 return v.clone(); // 485 } // 486 // handle other custom types // 487 if (EJSON._isCustomType(v)) { // 488 return EJSON.fromJSONValue(EJSON.clone(EJSON.toJSONValue(v)), true); // 489 } // 490 // handle other objects // 491 ret = {}; // 492 _.each(v, function (value, key) { // 493 ret[key] = EJSON.clone(value); // 494 }); // 495 return ret; // 496 }; // 497 // 498 /** // 499 * @summary Allocate a new buffer of binary data that EJSON can serialize. // 500 * @locus Anywhere // 501 * @param {Number} size The number of bytes of binary data to allocate. // 502 */ // 503 // EJSON.newBinary is the public documented API for this functionality, // 504 // but the implementation is in the 'base64' package to avoid // 505 // introducing a circular dependency. (If the implementation were here, // 506 // then 'base64' would have to use EJSON.newBinary, and 'ejson' would // 507 // also have to use 'base64'.) // 508 EJSON.newBinary = Base64.newBinary; // 509 // 510 /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// }).call(this); (function () { /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // // packages/ejson/stringify.js // // // /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // Based on json2.js from https://github.com/douglascrockford/JSON-js // 1 // // 2 // json2.js // 3 // 2012-10-08 // 4 // // 5 // Public Domain. // 6 // // 7 // NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. // 8 // 9 function quote(string) { // 10 return JSON.stringify(string); // 11 } // 12 // 13 var str = function (key, holder, singleIndent, outerIndent, canonical) { // 14 // 15 // Produce a string from holder[key]. // 16 // 17 var i; // The loop counter. // 18 var k; // The member key. // 19 var v; // The member value. // 20 var length; // 21 var innerIndent = outerIndent; // 22 var partial; // 23 var value = holder[key]; // 24 // 25 // What happens next depends on the value's type. // 26 // 27 switch (typeof value) { // 28 case 'string': // 29 return quote(value); // 30 case 'number': // 31 // JSON numbers must be finite. Encode non-finite numbers as null. // 32 return isFinite(value) ? String(value) : 'null'; // 33 case 'boolean': // 34 return String(value); // 35 // If the type is 'object', we might be dealing with an object or an array or // 36 // null. // 37 case 'object': // 38 // Due to a specification blunder in ECMAScript, typeof null is 'object', // 39 // so watch out for that case. // 40 if (!value) { // 41 return 'null'; // 42 } // 43 // Make an array to hold the partial results of stringifying this object value. // 44 innerIndent = outerIndent + singleIndent; // 45 partial = []; // 46 // 47 // Is the value an array? // 48 if (_.isArray(value) || _.isArguments(value)) { // 49 // 50 // The value is an array. Stringify every element. Use null as a placeholder // 51 // for non-JSON values. // 52 // 53 length = value.length; // 54 for (i = 0; i < length; i += 1) { // 55 partial[i] = str(i, value, singleIndent, innerIndent, canonical) || 'null'; // 56 } // 57 // 58 // Join all of the elements together, separated with commas, and wrap them in // 59 // brackets. // 60 // 61 if (partial.length === 0) { // 62 v = '[]'; // 63 } else if (innerIndent) { // 64 v = '[\n' + innerIndent + partial.join(',\n' + innerIndent) + '\n' + outerIndent + ']'; // 65 } else { // 66 v = '[' + partial.join(',') + ']'; // 67 } // 68 return v; // 69 } // 70 // 71 // 72 // Iterate through all of the keys in the object. // 73 var keys = _.keys(value); // 74 if (canonical) // 75 keys = keys.sort(); // 76 _.each(keys, function (k) { // 77 v = str(k, value, singleIndent, innerIndent, canonical); // 78 if (v) { // 79 partial.push(quote(k) + (innerIndent ? ': ' : ':') + v); // 80 } // 81 }); // 82 // 83 // 84 // Join all of the member texts together, separated with commas, // 85 // and wrap them in braces. // 86 // 87 if (partial.length === 0) { // 88 v = '{}'; // 89 } else if (innerIndent) { // 90 v = '{\n' + innerIndent + partial.join(',\n' + innerIndent) + '\n' + outerIndent + '}'; // 91 } else { // 92 v = '{' + partial.join(',') + '}'; // 93 } // 94 return v; // 95 } // 96 } // 97 // 98 // If the JSON object does not yet have a stringify method, give it one. // 99 // 100 EJSON._canonicalStringify = function (value, options) { // 101 // Make a fake root object containing our value under the key of ''. // 102 // Return the result of stringifying the value. // 103 options = _.extend({ // 104 indent: "", // 105 canonical: false // 106 }, options); // 107 if (options.indent === true) { // 108 options.indent = " "; // 109 } else if (typeof options.indent === 'number') { // 110 var newIndent = ""; // 111 for (var i = 0; i < options.indent; i++) { // 112 newIndent += ' '; // 113 } // 114 options.indent = newIndent; // 115 } // 116 return str('', {'': value}, options.indent, "", options.canonical); // 117 }; // 118 // 119 /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// }).call(this); /* Exports */ if (typeof Package === 'undefined') Package = {}; Package.ejson = { EJSON: EJSON, EJSONTest: EJSONTest }; })();