Repository: Yoctol/bottender
Pull request #731 (2020-04-13 07:07:43)

type(withProps): improve types of withProps
Check if the action uses the provided props correctly:

```ts
import { withProps } from 'bottender';

function MyAction({ x }: { x: string }) {}

function App() {
  return withProps(MyAction, { x: 1 }); // fail
}
```




Patch:
diff --git a/packages/bottender-dialogflow/src/index.ts b/packages/bottender-dialogflow/src/index.ts
index 1515da7d..bbbb4608 100644
--- a/packages/bottender-dialogflow/src/index.ts
+++ b/packages/bottender-dialogflow/src/index.ts
@@ -37,15 +37,15 @@ module.exports = function dialogflow({
 }: {
   projectId: string;
   languageCode: string;
-  actions: Record<string, Action<Context<any, any>, any>>;
+  actions: Record<string, Action<Context<any, any>, QueryResult>>;
   timeZone?: string;
-}) {
+}): Action<Context<any, any>> {
   const sessionsClient = new dialogflowSdk.SessionsClient();
 
   return async function Dialogflow(
     context: Context<any, any>,
-    { next }: { next?: Action<Context<any, any>, any> }
-  ) {
+    { next }: { next?: Action<Context<any, any>> }
+  ): Promise<Action<Context<any, any>> | void> {
     if (!context.event.isText || !context.session) {
       return next;
     }
diff --git a/packages/bottender-luis/src/index.ts b/packages/bottender-luis/src/index.ts
index abc41916..9ebfe52d 100644
--- a/packages/bottender-luis/src/index.ts
+++ b/packages/bottender-luis/src/index.ts
@@ -1,7 +1,7 @@
 import axios from 'axios';
 import { Action, Context, withProps } from 'bottender';
 
-import { LuisResult } from './types';
+import { EntityModel, IntentModel, LuisResult, Sentiment } from './types';
 
 /**
  * const Luis = luis({
@@ -34,18 +34,28 @@ module.exports = function luis({
   appKey: string;
   endpoint: string;
   scoreThreshold: number;
-  actions: Record<string, Action<Context<any, any>, any>>;
+  actions: Record<
+    string,
+    Action<
+      Context<any, any>,
+      {
+        topScoringIntent: IntentModel;
+        entities: EntityModel[];
+        sentimentAnalysis: Sentiment;
+      }
+    >
+  >;
   verbose?: boolean;
   timezoneOffset?: number;
   spellCheck?: boolean;
   staging?: boolean;
   bingSpellCheckSubscriptionKey?: string;
   log?: boolean;
-}) {
+}): Action<Context<any, any>> {
   return async function Luis(
     context: Context<any, any>,
-    { next }: { next?: Action<Context<any, any>, any> }
-  ) {
+    { next }: { next?: Action<Context<any, any>> }
+  ): Promise<Action<Context<any, any>> | void> {
     if (!context.event.isText) {
       return next;
     }
diff --git a/packages/bottender-qna-maker/src/index.ts b/packages/bottender-qna-maker/src/index.ts
index d0d584d4..bc13331f 100644
--- a/packages/bottender-qna-maker/src/index.ts
+++ b/packages/bottender-qna-maker/src/index.ts
@@ -31,11 +31,11 @@ module.exports = function qnaMaker({
   qnaId?: string;
   scoreThreshold?: number;
   strictFilters?: MetadataDTO[];
-}) {
+}): Action<Context<any, any>> {
   return async function QnaMaker(
     context: Context<any, any>,
-    { next }: { next?: Action<Context<any, any>, any> }
-  ) {
+    { next }: { next?: Action<Context<any, any>> }
+  ): Promise<Action<Context<any, any>> | void> {
     if (!context.event.isText || !context.session) {
       return next;
     }
diff --git a/packages/bottender-rasa/src/index.ts b/packages/bottender-rasa/src/index.ts
index 92ac70a3..1c104a5e 100644
--- a/packages/bottender-rasa/src/index.ts
+++ b/packages/bottender-rasa/src/index.ts
@@ -2,7 +2,7 @@ import axios from 'axios';
 import { Action, Context, withProps } from 'bottender';
 import { get } from 'lodash';
 
-import { ParsedResult } from './types';
+import { Entity, Intent, ParsedResult } from './types';
 
 /**
  * const Rasa = rasa({
@@ -23,15 +23,24 @@ module.exports = function rasa({
   jwt,
 }: {
   origin: string;
-  actions: Record<string, Action<Context<any, any>, any>>;
+  actions: Record<
+    string,
+    Action<
+      Context<any, any>,
+      {
+        intent: Intent;
+        entities: Entity[];
+      }
+    >
+  >;
   confidenceThreshold: number;
   emulationMode?: 'WIT' | 'LUIS' | 'DIALOGFLOW';
   jwt?: string;
-}) {
+}): Action<Context<any, any>> {
   return async function Rasa(
     context: Context<any, any>,
-    { next }: { next?: Action<Context<any, any>, any> }
-  ) {
+    { next }: { next?: Action<Context<any, any>> }
+  ): Promise<Action<Context<any, any>> | void> {
     if (!context.event.isText) {
       return next;
     }
diff --git a/packages/bottender/src/bot/Bot.ts b/packages/bottender/src/bot/Bot.ts
index 8d1a152a..4d21b4fa 100644
--- a/packages/bottender/src/bot/Bot.ts
+++ b/packages/bottender/src/bot/Bot.ts
@@ -270,7 +270,7 @@ export default class Bot<
       const promises = Promise.all(
         contexts.map((context: Ctx) =>
           Promise.resolve()
-            .then(() => run(handler)(context))
+            .then(() => run(handler)(context, {}))
             .then(() => {
               if (context.handlerDidEnd) {
                 return context.handlerDidEnd();
diff --git a/packages/bottender/src/getSessionStore.ts b/packages/bottender/src/getSessionStore.ts
index 16bf6399..d4e5deae 100644
--- a/packages/bottender/src/getSessionStore.ts
+++ b/packages/bottender/src/getSessionStore.ts
@@ -1,8 +1,9 @@
 import warning from 'warning';
 
+import SessionStore from './session/SessionStore';
 import getBottenderConfig from './shared/getBottenderConfig';
 
-function getSessionStore() {
+function getSessionStore(): SessionStore {
   const { session } = getBottenderConfig();
 
   const sessionDriver = (session && session.driver) || 'memory';
@@ -44,6 +45,14 @@ function getSessionStore() {
       );
     }
     default: {
+      // Support custom session stores by returning the session store instance
+      const customSessionStore:
+        | SessionStore
+        | undefined = (storesConfig as any)[sessionDriver];
+      if (customSessionStore) {
+        return customSessionStore;
+      }
+
       warning(
         false,
         `Received unknown driver: ${sessionDriver}, so fallback it to \`memory\` driver.`
diff --git a/packages/bottender/src/types.ts b/packages/bottender/src/types.ts
index 4db09b54..2801f170 100644
--- a/packages/bottender/src/types.ts
+++ b/packages/bottender/src/types.ts
@@ -8,6 +8,7 @@ import ConsoleEvent, { ConsoleRawEvent } from './console/ConsoleEvent';
 import Context from './context/Context';
 import LineEvent from './line/LineEvent';
 import MessengerEvent from './messenger/MessengerEvent';
+import SessionStore from './session/SessionStore';
 import SlackEvent from './slack/SlackEvent';
 import TelegramEvent from './telegram/TelegramEvent';
 import TwilioClient from './whatsapp/TwilioClient';
@@ -50,16 +51,16 @@ export type Body =
 
 export type Action<
   C extends Context<any, any>,
-  P extends Record<string, any> = {}
+  P extends Record<string, any> = {},
+  RAP extends Record<string, any> = {}
 > = (
   context: C,
-  props?: Props<C> & P
-) => void | Action<C, any> | Promise<Action<C, any> | void>;
+  props: Props<C> & P
+) => void | Action<C, RAP> | Promise<Action<C, RAP> | void>;
 
 export type Props<C extends Context<any, any>> = {
   next?: Action<C, any>;
   error?: Error;
-  [key: string]: any;
 };
 
 export type Plugin<C extends Context<any, any>> = (context: C) => void;
@@ -81,26 +82,30 @@ export enum SessionDriver {
 }
 
 export type SessionConfig = {
-  driver: SessionDriver;
+  driver: string;
   expiresIn?: number;
-  stores: {
-    memory?: {
-      maxSize?: number;
-    };
-    file?: {
-      dirname?: string;
-    };
-    redis?: {
-      port?: number;
-      host?: string;
-      password?: string;
-      db?: number;
-    };
-    mongo?: {
-      url?: string;
-      collectionName?: string;
-    };
-  };
+  stores:
+    | {
+        memory?: {
+          maxSize?: number;
+        };
+        file?: {
+          dirname?: string;
+        };
+        redis?: {
+          port?: number;
+          host?: string;
+          password?: string;
+          db?: number;
+        };
+        mongo?: {
+          url?: string;
+          collectionName?: string;
+        };
+      }
+    | {
+        [P in Exclude<string, SessionDriver>]: SessionStore;
+      };
 };
 
 export type BottenderConfig = {
diff --git a/packages/bottender/src/withProps.ts b/packages/bottender/src/withProps.ts
index 36675d1c..dc87418d 100644
--- a/packages/bottender/src/withProps.ts
+++ b/packages/bottender/src/withProps.ts
@@ -1,11 +1,11 @@
 import partial from 'lodash/partial';
 
 import Context from './context/Context';
-import { Action, Props } from './types';
+import { Action } from './types';
 
-function withProps<C extends Context<any, any>>(
-  action: Action<C, any>,
-  props: Props<C>
+function withProps<C extends Context<any, any>, P extends Record<string, any>>(
+  action: Action<C, P>,
+  props: P
 ): Action<C, any> {
   // TODO: we may only apply this on dev env
   Object.freeze(props);


Test patch:
diff --git a/packages/bottender/src/__tests__/getSessionStore.spec.ts b/packages/bottender/src/__tests__/getSessionStore.spec.ts
index 3d28b3e3..b99e733f 100644
--- a/packages/bottender/src/__tests__/getSessionStore.spec.ts
+++ b/packages/bottender/src/__tests__/getSessionStore.spec.ts
@@ -1,3 +1,4 @@
+import MemorySessionStore from '../session/MemorySessionStore';
 import getBottenderConfig from '../shared/getBottenderConfig';
 import getSessionStore from '../getSessionStore';
 import { SessionDriver } from '../types';
@@ -72,3 +73,18 @@ it('should get RedisSessionStore when assigning redis as driver', () => {
   });
   expect(getSessionStore().constructor.name).toEqual('RedisSessionStore');
 });
+
+it('should get provided SessionStore when assigning custom driver', () => {
+  const memory2SessionStore = new MemorySessionStore();
+
+  getBottenderConfigMocked.mockReturnValueOnce({
+    session: {
+      driver: 'memory2',
+      stores: {
+        memory2: memory2SessionStore,
+      },
+    },
+  });
+
+  expect(getSessionStore()).toEqual(memory2SessionStore);
+});