From be68a2efadb7548ca69444035095a2936ccffb0e Mon Sep 17 00:00:00 2001 From: Omkar Khair Date: Sat, 27 Jul 2024 21:19:25 +0530 Subject: [PATCH 01/18] Converting internal calls to async to support HTTP calls --- bin/mgenerate.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/bin/mgenerate.js b/bin/mgenerate.js index 819b84d..eb0eea1 100755 --- a/bin/mgenerate.js +++ b/bin/mgenerate.js @@ -87,8 +87,11 @@ function generate() { if (count >= argv.number) { return this.emit('end'); } - this.emit('data', mgenerate(template)); - callback(); + let _self = this; + mgenerate(template).then(function(doc) { + _self.emit('data', doc); + callback(); + }); }) .pipe(stringifyStream) .pipe(process.stdout); From e4383323df60d4f4a52a113e2dde24c2956d48ca Mon Sep 17 00:00:00 2001 From: Omkar Khair Date: Sat, 27 Jul 2024 21:46:50 +0530 Subject: [PATCH 02/18] Added template.json to gitignore for local testing --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 153216e..70ed4c7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .DS_Store node_modules/ npm-debug.log +template.json From babc1fc0e7464d1cab08d1e842ab14a26c9effbe Mon Sep 17 00:00:00 2001 From: Omkar Khair Date: Sat, 27 Jul 2024 23:52:12 +0530 Subject: [PATCH 03/18] Updated test to refactor based on tests --- test/index.test.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/index.test.js b/test/index.test.js index b24cc81..6b8193f 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -5,16 +5,16 @@ var assert = require('assert'); context('General', function() { it('should throw an error if additional key is present after operator', function() { - assert.throws(function() { + assert.throws(async function() { var template = { foo: { $string: { length: 3 }, something: 'else' } }; - mgenerate(template); + await mgenerate(template); }); }); - it('should work with arrays', function() { - var res = mgenerate({ + it('should work with arrays', async function() { + var res = await mgenerate({ foo: [{ $string: { length: 3 } }, 'foo', '$integer'] }); assert.equal(typeof res.foo[0], 'string'); @@ -23,14 +23,14 @@ context('General', function() { assert.equal(typeof res.foo[2], 'number'); }); - it('should work with nested objects', function() { - var res = mgenerate({ foo: { bar: '$age' } }); + it('should work with nested objects', async function() { + var res = await mgenerate({ foo: { bar: '$age' } }); assert.equal(typeof res.foo, 'object'); assert.equal(typeof res.foo.bar, 'number'); }); - it('should resolve object parameters first', function() { - var res = mgenerate({ + it('should resolve object parameters first', async function() { + var res = await mgenerate({ foo: { $string: { length: { $integer: { min: 4, max: 4 } } } } }); assert.equal(res.foo.length, 4); From 56e1f1e812dd1472a96ae8a500bfdcbca7c0ae9c Mon Sep 17 00:00:00 2001 From: Omkar Khair Date: Sun, 28 Jul 2024 00:33:34 +0530 Subject: [PATCH 04/18] refactored index.js and array, coordinates, integer operators to meet general test cases. Async errors are still failing due to limitations with mocha. --- lib/index.js | 128 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 83 insertions(+), 45 deletions(-) diff --git a/lib/index.js b/lib/index.js index 40848c6..341463a 100644 --- a/lib/index.js +++ b/lib/index.js @@ -27,8 +27,8 @@ var evalValue; * value, false otherwise. * @return {Any} return the value after evaluation */ -var evalTemplate = function(template, isValue) { - return isValue ? evalValue(template) : evalObject(template); +var evalTemplate = async function(template, isValue) { + return isValue ? await evalValue(template) : await evalObject(template); }; /** @@ -38,7 +38,7 @@ var evalTemplate = function(template, isValue) { * @param {object} opts options passed to the operator * @return {any} return value after operator is resolved */ -var callOperator = function(op, opts) { +var callOperator = async function(op, opts) { opts = opts || {}; // exception for $missing values, handled in evalObject() if (op === 'missing') { @@ -50,11 +50,11 @@ var callOperator = function(op, opts) { } // known built-in operator, call with `evalTemplate` function and options if (_.includes(opNames, op)) { - return operators[op](evalTemplate, opts); + return await operators[op](evalTemplate, opts); } // not a known operator, try chance.js try { - return chance[op](evalObject(opts)); + return chance[op](await evalObject(opts)); } catch (e) { throw new Error('unknown operator: $' + op + '\nMessage: ' + e); } @@ -68,8 +68,27 @@ var callOperator = function(op, opts) { * @param {object} template template to evaluate * @return {any} return value after template is evaluated */ -evalObject = function(template) { - var result = _.mapValues(template, evalValue); +async function asyncMapValues(obj, asyncFn) { + const entries = Object.entries(obj); + const mappedEntries = await Promise.all( + entries.map(async ([key, value]) => { + const newValue = await asyncFn(value); + return [key, newValue]; + }) + ); + return Object.fromEntries(mappedEntries); +} + +async function asyncMap(array, asyncFn) { + // Map over the array to create an array of promises + const promises = array.map(asyncFn); + // Wait for all the promises to resolve and return the results + return Promise.all(promises); +} + +evalObject = async function(template) { + //var result = await new Promise(_.mapValues(template, evalValue)); + var result = await asyncMapValues(template, evalValue); result = _.omitBy(result, function(val) { return val === '$missing'; }); @@ -84,48 +103,67 @@ evalObject = function(template) { * @param {object} template template to evaluate * @return {any} return value after template is evaluated */ -evalValue = function(template) { - if (_.isString(template)) { - if (_.startsWith(template, '$')) { - return callOperator(template.slice(1)); +evalValue = async function(template) { + let promise = new Promise((resolve, reject) => { + if (_.isString(template)) { + if (_.startsWith(template, '$')) { + callOperator(template.slice(1)).then(function(t) { + resolve(t); + }); + return; + } + // check if the string can be interpreted as mustache template + if (_.includes(template, '{{')) { + var compiled = _.template(template, { + imports: { + chance: chance, + faker: faker + } + }); + return resolve(compiled()); + } + // string constant + return resolve(template); } - // check if the string can be interpreted as mustache template - if (_.includes(template, '{{')) { - var compiled = _.template(template, { - imports: { - chance: chance, - faker: faker - } + if (_.isPlainObject(template)) { + // check if this is an object-style operator + var objKeys = _.keys(template); + var op = objKeys[0]; + if (_.startsWith(op, '$')) { + op = op.slice(1); + assert.equal( + objKeys.length, + 1, + 'operator object cannot have more than one key.' + ); + var options = _.values(template)[0]; + callOperator(op, options).then(function(t) { + resolve(t); + }); + return; + } + evalObject(template).then(function(t) { + resolve(t); }); - return compiled(); + return; } - // string constant - return template; - } - if (_.isPlainObject(template)) { - // check if this is an object-style operator - var objKeys = _.keys(template); - var op = objKeys[0]; - if (_.startsWith(op, '$')) { - op = op.slice(1); - assert.equal( - objKeys.length, - 1, - 'operator object cannot have more than one key.' - ); - var options = _.values(template)[0]; - return callOperator(op, options); + // handle arrays recursively, skip $missing values + if (_.isArray(template)) { + asyncMap(template, evalValue).then(result => { + let r = _.filter(_.map(template, evalValue), function(v) { + return v !== '$missing'; + }); + + Promise.all(r).then(q => { + resolve(q); + }); + }); + return; } - return evalObject(template); - } - // handle arrays recursively, skip $missing values - if (_.isArray(template)) { - return _.filter(_.map(template, evalValue), function(v) { - return v !== '$missing'; - }); - } - // don't know how to evalute, leave alone - return template; + // don't know how to evalute, leave alone + resolve(template); + }); + return promise; }; module.exports = evalObject; From 65d8516618b4d640cc29ef37fc0523c7c88bc599 Mon Sep 17 00:00:00 2001 From: Omkar Khair Date: Sun, 28 Jul 2024 00:34:17 +0530 Subject: [PATCH 05/18] commiting operators modified to meet general tests --- lib/operators/array.js | 8 +++++--- lib/operators/coordinates.js | 4 ++-- lib/operators/integer.js | 4 ++-- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/operators/array.js b/lib/operators/array.js index bf64519..9d4a57d 100644 --- a/lib/operators/array.js +++ b/lib/operators/array.js @@ -14,12 +14,14 @@ var _ = require('lodash'); * @param {Object} options options to configure the array operator * @return {Array} array of `number` elements */ -module.exports = function(evaluator, options) { +module.exports = async function(evaluator, options) { + console.log('Array'); var item = options.of; - var number = evaluator(options.number, true); + var number = await evaluator(options.number, true); var replacement = _.map(_.range(number), function() { return item; }); - var result = evaluator(replacement, true); + var result = await evaluator(replacement, true); + console.log(result); return result; }; diff --git a/lib/operators/coordinates.js b/lib/operators/coordinates.js index 060827f..5a7ebcf 100644 --- a/lib/operators/coordinates.js +++ b/lib/operators/coordinates.js @@ -21,8 +21,8 @@ var _ = require('lodash'); * @return {Array} array of `number` elements */ -module.exports = function(evaluator, options) { - options = evaluator( +module.exports = async function(evaluator, options) { + options = await evaluator( _.defaults(options, { long_lim: [-180, 180], lat_lim: [-90, 90] diff --git a/lib/operators/integer.js b/lib/operators/integer.js index 7db1343..22ca5eb 100644 --- a/lib/operators/integer.js +++ b/lib/operators/integer.js @@ -14,9 +14,9 @@ var chance = require('chance').Chance(); var MAX_VAL = Math.pow(2, 31); -module.exports = function(evaluator, options) { +module.exports = async function(evaluator, options) { // default options - options = evaluator( + options = await evaluator( _.defaults(options, { min: -MAX_VAL, max: MAX_VAL From bd9bd5b5b6dc922cf36741b374ca79a17129413e Mon Sep 17 00:00:00 2001 From: Omkar Khair Date: Sun, 28 Jul 2024 15:37:34 +0530 Subject: [PATCH 06/18] Modified operator tests to support testing of the async implementation --- test/index.test.js | 2 +- test/operators.test.js | 265 +++++++++++++++++++++++------------------ 2 files changed, 149 insertions(+), 118 deletions(-) diff --git a/test/index.test.js b/test/index.test.js index 6b8193f..bda63f2 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -4,7 +4,7 @@ var assert = require('assert'); // var debug = require('debug')('mgenerate:test'); context('General', function() { - it('should throw an error if additional key is present after operator', function() { + it('should throw an error if additional key is present after operator', async function() { assert.throws(async function() { var template = { foo: { $string: { length: 3 }, something: 'else' } diff --git a/test/operators.test.js b/test/operators.test.js index e2594e9..460d5ab 100644 --- a/test/operators.test.js +++ b/test/operators.test.js @@ -4,42 +4,69 @@ var assert = require('assert'); var _ = require('lodash'); var bson = require('bson'); +async function asyncMap(array, asyncFn) { + // Map over the array to create an array of promises + const promises = array.map(asyncFn); + // Wait for all the promises to resolve and return the results + return Promise.all(promises); +} + context('Operators', function() { describe('$inc', function() { beforeEach(function() { // reset the inc operator to start from scratch operators.inc.reset(); }); - it('should work with default parameters', function() { + it('should work with default parameters', async function() { var template = { id: '$inc' }; - var res = _.map(_.range(5), function() { - return mgenerate(template); + let res = await asyncMap(_.range(5), async () => { + return await mgenerate(template); }); assert.deepEqual(_.map(res, 'id'), [0, 1, 2, 3, 4]); }); - it('should work with non-default start parameter', function() { + it('should work with non-default start parameter', async function() { var template = { id: { $inc: { start: 42 } } }; - var res = _.map(_.range(5), function() { - return mgenerate(template); - }); + + // TODO: Fix sync calls in these test cases + let res = [ + await mgenerate(template), + await mgenerate(template), + await mgenerate(template), + await mgenerate(template), + await mgenerate(template) + ]; + /*let res = await asyncMap(_.range(5), async () => { + return await mgenerate(template) + });*/ assert.deepEqual(_.map(res, 'id'), [42, 43, 44, 45, 46]); }); - it('should work with non-default step parameter', function() { + it('should work with non-default step parameter', async function() { var template = { id: { $inc: { start: 13, step: 2 } } }; - var res = _.map(_.range(3), function() { - return mgenerate(template); - }); + let res = [ + await mgenerate(template), + await mgenerate(template), + await mgenerate(template) + ]; + /*let res = await asyncMap(_.range(3), async () => { + return await mgenerate(template) + });*/ assert.deepEqual(_.map(res, 'id'), [13, 15, 17]); }); - it('should work with step parameter expression', function() { + it('should work with step parameter expression', async function() { var template = { id: { $inc: { start: 13, step: { $number: { min: 1, max: 2 } } } } }; - var res = _.map(_.range(3), function() { - return mgenerate(template); - }); + let res = [ + await mgenerate(template), + await mgenerate(template), + await mgenerate(template) + ]; + /* + let res = await asyncMap(_.range(3), async () => { + return await mgenerate(template) + });*/ assert.ok( _.every(_.map(res, 'id'), function(id) { return id >= 13 && id <= 17; @@ -49,8 +76,8 @@ context('Operators', function() { }); describe('$missing', function() { - it('should discard a $missing value', function() { - var res = mgenerate({ + it('should discard a $missing value', async function() { + var res = await mgenerate({ a: '$integer', b: '$missing', c: '$ip' @@ -58,21 +85,23 @@ context('Operators', function() { assert.ok(!_.has(res, 'b')); }); - it('should discard missing values in arrays', function() { - var res = mgenerate({ a: [1, '$missing', 3] }); + it('should discard missing values in arrays', async function() { + var res = await mgenerate({ a: [1, '$missing', 3] }); assert.equal(res.a.length, 2); assert.deepEqual(res.a, [1, 3]); }); }); describe('$choose', function() { - it('should chose from the given choices without weights', function() { - var res = mgenerate({ foo: { $choose: { from: ['a', 'b', 'c'] } } }); + it('should chose from the given choices without weights', async function() { + var res = await mgenerate({ + foo: { $choose: { from: ['a', 'b', 'c'] } } + }); assert.equal(typeof res.foo, 'string'); assert.ok(_.includes(['a', 'b', 'c'], res.foo)); }); - it('should choose from the given choices with weights', function() { - var res = mgenerate({ + it('should choose from the given choices with weights', async function() { + var res = await mgenerate({ foo: { $choose: { from: ['a', 'b', 'c'], @@ -86,36 +115,36 @@ context('Operators', function() { }); describe('$pick', function() { - it('should pick the correct element from an array', function() { - var res = mgenerate({ + it('should pick the correct element from an array', async function() { + var res = await mgenerate({ color: { $pick: { array: ['green', 'red', 'blue'], element: 1 } } }); assert.equal(res.color, 'red'); }); - it('should pick the first element if `element` is not specified', function() { - var res = mgenerate({ + it('should pick the first element if `element` is not specified', async function() { + var res = await mgenerate({ color: { $pick: { array: ['green', 'red', 'blue'] } } }); assert.equal(res.color, 'green'); }); - it('should return $missing if element is out of array bounds', function() { - var res = mgenerate({ + it('should return $missing if element is out of array bounds', async function() { + var res = await mgenerate({ color: { $pick: { array: ['green', 'red', 'blue'], element: 3 } } }); assert.ok(!_.has(res, 'color')); - res = mgenerate({ + res = await mgenerate({ color: { $pick: { array: ['green', 'red', 'blue'], element: -1 } } }); assert.ok(!_.has(res, 'color')); }); - it('should return $missing if `array` is not an array', function() { - var res = mgenerate({ + it('should return $missing if `array` is not an array', async function() { + var res = await mgenerate({ color: { $pick: { array: 'red', element: 3 } } }); assert.ok(!_.has(res, 'color')); @@ -123,16 +152,16 @@ context('Operators', function() { }); describe('$pickset', function() { - it('should pick the correct number of element', function() { - var res = mgenerate({ + it('should pick the correct number of element', async function() { + var res = await mgenerate({ color: { $pickset: { array: ['green', 'red', 'blue'], quantity: 2 } } }); assert.equal(res.color.length, 2); }); - it('should not pick the same item twice', function() { - var res = mgenerate({ + it('should not pick the same item twice', async function() { + var res = await mgenerate({ color: { $pickset: { array: ['green', 'red', 'blue'], quantity: 3 } } @@ -140,28 +169,28 @@ context('Operators', function() { var expset = ['green', 'red', 'blue'].sort(); assert.deepEqual(res.color.sort(), expset); }); - it('should pick one element if `quantity` is not specified', function() { - var res = mgenerate({ + it('should pick one element if `quantity` is not specified', async function() { + var res = await mgenerate({ color: { $pickset: { array: ['green', 'red', 'blue'] } } }); assert.equal(res.color.length, 1); }); - it('should return $missing if quantity is out of array bounds', function() { - var res = mgenerate({ + it('should return $missing if quantity is out of array bounds', async function() { + var res = await mgenerate({ color: { $pickset: { array: ['green', 'red', 'blue'], quantity: 4 } } }); assert.ok(!_.has(res, 'color')); - res = mgenerate({ + res = await mgenerate({ color: { $pickset: { array: ['green', 'red', 'blue'], quantity: -1 } } }); assert.ok(!_.has(res, 'color')); }); - it('should return $missing if `array` is not an array', function() { - var res = mgenerate({ + it('should return $missing if `array` is not an array', async function() { + var res = await mgenerate({ color: { $pickset: { array: 'red', quantity: 3 } } }); assert.ok(!_.has(res, 'color')); @@ -169,8 +198,8 @@ context('Operators', function() { }); describe('$array', function() { - it('should create a fixed-length array', function() { - var res = mgenerate({ + it('should create a fixed-length array', async function() { + var res = await mgenerate({ person: { first: { $first: { gender: 'female' } }, last: '$last', @@ -185,13 +214,13 @@ context('Operators', function() { ); }); - it('should return an empty array if no number is specified', function() { - var res = mgenerate({ foo: '$array' }); + it('should return an empty array if no number is specified', async function() { + var res = await mgenerate({ foo: '$array' }); assert.deepEqual(res.foo, []); }); - it('should evaluate the `number` option before creating the array', function() { - var res = mgenerate({ + it('should evaluate the `number` option before creating the array', async function() { + var res = await mgenerate({ myarr: { $array: { of: '$integer', @@ -202,8 +231,8 @@ context('Operators', function() { assert.equal(res.myarr.length, 6); }); - it('should evaluate the `of` option after creating the array', function() { - var res = mgenerate({ + it('should evaluate the `of` option after creating the array', async function() { + var res = await mgenerate({ myarr: { $array: { of: { $integer: { min: 0, max: 0 } }, number: 3 } } @@ -213,41 +242,43 @@ context('Operators', function() { }); describe('$join', function() { - it('should join elements without explicit separator', function() { - var res = mgenerate({ + it('should join elements without explicit separator', async function() { + var res = await mgenerate({ code: { $join: { array: ['foo', 'bar', 'baz'] } } }); assert.equal(res.code, 'foobarbaz'); }); - it('should join elements with explicit separator', function() { - var res = mgenerate({ + it('should join elements with explicit separator', async function() { + var res = await mgenerate({ code: { $join: { array: ['foo', 'bar', 'baz'], sep: '-' } } }); assert.equal(res.code, 'foo-bar-baz'); }); - it('should join elements with multi-character separator', function() { - var res = mgenerate({ + it('should join elements with multi-character separator', async function() { + var res = await mgenerate({ code: { $join: { array: ['foo', 'bar', 'baz'], sep: ' ==> ' } } }); assert.equal(res.code, 'foo ==> bar ==> baz'); }); - it('should join elements with multi-character separator', function() { - var res = mgenerate({ code: { $join: { array: 'foo', sep: ',' } } }); + it('should join elements with multi-character separator', async function() { + var res = await mgenerate({ + code: { $join: { array: 'foo', sep: ',' } } + }); assert.ok(!_.has(res, 'code')); }); }); describe('$coordinates', function() { - it('should work with default bounds', function() { - var res = mgenerate({ loc: '$coordinates' }); + it('should work with default bounds', async function() { + var res = await mgenerate({ loc: '$coordinates' }); assert.ok(_.isArray(res.loc)); assert.ok(res.loc[0] >= -180); assert.ok(res.loc[0] <= 180); assert.ok(res.loc[1] >= -90); assert.ok(res.loc[1] <= 90); }); - it('should work for with custom bounds', function() { - var res = mgenerate({ + it('should work for with custom bounds', async function() { + var res = await mgenerate({ loc: { $coordinates: { long_lim: [-2, 2], lat_lim: [-5, 5] } } }); assert.ok(_.isArray(res.loc)); @@ -259,8 +290,8 @@ context('Operators', function() { }); describe('$point', function() { - it('should create a GeoJSON point', function() { - var res = mgenerate({ loc: '$point' }); + it('should create a GeoJSON point', async function() { + var res = await mgenerate({ loc: '$point' }); assert.ok(_.isObject(res.loc)); assert.ok(_.has(res.loc, 'type')); assert.equal(res.loc.type, 'Point'); @@ -271,8 +302,8 @@ context('Operators', function() { }); describe('$polygon', function() { - it('should create a GeoJSON polygon with default number of corners', function() { - var res = mgenerate({ polygon: '$polygon' }); + it('should create a GeoJSON polygon with default number of corners', async function() { + var res = await mgenerate({ polygon: '$polygon' }); assert.ok(_.isObject(res.polygon)); assert.ok(_.has(res.polygon, 'type')); assert.equal(res.polygon.type, 'Polygon'); @@ -287,8 +318,8 @@ context('Operators', function() { res.polygon.coordinates[0][res.polygon.coordinates[0].length - 1] ); }); - it('should create a GeoJSON polygon with custom number of corners', function() { - var res = mgenerate({ polygon: { $polygon: { corners: 5 } } }); + it('should create a GeoJSON polygon with custom number of corners', async function() { + var res = await mgenerate({ polygon: { $polygon: { corners: 5 } } }); assert.ok(_.isObject(res.polygon)); assert.ok(_.has(res.polygon, 'type')); assert.equal(res.polygon.type, 'Polygon'); @@ -306,24 +337,24 @@ context('Operators', function() { }); describe('$objectid', function() { - it('should generate an ObjectID', function() { - var res = mgenerate({ _id: '$objectid' }); + it('should generate an ObjectID', async function() { + var res = await mgenerate({ _id: '$objectid' }); assert.ok(_.has(res, '_id')); assert.ok(res._id instanceof bson.ObjectID); }); }); describe('$now', function() { - it('should generate a current date', function() { - var res = mgenerate({ when: '$now' }); + it('should generate a current date', async function() { + var res = await mgenerate({ when: '$now' }); assert.ok(_.has(res, 'when')); assert.ok(res.when instanceof Date); }); }); describe('$regex', function() { - it('should generate a regular expression', function() { - var res = mgenerate({ + it('should generate a regular expression', async function() { + var res = await mgenerate({ rx: { $regex: { string: 'foo+bar.*$', flags: 'i' } } }); assert.ok(_.has(res, 'rx')); @@ -333,8 +364,8 @@ context('Operators', function() { }); describe('$timestamp', function() { - it('should generate an Timestamp', function() { - var res = mgenerate({ ts: { $timestamp: { t: 15, i: 3 } } }); + it('should generate an Timestamp', async function() { + var res = await mgenerate({ ts: { $timestamp: { t: 15, i: 3 } } }); assert.ok(_.has(res, 'ts')); assert.ok(res.ts instanceof bson.Timestamp); }); @@ -349,24 +380,24 @@ context('Operators', function() { }); describe('$minkey', function() { - it('should generate a MinKey object', function() { - var res = mgenerate({ min: '$minkey' }); + it('should generate a MinKey object', async function() { + var res = await mgenerate({ min: '$minkey' }); assert.ok(_.has(res, 'min')); assert.ok(res.min instanceof bson.MinKey); }); }); describe('$maxkey', function() { - it('should generate a MaxKey object', function() { - var res = mgenerate({ max: '$maxkey' }); + it('should generate a MaxKey object', async function() { + var res = await mgenerate({ max: '$maxkey' }); assert.ok(_.has(res, 'max')); assert.ok(res.max instanceof bson.MaxKey); }); }); describe('$string', function() { - it('should work for string format operator', function() { - var res = mgenerate({ foo: '$string' }); + it('should work for string format operator', async function() { + var res = await mgenerate({ foo: '$string' }); assert.equal(typeof res.foo, 'string'); }); it('should work for object format operator', function() { @@ -380,75 +411,75 @@ context('Operators', function() { }); describe('$integer / $number', function() { - it('should work for string format operator', function() { - var res = mgenerate({ foo: '$integer' }); + it('should work for string format operator', async function() { + var res = await mgenerate({ foo: '$integer' }); assert.equal(typeof res.foo, 'number'); }); - it('should support min and max parameters', function() { - var res = mgenerate({ foo: { $integer: { min: -10, max: 10 } } }); + it('should support min and max parameters', async function() { + var res = await mgenerate({ foo: { $integer: { min: -10, max: 10 } } }); assert.ok(res.foo >= -10); assert.ok(res.foo <= 10); }); - it('should have a $number alias for $integer', function() { - var res = mgenerate({ foo: { $number: { min: -10, max: 10 } } }); + it('should have a $number alias for $integer', async function() { + var res = await mgenerate({ foo: { $number: { min: -10, max: 10 } } }); assert.ok(_.isNumber(res.foo)); }); - it('should have a $numberInt alias for $integer', function() { - var res = mgenerate({ foo: { $numberInt: { min: -10, max: 10 } } }); + it('should have a $numberInt alias for $integer', async function() { + var res = await mgenerate({ foo: { $numberInt: { min: -10, max: 10 } } }); assert.ok(_.isNumber(res.foo)); }); }); describe('$numberDecimal / $decimal', function() { - it('should work using $numberDecimal as string operator', function() { - var res = mgenerate({ foo: '$numberDecimal' }); + it('should work using $numberDecimal as string operator', async function() { + var res = await mgenerate({ foo: '$numberDecimal' }); assert.ok(res.foo instanceof bson.Decimal128); }); - it('should work using $decimal as string operator', function() { - var res = mgenerate({ foo: '$decimal' }); + it('should work using $decimal as string operator', async function() { + var res = await mgenerate({ foo: '$decimal' }); assert.ok(res.foo instanceof bson.Decimal128); }); - it('should work using $numberDecimal as object operator', function() { - var res = mgenerate({ foo: { $numberDecimal: {} } }); + it('should work using $numberDecimal as object operator', async function() { + var res = await mgenerate({ foo: { $numberDecimal: {} } }); assert.ok(res.foo instanceof bson.Decimal128); }); - it('should work using $decimal as object operator', function() { - var res = mgenerate({ foo: { $decimal: { min: 90, max: 100 } } }); + it('should work using $decimal as object operator', async function() { + var res = await mgenerate({ foo: { $decimal: { min: 90, max: 100 } } }); assert.ok(res.foo instanceof bson.Decimal128); }); - it('should support min and max parameters', function() { - var res = mgenerate({ + it('should support min and max parameters', async function() { + var res = await mgenerate({ foo: { $numberDecimal: { min: 9999, max: 9999 } } }); var valStr = _.values(res.foo.toJSON())[0]; assert.ok(_.startsWith(valStr, '9999')); }); - it('should support fixed parameter', function() { - var res = mgenerate({ foo: { $numberDecimal: { fixed: 5 } } }); + it('should support fixed parameter', async function() { + var res = await mgenerate({ foo: { $numberDecimal: { fixed: 5 } } }); var valStr = _.values(res.foo.toJSON())[0]; assert.ok(valStr.match(/\.\d{0,5}/)); }); }); describe('$numberLong / $long', function() { - it('should work using $numberLong as string operator', function() { - var res = mgenerate({ foo: '$numberLong' }); + it('should work using $numberLong as string operator', async function() { + var res = await mgenerate({ foo: '$numberLong' }); assert.ok(res.foo instanceof bson.Long); }); - it('should work using $decimal as string operator', function() { - var res = mgenerate({ foo: '$long' }); + it('should work using $decimal as string operator', async function() { + var res = await mgenerate({ foo: '$long' }); assert.ok(res.foo instanceof bson.Long); }); - it('should work using $numberLong as object operator', function() { - var res = mgenerate({ foo: { $numberLong: {} } }); + it('should work using $numberLong as object operator', async function() { + var res = await mgenerate({ foo: { $numberLong: {} } }); assert.ok(res.foo instanceof bson.Long); }); - it('should work using $long as object operator', function() { - var res = mgenerate({ foo: { $long: { min: 90, max: 100 } } }); + it('should work using $long as object operator', async function() { + var res = await mgenerate({ foo: { $long: { min: 90, max: 100 } } }); assert.ok(res.foo instanceof bson.Long); }); - it('should support min and max parameters', function() { - var res = mgenerate({ + it('should support min and max parameters', async function() { + var res = await mgenerate({ foo: { $numberLong: { min: 9999, max: 9999 } } }); var val = res.foo.toInt(); @@ -456,13 +487,13 @@ context('Operators', function() { }); }); describe('$binary', function() { - it('subtype should be `00` instead of integer 0', function() { - var res = mgenerate({ foo: { $binary: { length: 10 } } }); + it('subtype should be `00` instead of integer 0', async function() { + var res = await mgenerate({ foo: { $binary: { length: 10 } } }); var val = res.foo.sub_type; assert.equal(val, '00'); }); - it('should set the subtype to 01', function() { - var res = mgenerate({ + it('should set the subtype to 01', async function() { + var res = await mgenerate({ foo: { $binary: { length: 10, subtype: '01' } } }); var val = res.foo.sub_type; From fb99cb95622dfbc6bae1bde559c36be2c14e23e9 Mon Sep 17 00:00:00 2001 From: Omkar Khair Date: Sun, 28 Jul 2024 16:02:28 +0530 Subject: [PATCH 07/18] Fixed an inefficient mapping during async implementation --- lib/index.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/index.js b/lib/index.js index 341463a..67cdfc0 100644 --- a/lib/index.js +++ b/lib/index.js @@ -68,6 +68,8 @@ var callOperator = async function(op, opts) { * @param {object} template template to evaluate * @return {any} return value after template is evaluated */ + +// TODO: Move asyncMapValues into its own module async function asyncMapValues(obj, asyncFn) { const entries = Object.entries(obj); const mappedEntries = await Promise.all( @@ -79,6 +81,7 @@ async function asyncMapValues(obj, asyncFn) { return Object.fromEntries(mappedEntries); } +// TODO: Move asyncMap into its own module async function asyncMap(array, asyncFn) { // Map over the array to create an array of promises const promises = array.map(asyncFn); @@ -150,13 +153,10 @@ evalValue = async function(template) { // handle arrays recursively, skip $missing values if (_.isArray(template)) { asyncMap(template, evalValue).then(result => { - let r = _.filter(_.map(template, evalValue), function(v) { + let filtered = _.filter(result, function(v) { return v !== '$missing'; }); - - Promise.all(r).then(q => { - resolve(q); - }); + resolve(filtered); }); return; } From 3755b42ed016ebcee9fead3b247a9bd749c36649 Mon Sep 17 00:00:00 2001 From: Omkar Khair Date: Sun, 28 Jul 2024 16:05:06 +0530 Subject: [PATCH 08/18] added await on two tests that we missed earlier --- test/operators.test.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/operators.test.js b/test/operators.test.js index 460d5ab..b48e317 100644 --- a/test/operators.test.js +++ b/test/operators.test.js @@ -400,12 +400,12 @@ context('Operators', function() { var res = await mgenerate({ foo: '$string' }); assert.equal(typeof res.foo, 'string'); }); - it('should work for object format operator', function() { - var res = mgenerate({ foo: { $string: { length: 3 } } }); + it('should work for object format operator', async function() { + var res = await mgenerate({ foo: { $string: { length: 3 } } }); assert.equal(typeof res.foo, 'string'); }); - it('should support length and pool parameters', function() { - var res = mgenerate({ foo: { $string: { length: 1, pool: 'a' } } }); + it('should support length and pool parameters', async function() { + var res = await mgenerate({ foo: { $string: { length: 1, pool: 'a' } } }); assert.equal(res.foo, 'a'); }); }); From 1ee259af2b9ab5b747e9befa1359f95ebae8a3ff Mon Sep 17 00:00:00 2001 From: Omkar Khair Date: Sun, 28 Jul 2024 16:06:45 +0530 Subject: [PATCH 09/18] Converted operators that require nested evaluations to async implementations --- lib/operators/array.js | 2 -- lib/operators/binary.js | 4 ++-- lib/operators/choose.js | 4 ++-- lib/operators/date.js | 4 ++-- lib/operators/decimal.js | 4 ++-- lib/operators/geometries.js | 15 +++++++++++---- lib/operators/inc.js | 8 +++++--- lib/operators/join.js | 6 +++--- lib/operators/linestring.js | 16 ++++++++++++---- lib/operators/long.js | 4 ++-- lib/operators/pick.js | 6 +++--- lib/operators/pickset.js | 6 +++--- lib/operators/point.js | 4 ++-- lib/operators/polygon.js | 16 ++++++++++++---- lib/operators/regex.js | 4 ++-- 15 files changed, 63 insertions(+), 40 deletions(-) diff --git a/lib/operators/array.js b/lib/operators/array.js index 9d4a57d..f28b912 100644 --- a/lib/operators/array.js +++ b/lib/operators/array.js @@ -15,13 +15,11 @@ var _ = require('lodash'); * @return {Array} array of `number` elements */ module.exports = async function(evaluator, options) { - console.log('Array'); var item = options.of; var number = await evaluator(options.number, true); var replacement = _.map(_.range(number), function() { return item; }); var result = await evaluator(replacement, true); - console.log(result); return result; }; diff --git a/lib/operators/binary.js b/lib/operators/binary.js index 1a0f7a5..1e74f1b 100644 --- a/lib/operators/binary.js +++ b/lib/operators/binary.js @@ -12,9 +12,9 @@ var _ = require('lodash'); * @param {Object} options options to configure the array operator * @return {Array} array of `number` elements */ -module.exports = function(evaluator, options) { +module.exports = async function(evaluator, options) { // default options - options = evaluator( + options = await evaluator( _.defaults(options, { length: 10, subtype: '00' diff --git a/lib/operators/choose.js b/lib/operators/choose.js index 9e8d0ae..c2660ca 100644 --- a/lib/operators/choose.js +++ b/lib/operators/choose.js @@ -23,13 +23,13 @@ var chance = require('chance').Chance(); * @param {Object} options options to configure the choose operator * @return {Any} chosen value, evaluated */ -module.exports = function(evaluator, options) { +module.exports = async function(evaluator, options) { var replacement; if (options.weights) { replacement = chance.weighted(options.from, options.weights); } else { replacement = chance.pickone(options.from); } - var result = evaluator(replacement, true); + var result = await evaluator(replacement, true); return result; }; diff --git a/lib/operators/date.js b/lib/operators/date.js index 12bdd9a..5537be4 100644 --- a/lib/operators/date.js +++ b/lib/operators/date.js @@ -14,9 +14,9 @@ var chance = require('chance').Chance(); * @param {Object} options options to configure the date operator * @return {Array} random date between `min` and `max` */ -module.exports = function(evaluator, options) { +module.exports = async function(evaluator, options) { // default options - options = evaluator( + options = await evaluator( _.defaults(options, { min: '1900-01-01T00:00:00.000Z', max: '2099-12-31T23:59:59.999Z' diff --git a/lib/operators/decimal.js b/lib/operators/decimal.js index ceacc3c..6fe4aec 100644 --- a/lib/operators/decimal.js +++ b/lib/operators/decimal.js @@ -13,9 +13,9 @@ var bson = require('bson'); * @return {Array} Decimal128 value */ -module.exports = function(evaluator, options) { +module.exports = async function(evaluator, options) { // default options - options = evaluator( + options = await evaluator( _.defaults(options, { min: 0, max: 1000, diff --git a/lib/operators/geometries.js b/lib/operators/geometries.js index f5ba2c6..67523e5 100644 --- a/lib/operators/geometries.js +++ b/lib/operators/geometries.js @@ -17,7 +17,14 @@ var _ = require('lodash'); * @return {Array} array of `number` elements */ -module.exports = function(evaluator, options) { +async function asyncMap(array, asyncFn) { + // Map over the array to create an array of promises + const promises = array.map(asyncFn); + // Wait for all the promises to resolve and return the results + return Promise.all(promises); +} + +module.exports = async function(evaluator, options) { // default options options = _.defaults(options, { types: ['Polygon', 'LineString', 'Point'], @@ -31,14 +38,14 @@ module.exports = function(evaluator, options) { }; // evaluate options first - options = evaluator(options); + options = await evaluator(options); // remove corners from options and produce `corners` coordinate pairs - var geometries = _.map(_.range(options.number), function() { + var geometries = await asyncMap(_.range(options.number), async function() { var op = nameToOperator[chance.pickone(options.types)]; var geometry = {}; geometry[op] = _.omit(options, ['types', 'number']); - return evaluator(geometry, true); + return await evaluator(geometry, true); }); var result = { diff --git a/lib/operators/inc.js b/lib/operators/inc.js index 320e753..b78677d 100644 --- a/lib/operators/inc.js +++ b/lib/operators/inc.js @@ -16,11 +16,13 @@ var counter = null; -module.exports = function(evaluator, options) { +module.exports = async function(evaluator, options) { if (counter === null) { - counter = options.start !== undefined ? evaluator(options.start, true) : 0; + counter = + options.start !== undefined ? await evaluator(options.start, true) : 0; } else { - var step = options.step !== undefined ? evaluator(options.step, true) : 1; + var step = + options.step !== undefined ? await evaluator(options.step, true) : 1; counter += step; } return counter; diff --git a/lib/operators/join.js b/lib/operators/join.js index 41d260b..dadaa97 100644 --- a/lib/operators/join.js +++ b/lib/operators/join.js @@ -15,10 +15,10 @@ var _ = require('lodash'); * @param {Object} options options to configure the array operator * @return {Array} array of `number` elements */ -module.exports = function(evaluator, options) { +module.exports = async function(evaluator, options) { var replacement; // evaluate options object first before picking - options = evaluator( + options = await evaluator( _.defaults(options, { sep: '' }) @@ -28,5 +28,5 @@ module.exports = function(evaluator, options) { } else { replacement = '$missing'; } - return evaluator(replacement, true); + return await evaluator(replacement, true); }; diff --git a/lib/operators/linestring.js b/lib/operators/linestring.js index fa2796c..7a2fcb9 100644 --- a/lib/operators/linestring.js +++ b/lib/operators/linestring.js @@ -21,19 +21,27 @@ var _ = require('lodash'); * @return {Array} array of `number` elements */ -module.exports = function(evaluator, options) { +// TODO: Move asyncMap into its own module +async function asyncMap(array, asyncFn) { + // Map over the array to create an array of promises + const promises = array.map(asyncFn); + // Wait for all the promises to resolve and return the results + return Promise.all(promises); +} + +module.exports = async function(evaluator, options) { // default options options = _.defaults(options, { locs: 2 }); // evaluate corners option first - var locs = evaluator(options.locs, true); + var locs = await evaluator(options.locs, true); // remove corners from options and produce `corners` coordinate pairs options = _.omit(options, 'locs'); - var coords = _.map(_.range(locs), function() { - return coordinates(evaluator, options); + var coords = await asyncMap(_.range(locs), async function() { + return await coordinates(evaluator, options); }); var result = { diff --git a/lib/operators/long.js b/lib/operators/long.js index 2ebf2f9..1038966 100644 --- a/lib/operators/long.js +++ b/lib/operators/long.js @@ -16,9 +16,9 @@ var bson = require('bson'); // biggest number in Javascript var MAX_VAL = Math.pow(2, 53); -module.exports = function(evaluator, options) { +module.exports = async function(evaluator, options) { // default options - options = evaluator( + options = await evaluator( _.defaults(options, { min: -MAX_VAL, max: MAX_VAL diff --git a/lib/operators/pick.js b/lib/operators/pick.js index 414c66d..61cb139 100644 --- a/lib/operators/pick.js +++ b/lib/operators/pick.js @@ -15,10 +15,10 @@ var _ = require('lodash'); * @param {Object} options options to configure the array operator * @return {Array} array of `number` elements */ -module.exports = function(evaluator, options) { +module.exports = async function(evaluator, options) { var replacement; // evaluate options object first before picking - options = evaluator( + options = await evaluator( _.defaults(options, { element: 0 }) @@ -33,5 +33,5 @@ module.exports = function(evaluator, options) { } else { replacement = '$missing'; } - return evaluator(replacement, true); + return await evaluator(replacement, true); }; diff --git a/lib/operators/pickset.js b/lib/operators/pickset.js index b3ea722..8b8fe37 100644 --- a/lib/operators/pickset.js +++ b/lib/operators/pickset.js @@ -16,11 +16,11 @@ var chance = require('chance')(); * @param {Object} options options to configure the array operator * @return {Array} array of `quantity` elements */ -module.exports = function(evaluator, options) { +module.exports = async function(evaluator, options) { var replacement; // default returns one element - options = evaluator( + options = await evaluator( _.defaults(options, { quantity: 1 }) @@ -37,5 +37,5 @@ module.exports = function(evaluator, options) { replacement = '$missing'; } - return evaluator(replacement, true); + return await evaluator(replacement, true); }; diff --git a/lib/operators/point.js b/lib/operators/point.js index d483038..22d78c9 100644 --- a/lib/operators/point.js +++ b/lib/operators/point.js @@ -18,10 +18,10 @@ * @return {Array} array of `number` elements */ -module.exports = function(evaluator, options) { +module.exports = async function(evaluator, options) { var result = { type: 'Point', coordinates: { $coordinates: options } }; - return evaluator(result, true); + return await evaluator(result, true); }; diff --git a/lib/operators/polygon.js b/lib/operators/polygon.js index ea5b0e4..aa7163f 100644 --- a/lib/operators/polygon.js +++ b/lib/operators/polygon.js @@ -20,19 +20,27 @@ var _ = require('lodash'); * @return {Array} array of `number` elements */ -module.exports = function(evaluator, options) { +// TODO: Move asyncMap into its own module +async function asyncMap(array, asyncFn) { + // Map over the array to create an array of promises + const promises = array.map(asyncFn); + // Wait for all the promises to resolve and return the results + return Promise.all(promises); +} + +module.exports = async function(evaluator, options) { // default options options = _.defaults(options, { corners: 3 }); // evaluate corners option first - var corners = evaluator(options.corners, true); + var corners = await evaluator(options.corners, true); // remove corners from options and produce `corners` coordinate pairs options = _.omit(options, 'corners'); - var coords = _.map(_.range(corners), function() { - return coordinates(evaluator, options); + var coords = await asyncMap(_.range(corners), async function() { + return await coordinates(evaluator, options); }); // close polygon diff --git a/lib/operators/regex.js b/lib/operators/regex.js index 151d532..26257a7 100644 --- a/lib/operators/regex.js +++ b/lib/operators/regex.js @@ -9,9 +9,9 @@ var _ = require('lodash'); * @param {Object} options options to configure the array operator * @return {Array} array of `number` elements */ -module.exports = function(evaluator, options) { +module.exports = async function(evaluator, options) { // default options - options = evaluator( + options = await evaluator( _.defaults(options, { string: '.*', flags: '' From 924d37b3505d085dc4a89b9572a71cc5a66eafb3 Mon Sep 17 00:00:00 2001 From: Omkar Khair Date: Sun, 28 Jul 2024 16:07:11 +0530 Subject: [PATCH 10/18] Converted timestamp operator to async --- lib/operators/timestamp.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/operators/timestamp.js b/lib/operators/timestamp.js index 14234d2..c0efb7e 100644 --- a/lib/operators/timestamp.js +++ b/lib/operators/timestamp.js @@ -15,9 +15,9 @@ var bson = require('bson'); var MAX_TS = Math.pow(2, 31) - 1; -module.exports = function(evaluator, options) { +module.exports = async function(evaluator, options) { // default options - options = evaluator( + options = await evaluator( _.defaults(options, { t: chance.integer({ min: 0, max: MAX_TS }), i: chance.integer({ min: 0, max: MAX_TS }) From d7ad9268e0ad3fcce6f5dddf17508115e4493707 Mon Sep 17 00:00:00 2001 From: Omkar Khair Date: Sun, 28 Jul 2024 16:08:54 +0530 Subject: [PATCH 11/18] Updated templates tests with async call --- test/templates.test.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/templates.test.js b/test/templates.test.js index 206beea..62d90f5 100644 --- a/test/templates.test.js +++ b/test/templates.test.js @@ -5,20 +5,20 @@ var _ = require('lodash'); // var debug = require('debug')('mgenerate:test:templates'); context('Templates', function() { - it('should have `chance` available inside a template', function() { + it('should have `chance` available inside a template', async function() { var template = { twitter_user: '{{ chance.twitter() }}' }; - var res = mgenerate(template); + var res = await mgenerate(template); assert.ok(_.isString(res.twitter_user)); assert.ok(_.startsWith(res.twitter_user, '@')); }); - it('should have `faker` available inside a template', function() { + it('should have `faker` available inside a template', async function() { var template = { slogan: '{{ faker.company.catchPhrase() }}' }; - var res = mgenerate(template); + var res = await mgenerate(template); assert.ok(_.isString(res.slogan)); }); - it('should evaluate javascript', function() { + it('should evaluate javascript', async function() { var template = { result: '{{ 1+1+1 }}' }; - var res = mgenerate(template); + var res = await mgenerate(template); assert.ok(_.isString(res.result)); assert.equal(res.result, '3'); }); From af70aaff1225ad36facebc2114bfa4dfa244b50f Mon Sep 17 00:00:00 2001 From: Omkar Khair Date: Sun, 28 Jul 2024 16:33:12 +0530 Subject: [PATCH 12/18] Added support for $text operator with a default response --- lib/operators/index.js | 1 + lib/operators/text.js | 15 +++++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 lib/operators/text.js diff --git a/lib/operators/index.js b/lib/operators/index.js index 9ee2f35..5fc2f9a 100644 --- a/lib/operators/index.js +++ b/lib/operators/index.js @@ -11,6 +11,7 @@ module.exports = { inc: require('./inc'), date: require('./date'), now: require('./now'), + text: require('./text'), /* * Geospatial data diff --git a/lib/operators/text.js b/lib/operators/text.js new file mode 100644 index 0000000..f274b4b --- /dev/null +++ b/lib/operators/text.js @@ -0,0 +1,15 @@ +/* eslint new-cap: 0 */ +// var debug = require('debug')('mgenerate:text'); + +/** + * $text returns a text generated by a large language model (llm) served through an ollama endpoint + * Ollama endpoint must be configured as a environment variable MGENERATIVEJS_OLLAMA_ENDPOINT + * + * @param {Function} evaluator evaluator function, passed in for every operator + * @param {Object} options options to configure the array operator + * @return {String} Generated text + */ +module.exports = async function(evaluator, options) { + // default options + return 'Hello World!'; +}; From 754927a7bbe3cc4682d1731b4081b7517fea05bb Mon Sep 17 00:00:00 2001 From: Omkar Khair Date: Sun, 28 Jul 2024 17:27:07 +0530 Subject: [PATCH 13/18] Added a .env to gitignore to support local testing with a .env file with ollama endpoints --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 70ed4c7..e3d69b7 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ node_modules/ npm-debug.log template.json +.env From 49dd3550efdea1b4a1aa2ee2f7e3dd9faaba393d Mon Sep 17 00:00:00 2001 From: Omkar Khair Date: Sun, 28 Jul 2024 17:30:00 +0530 Subject: [PATCH 14/18] First prototype of a functioning generative text with $text operator --- lib/operators/text.js | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/lib/operators/text.js b/lib/operators/text.js index f274b4b..d95eb7f 100644 --- a/lib/operators/text.js +++ b/lib/operators/text.js @@ -1,15 +1,47 @@ /* eslint new-cap: 0 */ // var debug = require('debug')('mgenerate:text'); +var axios = require('axios'); /** * $text returns a text generated by a large language model (llm) served through an ollama endpoint - * Ollama endpoint must be configured as a environment variable MGENERATIVEJS_OLLAMA_ENDPOINT + * Ollama endpoint and model must be configured as a environment variables MGENERATIVEJS_OLLAMA_ENDPOINT + * and MGENERATIVE_OLLAMA_MODEL respectively * * @param {Function} evaluator evaluator function, passed in for every operator * @param {Object} options options to configure the array operator * @return {String} Generated text */ module.exports = async function(evaluator, options) { - // default options - return 'Hello World!'; + // Configure client for Ollama endpoint + const ollamaEndpoint = process.env.MGENERATIVEJS_OLLAMA_ENDPOINT; + const model = process.env.MGENERATIVE_OLLAMA_MODEL; + + const ollamaClient = axios.create({ + baseURL: ollamaEndpoint, + timeout: 30000 + }); + + // Augment user prompt + let prompt = `Please generate a ${options.prompt} `; + if (options.maxWordCount && options.minWordCount) { + prompt += `with a word count between {minWordCount} and {maxWordCount}`; + } else if (options.maxWordCount) { + prompt += `with a word count less than ${options.maxWordCount}`; + } else if (options.minWordCount) { + prompt += `with a word count greater than ${options.minWordCount}`; + } + prompt += `. `; + prompt += `Ensure the response contains only the requested text without any additional commentary or conversational elements.`; + + //console.log(prompt) + // Generate text from Ollama + let response = await ollamaClient.post('/generate', { + model: model, + prompt: prompt, + stream: false + }); + + //console.log(response.data.response); + + return response.data.response; }; From 54e0a3e4b23b53d4c7519e4081a7385886f1f381 Mon Sep 17 00:00:00 2001 From: Omkar Khair Date: Sun, 28 Jul 2024 17:30:37 +0530 Subject: [PATCH 15/18] removed unnecessary logs --- lib/operators/text.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/operators/text.js b/lib/operators/text.js index d95eb7f..a8967ae 100644 --- a/lib/operators/text.js +++ b/lib/operators/text.js @@ -33,7 +33,6 @@ module.exports = async function(evaluator, options) { prompt += `. `; prompt += `Ensure the response contains only the requested text without any additional commentary or conversational elements.`; - //console.log(prompt) // Generate text from Ollama let response = await ollamaClient.post('/generate', { model: model, @@ -41,7 +40,5 @@ module.exports = async function(evaluator, options) { stream: false }); - //console.log(response.data.response); - return response.data.response; }; From dd9b6da866700e1ec623af16e43c83f72537ed72 Mon Sep 17 00:00:00 2001 From: Omkar Khair Date: Sun, 28 Jul 2024 18:10:32 +0530 Subject: [PATCH 16/18] Updated readme for text operator --- README.md | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9aca259..7455033 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# mgeneratejs [![travis][travis_img]][travis_url] [![npm][npm_img]][npm_url] +# mgenerativejs [![travis][travis_img]][travis_url] [![npm][npm_img]][npm_url] _mgeneratejs_ generates structured, semi-random JSON data according to a template object. It offers both a command line script and a JavaScript API. @@ -138,6 +138,10 @@ mgeneratejs '{"ip_addresses": {"$array": {"of": "$ip", "number": {"$integer": {" - [`$polygon`](#polygon): Returns a GeoJSON Polygon. - [`$geometries`](#geometries): Returns a GeoJSON GeometryCollection. +### Generated Topical Text + +- [`$text`](#text): Returns text generated based on a prompt (requires a [Ollama API](https://github.com/ollama/ollama) endpoint) + #### Native and MongoDB-specific Types - [`$binary`](#binary): Returns a MongoDB Binary type. @@ -589,6 +593,24 @@ _Options_ > > Returns `{"expr":{"$regex":"^ab+c$","$options":"i"}}`. +### `$text` + +**Experimental Feature** | Returns text generated based on the provided `prompt`. This operator has a dependency on an accessible Ollama endpoint. The endpoint and the model to generate the text should be configured in the environment variables `MGENERATIVEJS_OLLAMA_ENDPOINT` and `MGENERATIVE_OLLAMA_MODEL`. Check [Ollama documentation](https://github.com/ollama/ollama?tab=readme-ov-file#start-ollama) for instructions. + +_Options_ + +- `prompt` (required) Topical prompt to generate text (example: Designation or job title found in Tech). +- `minWordCount` (optional) Minimum word count for the model to generate text. This constrain will be included in the prompt and relies on the model to respect this constrain. +- `maxWordCount` (optional) Maximum word count for the model to generate text. This limit will be included in the prompt and relies on the model to respect this limit. + +> **Example** +> +> ``` +> {"jobTitle": {"$text": {"prompt": "Designation or job title found in Tech"}}} +> ``` +> +> Returns `{"jobTitle": "DevOps Engineer"}`. + ### `$timestamp` Returns a MongoDB Timestamp object. From 0bc28ff4ddedabf568547e985c12473ed0b2e68c Mon Sep 17 00:00:00 2001 From: Omkar Khair Date: Sun, 28 Jul 2024 18:13:17 +0530 Subject: [PATCH 17/18] Fixed formating for experimental section --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 7455033..fa9ffcd 100644 --- a/README.md +++ b/README.md @@ -138,10 +138,6 @@ mgeneratejs '{"ip_addresses": {"$array": {"of": "$ip", "number": {"$integer": {" - [`$polygon`](#polygon): Returns a GeoJSON Polygon. - [`$geometries`](#geometries): Returns a GeoJSON GeometryCollection. -### Generated Topical Text - -- [`$text`](#text): Returns text generated based on a prompt (requires a [Ollama API](https://github.com/ollama/ollama) endpoint) - #### Native and MongoDB-specific Types - [`$binary`](#binary): Returns a MongoDB Binary type. @@ -156,6 +152,10 @@ mgeneratejs '{"ip_addresses": {"$array": {"of": "$ip", "number": {"$integer": {" - [`$regex`](#regex): Returns a Regular Expression object. - [`$timestamp`](#timestamp): Returns a MongoDB Timestamp. +#### (Experimental) Generative Data + +- [`$text`](#text): Returns text generated based on a prompt (requires a [Ollama API](https://github.com/ollama/ollama) endpoint). + ### All Built-in Operators in Alphabetical Order ### `$array` From a0e5041d421bce731e52690b6200d96d24f8ab30 Mon Sep 17 00:00:00 2001 From: Omkar Khair Date: Sun, 28 Jul 2024 21:43:06 +0530 Subject: [PATCH 18/18] Updated package json with axios dependency --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 9c1a3e2..72b5ab4 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "url": "git://github.com/rueckstiess/mgeneratejs.git" }, "dependencies": { + "axios": "^1.7.2", "bson": "^4.6.3", "chance": "^1.1.8", "event-stream": "^4.0.1",