diff --git a/packages/cli/package.json b/packages/cli/package.json new file mode 100644 index 0000000000000000000000000000000000000000..1247faa55274799cd910a1703d235a18499193ee --- /dev/null +++ b/packages/cli/package.json @@ -0,0 +1,17 @@ +{ + "name": "@jsonql/cli", + "version": "0.1.0", + "description": "Interactive cli program to setup your jsonql dev environment", + "main": "index.js", + "scripts": { + "test": "ava" + }, + "keywords": [ + "jsonql", + "cli", + "interactive", + "shell" + ], + "author": "Joel Chu ", + "license": "ISC" +} diff --git a/packages/contract-cli/cli.js b/packages/contract-cli/cli.js index fec3f681f46a7764993f483d88f4199450a068b7..9ec5386f6f843c7cb3a2cc564a93b194e4efabe3 100755 --- a/packages/contract-cli/cli.js +++ b/packages/contract-cli/cli.js @@ -5,7 +5,13 @@ const { version } = require('./package.json') * Using Jsdoc-api to generate our contract file * https://www.npmjs.com/package/jsdoc-api */ -const { applyDefaultOptions, generator, getPaths, checkFile } = require('./lib') +const { + applyDefaultOptions, + generator, + getPaths, + checkFile, + watcher +} = require('./lib') const { KEY_WORD } = require('jsonql-constants') const { join } = require('path') const debug = require('debug')('jsonql-contract:cli') @@ -28,12 +34,10 @@ const run = (cmd, argv) => { )) .then( ({result, config}) => ( - result.then(dist => ( - checkFile(config)(dist) - ) - ) + result.then(dist => checkFile(config)(dist)) ) ) + .then(watcher) .catch(err => { console.error('json:ql contract-cli error!', err) }) @@ -62,15 +66,6 @@ require('yargs') // we must check the inDir run('create', argv) }) - /* // @TODO - .command('remote [hostname]', 'Get public contract file from remote host', yargs => { - yargs - .positional('hostname', { - describe: 'Where to get the remote contract file' - }) - }, argv => { - run('remote', argv) - }) */ .command('config [configFile]', 'Pass a config file to execute the command', yargs => { yargs .positional('configFile', { @@ -92,8 +87,9 @@ require('yargs') alias: 'r', default: false }) - .option('out', { - describe: 'Where to save your remote contract file', - alias: 'o' + .option('watch', { + describe: 'Enable watch mode', + alias: 'w', + default: false }) .argv diff --git a/packages/contract-cli/index.js b/packages/contract-cli/index.js index 0b07cdd1ee3cfb8aa493b8b5fd86d6e945f28f97..c5146a0b2aafc16854358e1400a7d64ef6d0fc54 100755 --- a/packages/contract-cli/index.js +++ b/packages/contract-cli/index.js @@ -2,7 +2,8 @@ const { applyDefaultOptions, generator, - getPaths + getPaths, + watcher } = require('./lib') const { KEY_WORD } = require('jsonql-constants') // const debug = require('debug')('jsonql-contract:api'); @@ -10,6 +11,16 @@ const { KEY_WORD } = require('jsonql-constants') module.exports = function(config) { return applyDefaultOptions(config) .then( - opts => getPaths(opts).then(generator) + opts => getPaths(opts) + .then(generator) + .then(result => { + // let the watcher run + watcher(opts) + // always return the result for the next op + return result; + }) ) + .catch(err => { + console.error(`An error has occured`, err) + }) } diff --git a/packages/contract-cli/lib/generator/es-extra.js b/packages/contract-cli/lib/generator/es-extra.js index 6d387d7fa796bdca021fdb6ce2a03f8366130101..30f3fb61881b4d12d22e19e6ca7cef0d1c44a3d5 100644 --- a/packages/contract-cli/lib/generator/es-extra.js +++ b/packages/contract-cli/lib/generator/es-extra.js @@ -86,7 +86,7 @@ const createResolverListFile = (contract, resolverDir) => { * @return {void} nothing */ module.exports = function postProcess(sourceType, resolverDir, contract) { - debug(sourceType, resolverDir) + debug('postProcess: sourceType:', sourceType, resolverDir) if (sourceType === MODULE_TYPE) { let src = join(__dirname , IMPORT_JS_TEMPLATE) let dist = join(resolverDir, DEFAULT_RESOLVER_IMPORT_FILE_NAME) diff --git a/packages/contract-cli/lib/generator/files.js b/packages/contract-cli/lib/generator/files.js index c7cf0491d28aef1d4cfde314765b0bec691069d3..a9482eed41b225f395a108d6788adf3946a0eb5b 100644 --- a/packages/contract-cli/lib/generator/files.js +++ b/packages/contract-cli/lib/generator/files.js @@ -123,8 +123,9 @@ const readFilesOutContract = function(inDir, config, fileType) { */ const keepOrCleanContract = function(config, dist) { // @TODO we might want to keep the file based on the configuration - if (fsx.existsSync(dist)) { + if (fsx.existsSync(dist) && config.alwaysNew === true) { debug('[@TODO] Found existing contract [%s]', dist) + return fsx.removeSync(dist) } } diff --git a/packages/contract-cli/lib/index.js b/packages/contract-cli/lib/index.js index 7700c8acba270664569bd451789c49a9205aa227..c1b06e86d22aebf74286e0f1a2ca15d4fd31a427 100755 --- a/packages/contract-cli/lib/index.js +++ b/packages/contract-cli/lib/index.js @@ -1,10 +1,14 @@ const generator = require('./generator') const getPaths = require('./get-paths') const { checkFile, applyDefaultOptions } = require('./utils') +// export watcher +const watcher = require('./watcher') + // main module.exports = { applyDefaultOptions, generator, getPaths, - checkFile + checkFile, + watcher } diff --git a/packages/contract-cli/lib/options.js b/packages/contract-cli/lib/options.js index 30e31952c633a7e9244211b5f39c760d68c47008..0d757acfa43cf8e34f275a95252c8f2043a50b95 100644 --- a/packages/contract-cli/lib/options.js +++ b/packages/contract-cli/lib/options.js @@ -52,7 +52,10 @@ const defaultOptions = { // matching the name across the project - the above two will become alias to this resolverDir: createConfig(resolve(join(BASE_DIR, DEFAULT_RESOLVER_DIR)) , STRING_TYPE, {[ALIAS_KEY]: 'inDir'}), contractDir: createConfig(resolve(join(BASE_DIR, DEFAULT_CONTRACT_DIR)), STRING_TYPE, {[ALIAS_KEY]: 'outDir'}), + // show or hide the description field in the public contract contractWithDesc: createConfig(false, [BOOLEAN_TYPE]), + // remove the old one and always create a new one - useful during development + alwaysNew: createConfig(false, [BOOLEAN_TYPE]), // where to put the public method publicMethodDir: constructConfig(PUBLIC_KEY, STRING_TYPE), // just try this with string type first diff --git a/packages/contract-cli/lib/public-contract/index.js b/packages/contract-cli/lib/public-contract/index.js index 0e10e9ba8d7976d48f8c9edc1ca0594916fc5576..a5d25c31d62929154dcd6b83373c9be9ce0692cf 100644 --- a/packages/contract-cli/lib/public-contract/index.js +++ b/packages/contract-cli/lib/public-contract/index.js @@ -41,7 +41,7 @@ const cleanForPublic = (json, config) => { } } // don't need this in the public json - debug(json.sourceType) + // debug(json.sourceType) delete json.sourceType; // export return json; diff --git a/packages/contract-cli/lib/utils.js b/packages/contract-cli/lib/utils.js index 838efc15004315b3e39bc364a6c5aafe72124756..910833698a7bb4e4594afc677bb78facfd940ef4 100644 --- a/packages/contract-cli/lib/utils.js +++ b/packages/contract-cli/lib/utils.js @@ -85,21 +85,21 @@ const applyDefaultOptions = (config, cmd = false) => ( /** * create a message to tell the user where the file is * @param {object} config clean supply - * @return {function} accept dist param + * @return {function} accept dist param --> return config */ const checkFile = config => { return dist => { if (config.returnAs === 'file') { - if (fsx.existsSync(dist)) { - return console.info('Your contract file generated in: %s', dist) - } - debug(dist) - throw new JsonqlError('File is not generated!', dist) + if (!fsx.existsSync(dist)) { + throw new JsonqlError('File is not generated!', dist) + } + console.info('Your contract file generated in: %s', dist) } else { if (!isObject(dist)) { throw new JsonqlError('Contract json not in the correct format!') } } + return config; // now keep returning the config for next op } } diff --git a/packages/contract-cli/lib/watcher/run.js b/packages/contract-cli/lib/watcher/run.js index 1c5cb49eecd5262f4fa29a7bb0a6b89b6074045c..400ce0cd767dd7b05476d4087fbc1fec5317a5c7 100644 --- a/packages/contract-cli/lib/watcher/run.js +++ b/packages/contract-cli/lib/watcher/run.js @@ -1,16 +1,24 @@ // this will get call to run the generator const generator = require('../generator') -const socketClient = require('./socket') -const debug = require('debug')('jsonql-contract:watcher:run') +// forget the socket client for now +// const socketClient = require('./socket') +// const debug = require('debug')('jsonql-contract:watcher:run') -let fn, config; +// let fn, config; process.on('message', m => { + if (m.change && m.config) { + generator(config) + .then(result => { + process.send({ result }) + }) + } + /* if (m.config) { config = m.config; fn = socketClient(m.config) } else { - generator(config) fn(m) } + */ }) diff --git a/packages/contract-cli/lib/watcher/watcher.js b/packages/contract-cli/lib/watcher/watcher.js index 5de77d0f98fe23a4f4f9b8adcfa393c2e514d570..bcc1439537bfc8cefff07c0471c4b1f09da8d77b 100644 --- a/packages/contract-cli/lib/watcher/watcher.js +++ b/packages/contract-cli/lib/watcher/watcher.js @@ -23,32 +23,41 @@ const isEnable = config => { return join( config.resolverDir, '**', ['*', ext].join('.')) } return false; -}; +} /** * main interface * @param {object} config clean options - * @return {void} + * @return {boolean | function} false if it's not enable */ module.exports = function(config) { let watchPath; if ((watchPath = isEnable(config)) !== false) { - debug('watch this', watchPath); + debug('watching this', watchPath) // create the watcher const watcher = chokidar.watch(watchPath, {}) + + const closeFn = () => watcher.close() + // create a fork process const ps = fork(join(__dirname, 'run.js')) - ps.send({ config }); + // modify the config to make sure the contract file(s) get clean + config.alwaysNew = true; + // kick start the process + // ps.send({ config }) // now watch const stream = kefir.stream(emitter => { - watcher.on('raw', (evt, path, details) => { + watcher.on('change', (evt, path, details) => { ++counter; debug(`(${counter}) got even here`, evt, path, details) - emitter.emit({evt, path, details}) - }); + emitter.emit({ + change: true, + config + }) + }) // call exit - return () => watcher.close() - }); + return closeFn; + }) stream.throttle(config.interval).observe({ value(value) { @@ -56,14 +65,17 @@ module.exports = function(config) { } }) - // we can remotely shut it down + // we can remotely shut it down - when using the socket option ps.on('message', msg => { if (msg.end) { console.info(colors.green('Watcher is shutting down')) - watcher.close(); + watcher.close() } else if (msg.txt) { console.log(colors.yellow(msg.txt)) } }) + // return the close method + return closeFn; } + return false; // nothing happen } diff --git a/packages/contract-cli/package.json b/packages/contract-cli/package.json index c5d0172bd5157b3a089f8966deb944c0ae0702f3..15b6c8bde93c61936e7d73c8cbfbb1ebb1bf6949 100755 --- a/packages/contract-cli/package.json +++ b/packages/contract-cli/package.json @@ -1,6 +1,6 @@ { "name": "jsonql-contract", - "version": "1.7.4", + "version": "1.7.5", "description": "An command line tool to generate the contract.json for jsonql", "main": "index.js", "files": [ @@ -9,12 +9,11 @@ "index.js" ], "scripts": { - "build": "rollup", "dev": "DEBUG=jsonql-contract:* node ./test.js", "coverage": "nyc ava --verbose", - "cli": "DEBUG=jsonql-contract* node ./cmd.js", + "cli": "DEBUG=jsonql-contract* node ./cli.js", + "cli:watch": "npm run cli", "test:cli": "npm run cli configFile ./tests/fixtures/cmd-config-test.js", - "test:ts": "echo '@TODO'", "test": "DEBUG=jsonql-contract:jsdoc-api ava", "test:doc": "DEBUG=jsonql-contract:* ava ./tests/contract-with-doc.test.js", "test:gen": "DEBUG=jsonql-contract:* ava ./tests/generator.test.js", @@ -39,26 +38,26 @@ "jsonql-contract": "./cli.js" }, "dependencies": { - "acorn": "^6.1.1", - "chokidar": "^3.0.1", + "acorn": "^6.2.0", + "chokidar": "^3.0.2", "colors": "^1.3.3", "fs-extra": "^8.1.0", "glob": "^7.1.4", - "jsdoc-api": "^5.0.1", + "jsdoc-api": "^5.0.2", "jsonql-constants": "^1.7.8", "jsonql-errors": "^1.0.9", "jsonql-params-validator": "^1.4.3", "kefir": "^3.8.6", - "lodash": "^4.17.11", + "lodash": "^4.17.15", "socket.io-client": "^2.2.0", "ts-node": "^8.3.0", - "typescript": "^3.5.2", - "ws": "^7.0.1", - "yargs": "^13.2.4" + "typescript": "^3.5.3", + "ws": "^7.1.0", + "yargs": "^13.3.0" }, "devDependencies": { - "@types/node": "^12.0.10", - "ava": "^2.1.0", + "@types/node": "^12.6.8", + "ava": "^2.2.0", "debug": "^4.1.1", "nyc": "^14.1.1", "request": "^2.88.0" diff --git a/packages/contract-cli/tests/cmd.test.js b/packages/contract-cli/tests/cmd.test.js index 0b11c16f5ab65b00a3a785daa18a15d30d2ae537..cd5fd870332e824df95cb2924e9e115e83b8c55b 100644 --- a/packages/contract-cli/tests/cmd.test.js +++ b/packages/contract-cli/tests/cmd.test.js @@ -16,29 +16,26 @@ const cliFile = resolve(join(__dirname, '..' , 'cli.js')) test.after(t => { - fsx.removeSync(join(outDir, DEFAULT_CONTRACT_FILE_NAME)) - // fsx.removeSync(configTestFile) -}); + fsx.removeSync( outDir ) +}) test.cb('It should able to call the cmd and have correct output', t => { t.context.ps = spawn('node', [ cliFile, 'create', inDir, - outDir, - '-w', - true + outDir ]) t.plan(1) t.context.ps.stdout.on('data', data => { debug('stdout: ', data.toString()) - }); + }) t.context.ps.stderr.on('data', data => { debug('stderr: ', data.toString()) - }); + }) t.context.ps.on('close', code => { debug(`(1) Exited with ${code}`) diff --git a/packages/contract-cli/tests/config-params.test.js b/packages/contract-cli/tests/config-params.test.js index bed7c744aec3ec6372b80636292f7c4e5681f0a3..09b8735331673e93c760fd11abf53d7094b33708 100644 --- a/packages/contract-cli/tests/config-params.test.js +++ b/packages/contract-cli/tests/config-params.test.js @@ -23,7 +23,6 @@ const config = { test.before(async t => { t.context.result = await contractApi(config) - // debug(t.context.result); t.context.publicContract = await contractApi(extend(config, {public: true})) }) diff --git a/packages/contract-cli/tests/custom-login.test.js b/packages/contract-cli/tests/custom-login.test.js index 41dacd297bafa1764350de64f5b429498205e9cd..9ab87ac19fc06f36ee23b7c63eaba63cf96c6e69 100644 --- a/packages/contract-cli/tests/custom-login.test.js +++ b/packages/contract-cli/tests/custom-login.test.js @@ -26,8 +26,7 @@ test.before(async t => { }) test.after(async t => { - fsx.removeSync(baseContractFile) - fsx.removeSync(publicContractFile) + fsx.removeSync(contractDir) }) test('It should able to pick up the customLogin method instead of login', t => { diff --git a/packages/contract-cli/tests/extra-props.test.js b/packages/contract-cli/tests/extra-props.test.js index 7f57e9dae99c9f8f6ad27e42e63593c1b99a5930..35c5b5eec7b59890419da0e2c859301e7c4fa993 100644 --- a/packages/contract-cli/tests/extra-props.test.js +++ b/packages/contract-cli/tests/extra-props.test.js @@ -21,12 +21,11 @@ test.before(async t => { enableAuth: true }) t.context.json = fsx.readJsonSync(baseContractFile) -}); +}) test.after(async t => { - fsx.removeSync(baseContractFile); - fsx.removeSync(publicContractFile); -}); + fsx.removeSync(contractDir) +}) test('Should able to pass the extraContractProps option and read it back', async t => { t.is(true, t.context.json.socketServerType === 'socket.io') diff --git a/packages/contract-cli/tests/watch.test.js b/packages/contract-cli/tests/watch.test.js index dff2e428aa5c343ac4abfb1defc9860d653b78ff..cc4dcf78040570e66d48fe142536128d60d123af 100644 --- a/packages/contract-cli/tests/watch.test.js +++ b/packages/contract-cli/tests/watch.test.js @@ -1,8 +1,80 @@ const test = require('ava') +const { join, resolve } = require('path') +const { spawn } = require('child_process') +const fsx = require('fs-extra') +const { DEFAULT_CONTRACT_FILE_NAME } = require('jsonql-constants') -test.todo("It should able to watch the folder of files change") +const cliFile = resolve(join(__dirname, '..' , 'cli.js')) +const contractApi = require('../index') -test.todo("It should able to generate a new contract file") +const debug = require('debug')('jsonql-contract:test:watcher') -test.todo("It should able to call a socket end point to announce the change") +const srcDir = resolve(join(__dirname, 'fixtures', 'resolvers')) +const baseDir = resolve(join(__dirname, 'fixtures', 'tmp', 'watcher')) +const outDir = join(baseDir, 'contract') + +const srcFile = join(srcDir, 'mutation', 'set-with-destruction.js') +const destFile = join(baseDir, 'resolvers', 'mutation', 'set-with-destruction.js') + +// first we copy everything from the resolvers directory to the outDir +// because we need to change the files +test.before( async t => { + const inDir = join(baseDir, 'resolvers') + await fsx.copy(srcDir, inDir) + t.context.inDir = inDir; + t.context.outDir = outDir; + // we remove one file from the folder then add it back later + fsx.removeSync( destFile ) + + // first generate the contract file + t.context.result = await contractApi({ + resolverDir: inDir, + contractDir: outDir, + watch: true, + returnAs: 'json', + enableAuth: true + }) +}) + +test.after( async t => { + fsx.removeSync( baseDir ) +}) + +test.cb("It should able to watch the folder of files change", t => { + t.plan(2) + + t.falsy( t.context.result.mutation.setWithDestruction , 'It should not have a mutation.setDestruction method') + + setTimeout(() => { + // put the file back + fsx.copy(srcFile, destFile, (err) => { + if (err) { + console.error(err) + t.end() + return; + } + // wait a bit for the file to get generated + setTimeout(() => { + const json = fsx.readJsonSync( join(outDir, 'contract.json') ) + + t.truthy( json.mutation.setWithDestruction, 'Now it should have the setWithDestruction method' ) + t.end() + }, 1000) + + }) + }, 500) + + + +}) + +test.todo('Using the cli should able to do the same thing') + + + +// this is really just the same as above +// test.todo("It should able to generate a new contract file") + +// this is not going to be in this release +// test.todo("It should able to call a socket end point to announce the change") diff --git a/packages/contract-console/vue.config.js b/packages/contract-console/vue.config.js index 8725416a4c53c591c0fda81f7590d99f92c92eab..591c405d859f843946f11e38cf092ce3a402bcac 100644 --- a/packages/contract-console/vue.config.js +++ b/packages/contract-console/vue.config.js @@ -1,3 +1,6 @@ module.exports = { - lintOnSave: false + lintOnSave: false, + devServer: { + open: true + } } diff --git a/packages/event/src/event-service.js b/packages/event/src/event-service.js deleted file mode 100644 index 3f26fcef6778e7a38459280723069d9d7a631520..0000000000000000000000000000000000000000 --- a/packages/event/src/event-service.js +++ /dev/null @@ -1,600 +0,0 @@ -// this is the new implementation without the hash key -// only using Map and Set instead -import { - NB_EVENT_SERVICE_PRIVATE_STORE, - NB_EVENT_SERVICE_PRIVATE_LAZY -} from './store' -import genHaskKey from './hash-code.js' -// export -export default class EventService { - protected logger: any; - - /** - * class constructor - */ - constructor(config = {}) { - if (config.logger && typeof config.logger === 'function') { - this.logger = config.logger; - } - this.keep = config.keep; - this.suspend = false; - // for the $done setter - this.result = config.keep ? [] : null; - // we need to init the store first otherwise it could be a lot of checking later - this.normalStore = new Map() - this.lazyStore = new Map() - } - - /** - * logger function for overwrite - */ - logger() {} - - ////////////////////////// - // PUBLIC METHODS // - ////////////////////////// - - /** - * Register your evt handler, note we don't check the type here, - * we expect you to be sensible and know what you are doing. - * @param {string} evt name of event - * @param {function} callback bind method --> if it's array or not - * @param {object} [context=null] to execute this call in - * @return {number} the size of the store - */ - $on(evt , callback , context = null) { - const type = 'on'; - this.validate(evt, callback) - // first need to check if this evt is in lazy store - let lazyStoreContent = this.takeFromStore(evt) - // this is normal register first then call later - if (lazyStoreContent === false) { - this.logger('$on', `${evt} callback is not in lazy store`) - // @TODO we need to check if there was other listener to this - // event and are they the same type then we could solve that - // register the different type to the same event name - - return this.addToNormalStore(evt, type, callback, context) - } - this.logger('$on', `${evt} found in lazy store`) - // this is when they call $trigger before register this callback - let size = 0; - lazyStoreContent.forEach(content => { - let [ payload, ctx, t ] = content; - if (t && t !== type) { - throw new Error(`You are trying to register an event already been taken by other type: ${t}`) - } - this.run(callback, payload, context || ctx) - size += this.addToNormalStore(evt, type, callback, context || ctx) - }) - return size; - } - - /** - * once only registered it once, there is no overwrite option here - * @NOTE change in v1.3.0 $once can add multiple listeners - * but once the event fired, it will remove this event (see $only) - * @param {string} evt name - * @param {function} callback to execute - * @param {object} [context=null] the handler execute in - * @return {boolean} result - */ - $once(evt , callback , context = null) { - this.validate(evt, callback) - const type = 'once'; - let lazyStoreContent = this.takeFromStore(evt) - // this is normal register before call $trigger - let nStore = this.normalStore; - if (lazyStoreContent === false) { - this.logger('$once', `${evt} not in the lazy store`) - // v1.3.0 $once now allow to add multiple listeners - return this.addToNormalStore(evt, type, callback, context) - } else { - // now this is the tricky bit - // there is a potential bug here that cause by the developer - // if they call $trigger first, the lazy won't know it's a once call - // so if in the middle they register any call with the same evt name - // then this $once call will be fucked - add this to the documentation - this.logger('$once', lazyStoreContent) - const list = Array.from(lazyStoreContent) - // should never have more than 1 - const [ payload, ctx, t ] = list[0] - if (t && t !== type) { - throw new Error(`You are trying to register an event already been taken by other type: ${t}`) - } - this.run(callback, payload, context || ctx) - // remove this evt from store - this.$off(evt) - } - } - - /** - * This one event can only bind one callbackback - * @param {string} evt event name - * @param {function} callback event handler - * @param {object} [context=null] the context the event handler execute in - * @return {boolean} true bind for first time, false already existed - */ - $only(evt, callback, context = null) { - this.validate(evt, callback) - const type = 'only'; - let added = false; - let lazyStoreContent = this.takeFromStore(evt) - // this is normal register before call $trigger - let nStore = this.normalStore; - if (!nStore.has(evt)) { - this.logger(`$only`, `${evt} add to store`) - added = this.addToNormalStore(evt, type, callback, context) - } - if (lazyStoreContent !== false) { - // there are data store in lazy store - this.logger('$only', `${evt} found data in lazy store to execute`) - const list = Array.from(lazyStoreContent) - // $only allow to trigger this multiple time on the single handler - list.forEach( l => { - const [ payload, ctx, t ] = l; - if (t && t !== type) { - throw new Error(`You are trying to register an event already been taken by other type: ${t}`) - } - this.run(callback, payload, context || ctx) - }) - } - return added; - } - - /** - * $only + $once this is because I found a very subtile bug when we pass a - * resolver, rejecter - and it never fire because that's OLD adeed in v1.4.0 - * @param {string} evt event name - * @param {function} callback to call later - * @param {object} [context=null] exeucte context - * @return {void} - */ - $onlyOnce(evt, callback, context = null) { - this.validate(evt, callback) - const type = 'onlyOnce'; - let added = false; - let lazyStoreContent = this.takeFromStore(evt) - // this is normal register before call $trigger - let nStore = this.normalStore; - if (!nStore.has(evt)) { - this.logger(`$onlyOnce`, `${evt} add to store`) - added = this.addToNormalStore(evt, type, callback, context) - } - if (lazyStoreContent !== false) { - // there are data store in lazy store - this.logger('$onlyOnce', lazyStoreContent) - const list = Array.from(lazyStoreContent) - // should never have more than 1 - const [ payload, ctx, t ] = list[0] - if (t && t !== 'onlyOnce') { - throw new Error(`You are trying to register an event already been taken by other type: ${t}`) - } - this.run(callback, payload, context || ctx) - // remove this evt from store - this.$off(evt) - } - return added; - } - - /** - * This is a shorthand of $off + $on added in V1.5.0 - * @param {string} evt event name - * @param {function} callback to exeucte - * @param {object} [context = null] or pass a string as type - * @param {string} [type=on] what type of method to replace - * @return {} - */ - $replace(evt, callback, context = null, type = 'on') { - if (this.validateType(type)) { - this.$off(evt) - let method = this['$' + type] - return Reflect.apply(method, this, [evt, callback, context]) - } - throw new Error(`${type} is not supported!`) - } - - /** - * trigger the event - * @param {string} evt name NOT allow array anymore! - * @param {mixed} [payload = []] pass to fn - * @param {object|string} [context = null] overwrite what stored - * @param {string} [type=false] if pass this then we need to add type to store too - * @return {number} if it has been execute how many times - */ - $trigger(evt , payload = [] , context = null, type = false) { - this.validateEvt(evt) - let found = 0; - // first check the normal store - let nStore = this.normalStore; - this.logger('$trigger', nStore) - if (nStore.has(evt)) { - this.logger('$trigger', evt, 'found') - let nSet = Array.from(nStore.get(evt)) - let ctn = nSet.length; - let hasOnce = false; - let hasOnly = false; - for (let i=0; i < ctn; ++i) { - ++found; - // this.logger('found', found) - let [ _, callback, ctx, type ] = nSet[i] - this.run(callback, payload, context || ctx) - if (type === 'once' || type === 'onlyOnce') { - hasOnce = true; - } - } - if (hasOnce) { - nStore.delete(evt) - } - return found; - } - // now this is not register yet - this.addToLazyStore(evt, payload, context, type) - return found; - } - - /** - * this is an alias to the $trigger - * @NOTE breaking change in V1.6.0 we swap the parameter around - * @param {string} evt event name - * @param {*} params pass to the callback - * @param {string} type of call - * @param {object} context what context callback execute in - * @return {*} from $trigger - */ - $call(evt, params, type = false, context = null) { - let args = [evt, params] - args.push(context, type) - return Reflect.apply(this.$trigger, this, args) - } - - /** - * remove the evt from all the stores - * @param {string} evt name - * @return {boolean} true actually delete something - */ - $off(evt) { - this.validateEvt(evt) - let stores = [ this.lazyStore, this.normalStore ] - let found = false; - stores.forEach(store => { - if (store.has(evt)) { - found = true; - store.delete(evt) - } - }) - return found; - } - - /** - * return all the listener from the event - * @param {string} evtName event name - * @param {boolean} [full=false] if true then return the entire content - * @return {array|boolean} listerner(s) or false when not found - */ - $get(evt, full = false) { - this.validateEvt(evt) - let store = this.normalStore; - if (store.has(evt)) { - return this - .mapToArr(store.get(evt)) - .map((value: any, index: number, arr: any[]) => { - if (full) { - return value; - } - let [key, callback, ] = value; - return callback; - }) - } - return false; - } - - /** - * Holding off all the event firing and put them back into the lazy store - * until the suspend been lifted - * @param {string} [type=all] what type of event should be suspended - * @return {void} - */ - $suspend(type = 'all') { - this.suspend = type === 'all' ? true : this.validateType(type); - } - - /** - * Lifted the suspend - * @return {void} - */ - $resume() { - this.suspend = false; - } - - /** - * store the return result from the run - * @param {*} value whatever return from callback - */ - set $done(value) { - this.logger('set $done', value) - if (this.keep) { - this.result.push(value) - } else { - this.result = value; - } - } - - /** - * @TODO is there any real use with the keep prop? - * getter for $done - * @return {*} whatever last store result - */ - get $done() { - if (this.keep) { - this.logger(this.result) - return this.result[this.result.length - 1] - } - return this.result; - } - - ///////////////////////////// - // PRIVATE METHODS // - ///////////////////////////// - - /** - * validate the event name - * @param {string} evt event name - * @return {boolean} true when OK - */ - validateEvt(evt) { - if (typeof evt === 'string') { - return true; - } - throw new Error(`event name must be string type!`) - } - - /** - * Simple quick check on the two main parameters - * @param {string} evt event name - * @param {function} callback function to call - * @return {boolean} true when OK - */ - validate(evt, callback) { - if (this.validateEvt(evt)) { - if (typeof callback === 'function') { - return true; - } - } - throw new Error(`callback required to be function type!`) - } - - /** - * Check if this type is correct or not added in V1.5.0 - * @param {string} type for checking - * @return {boolean} true on OK - */ - validateType(type) { - const types = ['on', 'only', 'once', 'onlyOnce'] - return !!types.filter(t => type === t).length; - } - - /** - * Run the callback - * @param {function} callback function to execute - * @param {array} payload for callback - * @param {object} ctx context or null - * @return {void} the result store in $done - */ - run(callback, payload, ctx) { - this.logger('run', callback, payload, ctx) - this.$done = Reflect.apply(callback, ctx, this.toArray(payload)) - } - - /** - * Take the content out and remove it from store id by the name - * @param {string} evt event name - * @param {string} [storeName = lazyStore] name of store - * @return {object|boolean} content or false on not found - */ - takeFromStore(evt, storeName = 'lazyStore') { - let store = this[storeName]; // it could be empty at this point - if (store) { - this.logger('takeFromStore', storeName, store) - if (store.has(evt)) { - let content = store.get(evt) - this.logger('takeFromStore', content) - store.delete(evt) - return content; - } - return false; - } - throw new Error(`${storeName} is not supported!`) - } - - /** - * The add to store step is similar so make it generic for resuse - * @param {object} store which store to use - * @param {string} evt event name - * @param {spread} args because the lazy store and normal store store different things - * @return {array} store and the size of the store - */ - addToStore(store, evt, ...args) { - let fnSet; - if (store.has(evt)) { - this.logger('addToStore', `${evt} existed`) - fnSet = store.get(evt) - } else { - this.logger('addToStore', `create new Set for ${evt}`) - // this is new - fnSet = new Set() - } - // lazy only store 2 items - this is not the case in V1.6.0 anymore - // we need to check the first parameter is string or not - if (args.length > 2) { - if (Array.isArray(args[0])) { // lazy store - // check if this type of this event already register in the lazy store - let [,,t] = args; - if (!this.checkTypeInLazyStore(evt, t)) { - fnSet.add(args) - } - } else { - if (!this.checkContentExist(args, fnSet)) { - this.logger('addToStore', `insert new`, args) - fnSet.add(args) - } - } - } else { // add straight to lazy store - fnSet.add(args) - } - store.set(evt, fnSet) - return [store, fnSet.size] - } - - /** - * @param {array} args for compare - * @param {object} fnSet A Set to search from - * @return {boolean} true on exist - */ - checkContentExist(args, fnSet) { - let list = Array.from(fnSet) - return !!list.filter(l => { - let [hash,] = l; - if (hash === args[0]) { - return true; - } - return false; - }).length; - } - - /** - * get the existing type to make sure no mix type add to the same store - * @param {string} evtName event name - * @param {string} type the type to check - * @return {boolean} true you can add, false then you can't add this type - */ - checkTypeInStore(evtName, type) { - this.validateEvt(evtName) - this.validateEvt(type) - let all = this.$get(evtName, true) - if (all === false) { - // pristine it means you can add - return true; - } - // it should only have ONE type in ONE event store - return !all.filter(list => { - let [ ,,,t ] = list; - return type !== t; - }).length; - } - - /** - * This is checking just the lazy store because the structure is different - * therefore we need to use a new method to check it - */ - checkTypeInLazyStore(evtName, type) { - this.validateEvt(evtName) - this.validateEvt(type) - let store = this.lazyStore.get(evtName) - this.logger('checkTypeInLazyStore', store) - if (store) { - return !!Array - .from(store) - .filter(l => { - let [,,t] = l; - return t !== type; - }).length - } - return false; - } - - /** - * wrapper to re-use the addToStore, - * V1.3.0 add extra check to see if this type can add to this evt - * @param {string} evt event name - * @param {string} type on or once - * @param {function} callback function - * @param {object} context the context the function execute in or null - * @return {number} size of the store - */ - addToNormalStore(evt, type, callback, context = null) { - this.logger('addToNormalStore', evt, type, 'add to normal store') - // @TODO we need to check the existing store for the type first! - if (this.checkTypeInStore(evt, type)) { - this.logger(`${type} can add to ${evt} store`) - let key = this.hashFnToKey(callback) - let args = [this.normalStore, evt, key, callback, context, type] - let [_store, size] = Reflect.apply(this.addToStore, this, args) - this.normalStore = _store; - return size; - } - return false; - } - - /** - * Add to lazy store this get calls when the callback is not register yet - * so we only get a payload object or even nothing - * @param {string} evt event name - * @param {array} payload of arguments or empty if there is none - * @param {object} [context=null] the context the callback execute in - * @param {string} [type=false] register a type so no other type can add to this evt - * @return {number} size of the store - */ - addToLazyStore(evt, payload = [], context = null, type = false) { - // this is add in V1.6.0 - // when there is type then we will need to check if this already added in lazy store - // and no other type can add to this lazy store - let args = [this.lazyStore, evt, this.toArray(payload), context] - if (type) { - args.push(type) - } - let [_store, size] = Reflect.apply(this.addToStore, this, args) - this.lazyStore = _store; - return size; - } - - /** - * make sure we store the argument correctly - * @param {*} arg could be array - * @return {array} make sured - */ - toArray(arg) { - return Array.isArray(arg) ? arg : [arg]; - } - - /** - * setter to store the Set in private - * @param {object} obj a Set - */ - set normalStore(obj) { - NB_EVENT_SERVICE_PRIVATE_STORE.set(this, obj) - } - - /** - * @return {object} Set object - */ - get normalStore() { - return NB_EVENT_SERVICE_PRIVATE_STORE.get(this) - } - - /** - * setter to store the Set in lazy store - * @param {object} obj a Set - */ - set lazyStore(obj) { - NB_EVENT_SERVICE_PRIVATE_LAZY.set(this , obj) - } - - /** - * @return {object} the lazy store Set - */ - get lazyStore() { - return NB_EVENT_SERVICE_PRIVATE_LAZY.get(this) - } - - /** - * generate a hashKey to identify the function call - * The build-in store some how could store the same values! - * @param {function} fn the converted to string function - * @return {string} hashKey - */ - hashFnToKey(fn) { - return genHaskKey(fn.toString()) + ''; - } - -} diff --git a/packages/koa/src/lib/config-check/options.js b/packages/koa/src/lib/config-check/options.js index 4bbe97a0b8f3593369539149bcc4d6c2d7490806..83ec90342413413bad291658160ff4582141ac42 100755 --- a/packages/koa/src/lib/config-check/options.js +++ b/packages/koa/src/lib/config-check/options.js @@ -60,7 +60,8 @@ const appProps = { jwtTokenOption: createConfig(false, [BOOLEAN_TYPE, OBJECT_TYPE]), // add in v1.3.0 enableJsonp: createConfig(false, [BOOLEAN_TYPE]), - + // show or hide the description field in the public contract + contractWithDesc: createConfig(false, [BOOLEAN_TYPE]), keysDir: createConfig(join(dirname, DEFAULT_KEYS_DIR), [STRING_TYPE]), publicKeyFileName: createConfig(DEFAULT_PUBLIC_KEY_FILE, [STRING_TYPE]), privateKeyFileName: createConfig(DEFAULT_PRIVATE_KEY_FILE, [STRING_TYPE]),