├── .gitignore ├── .jshintrc ├── .npmignore ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── bin ├── api-docs.js └── mongodb-dataset-generator.js ├── conf.json ├── examples ├── 20_embedded_basic.json ├── 21_embedded_multi.json ├── 22_embedded_level.json ├── 23_embedded_complex.json ├── 30_array_field.json ├── 31_array_doc.json ├── 32_array_embed.json ├── 33_array_arrays.json ├── 91_complex.json ├── demo.json ├── girbal.json ├── me_in_a_nutshell.json └── v.json ├── index.js ├── lib ├── api │ ├── context.js │ ├── helpers.js │ ├── index.js │ ├── random.js │ └── type.js ├── generator.js ├── index.js └── schema │ ├── array.js │ ├── document.js │ ├── entry.js │ ├── field.js │ ├── helpers.js │ └── index.js ├── package.json └── test ├── configArray.test.js ├── generator.test.js ├── helpers.js ├── mocha.opts ├── scoping.test.js ├── smoke.test.js ├── structure ├── array.test.js ├── complex.test.js └── embed.test.js ├── types.test.js └── utils.test.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | .dist/ 4 | .build/ 5 | npm-debug.log 6 | coverage/ 7 | 8 | .idea/ 9 | *.iml 10 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "eqeqeq": true, 3 | "node": true, 4 | "browser": true, 5 | "newcap": true, 6 | "predef": [ 7 | "describe", 8 | "it", 9 | "before", 10 | "beforeEach", 11 | "after", 12 | "afterEach" 13 | ], 14 | "unused": true, 15 | "trailing": true, 16 | "loopfunc": true, 17 | "undef": true, 18 | "quotmark": "single", 19 | "maxlen": 80, 20 | "multistr": true 21 | } 22 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | .dist/ 4 | .build/ 5 | npm-debug.log 6 | coverage/ 7 | 8 | .idea/ 9 | *.iml 10 | 11 | test/ 12 | examples/ 13 | .jshintrc 14 | CONTRIBUTING.md 15 | .travis.yml 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.10' 4 | services: 5 | - mongodb 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Great to have you here. These are a few ways you can help make this project better: 4 | 5 | - scratch an itch and implement a new feature using our [workflow](#workflow) 6 | - add an example to README.md 7 | - avoid sending pull requests related to "style" or whitespace as we have 8 | tools in our workflow to do this already 9 | - [creating a new issue](http://github.com/imlucas/mongodb-datasets/issues) 10 | if there is something you need this project to do that it currently doesn't. 11 | We're always more than happy to chat about other projects. 12 | 13 | ## Workflow 14 | 15 | 1. Fork the repository on GitHub 16 | 1. Create a branch with a name that breifly describes your feature 17 | 1. Implement your feature or bug fix 18 | 1. Add new cases to `./tests.js` that verify your bug fix or make sure no one 19 | unintentionally breaks your feature in the future and run them with `npm test` 20 | 1. Add comments around your new code that explain what's happening 21 | 1. @todo: Run `npm run-script check` to tidy up your code and show you where you 22 | might want to change things to be safer or easier to use 23 | 1. Commit and push your changes to your branch then submit a pull request 24 | 25 | Don’t get discouraged! We estimate that the response time from the 26 | maintainers is less than 24 hours. 27 | 28 | ## Bugs 29 | 30 | It would be extremely helpful, if you have the time, to 31 | look at existing bugs and help us understand if: 32 | 33 | * The bug is reproducible? 34 | * Is it reproducible in other environments (browsers)? 35 | * If they weren't included originally, what are the steps to reproduce? 36 | 37 | You can report new bugs by 38 | [creating a new issue](http://github.com/imlucas/mongodb-datasets/issues). 39 | Please include as much information as possible about your environment 40 | ("I am using node.js v 0.10.28 on OSX Mavericks"). Actual code is always 41 | more valuable than an explanation, so please include a link to a GitHub 42 | gist or include a snippet directly in your issue description. 43 | 44 | ## Versioning 45 | 46 | This library aims to adhere to [Semantic Versioning2.0.0](http://semver.org/). 47 | Violations of this scheme should be reported as bugs. Specifically, if a 48 | minor or patch version is released that breaks backward compatibility, 49 | that version should be immediately yanked and/or a new version should be 50 | immediately released that restores compatibility. 51 | 52 | ## Pro 53 | 54 | If you've been doing JS for awhile, thanks for checking this out. The quick 55 | version of the house rules are: 56 | 57 | - Indent 2 spaces 58 | - Single quotes > double quotes 59 | - Don't use ASI 60 | - Maximum line length is 80 61 | - Don't check in `dist` files 62 | - Avoid mutation 63 | - Write tests that make sense 64 | - Test must always be green 65 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Lucas Hrabovsky 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mongodb-dataset-generator 2 | 3 | What's a database without any data? 4 | 5 | > :construction: **Warning** This was an experiment for the summer of 2014 MongoDB Summer Intern program and is not for production use. 6 | 7 | ## Installation 8 | 9 | To use the `mongodb-dataset-generator` command, install mongodb-dataset-generator globally: 10 | 11 | $ npm install -g mongodb-dataset-generator 12 | 13 | To use the Javascript API, in your project directory: 14 | 15 | $ npm install mongodb-dataset-generator --save 16 | 17 | ## A Simple Example 18 | 19 | test_schema.json 20 | ```json 21 | { 22 | "_id": "{{counter()}}", 23 | "name": "{{chance.name()}}", 24 | "phones": [ 3, "{{chance.phone()}}" ], 25 | "title": "Software {{util.sample(['Engineer', 'Programmer'])}}" 26 | } 27 | ``` 28 | 29 | Using the command: 30 | 31 | $ mongodb-dataset-generator test_schema.json -n 10 -o datasets.json 32 | 33 | Or using the Javascript API: 34 | ```javascript 35 | var fs = require('fs'), 36 | es = require('event-stream'), 37 | assert = require('assert'), 38 | datasets = require('mongodb-dataset-generator'); 39 | 40 | fs.createReadStream('./test_schema.json') 41 | .pipe(datasets.createGeneratorStream({size: 10}) 42 | .pipe(es.writeArray(function (err, array) { 43 | assert.equal(10, array.length); 44 | }); 45 | ``` 46 | 47 | ## Usage 48 | 49 | `createGeneratorStream(options)` creates a 50 | [Transform](http://nodejs.org/api/stream.html#stream_class_stream_transform) 51 | stream, which consumes a Readable stream containing a template schema and 52 | produces a stream of generated documents in the form of Javascript objects. 53 | 54 | ### Options 55 | 56 | * `size` - the number of documents to be populated 57 | * `schema` - an object representing the template schema of your data. 58 | If present, the returned stream effectively behaves as a Writable stream. 59 | 60 | ### Command line 61 | 62 | You can also use mongodb-dataset-generator in cli. Examples: 63 | 64 | $ mongodb-dataset-generator schema.json -n 10 -o dump.out 65 | $ cat schema.json | mongodb-dataset-generator -n 10 -o - 66 | 67 | ``` 68 | Options: 69 | -0 Path to a template schema file [string] 70 | -n, --size Number of documents to generate [required] 71 | -o, --out Path to output file. Use "-" for stdout [required] 72 | --pretty Whether to format results 73 | -h, --help Show help message 74 | ``` 75 | 76 | ## Building your schema 77 | 78 | The schema is a JSON or Javascript object which is used as the template of every 79 | single document to be inserted into MongoDB database. The following content in 80 | this section discusses how to specify the value of each name/value pair of the 81 | object. 82 | 83 | ### Basics 84 | 85 | The value can be any primitive data types, such as boolean, number, array, and 86 | object. When the value is a string, it can be used to evaluate Javascript 87 | expressions. All string segments intended to be treated as expressions must be 88 | surrounded by `{{ }}`. A mix of regular string and expressions are 89 | allowed, whereas a mix of different types is not. Some examples: 90 | * `{ "boolean": true }` 91 | * `{ "brackets_parade": [ { 1: { 2: [ { 3: 3 } ] } } ] }` 92 | * `{ "mix": "1 + 1 = {{ 1+1 }}" }` 93 | 94 | ### Random data 95 | 96 | This project uses [chance.js](http://chancejs.com/) and 97 | [faker.js](https://github.com/FotoVerite/Faker.js) as the internal random data 98 | generator. To invoke them, use `faker.` and `chance.`: 99 | * `{ "use_chance": "{{ chance.name({ gender: 'female' }) }}" }` 100 | * `{ "use_faker": "{{ faker.Company.catchPhrase() }}" }` 101 | 102 | ### Type conversion 103 | 104 | Maybe you've already noticed. It's not very useful to generate a string from 105 | `"{{chance.year()}}"` which is expected to apply commands such as `$gte`. 106 | Since its MongoDB-specific nature, the package currently supports common bson 107 | types as in [bson](https://github.com/mongodb/js-bson) module, such as Number, 108 | Timestamp, Date, and ObjectID. Note that once conversion is triggered, the 109 | target object will be the only produced content. Some examples: 110 | * `{ "date": "{{ Date(chance.date()) }}" }` becomes `ISODate(...)` in MongoDB 111 | * `{ "two": "{{ Number(1) + Number(1) }}" }` produces `{ "two": 1 }` 112 | 113 | ### Document-level scope 114 | 115 | You can make use of `this` keyword in expressions to get access to values of 116 | other name/value pairs. But its behavior is different from the default `this` 117 | in a Javascript object in the sense that all properties must correspond to 118 | a name/value pair. Using values not yet generated is also supported, and the 119 | invoked values will not be generated more than once per inserted document. You 120 | can also access a field's parent using `this._$parent`. 121 | * `{ "one": 1, "two: "{{ Number(this.one + 1) }}" }` 122 | * `{ "first_name": "{{ this.name.first }}", 123 | "name": { "first": "{{ chance.first() }}" } }` produces consistent result. 124 | * `{ "echo": {{ Object.keys(this) }} }` returns `{ "echo": [ "echo" ] }` 125 | * `{ "1": { "2": "{{ this._$parent.3 }}" }, "3": "{{ chance.d6() }}" }` 126 | 127 | ### Field visibility 128 | 129 | You also have control on the visibility of each field. By default, fields with 130 | name starting with `_$` are hidden. To toggle the visibility, simply call 131 | `show(boolean)` or `hide(boolean)` anywhere in the target field's expression. 132 | Note that while hidden one can still access its newly generated value. 133 | * `{ "_$digit": 3, "lat": "{{ chance.latitude({fixed: this._$digit}) }}", 134 | "lng": "{{ chance.longitude({fixed: this._$digit}) }}"}` 135 | * `{ "temperature": "{{ util.random(40, 130) }}", 136 | "warning": "Too hot! {{ hide(this.temperature < 100) }}" }` 137 | 138 | ### Utility methods 139 | 140 | We are happy to add more methods in a prompt manner should you find any could be 141 | potentially helpful. Currently we have: 142 | * `_$size` - the total number of generated docs in the current run 143 | * `_$index` - the index of the current document, starting from 0 144 | * `counter([id], [start], [step])` - the underlying counts are accessble 145 | anywhere in the outmost document so that you can use the same counter 146 | consistently regardless of its position 147 | + `id` - the index of the counter to use, default is 0 148 | + `start` - the first count, default is 0 149 | + `step` - increment of each count, default is 1 150 | * `util.sample(list, [n])` - identical to [underscore.js](http://underscorejs.org/#sample) 151 | 152 | ### Imperfections 153 | 154 | * Due to the use of underscore.js, `_` is accessible from within the template 155 | schema. Its many powerful methods may cause unwanted effects if used unchecked. 156 | * A lot of efforts were made to avoid exposing internal variables, but there is 157 | still one not properly handled, such as `_state`. Please do not mess with it. 158 | * Expressions provided by users are directly evaluated without any error 159 | checking, which may cause the program to crash without supplying much helpful 160 | information to users 161 | 162 | ## Purpose of this project 163 | 164 | With the explosion of data volume and availability, users are transitioning 165 | their focus to analysis and data-mining on these vast datasets. We believe 166 | MongoDB is ideally positioned to provide the backbone to meet these market 167 | needs. While several users have already begun to exploit this, it requires 168 | substantial sunk costs including mapping the aggregation framework to their 169 | current mental model, designing efficient schemas, and acquiring datasets for 170 | prototyping. Work to humanize the aggregation framework is already underway. We 171 | believe supplying users with example schemas for common use cases, such as user 172 | activity streams, time series data, and entity management, and more importantly, 173 | corresponding datasets for prototyping will establish MongoDB as a leader in 174 | this emerging market. 175 | 176 | ## Change log 177 | 178 | ### 0.1.6 179 | * Upgraded chance library 180 | 181 | ### 0.1.0 - Jul. 23, 2014 182 | First release! 183 | 184 | ### 0.1.1 - Jul. 23, 2014 185 | * Fixed bug that causes `mongodb-dataset-generator` command crash 186 | * Updated README to include all features 187 | 188 | ### 0.1.3 - Jul. 24, 2014 189 | * Fixed bug that causes inconsistent content if `this.` is used 190 | 191 | ### 0.1.4 - Jul. 28, 2014 192 | * Changed `Double` to `Number` to hide Javascript's `Number` constructor 193 | * Added `_$index` 194 | * Made the former `_$size()` a property `_$size` 195 | 196 | ### Changed not yet published 197 | 198 | #### branch:API - Jul. 29, 2014 199 | * Output nice formatted json array with flag `--pretty` enabled 200 | * Mapped chance and faker's random functions to higher level API of datasets. 201 | Please consult comments in `lib/schema/context.js' for detailed usage. 202 | 203 | #### branch:arrayField - Jul. 31, 2014 204 | * New syntax for defining arrays in template schema 205 | * All configured arrays must conform to the format: 206 | + `[ "{{_$config}}", {/*options*/}, , /*optionally*/ ]` 207 | * Arrays whose first element is not `{{_$config}}` are treated as ordinary arrays 208 | + multiple elements are ok, `[ '{{chance.name()}}', '{{chance.age()}}' ]` 209 | + different types are ok `[ true, 3, { doc: 'doc' }, [ "array" ]]` 210 | * Supported options: 211 | + `size` - can be a number or an array of two numbers ([min, max]). the number of times the contents will be repeated 212 | 213 | ## License 214 | 215 | MIT 216 | -------------------------------------------------------------------------------- /bin/api-docs.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var dox = require('dox'), 3 | fs = require('fs'); 4 | 5 | var filenames = [ 'lib/schema/context.js' ]; 6 | var comments = filenames.map(function (filename) { 7 | return fs.readFileSync(__dirname + '/../' + filename, 'utf-8'); 8 | }); 9 | var methods = dox.parseComments(comments.join('\n\n'), {raw: true}); 10 | 11 | var apis = []; 12 | methods.forEach(function (method) { 13 | if (method.ignore || method.isPrivate) return; 14 | 15 | var isApi = false; // whether @api tag is present 16 | 17 | method.tags.forEach(function (tag) { 18 | if (tag.type === 'api') isApi = true; 19 | }); 20 | 21 | if (isApi) apis.push(method); 22 | }); 23 | 24 | console.log(apis); 25 | 26 | // var stability, 27 | // isProperty = false, 28 | // params = [], 29 | // args = [], 30 | // opts_arg, 31 | // group, 32 | // options = [], 33 | // todos = [], 34 | // examples = [], 35 | // streamable = false; 36 | 37 | // method.tags.forEach(function (tag){ 38 | // var matches; 39 | 40 | // if(tag.type === 'param'){ 41 | // tag.optional = (tag.name.indexOf('[') === 0); 42 | // tag.name = tag.name.replace('[', '').replace(']', ''); 43 | // if(tag.name === 'opts'){ 44 | // opts_arg = tag; 45 | // } 46 | // args.push(tag.name); 47 | 48 | // return params.push(tag); 49 | // } 50 | // if(tag.type === 'option'){ 51 | // matches = /\{(\w+)\} (\w+) ?(.*)?/.exec(tag.string); 52 | // tag.types = [matches[1]]; 53 | // tag.name = matches[2]; 54 | // tag.description = matches[3] || '@todo'; 55 | // return options.push(tag); 56 | // } 57 | // if(tag.type === 'example'){ 58 | // matches = /([\w\d\/\:\.]+) (.*)/.exec(tag.string); 59 | // return examples.push({ 60 | // name: matches[2], 61 | // url: matches[1] 62 | // }); 63 | // } 64 | // if(tag.type === 'group') return group = tag.string; 65 | // if(tag.type === 'stability') return stability = tag.string; 66 | // if(tag.type === 'streamable') return streamable = true; 67 | // if(tag.type === 'todo') todos.push(tag.string); 68 | // }); 69 | 70 | // if(!isProperty && opts_arg && options.length > 0){ 71 | // opts_arg.description += '\n' + options.map(function(opt){ 72 | // return ' - `' + opt.name + '` (' + opt.types.join('|') + ') ... ' + opt.description; 73 | // }).join('\n') + '\n'; 74 | // } 75 | 76 | // apis.push({ 77 | // name: method.ctx.name, 78 | // group: group, 79 | // stability: stability, 80 | // streamable: streamable, 81 | // description: method.description.summary, 82 | // params: params, 83 | // args: args, 84 | // options: options, 85 | // todos: todos, 86 | // source: method.code, 87 | // examples: examples 88 | // }); 89 | // }); 90 | 91 | // var group; 92 | // apis.map(function(api, i){ 93 | // if(i === 0) return; // @todo handle module level docs... 94 | // if(api.group !== group){ 95 | // console.log('### ' + api.group); 96 | // group = api.group; 97 | // } 98 | 99 | // console.log('#### mongoscope.' + api.name + '('+api.args.join(', ')+')\n'); 100 | 101 | // if(badges[api.stability]){ 102 | // console.log(badges[api.stability] + '\n'); 103 | // } 104 | 105 | // console.log(api.description); 106 | 107 | // if(api.examples.length > 0){ 108 | // console.log('##### Examples\n'); 109 | // api.examples.map(function(example){ 110 | // console.log('- ['+example.name+']('+example.url+')'); 111 | // }); 112 | // console.log(); 113 | // } 114 | 115 | // if(!api.isProperty && api.params.length > 0){ 116 | // console.log('##### Parameters\n'); 117 | // api.params.map(function(param){ 118 | // console.log('- `'+param.name+'` ('+(param.optional ? 'optional' : 'required')+', '+param.types.join('|')+') ' + (param.description ? '... '+ param.description : '')); 119 | // }); 120 | // console.log(); 121 | // } 122 | 123 | // if(api.todos.length > 0){ 124 | // console.log('##### Todo\n'); 125 | // api.todos.map(function(t){ 126 | // console.log('- [ ] ' + t); 127 | // }); 128 | // console.log(); 129 | // } 130 | // }); 131 | -------------------------------------------------------------------------------- /bin/mongodb-dataset-generator.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var datasets = require('../'), 4 | fs = require('fs'), 5 | yargs = require('yargs') 6 | .usage('MongoDB-Datasets version ' + datasets.version + '\n' + 7 | 'Usage: mongodb-datasets [schema] [options]') 8 | .example('$0 schema.json -n 10 -o dump.out', '') 9 | .example('cat schema.json | $0 -n 10 -o -', '') 10 | .describe(0, 'Path to a template schema file') 11 | .options('n', { 12 | demand: true, 13 | alias: 'size', 14 | describe: 'Number of documents to generate' 15 | }) 16 | .options('o', { 17 | demand: true, 18 | alias: 'out', 19 | string: true, 20 | describe: 'Path to output file. Use "-" for stdout' 21 | }) 22 | .options('pretty', { 23 | boolean: true, 24 | describe: 'Whether to format results' 25 | }) 26 | .options('h', { 27 | alias: 'help', 28 | boolean: true, 29 | describe: 'Show help message' 30 | }), 31 | argv = yargs.argv; 32 | 33 | if (argv.h || argv._[0] === 'help') return yargs.showHelp(); 34 | 35 | // the schema file can also be piped in through stdin 36 | if(argv._[0] && !fs.existsSync(argv._[0])){ 37 | console.error('File `'+argv._[0]+'` does not exist!'); 38 | return yargs.showHelp(); 39 | } 40 | 41 | var stringify = datasets.stringify({spaces: (argv.pretty ? 2 : 0)}), 42 | src = argv._[0] ? fs.createReadStream(argv._[0]) : process.stdin, 43 | dest = (argv.out === '-') ? process.stdout : fs.createWriteStream(argv.out), 44 | generator = datasets.createGeneratorStream({size: argv.size}); 45 | 46 | src.pipe(generator).pipe(stringify).pipe(dest); 47 | -------------------------------------------------------------------------------- /conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "tags": { 3 | "allowUnknownTags": true 4 | }, 5 | "source": { 6 | "include": [ "./lib/", "./index.js" ], 7 | "exclude": [ ], 8 | "includePattern": ".+\\.js(doc)?$", 9 | "excludePattern": "(^|\\/|\\\\)_" 10 | }, 11 | "plugins": [], 12 | "templates": { 13 | "cleverLinks": false, 14 | "monospaceLinks": false 15 | }, 16 | "opts": { 17 | "template": "templates/default", 18 | "encoding": "utf8", 19 | "destination": "./docs/", 20 | "recurse": true, 21 | "tutorials": "", 22 | "query": "", 23 | "private": true, 24 | "lenient": false, 25 | "version": false, 26 | "explain": false, 27 | "test": false, 28 | "help": false, 29 | "verbose": false, 30 | "match": "value", 31 | "nocolor": false 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/20_embedded_basic.json: -------------------------------------------------------------------------------- 1 | { 2 | "user_email": "{{chance.email()}}", 3 | "job": { 4 | "company": "{{chance.word()}}", 5 | "phone": "{{chance.phone()}}", 6 | "duties": "{[chance.sentence()}}" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/21_embedded_multi.json: -------------------------------------------------------------------------------- 1 | { 2 | "user_email": "{{chance.email()}}", 3 | "job": { 4 | "company": "{{chance.word()}}", 5 | "phone": "{{chance.phone()}}", 6 | "duties": "{{chance.sentence()}}" 7 | }, 8 | "payment_method": { 9 | "type": "{{chance.cc_type()}}", 10 | "card": "{{chance.cc()}}", 11 | "expiration": "{{chance.exp()}}" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/22_embedded_level.json: -------------------------------------------------------------------------------- 1 | { 2 | "user_email": "{{chance.email()}}", 3 | "personalities": { 4 | "favorites": { 5 | "number": "{{chance.d10()}}", 6 | "city": "{{chance.city()}}", 7 | "radio": "{{chance.radio()}}" 8 | }, 9 | "rating": "{{chance.d6()}}" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/23_embedded_complex.json: -------------------------------------------------------------------------------- 1 | { 2 | "user_email": "{{chance.email()}}", 3 | "job": { 4 | "company": "{{chance.word()}}", 5 | "phone": "{{chance.phone()}}", 6 | "duties": "{{chance.sentence()}}" 7 | }, 8 | "personalities": { 9 | "favorites": { 10 | "number": "{{chance.d10()}}", 11 | "city": "{{chance.city()}}", 12 | "radio": "{{chance.radio()}}" 13 | }, 14 | "rating": "{{chance.d6()}}" 15 | }, 16 | "payment_method": { 17 | "type": "{{chance.cc_type()}}", 18 | "card": "{{chance.cc()}}", 19 | "expiration": "{{chance.exp()}}" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/30_array_field.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "{{chance.name()}}", 3 | "friends": [ "{{_$config}}", {}, "{{chance.name()}}" ] 4 | } 5 | -------------------------------------------------------------------------------- /examples/31_array_doc.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "{{chance.name()}}", 3 | "friends": [ "{{_$config}}", {}, { 4 | "name": "{{chance.name()}}", 5 | "phone": "{{chance.phone()}}" 6 | }] 7 | } 8 | -------------------------------------------------------------------------------- /examples/32_array_embed.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "{{chance.name()}}", 3 | "friends": [ "{{_$config}}", {}, { 4 | "name": "{{chance.name()}}", 5 | "payment_method": { 6 | "type": "{{chance.cc_type()}}", 7 | "card": "{{chance.cc()}}", 8 | "expiration": "{{chance.exp()}}" 9 | } 10 | }] 11 | } 12 | -------------------------------------------------------------------------------- /examples/33_array_arrays.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "{{chance.name()}}", 3 | "friends": [ "{{_$config}}", {}, { 4 | "name": "{{chance.name()}}", 5 | "payment_method": [ "{{_$config}}", {}, { 6 | "type": "{{chance.cc_type()}}", 7 | "card": "{{chance.cc()}}", 8 | "expiration": "{{chance.exp()}}" 9 | }] 10 | }] 11 | } 12 | -------------------------------------------------------------------------------- /examples/91_complex.json: -------------------------------------------------------------------------------- 1 | { 2 | "user_email": "{{chance.email()}}", 3 | "job": { 4 | "company": "{{chance.word()}}", 5 | "phones": { 6 | "mobile": "{{chance.phone()}}", 7 | "work": "{{chance.phone()}}" 8 | }, 9 | "duties": "{{chance.sentence()}}" 10 | }, 11 | "personalities": { 12 | "favorites": { 13 | "number": "{{chance.d10()}}", 14 | "city": "{{chance.city()}}", 15 | "radio": "{{chance.radio()}}" 16 | }, 17 | "violence-rating": "{{chance.d6()}}" 18 | }, 19 | "friends" : [ "{{_$config}}", {}, { 20 | "name": "{{chance.name()}}", 21 | "phones": [ "{{_$config}}", {}, "{{chance.phone()}}"] 22 | }] 23 | } 24 | -------------------------------------------------------------------------------- /examples/demo.json: -------------------------------------------------------------------------------- 1 | { 2 | "_id": "{{ObjectID(chance.hash({length: 12}))}}", 3 | "name": "{{chance.name({gender: 'male'})}}", 4 | "twitter": "{{chance.twitter()}}", 5 | "card": "{{chance.cc({type: 'Visa'})}}", 6 | "date": "{{Date(chance.date())}}", 7 | "bool": "{{Boolean(true)}}", 8 | "info": { 9 | "first":"{{this._$parent.name.split(' ')[0]}}", 10 | "last": "{{this._$parent.name.split(' ')[1]}}" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /examples/girbal.json: -------------------------------------------------------------------------------- 1 | { 2 | "_id": "{{ObjectID(chance.hash({length: 12}))}}", 3 | "name": "{{chance.sentence({words: _.random(3, 8)})}}", 4 | "dep": "{{chance.string({pool: '0123456789', length: 5})}}", 5 | "cat": "/{{this.dep}}/{{chance.natural()}}/{{chance.natural()}}", 6 | "created_at": "{{Date(faker.Date.past(5))}}", 7 | "updated_at": "{{Date(faker.Date.recent(5))}}", 8 | "desc": [ 1, 9 | { 10 | "lang": "en", 11 | "val": "{{faker.Company.bs()}}" 12 | } 13 | ], 14 | "img": [ 15 | { 16 | "height": "{{10 * chance.natural({max: 128})}}", 17 | "width": "{{10 * chance.natural({max: 108})}}", 18 | "src": "https://{{chance.domain()}}/{{this.title}}.jpg", 19 | "title": "{{chance.hash({length: 5})}}" 20 | } 21 | ], 22 | "attrs": [ "{{_.random(1, 2)}}", 23 | "{{chance.word()}}={{chance.word()}}" 24 | ], 25 | "sattrs": [ "{{_.random(1, 2)}}", 26 | "{{chance.word()}}={{chance.word()}}" 27 | ], 28 | "vars": [ "{{_.random(2, 4)}}", 29 | { 30 | "id": "{{Number(counter(0))}}", 31 | "img": [ 32 | { 33 | "height": "{{10 * chance.natural({max: 128})}}", 34 | "width": "{{10 * chance.natural({max: 108})}}", 35 | "src": "https://{{chance.domain()}}/{{this.title}}.jpg", 36 | "title": "{{chance.hash({length: 5})}}" 37 | } 38 | ], 39 | "attrs": [ "{{_.random(1, 2)}}", 40 | "{{chance.word()}}={{chance.word()}}" 41 | ], 42 | "sattrs": [ "{{_.random(1, 2)}}", 43 | "{{chance.word()}}={{chance.word()}}" 44 | ] 45 | } 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /examples/me_in_a_nutshell.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mongodb-datasets", 3 | "intro": "Welcome! I'm the tool you will use to get data into your db!", 4 | "repo": "https://github.com/imlucas/mongodb-datasets", 5 | "usage": "coming soon", 6 | "features": { 7 | "structural": { 8 | "flat": "just an ordinary field", 9 | "doc": { 10 | "intro": "hey my parent is a document!" 11 | }, 12 | "flat_array": [ "a lonely element in array, but just for now" ], 13 | "doc_array": [{ 14 | "intro": "not any way better, a lonely doc" 15 | }], 16 | "embedded": { 17 | "doc": { 18 | "whoops": "I'm already 4-level embedded, no need to show again" 19 | }, 20 | "array": [{ 21 | "son": [ "I have no idea why my master made an array of arrays" ] 22 | }] 23 | } 24 | }, 25 | "data_generator": { 26 | "string": "I'm not a string", 27 | "random_string": { 28 | "chancejs": { 29 | "doc": "http://chancejs.com/", 30 | "eg": "{{chance.name({male: true})}}" 31 | }, 32 | "faker": { 33 | "repo": "https://github.com/FotoVerite/Faker.js", 34 | "eg": "{{faker.Company.catchPhrase()}}" 35 | } 36 | }, 37 | "object": { 38 | "ISODate": "{{Date(chance.date())}}", 39 | "number": "{{Number(2)}}", 40 | "boolean": "{{Boolean(true)}}", 41 | "long": "{{NumberLong(3)}}", 42 | "more": "Please refer to docs for the complete list" 43 | }, 44 | "useful_gadgets": { 45 | "sample": "{{util.sample(['1', '2', '3'])}}", 46 | "counter": "{{counter(0, 0, 1)}}" 47 | }, 48 | "scoping": { 49 | "name": "{{chance.name()}}", 50 | "greetings": "This is {{this.name}}. I got ${{this.bank.balance}}.", 51 | "bank": { 52 | "balance": 100 53 | } 54 | }, 55 | "combination": "I'm {{counter(1, 10, 1)}}-year-old! I grow fast!", 56 | "more": "still adding more functions!" 57 | }, 58 | "data_importer": "coming soon" 59 | }, 60 | "version": "v0.0.0" 61 | } 62 | -------------------------------------------------------------------------------- /examples/v.json: -------------------------------------------------------------------------------- 1 | { 2 | "_$max_version": "{{Number(_$size)}}", 3 | "_$attr2_released": "{{Number(this._$max_version / 2)}}", 4 | "docId": "{{Number(chance.natural({max: 1000}))}}", 5 | "v": "{{Number(counter(0, 1, 1))}}", 6 | "attr1": "{{chance.color()}}", 7 | "attr2": "{{Number(chance.natural())}}{{hide(this.v < this._$attr2_released)}}", 8 | "current": "{{Boolean(true)}}{{hide(this.v < this._$max_version)}}" 9 | } 10 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var datasets = require('./lib'), 2 | pkg = require('./package.json'); 3 | 4 | module.exports = datasets; 5 | module.exports.version = pkg.version; 6 | -------------------------------------------------------------------------------- /lib/api/context.js: -------------------------------------------------------------------------------- 1 | var debug = require('debug')('mongodb-datasets:api:context'), 2 | _ = require('underscore'), 3 | helpers = require('./helpers'); 4 | // util = require('util'), 5 | 6 | /** 7 | * @module api 8 | */ 9 | 10 | /** 11 | * Context object is solely used as context in function _.template 12 | * @class 13 | * @param {!Document} host The Document this Context is used 14 | */ 15 | function Context (host) { 16 | if (!(this instanceof Context)) return new Context(host); 17 | // APIRandom.call(this); 18 | 19 | /** 20 | * Internal state of this Context 21 | * @member {Object} 22 | * @private 23 | * @property {boolean} display Visibility of the current Field 24 | * @property {*} override Value overriding the default `_.template` output 25 | */ 26 | this._state = { 27 | display: undefined, // when true, the current field will not be visible 28 | override: undefined // if present, used to override the template output 29 | }; 30 | 31 | /** 32 | * Collection of utility methods for clients 33 | * @member {Object} 34 | * @api public 35 | * @property {function} sample {@link http://underscorejs.org/#sample} 36 | * @since 0.1.0 37 | */ 38 | this.util = { 39 | sample: _.sample 40 | }; 41 | 42 | /** 43 | * Counter 44 | * @method 45 | * @api public 46 | * @param {number} [id=0] Index of the counter to increment 47 | * @param {number} [start=0] Initial value, 48 | * valid when the counter is initialized 49 | * @param {number} [step=1] Increment of each count 50 | * @return {number} Current value of the specified counter 51 | * @since 0.1.0 52 | */ 53 | this.counter = function (id, start, step) { 54 | id = id || 0; // though id=0 is false, does not matter 55 | debug('counter called id=%d', id); 56 | var counter = host._schema._state.counter; //pointer 57 | if (typeof counter[id] === 'undefined') { 58 | return (counter[id] = start || 0); 59 | } 60 | return (counter[id] += (step || 1)); 61 | }; 62 | 63 | /** 64 | * Total number of generated Document's in the current run 65 | * @instance 66 | * @api public 67 | * @memberOf Context 68 | * @member {number} _$size 69 | * @since 0.1.4 70 | */ 71 | Object.defineProperty(this, '_$size', { 72 | get: function () { 73 | return host._schema._state.size; 74 | } 75 | }); 76 | 77 | /** 78 | * Index of the current Document being generated, starting from 0 79 | * @instance 80 | * @api public 81 | * @memberOf Context 82 | * @member {number} _$index 83 | * @since 0.1.4 84 | */ 85 | Object.defineProperty(this, '_$index', { 86 | get: function () { 87 | return host._schema._state.index; 88 | } 89 | }); 90 | 91 | } 92 | // util.inherits(Context, APIRandom); 93 | // Context.prototype = APIRandom; 94 | helpers.inheritsAPI(Context); 95 | 96 | /** 97 | * Change the visibility of current Field 98 | * @method 99 | * @api public 100 | * @param {boolean} pred Effective if true 101 | * @since 0.1.4 102 | */ 103 | Context.prototype.hide = function (pred) { 104 | if (pred) this._state.display = false; 105 | }; 106 | 107 | /** 108 | * Convenient method to reset the internal state of this Context 109 | * @method 110 | * @private 111 | * @param {boolean} hard True when the current Document finishes generating 112 | */ 113 | Context.prototype._reset = function (hard) { 114 | this._state.override = undefined; 115 | if (hard) { 116 | this._state.display = undefined; 117 | } 118 | }; 119 | 120 | module.exports = Context; 121 | -------------------------------------------------------------------------------- /lib/api/helpers.js: -------------------------------------------------------------------------------- 1 | var _ = require('underscore'); 2 | var apis = [ 3 | require('./random'), 4 | require('./type') 5 | ]; 6 | 7 | // aggregate all apis 8 | module.exports.inheritsAPI = function (ctor) { 9 | var apisProp = {}; 10 | apis.forEach(function (api) { 11 | for (var funcName in api) { 12 | apisProp[funcName] = { 13 | value: api[funcName] 14 | }; 15 | } 16 | }); 17 | ctor.prototype = Object.create({}, apisProp); 18 | }; 19 | 20 | // exports.inherits = function(ctor, superCtor) { 21 | // ctor.super_ = superCtor; 22 | // ctor.prototype = Object.create(superCtor.prototype, { 23 | // constructor: { 24 | // value: ctor, 25 | // enumerable: false, 26 | // writable: true, 27 | // configurable: true 28 | // } 29 | // }); 30 | // }; 31 | 32 | /** 33 | * Each datasets random function (i.e. except chance, faker) accepts a variable 34 | * number of OptionHash's which aggregate into a single Object internally. 35 | * @memberOf Context 36 | * @typedef OptionHash 37 | * @type {(string|Object)} 38 | * @since 0.2.0 39 | * 40 | * @example 41 | * // as {a: true} 42 | * randomFunc('a'); 43 | * @example 44 | * // as {} 45 | * randomFunc({a: 1}, {a: null}); 46 | * @example 47 | * // as {a: 1, b: 2, c: true} 48 | * randomFunc({a: 0}, {b: 2}, 'c', {a: 1}) 49 | */ 50 | 51 | /** 52 | * Aggregate variable number of option hashes to a single option Object 53 | * @see {@link Context.OptionHash} 54 | * @method 55 | * @api public 56 | * @param {Array.<(string|Object)>} args 57 | * @return {Object} 58 | * @since 0.2.0 59 | */ 60 | module.exports.mix = function (args) { 61 | var input = arguments.length > 1 ? arguments : args; 62 | var opts = {}; 63 | for (var i = 0; i < input.length; i++ ) { 64 | var item = input[i]; 65 | if (typeof item === 'object') { 66 | opts = _.extend(opts, item); 67 | opts = _.omit(opts, _.pairs(item).map(function (e) { 68 | return e[1] === null ? e[0] : undefined; 69 | })); 70 | } else { 71 | opts[item] = true; 72 | } 73 | } 74 | return opts; 75 | }; 76 | -------------------------------------------------------------------------------- /lib/api/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./context'); -------------------------------------------------------------------------------- /lib/api/random.js: -------------------------------------------------------------------------------- 1 | var chance = require('chance').Chance(), 2 | faker = require('faker'), 3 | helpers = require('./helpers'); 4 | 5 | ///////////////////////////////// 6 | // new API for random function // 7 | ///////////////////////////////// 8 | 9 | // function APIRandom () { 10 | // if (!(this instanceof APIRandom)) return new APIRandom(); 11 | // } 12 | 13 | // @todo how to isolate the random function API. 14 | 15 | /** 16 | * API for random data functions 17 | * @namespace 18 | */ 19 | var APIRandom = {}; 20 | 21 | /** 22 | * Expose Chance.js library to clients 23 | * @method 24 | * @api public 25 | * @see {@link http://chancejs.com|Chance} 26 | * @since 0.1.0 27 | * @deprecated since 0.2.0 28 | */ 29 | APIRandom.chance = chance; 30 | 31 | /** 32 | * Expose Faker.js library to clients 33 | * @method 34 | * @api public 35 | * @see {@link https://github.com/Marak/Faker.js|Faker} 36 | * @since 0.1.0 37 | * @deprecated since 0.2.0 38 | */ 39 | APIRandom.faker = faker; 40 | 41 | // tile 1: heavy aggregation 42 | 43 | /** 44 | * Generate a random number (floating point, or integer) 45 | * @method 46 | * @api public 47 | * @param {...APIRandom.OptionHash} 48 | * @property {boolean} integer Produce an integer. True by default. 49 | * @property {boolean} float Produce a floating point number. 50 | * @property {number} min Lower range of result 51 | * @property {number} max Upper range of result 52 | * @property {boolean} fixed Number of decimal digits for floats 53 | * @property {boolean} natural Natural number, i.e. {min: 0, integer: true} 54 | * @property {number} dice Roll a dice, i.e. {min: 0, max: arg, integer: true} 55 | * @property {(boolean|string)} age Return an age. 56 | * + `true` - using default range, 0 ~ 120 57 | * + `child`, `teen`, `adult`, `senior` 58 | * @return {number} 59 | * @since 0.2.0 60 | * 61 | * @example 62 | * // returns, for example, 3404660580810752 63 | * num(); 64 | * @example 65 | * // returns, for example, 892547889941708.8 66 | * num('float', {fixed: 1, min: 0}); 67 | * @example 68 | * // returns, for example, 19 69 | * num({age: 'teen'}); 70 | */ 71 | APIRandom.num = function () { 72 | var opts = helpers.mix(arguments); 73 | var mm = opts.min !== undefined ? {min: Number(opts.min)} : {}; 74 | var fix = opts.fixed !== undefined ? {fixed: Number(opts.fixed)} : {}; 75 | if (opts.max !== undefined) mm.max = Number(opts.max); 76 | if (opts.natural) return chance.natural(mm); 77 | if (opts.float) return chance.floating(helpers.mix(mm, fix)); 78 | if (opts.dice) return chance.integer({min: 1, max: Number(opts.dice)}); 79 | if (opts.age) { 80 | if (opts.age === true) return chance.age(); 81 | return chance.age({type: opts.age}); 82 | } 83 | return chance.integer(mm); 84 | }; 85 | 86 | /** 87 | * Generate a random name containing prefix, first name, middle name, last name. 88 | * By default, generate a name without prefix or middle name. 89 | * @method 90 | * @api public 91 | * @param {...APIRandom.OptionHash} 92 | * @property {string} gender Gender: `male`/`female` 93 | * @property {boolean} first Only first name 94 | * @property {boolean} last Only last name 95 | * @property {(boolean|string)} middle With middle name? 96 | * + `true` - middle initial 97 | * + `full` - full middle name 98 | * @property {(boolean|string)} prefix With prefix? 99 | * + `true` - short prefix, e.g. "Mr." 100 | * + `full` - long prefix, e.g. "Mister" 101 | * @return {string} 102 | * @since 0.2.0 103 | * 104 | * @example 105 | * // returns, for example, "Mr. Bruse Morrison" 106 | * name(); 107 | * @example 108 | * // returns a random first name 109 | * name('first'); 110 | * @example 111 | * // returns, for example, "Mr. Bruce Dale Morrison" 112 | * name('prefix', {gender: 'male', middle: 'full'}); 113 | */ 114 | APIRandom.name = function () { 115 | var opts = helpers.mix(arguments); 116 | var g = opts.gender ? {gender: opts.gender} : {}; 117 | if (opts.first) return chance.first(g); 118 | if (opts.last) return chance.last(g); 119 | var m = {}, p = {}, prefix = '', name; 120 | if (opts.middle) 121 | m = opts.middle === 'full' ? {middle: true} : {middle_initial: true}; 122 | if (opts.prefix) { 123 | p = helpers.mix(g, opts.prefix === 'full' ? {full: true} : {}); 124 | prefix = chance.prefix(p) + ' '; 125 | } 126 | name = chance.name(helpers.mix(g, m)); 127 | return prefix + name; 128 | }; 129 | 130 | /** 131 | * Generate a random address containing street address, city, state, zipcode 132 | * By default, generate a random street address. 133 | * @method 134 | * @api public 135 | * @param {...APIRandom.OptionHash} 136 | * @property {boolean} array Format the output as an array? 137 | * @property {string} delimiter Used to join components to a single string. 138 | * Default is " ". 139 | * @property {(boolean|string)} street Contain street address? True by default. 140 | * + `true` - full street name, e.g. "300 Fifth Avenue" 141 | * + `short` - short street name, e.g. "300 Fifth Ave" 142 | * @property {boolean} city With city? 143 | * @property {(boolean|string)} state Contain state? 144 | * + `true` - state initials, e.g. "CA" 145 | * + `long` - full state name, e.g. "California" 146 | * @property {(boolean|string)} zip Contain zipcode? 147 | * + `true` - five digit zipcode 148 | * + `long` - with extra four digits, e.g. "10011-3444" 149 | * @property {boolean} full Shortcut to enable all components 150 | * @return {(string|Array.)} 151 | * @since 0.2.0 152 | * 153 | * @example 154 | * // returns, for example, "32 Karki Street" 155 | * address(); 156 | * @example 157 | * // returns, for example, "32 Karki Street, Keiduciv, CA, 10036" 158 | * address('full', {delimiter: ', '}); 159 | * @example 160 | * // returns, for example, [ "32 Karki Street", "Keiduciv" ] 161 | * address('array', 'city', 'street'); 162 | */ 163 | APIRandom.address = function () { 164 | var opts = helpers.mix(arguments); 165 | if (opts.full) opts = helpers.mix('street', 'city', 'state', 'zip', opts); 166 | var dl = opts.delimiter ? '' + opts.delimiter : ', '; 167 | var rtn = [], str, st, z; 168 | if (opts.street) { 169 | str = opts.street === 'short' ? {short_suffix: true} : {}; 170 | rtn.push(chance.address(str)); 171 | } 172 | if (opts.city) rtn.push(chance.city()); 173 | if (opts.state) { 174 | st = opts.state === 'long' ? {full: true} : {}; 175 | rtn.push(chance.state(st)); 176 | } 177 | if (opts.zip) { 178 | z = opts.zip === 'long' ? {plusfour: true} : {}; 179 | rtn.push(chance.zip(z)); 180 | } 181 | if (rtn.length === 0) return chance.address(); 182 | if (opts.array) return rtn; 183 | if (rtn.length === 1) return '' + rtn[0]; 184 | return rtn.join(dl); 185 | }; 186 | 187 | /** 188 | * Generate a random date 189 | * @method 190 | * @api public 191 | * @param {...APIRandom.OptionHash} 192 | * @property {(boolean|string)} string Return a string instead with format of: 193 | * + `true` - short format, i.e. "mm/dd/yyyy" 194 | * + `long` - `Date.prototype.toDateString()`, e.g. "Tue Dec 30 2014" 195 | * + `json` - `Date.prototype.toJSON()` 196 | * + `time` - `Date.prototype.toTimeString()` 197 | * @property {(boolean|string)} integer Return an integer instead. Options are: 198 | * + `true` - return hammertime, `Date.prototype.getTime()` 199 | * + year/month/day/hour/min/sec/ms - return the part respectively 200 | * @property {*} min Lower range, new Date(min) must be a valid date. 201 | * If undefined, new Date() will be used. 202 | * @property {*} max Lower range, new Date(max) must be a valid date 203 | * If undefined, new Date() will be used. 204 | * @property {number} year With a fixed year 205 | * @property {number} month With a fixed month 206 | * @property {number} day With a fixed day 207 | * @return {(Date|string)} 208 | * @since 0.2.0 209 | */ 210 | APIRandom.date = function () { 211 | var opts = helpers.mix(arguments); 212 | var d; 213 | if (opts.min || opts.max) { 214 | d = new Date(faker.Date.between( 215 | opts.min ? new Date(opts.min) : new Date(), 216 | opts.max ? new Date(opts.max) : new Date())); 217 | } else { 218 | d = chance.date({ 219 | year: Number(opts.year), 220 | month: opts.month ? Number(opts.month)+1 : undefined, 221 | day: Number(opts.day) 222 | }); 223 | } 224 | if (opts.integer) { 225 | if (opts.integer === true) return d.getTime(); 226 | if (opts.integer === 'year') return d.getFullYear(); 227 | if (opts.integer === 'month') return d.getMonth() + 1; 228 | if (opts.integer === 'day') return d.getDate(); 229 | if (opts.integer === 'hour') return d.getHours(); 230 | if (opts.integer === 'min') return d.getMinutes(); 231 | if (opts.integer === 'sec') return d.getSeconds(); 232 | if (opts.integer === 'ms') return d.getMilliseconds(); 233 | } 234 | if (opts.string) { 235 | if (opts.string === true) 236 | return ''+(d.getMonth()+1)+'/'+d.getDate()+'/'+d.getFullYear(); 237 | if (opts.string === 'long') return d.toDateString(); 238 | if (opts.string === 'json') return d.toJSON(); 239 | if (opts.string === 'time') return d.toTimeString(); 240 | } 241 | return (isNaN(d.valueOf()) ? chance.date() : d); 242 | }; 243 | 244 | /** 245 | * Generate a random string. 246 | * By default, generate a random one-char string. 247 | * @method 248 | * @api public 249 | * @param {...APIRandom.OptionHash} 250 | * @property {number} length Default is 1 251 | * @property {string} pool Default is all alphanum and '!@#$%^&*()' 252 | * @property {string} casing Options are `lower` and `upper` 253 | * @property {boolean} alpha Only alphanumeric characters 254 | * @property {boolean} symbol Only symbols 255 | * @property {boolean} hash Hash. Shortcut for {casing: 'lower', alpha: true} 256 | * @return {string} 257 | * @since 0.2.0 258 | * 259 | * @example 260 | * // returns, for example, "g6dnc4rqhi6h" 261 | * str('hash', {length: 12}); 262 | */ 263 | APIRandom.str = function () { 264 | var opts = helpers.mix(arguments); 265 | var p = {}, c = opts.casing ? {casing: opts.casing} : {}; 266 | p = opts.symbol ? {symbols: true} : p; 267 | p = !opts.alpha ? p : 268 | {pool: 'qwertyuiopasdfghjklzxcvbnmQWERTYUIOPLKJHGFDSAZXCVBNM1234567890'}; 269 | if (opts.hash) { 270 | p = {pool: 'qwertyuiopasdfghjklzxcvbnm0123456789'}; 271 | } 272 | p = opts.pool ? {pool: '' + opts.pool} : p; 273 | if (!opts.length) 274 | return chance.character(helpers.mix(p, c)); 275 | var rtn = ''; 276 | for (var i = Number(opts.length); i > 0; i--) { 277 | rtn += chance.character(helpers.mix(p, c)); 278 | } 279 | return rtn; 280 | }; 281 | 282 | /** 283 | * Generate random credit card information. 284 | * By default, generate a string of random credit card number. 285 | * @method 286 | * @api public 287 | * @param {...APIRandom.OptionHash} 288 | * @property {boolean} array Format the output as an array? 289 | * @property {string} delimiter Used to join components to a single string. 290 | * Default is " ". 291 | * @property {(boolean|string)} type Contain type of credit card? 292 | * + `true` - short name, e.g. 'discover' 293 | * + `full` - full name, e.g. 'Discover Card' 294 | * @property {boolean} num Contain credit card number? 295 | * @property {boolean} exp Contain expiration date? e.g. '02/2019' 296 | * @return {(string|Array.)} 297 | * @since 0.2.0 298 | * 299 | * @example 300 | * // returns, for example, "6011652062950102" 301 | * cc(); 302 | * @example 303 | * // returns, for example, "Diners Club International" 304 | * cc({type: 'full'}); 305 | * @example 306 | * // returns, for example, [ 'visa', '4174049365395374', '01/2023' ] 307 | * cc('array', 'type', 'num', 'exp'); 308 | */ 309 | APIRandom.cc = function () { 310 | var opts = helpers.mix(arguments); 311 | var rtn = [], t = chance.cc_type({raw: true}); 312 | if (opts.type) { 313 | if (opts.type === true) rtn.push(t.short_name); 314 | if (opts.type === 'full') rtn.push(t.name); 315 | } 316 | if (opts.num) rtn.push(chance.cc({type: t.name})); 317 | if (opts.exp) rtn.push(chance.exp()); 318 | if (opts.array) return rtn; 319 | if (rtn.length === 0) return chance.cc({type: t.name}); 320 | return rtn.join(opts.delimiter ? '' + opts.delimiter : ', '); 321 | }; 322 | 323 | // tile 2: mostly switch statement 324 | 325 | /** 326 | * Generate a random text segment. 327 | * By default, generate one `lorem`-like sentence. 328 | * @method 329 | * @api public 330 | * @param {...APIRandom.OptionHash} 331 | * @property {number} sentence Generate a paragraph of num sentences 332 | * @property {number} word Generate a sentence of num words 333 | * @property {number} syllable Generate a word with num syllables 334 | * @property {number} char Generate a word with num characters 335 | * @return {string} 336 | * @since 0.2.0 337 | * 338 | * @example 339 | * // returns, for example, "fatelu" 340 | * text({syllable: 3}); 341 | */ 342 | APIRandom.text = function () { 343 | var opts = helpers.mix(arguments); 344 | if (opts.sentence) 345 | return chance.paragraph({sentences: Number(opts.sentence)}); 346 | if (opts.word) 347 | return chance.sentence({words: Number(opts.word)}); 348 | if (opts.syllable) 349 | return chance.word({syllables: Number(opts.syllable)}); 350 | if (opts.char) 351 | return chance.word({length: Number(opts.char)}); 352 | return chance.sentence(); 353 | }; 354 | 355 | /** 356 | * Generate a random ID for different purposes. 357 | * @method 358 | * @api public 359 | * @param {...APIRandom.OptionHash} 360 | * @property {boolean} guid Generate a guid (default behavior) 361 | * @property {boolean} fb Generate a facebook ID 362 | * @property {boolean} twitter Generate a twitter ID 363 | * @property {boolean} gganl Generate a Google Analytics tracking code 364 | * @property {boolean} username Generate a plausible username 365 | * @return {string} 366 | * @since 0.2.0 367 | * 368 | * @example 369 | * // returns, for example, '9202D6EB-27B4-5DB5-8C20-444CDFB5B32E' 370 | * id(); 371 | * @example 372 | * // returns, for example, '1000052961667394' 373 | * id('fb'); 374 | */ 375 | APIRandom.id = function () { 376 | var opts = helpers.mix(arguments); 377 | if (opts.fb) return chance.fbid(); 378 | if (opts.twitter) return chance.twitter(); 379 | if (opts.hashtag) return chance.hashtag(); 380 | if (opts.gganl) return chance.google_analytics(); 381 | if (opts.username) return faker.Internet.userName(); 382 | return chance.guid(); 383 | }; 384 | 385 | /** 386 | * Generate random coordinates. 387 | * By default, generate "latitude, longitude". 388 | * @method 389 | * @api public 390 | * @param {...APIRandom.OptionHash} 391 | * @property {boolean} array Format the output as an array? 392 | * @property {string} delimiter Used to join components to a single string. 393 | * Default is " ". 394 | * @property {boolean} lat Only latitude? 395 | * @property {boolean} lng Only longitude? 396 | * @property {number} fixed Number of demical digits. 397 | * @return {(Array.|string)} 398 | * @since 0.2.0 399 | * 400 | * @example 401 | * // returns, for example, '-75.00988, -37.85036' 402 | * coordinates(); 403 | * // returns, for example, [ -75.00988, -37.85036 ] 404 | * coordinates('array'); 405 | * // returns, for example, -75.1 406 | * coordinates('lat', {fixed: 1}); 407 | */ 408 | APIRandom.coordinates = function () { 409 | var opts = helpers.mix(arguments); 410 | var dl = opts.delimiter ? opts.delimiter : ', '; 411 | var f = opts.fixed === undefined ? {} : {fixed: Number(opts.fixed)}; 412 | var lat = chance.latitude(f), lng = chance.longitude(f); 413 | if (opts.lat) return lat; 414 | if (opts.lng) return lng; 415 | if (opts.array) return [lat, lng]; 416 | return '' + lat + dl + lng; 417 | }; 418 | 419 | /** 420 | * Generate a random element from the specified pool. 421 | * @method 422 | * @api public 423 | * @param {string} pool Which pool to use. Options are: 424 | * + tld: top level domain, e.g. 'gov', 'com' 425 | * + radio: {@link http://chancejs.com/#radio} 426 | * + tv: {@link http://chancejs.com/#tv} 427 | * + currency: {@link http://chancejs.com/#currency} 428 | * + gender, ampm, state, city 429 | * @return {string} 430 | * @since 0.2.0 431 | */ 432 | APIRandom.random = function (pool) { 433 | // var opts = helpers.mix(pool); 434 | switch (pool) { 435 | case 'tld': 436 | return chance.tld(); 437 | case 'gender': 438 | return chance.gender(); 439 | case 'radio': 440 | return chance.radio(); 441 | case 'tv': 442 | return chance.tv(); 443 | case 'currency': 444 | return chance.currency(); 445 | case 'state': 446 | return chance.state(); 447 | case 'city': 448 | return chance.city(); 449 | case 'ampm': 450 | return chance.ampm(); 451 | } 452 | }; 453 | 454 | // tile 3: limited aggregation 455 | 456 | /** 457 | * Generate a random phone number 458 | * @method 459 | * @api public 460 | * @param {...APIRandom.OptionHash} 461 | * @property {boolean} areacode Only return areacode, e.g. "(510)" 462 | * @return {string} 463 | * @since 0.2.0 464 | * 465 | * @example 466 | * // returns, for example, '(622) 941-1923' 467 | * phone(); 468 | */ 469 | APIRandom.phone = function () { 470 | var opts = helpers.mix(arguments); 471 | if (opts.areacode) return chance.areacode(); 472 | return chance.phone(); 473 | }; 474 | 475 | /** 476 | * Generate a random IP address 477 | * @method 478 | * @api public 479 | * @param {...APIRandom.OptionHash} 480 | * @property {boolean} v6 Use IPv6 481 | * @return {string} 482 | * @since 0.2.0 483 | * 484 | * @example 485 | * // returns, for example, '47.55.14.224' 486 | * ip(); 487 | */ 488 | APIRandom.ip = function () { 489 | var opts = helpers.mix(arguments); 490 | if (opts.v6) return chance.ipv6(); 491 | return chance.ip(); 492 | }; 493 | 494 | // tile 4: no aggregation 495 | 496 | /** 497 | * Generate a random domain 498 | * @method 499 | * @api public 500 | * @param {...APIRandom.OptionHash} 501 | * @property {string} tld With a fixed a top level domain 502 | * @return {string} 503 | * @since 0.2.0 504 | */ 505 | APIRandom.domain = function () { 506 | return chance.domain(helpers.mix(arguments)); 507 | }; 508 | 509 | /** 510 | * Generate a random email 511 | * @method 512 | * @api public 513 | * @param {...APIRandom.OptionHash} 514 | * @property {string} domain With a fixed domain 515 | * @return {string} 516 | * @since 0.2.0 517 | */ 518 | APIRandom.email = function () { 519 | return chance.email(helpers.mix(arguments)); 520 | }; 521 | 522 | /** 523 | * Generate a random color 524 | * @method 525 | * @api public 526 | * @see {@link http://chancejs.com/#color} 527 | * @param {...APIRandom.OptionHash} 528 | * @property {string} format Format of color. Options are hex, shorthex, rgb 529 | * @property {boolean} greyscale Only grayscale color 530 | * @return {string} 531 | * @since 0.2.0 532 | * 533 | * @example 534 | * // returns, for example, '#d67118' 535 | * color({format: 'hex'}); 536 | */ 537 | APIRandom.color = function () { 538 | return chance.color(helpers.mix(arguments)); 539 | }; 540 | 541 | /** 542 | * Generate a random Social Security Number 543 | * @method 544 | * @api public 545 | * @see {@link http://chancejs.com/#ssn} 546 | * @param {...APIRandom.OptionHash} 547 | * @property {boolean} ssnFour Only last four digits 548 | * @property {boolean} dashes With dashes? 549 | * @return {string} 550 | * @since 0.2.0 551 | */ 552 | APIRandom.ssn = function () { 553 | var opts = helpers.mix(arguments); 554 | if (!opts.dashes) opts.dashes = false; 555 | return chance.ssn(opts); 556 | }; 557 | 558 | module.exports = APIRandom; 559 | -------------------------------------------------------------------------------- /lib/api/type.js: -------------------------------------------------------------------------------- 1 | var bson = require('bson'); 2 | 3 | ///////////////////////////////////////// 4 | // Constructors of all supported APITypes // 5 | ///////////////////////////////////////// 6 | 7 | /** 8 | * @namespace 9 | */ 10 | var APIType = {}; 11 | 12 | /** 13 | * Replace `_.template` ouput 14 | * @method 15 | * @api public 16 | * @param {} i Must be acceptable by `Number` constructor 17 | * @since 0.1.0 18 | */ 19 | APIType.Number = function (i) { 20 | this._state.override = Number(i); 21 | }; 22 | 23 | /** 24 | * Replace `_.template` ouput 25 | * @method 26 | * @api public 27 | * @param {} b Must be acceptable by `Boolean` constructor 28 | * @since 0.1.0 29 | */ 30 | APIType.Boolean = function (b) { 31 | this._state.override = Boolean(b); 32 | }; 33 | 34 | /** 35 | * Replace `_.template` ouput 36 | * @method 37 | * @api public 38 | * @param {} s Must be acceptable by `String` constructor 39 | * @since 0.1.0 40 | */ 41 | APIType.String = function (s) { 42 | this._state.override = String(s); 43 | }; 44 | 45 | /** 46 | * Replace `_.template` ouput 47 | * @method 48 | * @api public 49 | * @param {} d Must be acceptable by `Date` constructor 50 | * @since 0.1.0 51 | */ 52 | APIType.Date = function (d) { 53 | this._state.override = new Date(d); 54 | }; 55 | 56 | /** 57 | * Replace `_.template` ouput 58 | * @method 59 | * @api public 60 | * @param {} i Must be acceptable by `Long` constructor in bson package 61 | * @see {@link https://github.com/mongodb/js-bson|bson} 62 | * @since 0.1.0 63 | */ 64 | APIType.NumberLong = function (i) { 65 | this._state.override = new bson.Long(i); 66 | }; 67 | 68 | /** 69 | * Replace `_.template` ouput 70 | * @method 71 | * @api public 72 | * @see {@link https://github.com/mongodb/js-bson|bson} 73 | * @since 0.1.0 74 | */ 75 | APIType.MinKey = function () { 76 | this._state.override = new bson.MinKey(); 77 | }; 78 | 79 | /** 80 | * Replace `_.template` ouput 81 | * @method 82 | * @api public 83 | * @see {@link https://github.com/mongodb/js-bson|bson} 84 | * @since 0.1.0 85 | */ 86 | APIType.MaxKey = function () { 87 | this._state.override = new bson.MaxKey(); 88 | }; 89 | 90 | /** 91 | * Replace `_.template` ouput 92 | * @method 93 | * @api public 94 | * @see {@link https://github.com/mongodb/js-bson|bson} 95 | * @since 0.1.0 96 | */ 97 | APIType.Timestamp = function () { 98 | this._state.override = new bson.Timestamp(); 99 | }; 100 | 101 | /** 102 | * Replace `_.template` ouput 103 | * @method 104 | * @api public 105 | * @param {} i Must be acceptable by `ObjectId` constructor in bson package 106 | * @see {@link https://github.com/mongodb/js-bson|bson} 107 | * @since 0.1.0 108 | */ 109 | APIType.ObjectID = function (i) { 110 | this._state.override = new bson.ObjectId(i); 111 | }; 112 | 113 | module.exports = APIType; -------------------------------------------------------------------------------- /lib/generator.js: -------------------------------------------------------------------------------- 1 | var debug = require('debug')('mongodb-datasets:generator'), 2 | Transform = require('stream').Transform, 3 | util = require('util'), 4 | Schema = require('./schema'), 5 | EJSON = require('mongodb-extended-json'); 6 | 7 | function Generator(opts) { 8 | if(!(this instanceof Generator)) return new Generator(opts); 9 | 10 | Transform.call(this, {objectMode: true}); 11 | this.size = opts.size || 1; 12 | this.chunks = []; 13 | this.data = null; 14 | } 15 | util.inherits(Generator, Transform); 16 | 17 | Generator.prototype._transform = function (chunk, encoding, done) { 18 | if(['buffer', 'utf8'].indexOf(encoding) === -1){ 19 | return done(new Error('Dont understand encoding `'+encoding+'`')); 20 | } 21 | if(Buffer.isBuffer(chunk)){ 22 | this.chunks.push(chunk); 23 | } 24 | else { 25 | this.data = chunk; 26 | } 27 | done(); 28 | }; 29 | 30 | Generator.prototype._flush = function(callback){ 31 | if(!this.data){ 32 | if(this.chunks.length === 0){ 33 | return debug('nothing to transform. noop to allow actual error to bubble'); 34 | } 35 | this.data = EJSON.parse(Buffer.concat(this.chunks)); 36 | } 37 | var schema = new Schema(this.data, this.size); 38 | this.emit('schema', schema); 39 | for (var i = 0; i < this.size; i++){ 40 | this.push(schema.next()); 41 | } 42 | callback(); 43 | }; 44 | 45 | module.exports = Generator; 46 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | var Generator = require('./generator'), 2 | EJSON = require('mongodb-extended-json'), 3 | es = require('event-stream'), 4 | stream = require('stream'); 5 | 6 | module.exports = function(size, schema){ 7 | var dest = new Generator({size: size}), 8 | src = new stream.Readable({objectMode: true}); 9 | 10 | src.push(schema); 11 | src.push(null); 12 | return src.pipe(dest); 13 | }; 14 | 15 | module.exports.stringify = function(opts){ 16 | opts = opts || {}; 17 | opts.spaces = opts.spaces || undefined; 18 | var i = 0; 19 | return es.through(function(data){ 20 | var s = EJSON.stringify(data, null, opts.spaces); 21 | var msg = i === 0 ? '[' + s : ',\n' + s; 22 | i++; 23 | this.emit('data', msg); 24 | }, function() { 25 | this.emit('data', ']\n'); 26 | this.emit('end'); 27 | }); 28 | }; 29 | 30 | module.exports.createGeneratorStream = function(opts){ 31 | return new Generator(opts); 32 | }; 33 | -------------------------------------------------------------------------------- /lib/schema/array.js: -------------------------------------------------------------------------------- 1 | var debug = require('debug')('mongodb-datasets:schema:array'), 2 | util = require('util'), 3 | helpers = require('./helpers'), 4 | Entry = require('./entry'), 5 | _ = require('underscore'); 6 | 7 | var DEFAULTS = { 8 | size: [2, 5] 9 | }; 10 | 11 | /** 12 | * Internal representation of Arrays in template schema 13 | * @class 14 | * @augments {Entry} 15 | * @memberOf module:schema 16 | * @param {!Array} array Raw user-supplied array 17 | * @param {string} name Key corresponding to the field 18 | * @param {!Entry} parent Immediate enclosing Entry 19 | */ 20 | function ArrayField (array, name, parent) { 21 | if (!(this instanceof ArrayField)) return new ArrayField(array, name, parent); 22 | debug('array <%s> being built', name); 23 | Entry.call(this, array, name, parent); 24 | this._context = parent._context; 25 | this._this = parent._this; 26 | this._currVal = {}; 27 | 28 | var data = this._content; 29 | this._config = { 30 | size: function () { return 1; } 31 | }; 32 | if (this._content.length > 0 && 33 | this._content[0] === '{{_$config}}') { 34 | data = this._content.slice(2); 35 | debug('array <%s> is configured with options <%s>', name, this._content[1]); 36 | var opts = _.defaults(this._content[1], DEFAULTS); 37 | if (Array.isArray(opts.size)) { 38 | this._config.size = function () { 39 | return _.random(opts.size[0], opts.size[1]); 40 | }; 41 | } else { 42 | this._config.size = function () { 43 | return Number(opts.size); 44 | }; 45 | } 46 | } 47 | this._children = []; 48 | data.forEach(function (child, index) { 49 | this._children.push(helpers.build(child, index, this)); 50 | (function (method) { 51 | var child = this._children[method]; 52 | Object.defineProperty(this._currVal, method, { 53 | enumerable: true, 54 | get: function () { 55 | if (child._currVal === undefined) 56 | return child.next(); 57 | return child._currVal; // doc and array's currval never empty 58 | } 59 | }); 60 | }).call(this, index); 61 | }, this); 62 | 63 | debug('array <%s> build complete', name); 64 | } 65 | util.inherits(ArrayField, Entry); 66 | 67 | ArrayField.prototype.next = function () { 68 | var data = []; 69 | for (var i = this._config.size(); i > 0; i--) { 70 | this._clean(); 71 | this._children.forEach(function (child, index) { 72 | debug('array <%s> calls child <%s>', this._name, index); 73 | var res = child.next(); 74 | if (this._context._state.display || 75 | (!child._hidden && this._context._state.display === undefined)) 76 | data.push(res); 77 | this._context._reset(true); 78 | }, this); 79 | } 80 | return data; 81 | }; 82 | 83 | ArrayField.prototype._clean = function () { 84 | this._children.forEach(function (child) { 85 | child._clean(); 86 | }); 87 | }; 88 | 89 | module.exports = ArrayField; 90 | -------------------------------------------------------------------------------- /lib/schema/document.js: -------------------------------------------------------------------------------- 1 | var util = require('util'); 2 | var helpers = require('./helpers'); 3 | var Entry = require('./entry'); 4 | var debug = require('debug')('mongodb-datasets:schema:document'); 5 | 6 | /** 7 | * Internal representation of Objects (`{}`) 8 | * @class 9 | * @augments {Entry} 10 | * @memberOf module:schema 11 | * @param {object} document 12 | * @param {string} name 13 | * @param {(Entry|Schema)} parent 14 | */ 15 | function Document (document, name, parent) { 16 | if (!(this instanceof Document)) return new Document(document, name, parent); 17 | debug('document <%s> being built', name); 18 | Entry.call(this, document, name, parent); 19 | this._context = helpers.getAPI(this); 20 | this._currVal = {}; // 'this' object in evaluating user expressions 21 | this._this = this._currVal; 22 | Object.defineProperty(this._currVal, '_$parent', { 23 | get: function () { 24 | return parent._currVal; 25 | } 26 | }); 27 | 28 | this._children = {}; 29 | for (var childName in this._content) { 30 | this._children[childName] = 31 | helpers.build(this._content[childName], childName, this); 32 | (function (method) { 33 | var child = this._children[method]; 34 | Object.defineProperty(this._currVal, method, { 35 | enumerable: true, 36 | get: function () { 37 | if (child._currVal === undefined) 38 | return child.next(); 39 | return child._currVal; // doc and array's currval never empty 40 | } 41 | }); 42 | }).call(this, childName); 43 | } 44 | debug('document <%s> build complete', name); 45 | } 46 | util.inherits(Document, Entry); 47 | 48 | Document.prototype.next = function () { 49 | this._clean(); 50 | var data = {}; 51 | for (var name in this._children) { 52 | debug('document <%s> calls child <%s>', this._name, name); 53 | var child = this._children[name]; 54 | var res = child.next(); 55 | if (this._context._state.display || 56 | (!child._hidden && this._context._state.display === undefined)) 57 | data[name] = res; 58 | this._context._reset(true); 59 | } 60 | return data; 61 | }; 62 | 63 | Document.prototype._clean = function () { 64 | for (var child in this._children) { 65 | this._children[child]._clean(); 66 | } 67 | }; 68 | 69 | module.exports = Document; 70 | -------------------------------------------------------------------------------- /lib/schema/entry.js: -------------------------------------------------------------------------------- 1 | var debug = require('debug')('mongodb-datasets:schema:entry'); 2 | 3 | /** 4 | * High level representation of all fields in template schemas 5 | * @class 6 | * @abstract 7 | * @memberOf module:schema 8 | * @param {*} content 9 | * @param {string} name 10 | * @param {(Entry|Schema)} parent 11 | */ 12 | function Entry (content, name, parent) { 13 | if (!(this instanceof Entry)) return new Entry(content, name, parent); 14 | 15 | this._parent = parent; // parent can be a Document or ArrayField 16 | this._schema = parent._schema; // the root Document 17 | this._name = name; // the field name supplied by the user 18 | this._hidden = isHidden(name); // visibility in the output 19 | this._prevVal = undefined; 20 | this._currVal = undefined; // generated value in the current next() call 21 | this._content = content; // the original content supplied by the user 22 | this._context = null; // Context object used as compiled()'s argument 23 | this._this = null; // 'this' object user has access to 24 | } 25 | 26 | Entry.build = function () { 27 | debug('build'); 28 | }; 29 | 30 | Entry.prototype.next = function () { 31 | throw 'Abstract Method'; 32 | }; 33 | 34 | Entry.prototype._clean = function () { 35 | throw 'Abstract Method'; 36 | }; 37 | 38 | // helper functions 39 | 40 | function isHidden (name) { 41 | return /^_\$.*/.test(name); 42 | } 43 | 44 | module.exports = Entry; 45 | -------------------------------------------------------------------------------- /lib/schema/field.js: -------------------------------------------------------------------------------- 1 | var debug = require('debug')('mognodb-datasets:schema:field'); 2 | var util = require('util'); 3 | var _ = require('underscore'); 4 | var Entry = require('./entry'); 5 | 6 | _.templateSettings = { 7 | interpolate: /\{\{(.+?)\}\}/g 8 | }; 9 | 10 | /** 11 | * Internal representation of non-object non-array fields in template schema 12 | * @class 13 | * @augments {Entry} 14 | * @memberOf module:schema 15 | * @param {(string|number|boolean)} field 16 | * @param {string} name 17 | * @param {Entry} parent 18 | */ 19 | function Field (field, name, parent) { 20 | if (!(this instanceof Field)) return new Field(field, name, parent); 21 | 22 | debug('field <%s> being built from <%s>', name, field); 23 | Entry.call(this, field, name, parent); 24 | this._context = parent._context; 25 | this._this = parent._this; 26 | 27 | if (typeof this._content !== 'string') { 28 | this._compiled = function () { 29 | return this._content; 30 | }.bind(this); 31 | } else { 32 | this._compiled = _.template(this._content); 33 | } 34 | } 35 | util.inherits(Field, Entry); 36 | 37 | Field.prototype.next = function () { 38 | if (typeof this._currVal !== 'undefined') { 39 | debug('field <%s> value already generated', this._name); 40 | return this._currVal; 41 | } 42 | debug('field <%s> starts generating', this._name); 43 | 44 | this._context._reset(); 45 | var res = this._compiled.call(this._this, this._context); 46 | var alt = this._context._state.override; 47 | var data = (typeof alt === 'undefined') ? res : alt; 48 | debug('field <%s> completes generating', this._name); 49 | return (this._currVal = data); 50 | }; 51 | 52 | Field.prototype._clean = function () { 53 | this._prevVal = this._currVal; 54 | this._currVal = undefined; 55 | }; 56 | 57 | module.exports = Field; 58 | -------------------------------------------------------------------------------- /lib/schema/helpers.js: -------------------------------------------------------------------------------- 1 | var Document = require('./document'), 2 | Field = require('./field'), 3 | ArrayField = require('./array'), 4 | Joi = require('joi'), 5 | API = require('../api'); 6 | 7 | // @todo better isolation between API and class logic 8 | // maybe move all API-related default value, behavior to ../api? 9 | module.exports.getAPI = function (host) { 10 | return new API(host); 11 | }; 12 | 13 | module.exports.build = function (content, name, parent) { 14 | if (Array.isArray(content)) 15 | return new ArrayField(content, name, parent); 16 | if (typeof content === 'object') 17 | return new Document(content, name, parent); 18 | return new Field(content, name, parent); 19 | }; 20 | 21 | var SCHEMAS = { 22 | name: Joi.any(), 23 | field: Joi.any(), 24 | meta: Joi.any() 25 | }; 26 | 27 | module.exports.validate = function (schema) { 28 | Joi.validate(schema, SCHEMAS.field, function (err) { 29 | if (err) throw err; 30 | }); 31 | }; 32 | -------------------------------------------------------------------------------- /lib/schema/index.js: -------------------------------------------------------------------------------- 1 | // @todo wrong way to mark class as a member of a module 2 | 3 | /** 4 | * @module schema 5 | */ 6 | 7 | var helpers = require('./helpers'), 8 | debug = require('debug')('mongodb-datasets:schema:index'); 9 | 10 | /** 11 | * Internal representation of a user-defined schema 12 | * @class 13 | * @memberOf module:schema 14 | * @param {object} sc 15 | * @param {number} size 16 | */ 17 | function Schema (sc, size) { 18 | if (!(this instanceof Schema)) return new Schema(sc); 19 | debug('======BUILDING PHASE======'); 20 | helpers.validate(sc); 21 | debug('schema size=%d being built', size); 22 | this._schema = this; 23 | this._document = helpers.build(sc, '$root', this); 24 | this._state = { // serve as global state 25 | index: -1, 26 | size: size, 27 | counter: [] 28 | }; 29 | debug('======BUILDING ENDS======'); 30 | } 31 | 32 | Schema.prototype.next = function () { 33 | this._state.index += 1; 34 | debug('======GENERATING ROUND %s======', this._state.index); 35 | return this._document.next(); 36 | }; 37 | 38 | module.exports = Schema; 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mongodb-dataset-generator", 3 | "version": "0.1.6", 4 | "description": "What's a database without any data?", 5 | "scripts": { 6 | "test": "mocha", 7 | "docs-full": "jsdoc -c conf.json && open ./docs/index.html" 8 | }, 9 | "bin": { 10 | "mongodb-dataset-generator": "bin/mongodb-dataset-generator.js" 11 | }, 12 | "dependencies": { 13 | "bson": "^0.2.19", 14 | "chance": "^1.0.10", 15 | "debug": "^2.1.1", 16 | "event-stream": "^3.2.2", 17 | "faker": "^1.0.0", 18 | "joi": "^4.6.2", 19 | "mongodb-extended-json": "^1.1.2", 20 | "mongodb-uri": "^0.9.7", 21 | "underscore": "^1.8.2", 22 | "yargs": "^3.4.4" 23 | }, 24 | "devDependencies": { 25 | "dox": "^0.4.6", 26 | "gulp": "^3.8.6", 27 | "mocha": "^1.20.1", 28 | "gulp": "^3.8.11", 29 | "mocha": "^2.1.0", 30 | "mongodb-importer": "^0.0.0" 31 | }, 32 | "keywords": [ 33 | "mongodb", 34 | "mongodb.js" 35 | ], 36 | "repository": { 37 | "type": "git", 38 | "url": "http://github.com/mongodb-js/dataset-generator.git" 39 | }, 40 | "author": "Lucas Hrabovsky (http://imlucas.com)", 41 | "license": "MIT", 42 | "homepage": "http://github.com/mongodb-js/dataset-generator" 43 | } 44 | -------------------------------------------------------------------------------- /test/configArray.test.js: -------------------------------------------------------------------------------- 1 | var helpers = require('./helpers'); 2 | var assert = require('assert'); 3 | 4 | describe('configurable arrays', function() { 5 | var res = { items: null }; 6 | before(function(done) { 7 | var schema = { 8 | 'normal': { 9 | 'empty': [ ], 10 | 'single': [ 0 ], 11 | 'multi': [ 0, 1 ], 12 | 'random': [ 13 | '{{Number(chance.d10())}}', 14 | '{{chance.name()}}' 15 | ], 16 | 'mix': [ 17 | 1, 18 | { 'doc': 'got it' }, 19 | [ 0, 1 ] 20 | ] 21 | }, 22 | 'hardcoded': { 23 | 'zero': [ '{{_$config}}', 24 | { size: 0 }, 25 | 'nope' 26 | ], 27 | 'count': [ '{{_$config}}', 28 | { size: 3 }, 29 | '{{counter()}}' 30 | ] 31 | }, 32 | 'range': { 33 | 'names': [ '{{_$config}}', 34 | { size: [2, 4] }, 35 | '{{chance.name()}}' 36 | ] 37 | } 38 | }; 39 | var opts = { 40 | size: 10, 41 | }; 42 | helpers.generate(schema, opts, function (err, items) { 43 | if (err) return done(err); 44 | res.items = items; 45 | done(); 46 | }); 47 | }); 48 | 49 | it('should support constant normal array', function () { 50 | var normal = res.items[0].normal; 51 | assert.ok(Array.isArray(normal.empty)); 52 | assert.equal(0, normal.empty.length); 53 | assert.ok(Array.isArray(normal.single)); 54 | assert.equal(1, normal.single.length); 55 | assert.equal(0, normal.single[0]); 56 | assert.ok(Array.isArray(normal.multi)); 57 | assert.equal(2, normal.multi.length); 58 | assert.equal(0, normal.multi[0]); 59 | assert.equal(1, normal.multi[1]); 60 | }); 61 | 62 | it('should support normal array of mixed types', function () { 63 | var mix = res.items[0].normal.mix; 64 | assert.ok(Array.isArray(mix)); 65 | assert.equal(3, mix.length); 66 | assert.equal(1, mix[0]); 67 | assert.ok(typeof mix[1] === 'object'); 68 | assert.equal('got it', mix[1].doc); 69 | assert.ok(Array.isArray(mix[2])); 70 | assert.equal(2, mix[2].length); 71 | assert.equal(0, mix[2][0]); 72 | assert.equal(1, mix[2][1]); 73 | }); 74 | 75 | it('should support random normal array', function (done) { 76 | var rand = res.items[0].normal.random; 77 | assert.ok(Array.isArray(rand)); 78 | assert.equal(2, rand.length); 79 | assert.ok(typeof rand[0] === 'number'); 80 | assert.ok(typeof rand[1] === 'string'); 81 | helpers.sampleAndStrip(res.items, 3, function (sample) { 82 | var dices = sample.map(function (s) { 83 | return s.normal.random[0]; 84 | }); 85 | var names = sample.map(function (s) { 86 | return s.normal.random[1]; 87 | }); 88 | assert.ok(dices[0] !== dices[1] || dices[0] !== dices[2]); 89 | assert.ok(names[0] !== names[1] || names[0] !== names[2]); 90 | done(); 91 | }); 92 | }); 93 | 94 | it('should support hardcoded length', function () { 95 | var hardcoded = res.items[0].hardcoded; 96 | assert.ok(Array.isArray(hardcoded.zero)); 97 | assert.equal(0, hardcoded.zero.length); 98 | assert.ok(Array.isArray(hardcoded.count)); 99 | assert.equal(3, hardcoded.count.length); 100 | assert.equal(0, hardcoded.count[0]); 101 | assert.equal(1, hardcoded.count[1]); 102 | assert.equal(2, hardcoded.count[2]); 103 | }); 104 | 105 | it('should support random array length', function () { 106 | res.items.forEach(function (item) { 107 | assert.ok(Array.isArray(item.range.names)); 108 | var l = item.range.names.length; 109 | assert.ok(l > 1 && l < 5); 110 | }); 111 | }); 112 | 113 | }); 114 | -------------------------------------------------------------------------------- /test/generator.test.js: -------------------------------------------------------------------------------- 1 | var datasets = require('../'), 2 | assert = require('assert'), 3 | es = require('event-stream'); 4 | 5 | describe('Generator', function(){ 6 | it('should create a stream automatically', function(done) { 7 | datasets(1, {email: '{{chance.email()}}'}) 8 | .pipe(es.writeArray(function(err, docs) { 9 | if(err) return done(err); 10 | assert.equal(1, docs.length, 'generate one document'); 11 | assert(docs[0].email, 'with an email field'); 12 | done(); 13 | })); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /test/helpers.js: -------------------------------------------------------------------------------- 1 | var _ = require('underscore'), 2 | es = require('event-stream'), 3 | fs = require('fs'), 4 | datasets = require('../'); 5 | 6 | module.exports.regex = { 7 | phone: /(\(\d{3}\)\s*)(\d{3})-(\d{4})/, 8 | exp: /(\d{2})\/(\d{4})/ 9 | }; 10 | 11 | module.exports.generate = function (schema, opts, fn) { 12 | if(Object.prototype.toString.call(schema) === '[object Object]'){ 13 | return datasets(opts.size, schema).pipe(es.writeArray(fn)); 14 | } 15 | 16 | fs.createReadStream(schema) 17 | .pipe(datasets.createGeneratorStream({size: opts.size})) 18 | .pipe(es.writeArray(fn)); 19 | }; 20 | 21 | module.exports.resolveSchemaPath = function (name) { 22 | return './examples/' + name; 23 | }; 24 | 25 | module.exports.sampleAndStrip = function (array, count, fn) { 26 | var sample = _.sample(array, count); 27 | sample.forEach(function (item) { 28 | item._id = undefined; 29 | }); 30 | fn(sample); 31 | }; 32 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --recursive -R spec 2 | -------------------------------------------------------------------------------- /test/scoping.test.js: -------------------------------------------------------------------------------- 1 | var helpers = require('./helpers'); 2 | var assert = require('assert'); 3 | 4 | describe('scoping', function() { 5 | var res = { item: null }; 6 | before(function(done) { 7 | var schema = { 8 | 'greetings': 'This is {{this.name}}', 9 | 'name': '{{chance.name()}}', 10 | 'zip': '00000', 11 | 'full_zip': '{{this.zip+"-0000"}}', 12 | 'num': { 13 | 'neg': '{{Number(this._$parent.array.data[0])}}', 14 | 'one': 1, 15 | 'zip': '{{this._$parent.zip}}', 16 | 'name': '{{this._$parent.name}}', 17 | 's2': '{{this._$parent.s2}}', 18 | 'index': '{{Number(counter())}}' 19 | }, 20 | 'two': '{{Number(this.num.one+1)}}', 21 | 'dependent': '{{this.s1}}-{{this.s2}}', 22 | 's1': 'Constant', 23 | 's2': '{{chance.name()}}', 24 | 'index': '{{Number(this.num.index)}}', 25 | 'array': { 26 | 'data': [ -1 ], 27 | 'ref': [ 28 | '{{Number(this._$parent.num.one)}}', 29 | '{{this._$parent.name}}' 30 | ] 31 | } 32 | }; 33 | var options = { 34 | size: 1, 35 | }; 36 | helpers.generate(schema, options, function (err, items) { 37 | if (err) return done(err); 38 | res.item = items[0]; 39 | done(); 40 | }); 41 | }); 42 | 43 | it('should work with constants', function () { 44 | assert.equal('00000', res.item.zip); 45 | assert.equal('00000-0000', res.item.full_zip); 46 | }); 47 | 48 | it('should work with random content', function () { 49 | var name = res.item.name; 50 | assert.equal('This is ' + name, res.item.greetings); 51 | }); 52 | 53 | it('should work with embedded docs', function () { 54 | assert.ok(typeof res.item.num.one === 'number'); 55 | assert.deepEqual(1, res.item.num.one); 56 | assert.ok(typeof res.item.two === 'number'); 57 | assert.deepEqual(2, res.item.two); 58 | }); 59 | 60 | it('should support using not-yet-generated variables', function () { 61 | var d = res.item.dependent; 62 | assert.ok(typeof d === 'string'); 63 | var comps = d.split('-'); 64 | assert.equal('Constant', comps[0]); 65 | assert.equal(res.item.s2, comps[1]); 66 | }); 67 | 68 | it('should have access to parent variables', function () { 69 | assert.equal(res.item.num.zip, res.item.zip); 70 | assert.equal(res.item.num.name, res.item.name); 71 | assert.equal(res.item.num.s2, res.item.s2); 72 | }); 73 | 74 | it('should not cause redundant generating via getter of a doc', function () { 75 | assert.equal(0, res.item.index); 76 | assert.equal(0, res.item.num.index); 77 | }); 78 | 79 | it('should be able to refer elements in an array', function () { 80 | assert.ok(Array.isArray(res.item.array.data)); 81 | assert.equal(1, res.item.array.data.length); 82 | assert.equal(-1, res.item.array.data[0]); 83 | assert.equal(-1, res.item.num.neg); 84 | }); 85 | 86 | it('should support accessing data from inside an array', function () { 87 | assert.ok(Array.isArray(res.item.array.ref)); 88 | assert.equal(2, res.item.array.ref.length); 89 | assert.equal(1, res.item.array.ref[0]); 90 | assert.equal(res.item.name, res.item.array.ref[1]); 91 | }); 92 | 93 | }); 94 | -------------------------------------------------------------------------------- /test/smoke.test.js: -------------------------------------------------------------------------------- 1 | var helpers = require('./helpers'); 2 | var Joi = require('joi'); 3 | var assert = require('assert'); 4 | 5 | describe('generate other datatypes', function() { 6 | var res = { items: null }; 7 | var expected = { 8 | count: 5, 9 | schema: Joi.object().keys({ 10 | username: Joi.string().required(), 11 | email: Joi.string().email().required(), 12 | }).length(2) 13 | }; 14 | 15 | before(function(done) { 16 | var schema = { 17 | username: '{{chance.name()}}', 18 | email: '{{chance.email()}}' 19 | }; 20 | var opts = { 21 | size: 5, 22 | }; 23 | helpers.generate(schema, opts, function (err, items) { 24 | if (err) return done(err); 25 | res.items = items; 26 | done(); 27 | }); 28 | }); 29 | 30 | it('should have the correct size', function () { 31 | assert.equal(expected.count, res.items.length); 32 | }); 33 | 34 | it('should produce correct schema structure', function () { 35 | res.items.forEach(function (item) { 36 | Joi.validate(item, expected.schema, function(err) { 37 | assert.ifError(err); 38 | }); 39 | }); 40 | }); 41 | 42 | it('should produce entries with random content', function (done) { 43 | helpers.sampleAndStrip(res.items, 2, function (sample) { 44 | assert.notDeepEqual(sample[0], sample[1]); 45 | done(); 46 | }); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /test/structure/array.test.js: -------------------------------------------------------------------------------- 1 | var helpers = require('../helpers'); 2 | var Joi = require('joi'); 3 | var assert = require('assert'); 4 | 5 | describe('array of primitive types', function() { 6 | var res = { items: null }; 7 | var expected = { 8 | count: 31, 9 | schema: Joi.object().keys({ 10 | name: Joi.string().required(), 11 | friends: Joi.array().includes(Joi.string()). 12 | excludes(Joi.object()).required() 13 | }).length(2) 14 | }; 15 | 16 | before(function(done) { 17 | var schemaPath = helpers.resolveSchemaPath('30_array_field.json'); 18 | var opts = { 19 | size: 31, 20 | }; 21 | helpers.generate(schemaPath, opts, function (err, items) { 22 | if (err) return done(err); 23 | res.items = items; 24 | done(); 25 | }); 26 | }); 27 | 28 | it('should have the correct size', function () { 29 | assert.equal(expected.count, res.items.length); 30 | }); 31 | 32 | it('should produce correct schema structure', function () { 33 | res.items.forEach(function (item) { 34 | Joi.validate(item, expected.schema, function(err) { 35 | assert.ifError(err); 36 | }); 37 | }); 38 | }); 39 | 40 | it('should produce entries with random content', function (done) { 41 | helpers.sampleAndStrip(res.items, 2, function (sample) { 42 | assert.notDeepEqual(sample[0], sample[1]); 43 | done(); 44 | }); 45 | }); 46 | 47 | it('should produce arrays with random content', function (done) { 48 | var validItems = res.items.filter(function (item) { 49 | return item.friends.length > 1; 50 | }); 51 | helpers.sampleAndStrip(validItems, 1, function (sample) { 52 | var friends = sample[0].friends; 53 | assert.notDeepEqual(friends[0], friends[1]); 54 | done(); 55 | }); 56 | }); 57 | 58 | }); 59 | 60 | describe('array of documents', function() { 61 | var res = { items: null }; 62 | var expected = { 63 | count: 99, 64 | schema: Joi.object().keys({ 65 | name: Joi.string().required(), 66 | friends: Joi.array().includes(Joi.object().keys({ 67 | name: Joi.string().required(), 68 | phone: Joi.string().regex(helpers.regex.phone).required() 69 | }).length(2)).required() 70 | }).length(2) 71 | }; 72 | 73 | before(function(done) { 74 | var schemaPath = helpers.resolveSchemaPath('31_array_doc.json'); 75 | var opts = { 76 | size: 99, 77 | }; 78 | helpers.generate(schemaPath, opts, function (err, items) { 79 | if (err) return done(err); 80 | res.items = items; 81 | done(); 82 | }); 83 | }); 84 | 85 | it('should have the correct size', function () { 86 | assert.equal(expected.count, res.items.length); 87 | }); 88 | 89 | it('should produce correct schema structure', function () { 90 | res.items.forEach(function (item) { 91 | Joi.validate(item, expected.schema, function(err) { 92 | assert.ifError(err); 93 | }); 94 | }); 95 | }); 96 | 97 | it('should produce entries with random content', function (done) { 98 | helpers.sampleAndStrip(res.items, 2, function (sample) { 99 | assert.notDeepEqual(sample[0], sample[1]); 100 | done(); 101 | }); 102 | }); 103 | 104 | it('should produce arrays with random content', function (done) { 105 | var validItems = res.items.filter(function (item) { 106 | return item.friends.length > 1; 107 | }); 108 | helpers.sampleAndStrip(validItems, 1, function (sample) { 109 | var friends = sample[0].friends; 110 | assert.notDeepEqual(friends[0], friends[1]); 111 | done(); 112 | }); 113 | }); 114 | 115 | }); 116 | 117 | describe('array of embedded docs', function() { 118 | var res = { items: null }; 119 | var expected = { 120 | count: 19, 121 | schema: Joi.object().keys({ 122 | name: Joi.string().required(), 123 | friends: Joi.array().includes(Joi.object().keys({ 124 | name: Joi.string().required(), 125 | payment_method: Joi.object().keys({ 126 | type: Joi.string().required(), 127 | card: Joi.number().integer().required(), 128 | expiration: Joi.string().regex(helpers.regex.exp).required() 129 | }).length(3).required() 130 | }).length(2)).required() 131 | }).length(2) 132 | }; 133 | 134 | before(function(done) { 135 | var schemaPath = helpers.resolveSchemaPath('32_array_embed.json'); 136 | var opts = { 137 | size: 19, 138 | }; 139 | helpers.generate(schemaPath, opts, function (err, items) { 140 | if (err) return done(err); 141 | res.items = items; 142 | done(); 143 | }); 144 | }); 145 | 146 | it('should have the correct size', function () { 147 | assert.equal(expected.count, res.items.length); 148 | }); 149 | 150 | it('should produce correct schema structure', function () { 151 | res.items.forEach(function (item) { 152 | Joi.validate(item, expected.schema, function(err) { 153 | assert.ifError(err); 154 | }); 155 | }); 156 | }); 157 | 158 | it('should produce entries with random content', function (done) { 159 | helpers.sampleAndStrip(res.items, 2, function (sample) { 160 | assert.notDeepEqual(sample[0], sample[1]); 161 | done(); 162 | }); 163 | }); 164 | 165 | it('should produce arrays with random content', function (done) { 166 | var validItems = res.items.filter(function (item) { 167 | return item.friends.length > 1; 168 | }); 169 | helpers.sampleAndStrip(validItems, 1, function (sample) { 170 | var friends = sample[0].friends; 171 | assert.notDeepEqual(friends[0], friends[1]); 172 | done(); 173 | }); 174 | }); 175 | 176 | }); 177 | 178 | describe('embedded arrays', function() { 179 | var res = { items: null }; 180 | var expected = { 181 | count: 11, 182 | schema: Joi.object().keys({ 183 | name: Joi.string().required(), 184 | friends: Joi.array().includes(Joi.object().keys({ 185 | name: Joi.string().required(), 186 | payment_method: Joi.array().includes(Joi.object().keys({ 187 | type: Joi.string().required(), 188 | card: Joi.number().integer().required(), 189 | expiration: Joi.string().regex(helpers.regex.exp).required() 190 | }).length(3)).required() 191 | }).length(2)).required() 192 | }).length(2) 193 | }; 194 | 195 | before(function(done) { 196 | var schemaPath = helpers.resolveSchemaPath('33_array_arrays.json'); 197 | var opts = { 198 | size: 11, 199 | }; 200 | helpers.generate(schemaPath, opts, function (err, items) { 201 | if (err) return done(err); 202 | res.items = items; 203 | done(); 204 | }); 205 | }); 206 | 207 | it('should have the correct size', function () { 208 | assert.equal(expected.count, res.items.length); 209 | }); 210 | 211 | it('should produce correct schema structure', function () { 212 | res.items.forEach(function (item) { 213 | Joi.validate(item, expected.schema, function(err) { 214 | assert.ifError(err); 215 | }); 216 | }); 217 | }); 218 | 219 | it('should produce entries with random content', function (done) { 220 | helpers.sampleAndStrip(res.items, 2, function (sample) { 221 | assert.notDeepEqual(sample[0], sample[1]); 222 | done(); 223 | }); 224 | }); 225 | 226 | it('should produce arrays with random content', function (done) { 227 | var validItems = res.items.filter(function (item) { 228 | return item.friends.length > 1; 229 | }); 230 | helpers.sampleAndStrip(validItems, 1, function (sample) { 231 | var friends = sample[0].friends; 232 | assert.notDeepEqual(friends[0], friends[1]); 233 | done(); 234 | }); 235 | }); 236 | 237 | it('should produce embedded arrays with random content', function (done) { 238 | var validItems = res.items.filter(function (item) { 239 | return item.friends.filter(function (item) { 240 | return item.payment_method.length > 1; 241 | }).length > 0; 242 | }); 243 | helpers.sampleAndStrip(validItems, 1, function (sample) { 244 | var validSubItems = sample[0].friends.filter(function (item) { 245 | return item.payment_method.length > 1; 246 | }); 247 | helpers.sampleAndStrip(validSubItems, 1, function (sample) { 248 | var payment_methods = sample[0].payment_method; 249 | assert.notDeepEqual(payment_methods[0], payment_methods[1]); 250 | done(); 251 | }); 252 | }); 253 | }); 254 | 255 | }); 256 | -------------------------------------------------------------------------------- /test/structure/complex.test.js: -------------------------------------------------------------------------------- 1 | var helpers = require('../helpers'); 2 | var Joi = require('joi'); 3 | var assert = require('assert'); 4 | 5 | describe('complex schema', function() { 6 | var res = { items: null }; 7 | var expected = { 8 | count: 31, 9 | schema: Joi.object().keys({ 10 | user_email: Joi.string().email().required(), 11 | job: Joi.object().keys({ 12 | company: Joi.string().required(), 13 | phones: Joi.object().keys({ 14 | mobile: Joi.string().regex(helpers.regex.phone).required(), 15 | work: Joi.string().regex(helpers.regex.phone).required() 16 | }).length(2).required(), 17 | duties: Joi.string().required(), 18 | }).length(3).required(), 19 | personalities: Joi.object().keys({ 20 | favorites: Joi.object().keys({ 21 | number: Joi.number().integer().max(10).required(), 22 | city: Joi.string().required(), 23 | radio: Joi.string().required() 24 | }).length(3).required(), 25 | 'violence-rating': Joi.number().integer().max(6).required(), 26 | }).length(2).required(), 27 | friends: Joi.array().includes(Joi.object().keys({ 28 | name: Joi.string().required(), 29 | phones: Joi.array() 30 | .includes(Joi.string().regex(helpers.regex.phone)) 31 | .excludes(Joi.object()).required() 32 | }).length(2)).required() 33 | }).length(4) 34 | }; 35 | 36 | before(function(done) { 37 | var schemaPath = helpers.resolveSchemaPath('91_complex.json'); 38 | var opts = { 39 | size: 31, 40 | }; 41 | helpers.generate(schemaPath, opts, function (err, items) { 42 | if (err) return done(err); 43 | res.items = items; 44 | done(); 45 | }); 46 | }); 47 | 48 | it('should have the correct size', function () { 49 | assert.equal(expected.count, res.items.length); 50 | }); 51 | 52 | it('should produce correct schema structure', function () { 53 | res.items.forEach(function (item) { 54 | Joi.validate(item, expected.schema, function(err) { 55 | assert.ifError(err); 56 | }); 57 | }); 58 | }); 59 | 60 | it('should produce entries with random content', function (done) { 61 | helpers.sampleAndStrip(res.items, 2, function (sample) { 62 | assert.notDeepEqual(sample[0], sample[1]); 63 | done(); 64 | }); 65 | }); 66 | 67 | it('should produce arrays with random content', function (done) { 68 | var validItems = res.items.filter(function (item) { 69 | return item.friends.length > 1; 70 | }); 71 | helpers.sampleAndStrip(validItems, 1, function (sample) { 72 | var friends = sample[0].friends; 73 | assert.notDeepEqual(friends[0], friends[1]); 74 | done(); 75 | }); 76 | }); 77 | 78 | it('should produce embedded arrays with random content', function (done) { 79 | var validItems = res.items.filter(function (item) { 80 | return item.friends.filter(function (item) { 81 | return item.phones.length > 1; 82 | }).length > 0; 83 | }); 84 | helpers.sampleAndStrip(validItems, 1, function (sample) { 85 | var validSubItems = sample[0].friends.filter(function (item) { 86 | return item.phones.length > 1; 87 | }); 88 | helpers.sampleAndStrip(validSubItems, 1, function (sample) { 89 | var phones = sample[0].phones; 90 | assert.notDeepEqual(phones[0], phones[1]); 91 | done(); 92 | }); 93 | }); 94 | }); 95 | 96 | }); 97 | -------------------------------------------------------------------------------- /test/structure/embed.test.js: -------------------------------------------------------------------------------- 1 | var helpers = require('../helpers'); 2 | var Joi = require('joi'); 3 | var assert = require('assert'); 4 | 5 | describe('basic embedded schema', function() { 6 | var res = { items: null }; 7 | var expected = { 8 | count: 50, 9 | schema: Joi.object().keys({ 10 | user_email: Joi.string().email().required(), 11 | job: Joi.object().keys({ 12 | company: Joi.string().required(), 13 | phone: Joi.string().regex(helpers.regex.phone).required(), 14 | duties: Joi.string().required(), 15 | }).length(3).required() 16 | }).length(2) 17 | }; 18 | 19 | before(function(done) { 20 | var schemaPath = helpers.resolveSchemaPath('20_embedded_basic.json'); 21 | var opts = { 22 | size: 50, 23 | }; 24 | helpers.generate(schemaPath, opts, function (err, items) { 25 | if (err) return done(err); 26 | res.items = items; 27 | done(); 28 | }); 29 | }); 30 | 31 | it('should have the correct size', function () { 32 | assert.equal(expected.count, res.items.length); 33 | }); 34 | 35 | it('should produce correct schema structure', function () { 36 | res.items.forEach(function (item) { 37 | Joi.validate(item, expected.schema, function(err) { 38 | assert.ifError(err); 39 | }); 40 | }); 41 | }); 42 | 43 | it('should produce entries with random content', function (done) { 44 | helpers.sampleAndStrip(res.items, 2, function (sample) { 45 | assert.notDeepEqual(sample[0], sample[1]); 46 | done(); 47 | }); 48 | }); 49 | 50 | }); 51 | 52 | describe('schema with parallel embedded fields', function() { 53 | var res = { items: null }; 54 | var expected = { 55 | count: 33, 56 | schema: Joi.object().keys({ 57 | user_email: Joi.string().email().required(), 58 | job: Joi.object().keys({ 59 | company: Joi.string().required(), 60 | phone: Joi.string().regex(helpers.regex.phone).required(), 61 | duties: Joi.string().required(), 62 | }).length(3).required(), 63 | payment_method: Joi.object().keys({ 64 | type: Joi.string().required(), 65 | card: Joi.number().integer().required(), 66 | expiration: Joi.string().regex(helpers.regex.exp).required() 67 | }).length(3).required() 68 | }).length(3) 69 | }; 70 | 71 | before(function(done) { 72 | var schemaPath = helpers.resolveSchemaPath('21_embedded_multi.json'); 73 | var opts = { 74 | size: 33, 75 | }; 76 | helpers.generate(schemaPath, opts, function (err, items) { 77 | if (err) return done(err); 78 | res.items = items; 79 | done(); 80 | }); 81 | }); 82 | 83 | it('should have the correct size', function () { 84 | assert.equal(expected.count, res.items.length); 85 | }); 86 | 87 | it('should produce correct schema structure', function () { 88 | res.items.forEach(function (item) { 89 | Joi.validate(item, expected.schema, function(err) { 90 | assert.ifError(err); 91 | }); 92 | }); 93 | }); 94 | 95 | it('should produce entries with random content', function (done) { 96 | helpers.sampleAndStrip(res.items, 2, function (sample) { 97 | assert.notDeepEqual(sample[0], sample[1]); 98 | done(); 99 | }); 100 | }); 101 | 102 | }); 103 | 104 | describe('schema with high level of embedding', function() { 105 | var res = { items: null }; 106 | var expected = { 107 | count: 23, 108 | schema: Joi.object().keys({ 109 | user_email: Joi.string().email().required(), 110 | personalities: Joi.object().keys({ 111 | favorites: Joi.object().keys({ 112 | number: Joi.number().integer().max(10).required(), 113 | city: Joi.string().required(), 114 | radio: Joi.string().required() 115 | }).length(3).required(), 116 | rating: Joi.number().integer().max(6).required(), 117 | }).length(2).required() 118 | }).length(2) 119 | }; 120 | 121 | before(function(done) { 122 | var schemaPath = helpers.resolveSchemaPath('22_embedded_level.json'); 123 | var opts = { 124 | size: 23, 125 | }; 126 | helpers.generate(schemaPath, opts, function (err, items) { 127 | if (err) return done(err); 128 | res.items = items; 129 | done(); 130 | }); 131 | }); 132 | 133 | it('should have the correct size', function () { 134 | assert.equal(expected.count, res.items.length); 135 | }); 136 | 137 | it('should produce correct schema structure', function () { 138 | res.items.forEach(function (item) { 139 | Joi.validate(item, expected.schema, function(err) { 140 | assert.ifError(err); 141 | }); 142 | }); 143 | }); 144 | 145 | it('should produce entries with random content', function (done) { 146 | helpers.sampleAndStrip(res.items, 2, function (sample) { 147 | assert.notDeepEqual(sample[0], sample[1]); 148 | done(); 149 | }); 150 | }); 151 | 152 | }); 153 | 154 | describe('complex embedded schema', function() { 155 | var res = { items: null }; 156 | var expected = { 157 | count: 19, 158 | schema: Joi.object().keys({ 159 | user_email: Joi.string().email().required(), 160 | job: Joi.object().keys({ 161 | company: Joi.string().required(), 162 | phone: Joi.string().regex(helpers.regex.phone).required(), 163 | duties: Joi.string().required(), 164 | }).length(3).required(), 165 | personalities: Joi.object().keys({ 166 | favorites: Joi.object().keys({ 167 | number: Joi.number().integer().max(10).required(), 168 | city: Joi.string().required(), 169 | radio: Joi.string().required() 170 | }).length(3).required(), 171 | rating: Joi.number().integer().max(6).required(), 172 | }).length(2).required(), 173 | payment_method: Joi.object().keys({ 174 | type: Joi.string().required(), 175 | card: Joi.number().integer().required(), 176 | expiration: Joi.string().regex(helpers.regex.exp).required() 177 | }).length(3).required() 178 | }).length(4) 179 | }; 180 | 181 | before(function(done) { 182 | var schemaPath = helpers.resolveSchemaPath('23_embedded_complex.json'); 183 | var opts = { 184 | size: 19, 185 | }; 186 | helpers.generate(schemaPath, opts, function (err, items) { 187 | if (err) return done(err); 188 | res.items = items; 189 | done(); 190 | }); 191 | }); 192 | 193 | it('should have the correct size', function () { 194 | assert.equal(expected.count, res.items.length); 195 | }); 196 | 197 | it('should produce correct schema structure', function () { 198 | res.items.forEach(function (item) { 199 | Joi.validate(item, expected.schema, function(err) { 200 | assert.ifError(err); 201 | }); 202 | }); 203 | }); 204 | 205 | it('should produce entries with random content', function (done) { 206 | helpers.sampleAndStrip(res.items, 2, function (sample) { 207 | assert.notDeepEqual(sample[0], sample[1]); 208 | done(); 209 | }); 210 | }); 211 | 212 | }); 213 | -------------------------------------------------------------------------------- /test/types.test.js: -------------------------------------------------------------------------------- 1 | var helpers = require('./helpers'); 2 | var assert = require('assert'); 3 | 4 | describe('generate different types of data', function() { 5 | var res = { item: null }; 6 | before(function(done) { 7 | var schema = { 8 | double: { 9 | zero: '{{Number(0)}}', 10 | one: '{{Number(1)}}', 11 | decimal: '{{Number(0.1)}}', 12 | neg: '{{Number(-0.1)}}', 13 | array: ['{{Number(0)}}'] 14 | }, 15 | boolean: { 16 | basic: '{{Boolean(true)}}', 17 | interp: '{{Boolean(0)}}', 18 | string: '{{Boolean("false")}}' 19 | }, 20 | date: { 21 | basic: '{{Date(0)}}', 22 | string: '{{Date("01/01/2000")}}', 23 | type: '{{Date(chance.date())}}' 24 | }, 25 | primitive: { 26 | number: 1, 27 | bool1: true, 28 | bool0: false, 29 | null: null 30 | } 31 | }; 32 | var opts = { 33 | size: 1, 34 | }; 35 | helpers.generate(schema, opts, function (err, items) { 36 | if (err) return done(err); 37 | res.item = items[0]; 38 | done(); 39 | }); 40 | }); 41 | 42 | describe('#number', function() { 43 | it('should support integer', function () { 44 | assert.ok(typeof res.item.double.zero === 'number'); 45 | assert.strictEqual(0, res.item.double.zero); 46 | assert.ok(typeof res.item.double.one === 'number'); 47 | assert.strictEqual(1, res.item.double.one); 48 | }); 49 | 50 | it('should support decimal', function () { 51 | assert.ok(typeof res.item.double.decimal === 'number'); 52 | assert.strictEqual(0.1, res.item.double.decimal); 53 | }); 54 | 55 | it('should support negatives', function () { 56 | assert.ok(typeof res.item.double.neg === 'number'); 57 | assert.strictEqual(-0.1, res.item.double.neg); 58 | }); 59 | 60 | it('should work in array', function () { 61 | res.item.double.array.forEach(function (i) { 62 | assert.ok(typeof i === 'number'); 63 | assert.strictEqual(0, i); 64 | }); 65 | }); 66 | }); 67 | 68 | describe('#boolean', function() { 69 | it('should be able to build from boolean', function () { 70 | assert.ok(typeof res.item.boolean.basic === 'boolean'); 71 | assert.strictEqual(true, res.item.boolean.basic); 72 | }); 73 | 74 | it('should be able to interpolate from other values', function () { 75 | assert.ok(typeof res.item.boolean.interp === 'boolean'); 76 | assert.strictEqual(false, res.item.boolean.interp); 77 | assert.ok(typeof res.item.boolean.string === 'boolean'); 78 | assert.strictEqual(true, res.item.boolean.string); 79 | }); 80 | }); 81 | 82 | describe('#date', function() { 83 | it('should be able to build from integer', function () { 84 | assert.ok(res.item.date.basic instanceof Date); 85 | assert.strictEqual(0, res.item.date.basic.valueOf()); 86 | }); 87 | 88 | it('should be able to build from string', function () { 89 | assert.ok(res.item.date.string instanceof Date); 90 | assert.strictEqual(new Date('01/01/2000').valueOf(), 91 | res.item.date.string.valueOf()); 92 | }); 93 | 94 | it('should work with chance', function () { 95 | assert.ok(res.item.date.type instanceof Date); 96 | }); 97 | }); 98 | 99 | describe('#primitive', function() { 100 | it('should accept number', function () { 101 | assert.ok(typeof res.item.primitive.number === 'number'); 102 | assert.strictEqual(1, res.item.primitive.number); 103 | }); 104 | 105 | it('should accept boolean', function () { 106 | assert.ok(typeof res.item.primitive.bool0 === 'boolean'); 107 | assert.ok(typeof res.item.primitive.bool1 === 'boolean'); 108 | assert.strictEqual(false, res.item.primitive.bool0); 109 | assert.strictEqual(true, res.item.primitive.bool1); 110 | }); 111 | 112 | // it('should accept null', function () { 113 | // assert.strictEqual(null, res.item.primitive.null); 114 | // }); 115 | }); 116 | 117 | }); 118 | -------------------------------------------------------------------------------- /test/utils.test.js: -------------------------------------------------------------------------------- 1 | var helpers = require('./helpers'); 2 | var assert = require('assert'); 3 | 4 | describe('util methods for schema config file', function() { 5 | var res = { items: null }; 6 | before(function(done) { 7 | var schema = { 8 | counter: { 9 | normal: '{{Number(counter())}}', 10 | start: '{{Number(counter(2, 100))}}', 11 | step: '{{Number(counter(3, 0, 10))}}' 12 | }, 13 | index: '{{Number(_$index)}}', 14 | size: '{{Number(_$size)}}', 15 | v: { 16 | half: '{{hide(this._$parent.index < 5)}}mark', 17 | data: '{{this.half}}', 18 | '_$hide': 'wont be showed anyway', 19 | echo: '{{this._$hide}}' 20 | } 21 | }; 22 | var opts = { 23 | size: 10, 24 | }; 25 | helpers.generate(schema, opts, function (err, items) { 26 | if (err) return done(err); 27 | res.items = items; 28 | done(); 29 | }); 30 | }); 31 | 32 | describe('#counter', function() { 33 | it('should work in defaults', function () { 34 | var clock = 0; 35 | res.items.forEach(function (item) { 36 | assert.deepEqual(clock++, item.counter.normal); 37 | }); 38 | }); 39 | 40 | it('should work with customized start', function () { 41 | var clock = 100; 42 | res.items.forEach(function (item) { 43 | assert.deepEqual(clock++, item.counter.start); 44 | }); 45 | }); 46 | 47 | it('should work with customized step', function () { 48 | var clock = -10; 49 | res.items.forEach(function (item) { 50 | assert.deepEqual((clock += 10), item.counter.step); 51 | }); 52 | }); 53 | }); 54 | 55 | describe('#_$size', function () { 56 | it('should return the correct size', function () { 57 | assert.equal(10, res.items[0].size); 58 | }); 59 | }); 60 | 61 | describe('#hide(cond)', function () { 62 | it('should hide a field', function () { 63 | res.items.forEach(function (item) { 64 | if (item.index < 5) 65 | assert.ok(item.v.half === undefined); 66 | else 67 | assert.equal('mark', item.v.half); 68 | }); 69 | }); 70 | 71 | it('should make the hidden field accessible', function () { 72 | res.items.forEach(function (item) { 73 | assert.equal('mark', item.v.data); 74 | }); 75 | }); 76 | }); 77 | 78 | describe('#_$index', function () { 79 | it('should return the index of the current doc', function () { 80 | res.items.forEach(function (item, index) { 81 | assert.equal(index, item.index); 82 | }); 83 | }); 84 | }); 85 | 86 | describe('#_$fields', function () { 87 | it('should be hidden by default', function () { 88 | res.items.forEach(function (item) { 89 | assert.ok(item.v._$hide === undefined); 90 | }); 91 | }); 92 | 93 | it('should make the auto hidden field accessible', function () { 94 | res.items.forEach(function (item) { 95 | assert.equal('wont be showed anyway', item.v.echo); 96 | }); 97 | }); 98 | }); 99 | 100 | }); 101 | --------------------------------------------------------------------------------