diff --git a/Changes.md b/Changes.md index 9c5627c..a06cc89 100644 --- a/Changes.md +++ b/Changes.md @@ -1,119 +1,22 @@ # Changes -## 2.0.0 Beta: Async/Await +## 2.0.0: The Async/Await Update This is a major new version of RiveScript. It adds async/await support and makes the API asynchronous everywhere. It also decaffeinates the code, migrating it from CoffeeScript back to native ES2015+ with modern syntax. -### Backwards Incompatible Changes - -* The `reply()` method now returns a Promise instead of a string. In that - regard, it works just like the old `replyAsync()` method did. -* The `loadFile()` and `loadDirectory()` functions return Promises now - instead of using callbacks. -* The `replyAsync()` method is now deprecated in favor of `reply()`. - -The user variable session manager is now pluggable and replaceable, with -the default continuing to be in-memory storage of user variables. - -For session managers to support async database drivers, all of the user -variable functions now return their result as a Promise: - -* `setUservar(user, name, value)` -* `setUservars(user, data)` -* `getUservar(user, name)` -* `getUservars([user])` -* `clearUservars([user])` -* `freezeUservars(user)` -* `thawUservars(user, action="thaw")` -* `lastMatch(user)` -* `initialMatch(user)` -* `lastTriggers(user)` -* `getUserTopicTriggers(user)` - -### User Variable Session Managers - -RiveScript-JS finally joins its Python, Go, and Java cousins in supporting -pluggable user variable session managers. - -This feature will allow you to replace the default in-memory user variable -store with one backed by a database, like MongoDB or Redis. - -```javascript -// MemorySessionManager is the default session store, but you -// could implement Redis or anything by making a SessionManager -// compatible class. -const { MemorySessionManager } = require("./src/sessions"); - -// Use your session manager. -var bot = new RiveScript({ - sessionManager: new MemorySessionManager() -}); -``` - -See the [sessions](./sessions.md) documentation for more details. - -### Async Objects in Conditions - -Async/Await enables asynchronous object macros that return Promises to -be used *anywhere* throughout RiveScript. This means they can finally be -used inside of `*Condition`s! Example: - -```rivescript -// wait-limited $timeout $maxTimeout -// If the $timeout > $maxTimeout, it resolves "too long" immediately. -// Otherwise it waits $timeout seconds and resolves "done" -> object wait-limited javascript - var timeout = parseInt(args[0]); - var max = parseInt(args[1]); - - return new Promise(function(resolve, reject) { - if (timeout > max) { - resolve("too long"); - } else { - setTimeout(function() { - resolve("done"); - }, timeout*1000); - } - }); -< object - -+ can you wait # seconds -* wait-limited 6 == done => I can! -- No the longest I'll wait is 6 seconds. -``` - -### Upgrading - -To support the async features, the RiveScript API had to break backward -compatibility. - -The `reply()` function now returns a Promise instead of the string -result. It works like `replyAsync()` did before; and so now `replyAsync()` -is a deprecated function. - -Here is an example of how to migrate your code if you were using `reply()` -before to get the string reply: - -```diff -- var reply = bot.reply(username, message, this); -- console.log("Bot>", reply); -+ rs.reply(username, message, this).then(function(reply) { - console.log("Bot>", reply); -+ }) -``` - -Likewise, the `loadDirectory()` and `loadFile()` functions were made -to be Promise-based instead of callback-based. Switching over is simple: - -```diff -- bot.loadDirectory("./brain", onSuccess, onError); -+ bot.loadDirectory("./brain").then(onSuccess).catch(onError); - -- bot.loadFile("./brain/admin.rive", onSuccess, onError); -+ bot.loadFile("./brain/admin.rive").then(onSuccess).catch(onError); -``` +See the [Upgrading-v2](https://github.com/aichaos/rivescript-js/blob/master/Upgrading-v2.md) document for information about what's +new and how to upgrade your code. + +v2.0.0 last minute changes: + +- Fix a bug where `` in `+Trigger` wasn't being formatted properly + (lowercased, etc.) so matching was difficult. +- Add a Redis driver for an example User Variable Session Manager that + stores variables directly to the cache, and an `eg/redis` example bot + that demonstrates it. +- Write documentation for final v2.0.0 launch. ### 2.0.0-beta.1 - Jan 16 2019 diff --git a/README.md b/README.md index 7e9f1f4..37c9a82 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,20 @@ pairs for building up a bot's intelligence. This library can be used both in a web browser or as a Node module. See the `eg/` folder for examples. +## NOTICE: CHANGES IN v2.0.0 + +RiveScript v2.0.0 comes with a **massive** refactor of the codebase to +implement modern Async/Await features all throughout. The refactor now +allows features like "storing user variables directly in Redis" or +"using asynchronous macros in conditionals" + +But it necessarily had to break some backwards compatibility -- slightly! +-- by turning previously synchronous functions like `reply()` into +async ones that return Promises like `replyAsync()` did. + +See the [Upgrading-v2](https://github.com/aichaos/rivescript-js/blob/master/Upgrading-v2.md) document for information on the changes +and how to fix your code for the new version. + ## USAGE ```javascript @@ -80,37 +94,6 @@ Both shell scripts accept command line parameters: When using RiveScript.js as a library, the synopsis is as follows: -## NOTICE: CHANGES IN v2.0.0 - -RiveScript v2.0.0 comes with a **massive** refactor of the codebase to -implement modern Async/Await features all throughout. This refactor -enables the following **new features**: - -* You can now `` asynchronous object macros inside of `*Condition` - checks. -* You can **actively** store users' variables into a database or - Redis cache. The module `rivescript-redis` provides a Redis cache - driver for this feature and there's an example bot at - [eg/redis](https://github.com/aichaos/rivescript-js/tree/master/eg/redis). - Other drivers (MongoDB, etc.) are up to you to write (send a pull - request! Use the Redis driver as an example) - -Because everything had to upgrade to async functions for this to work, -it necessarily had to break backwards compatibility slightly: - -* **reply()** now returns a Promise instead of a string, like replyAsync() - has always done. -* All user variable functions (getUservars, setUservars, etc.) now return - a Promise rather than their values directly; this change is what makes - it possible to swap out async database drivers instead of the default - in-memory store! -* **loadFile()** and **loadDirectory()** now return Promises. However, the - old callback-based syntax still works but is now deprecated. -* **replyAsync()** is now deprecated in favor of just **reply()** since - they both do the same thing. - -See the [Change Log](https://github.com/aichaos/rivescript-js/blob/master/Changes.md) for more details. - ## DOCUMENTATION There is generated Markdown and HTML documentation of the modules in the diff --git a/Upgrading-v2.md b/Upgrading-v2.md index d65ff9f..b078038 100644 --- a/Upgrading-v2.md +++ b/Upgrading-v2.md @@ -15,6 +15,85 @@ The major highlights of the RiveScript v2.0.0 release are: syntax. CoffeeScript was holding us back from the `await` keyword and JavaScript is actually nice to program in nowadays. +# New Features + +## User Variables Session Managers + +Previously, RiveScript only stored user variables in its own process +memory (keyed by username), and if you wanted the bot to remember user +information across reboots, you needed to use functions like getUservars +and setUservars to get data in and out of the bot's memory. + +With the new Async/Await features, now, it is possible to replace the +User Variable Session Manager with a more active one, such as a Redis +cache or a MongoDB database. So every time a user variable gets ``, +it's put directly into a database rather than just kept in memory. + +The default is still an in-memory store, but you can implement a custom +async driver following the SessionManager interface. There is a Redis +driver in the rivescript-js git repository called `rivescript-redis` + +```javascript +// Import rivescript and rivescript-redis +const RiveScript = require("rivescript"), + RedisSessionManager = require("rivescript-redis"); + +// Construct your RiveScript bot as normal... +let bot = new RiveScript({ + utf8: true, + + // Give it a new Redis session manager. + sessionManager: new RedisSessionManager({ + // The constructor takes an `opts` object, and mostly passes it + // directly along to the underlying `redis` module. So all these + // parameters come from `redis` + host: "localhost", // default + port: 6369, + + // NOTE: this option is used by `redis` and is also noticed by + // rivescript-redis: it's optional but recommended to set a + // prefix. The Redis keys otherwise are simply the username + // given to RiveScript. + prefix: "rivescript/" + }) +}); + +// And carry on as normal. All user variables will be actively persisted +// in Redis (no need to call `getUservars()` and `setUservars()` to manage +// them yourself -- though these functions DO work and will get you current +// data from your Redis cache!) +``` + +# Async Objects in Conditions + +Async/Await enables asynchronous object macros that return Promises to +be used *anywhere* throughout RiveScript. This means they can finally be +used inside of `*Condition`s! Example: + +```rivescript +// wait-limited $timeout $maxTimeout +// If the $timeout > $maxTimeout, it resolves "too long" immediately. +// Otherwise it waits $timeout seconds and resolves "done" +> object wait-limited javascript + var timeout = parseInt(args[0]); + var max = parseInt(args[1]); + + return new Promise(function(resolve, reject) { + if (timeout > max) { + resolve("too long"); + } else { + setTimeout(function() { + resolve("done"); + }, timeout*1000); + } + }); +< object + ++ can you wait # seconds +* wait-limited 6 == done => I can! +- No the longest I'll wait is 6 seconds. +``` + # JavaScript API Changes ## Changed: reply() now returns a Promise @@ -136,10 +215,6 @@ bot.setUservar(user, "name", "Alice").then(() => { }); ``` -# Background - -TBD. - [1]: https://github.com/aichaos/rivescript-js [2]: https://www.twilio.com/blog/2015/10/asyncawait-the-hero-javascript-deserved.html [3]: https://github.com/aichaos/rivescript-js/commit/2e6aae30bbecd4cba946ef95b085f023954dcb97?w=1 diff --git a/contrib/redis/README.md b/contrib/redis/README.md new file mode 100644 index 0000000..98f163b --- /dev/null +++ b/contrib/redis/README.md @@ -0,0 +1,54 @@ +# Redis for RiveScript + +``` +npm install rivescript-redis +``` + +This module implements a Redis cache driver for the RiveScript User Variable +Session Manager. + +It lets you **actively** persist your user variables to Redis instead of +just in the bot's memory, so that your bot can easily recall user +information across reboots and even across separate machines by storing +them in Redis. + +This module is part of the `rivescript-js` project which can be found at +https://github.com/aichaos/rivescript-js and is released under the same +license (MIT). + +## Usage + +```javascript +// Import rivescript and rivescript-redis +const RiveScript = require("rivescript"), + RedisSessionManager = require("rivescript-redis"); + +// Construct your RiveScript bot as normal... +let bot = new RiveScript({ + utf8: true, + + // Give it a new Redis session manager. + sessionManager: new RedisSessionManager({ + // The constructor takes an `opts` object, and mostly passes it + // directly along to the underlying `redis` module. So all these + // parameters come from `redis` + host: "localhost", // default + port: 6369, + + // NOTE: this option is used by `redis` and is also noticed by + // rivescript-redis: it's optional but recommended to set a + // prefix. The Redis keys otherwise are simply the username + // given to RiveScript. + prefix: "rivescript/" + }) +}); + +// And carry on as normal. All user variables will be actively persisted +// in Redis (no need to call `getUservars()` and `setUservars()` to manage +// them yourself -- though these functions DO work and will get you current +// data from your Redis cache!) +``` + +## License + +MIT. diff --git a/package.json b/package.json index b9905ae..e9c36de 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rivescript", - "version": "2.0.0-beta.1", + "version": "2.0.0", "description": "RiveScript is a scripting language for chatterbots, making it easy to write trigger/response pairs for building up a bot's intelligence.", "keywords": [ "bot", diff --git a/src/rivescript.js b/src/rivescript.js index 00d0d0c..2a1e4c1 100644 --- a/src/rivescript.js +++ b/src/rivescript.js @@ -22,7 +22,7 @@ near future. */ // Constants -const VERSION = "2.0.0-beta.1"; +const VERSION = "2.0.0"; // Helper modules const Parser = require("./parser"); diff --git a/webpack.config.js b/webpack.config.js index b8bc7cc..09b99cb 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -18,5 +18,6 @@ module.exports = { node: { fs: "empty" }, - target: "web" + target: "web", + mode: "development", }