Repository: Azure/azure-functions-durable-js
Pull request #110 (2019-10-24 00:24:48)

Turn on strict null checks
Not required for any bug-fixes or features, but hopefully should help prevent null/undefined oriented bugs in the future.


Patch:
diff --git a/src/durableentitybindinginfo.ts b/src/durableentitybindinginfo.ts
index 2850944..a77f7a3 100644
--- a/src/durableentitybindinginfo.ts
+++ b/src/durableentitybindinginfo.ts
@@ -5,7 +5,7 @@ export class DurableEntityBindingInfo {
     constructor(
         public readonly self: EntityId,
         public readonly exists: boolean,
-        public readonly state: string,
+        public readonly state: string | undefined,
         public readonly batch: RequestMessage[],
     ) { }
 }
diff --git a/src/durableentitycontext.ts b/src/durableentitycontext.ts
index 4cdb8c3..8d71750 100644
--- a/src/durableentitycontext.ts
+++ b/src/durableentitycontext.ts
@@ -26,7 +26,7 @@ export class DurableEntityContext {
      * An operation invocation on an entity includes an operation name, which
      * states what operation to perform, and optionally an operation input.
      */
-    public readonly operationName: string;
+    public readonly operationName: string | undefined;
 
     /**
      * Whether this entity is freshly constructed, i.e. did not exist prior to
diff --git a/src/durableorchestrationbindinginfo.ts b/src/durableorchestrationbindinginfo.ts
index 2ca058d..dc58e24 100644
--- a/src/durableorchestrationbindinginfo.ts
+++ b/src/durableorchestrationbindinginfo.ts
@@ -5,8 +5,8 @@ export class DurableOrchestrationBindingInfo {
     constructor(
         public readonly history: HistoryEvent[] = [],
         public readonly input?: unknown,
-        public readonly instanceId?: string,
-        public readonly isReplaying?: boolean,
+        public readonly instanceId: string = "",
+        public readonly isReplaying: boolean = false,
         public readonly parentInstanceId?: string,
         // TODO: Implement entity locking
         // public readonly contextLocks?: EntityId[],
diff --git a/src/durableorchestrationclient.ts b/src/durableorchestrationclient.ts
index 4871653..468f0ab 100644
--- a/src/durableorchestrationclient.ts
+++ b/src/durableorchestrationclient.ts
@@ -150,7 +150,7 @@ export class DurableOrchestrationClient {
      * @returns An HTTP 202 response with a Location header and a payload
      *  containing instance management URLs.
      */
-    public createCheckStatusResponse(request: IHttpRequest | HttpRequest, instanceId: string): IHttpResponse {
+    public createCheckStatusResponse(request: IHttpRequest | HttpRequest | undefined, instanceId: string): IHttpResponse {
         const httpManagementPayload = this.getClientResponseLinks(request, instanceId);
 
         return {
@@ -234,8 +234,8 @@ export class DurableOrchestrationClient {
      *  the runtimeStatus values in this array.
      */
     public async getStatusBy(
-        createdTimeFrom: Date,
-        createdTimeTo: Date,
+        createdTimeFrom: Date | undefined,
+        createdTimeTo: Date | undefined,
         runtimeStatus: OrchestrationRuntimeStatus[],
         ): Promise<DurableOrchestrationStatus[]> {
         try {
@@ -410,6 +410,10 @@ export class DurableOrchestrationClient {
      * @returns A response containing the current state of the entity.
      */
     public async readEntityState<T>(entityId: EntityId, taskHubName?: string, connectionName?: string): Promise<EntityStateResponse<T>> {
+        if (!(this.clientData.baseUrl && this.clientData.requiredQueryStringParameters)) {
+            throw new Error("Cannot use the readEntityState API with this version of the Durable Task Extension.");
+        }
+
         const requestUrl = WebhookUtils.getReadEntityUrl(this.clientData.baseUrl,
             this.clientData.requiredQueryStringParameters,
             entityId.name,
@@ -478,6 +482,10 @@ export class DurableOrchestrationClient {
         taskHubName?: string,
         connectionName?: string,
         ): Promise<void> {
+        if (!(this.clientData.baseUrl && this.clientData.requiredQueryStringParameters)) {
+            throw new Error("Cannot use the signalEntity API with this version of the Durable Task Extension.");
+        }
+
         const requestUrl = WebhookUtils.getSignalEntityUrl(this.clientData.baseUrl,
             this.clientData.requiredQueryStringParameters,
             entityId.name,
@@ -524,11 +532,11 @@ export class DurableOrchestrationClient {
             .replace(this.instanceIdPlaceholder, (instanceId ? `/${instanceId}` : ""));
 
         try {
-            const response = await this.axiosInstance.post(requestUrl, JSON.stringify(input));
-            if (response.status > 202) {
-                return Promise.reject(new Error(response.data as string));
-            } else if (response.data) {
+            const response = await this.axiosInstance.post(requestUrl, input !== undefined ? JSON.stringify(input) : "");
+            if (response.data && response.status <= 202) {
                 return (response.data as HttpManagementPayload).id;
+            } else  {
+                return Promise.reject(new Error(response.data as string));
             }
         } catch (message) {
             throw new Error(message.error);
@@ -634,7 +642,7 @@ export class DurableOrchestrationClient {
         };
     }
 
-    private getClientResponseLinks(request: IHttpRequest | HttpRequest, instanceId: string): HttpManagementPayload {
+    private getClientResponseLinks(request: IHttpRequest | HttpRequest | undefined, instanceId: string): HttpManagementPayload {
         const payload = { ...this.clientData.managementUrls };
 
         (Object.keys(payload) as Array<(keyof HttpManagementPayload)>).forEach((key) => {
@@ -650,7 +658,7 @@ export class DurableOrchestrationClient {
         return payload;
     }
 
-    private hasValidRequestUrl(request: IHttpRequest | HttpRequest): boolean {
+    private hasValidRequestUrl(request: IHttpRequest | HttpRequest | undefined): boolean {
         const isHttpRequest = request !== undefined && (request as HttpRequest).url !== undefined;
         const isIHttpRequest = request !== undefined && (request as IHttpRequest).http !== undefined;
         return isHttpRequest || isIHttpRequest && (request as IHttpRequest).http.url !== undefined;
diff --git a/src/durableorchestrationcontext.ts b/src/durableorchestrationcontext.ts
index da91f08..58a00ad 100644
--- a/src/durableorchestrationcontext.ts
+++ b/src/durableorchestrationcontext.ts
@@ -37,7 +37,7 @@ export class DurableOrchestrationContext {
      * which case it is formatted as a GUID, or it can be user-specified with
      * any format.
      */
-    public readonly parentInstanceId: string;
+    public readonly parentInstanceId: string | undefined;
 
     /**
      * Gets the current date/time in a way that is safe for use by orchestrator
diff --git a/src/entities/entitystate.ts b/src/entities/entitystate.ts
index 0ddf7e0..0c5aa6e 100644
--- a/src/entities/entitystate.ts
+++ b/src/entities/entitystate.ts
@@ -3,7 +3,7 @@ import { OperationResult, Signal } from "../classes";
 /** @hidden */
 export class EntityState {
     public entityExists: boolean;
-    public entityState: string;
+    public entityState: string | undefined;
     public readonly results: OperationResult[];
     public readonly signals: Signal[];
 
diff --git a/src/entities/entitystateresponse.ts b/src/entities/entitystateresponse.ts
index 0d47125..bf6b4b9 100644
--- a/src/entities/entitystateresponse.ts
+++ b/src/entities/entitystateresponse.ts
@@ -7,6 +7,6 @@ export class EntityStateResponse<T> {
         public entityExists: boolean,
 
         /** The current state of the entity, if it exists, or default value otherwise. */
-        public entityState: T,
+        public entityState: T | undefined,
     ) {}
 }
diff --git a/src/entity.ts b/src/entity.ts
index e74ac71..4579090 100644
--- a/src/entity.ts
+++ b/src/entity.ts
@@ -15,7 +15,7 @@ export class Entity {
 
     private async handle(context: IEntityFunctionContext): Promise<void> {
         const entityBinding = Utils.getInstancesOf<DurableEntityBindingInfo>(
-            context.bindings, new DurableEntityBindingInfo(null, null, null, []))[0];
+            context.bindings, new DurableEntityBindingInfo(new EntityId("samplename", "samplekey"), true, "", []))[0];
 
         if (entityBinding === undefined) {
             throw new Error("Could not find an entityTrigger binding on context.");
diff --git a/src/history/eventraisedevent.ts b/src/history/eventraisedevent.ts
index 467e2e5..bc2235b 100644
--- a/src/history/eventraisedevent.ts
+++ b/src/history/eventraisedevent.ts
@@ -3,7 +3,7 @@ import { HistoryEvent, HistoryEventOptions, HistoryEventType } from "../classes"
 /** @hidden */
 export class EventRaisedEvent extends HistoryEvent {
     public Name: string;
-    public Input: string;
+    public Input: string | undefined;
 
     constructor(
         options: HistoryEventOptions,
@@ -15,7 +15,12 @@ export class EventRaisedEvent extends HistoryEvent {
             options.timestamp,
         );
 
-        this.Name = options.name;
+        if (options.name === undefined) {
+            throw new Error("EventRaisedEvent needs a name provided.");
+        } else {
+            this.Name = options.name;
+        }
+
         this.Input = options.input;
     }
 }
diff --git a/src/history/eventsentevent.ts b/src/history/eventsentevent.ts
index 52e690d..1350ab8 100644
--- a/src/history/eventsentevent.ts
+++ b/src/history/eventsentevent.ts
@@ -3,7 +3,7 @@ import { HistoryEvent, HistoryEventOptions, HistoryEventType } from "../classes"
 /** @hidden */
 export class EventSentEvent extends HistoryEvent {
     public Name: string;
-    public Input: string;
+    public Input: string | undefined;
     public InstanceId: string;
 
     constructor(
@@ -16,8 +16,16 @@ export class EventSentEvent extends HistoryEvent {
             options.timestamp,
         );
 
-        this.Name = options.name;
+        if (options.name === undefined) {
+            throw new Error("EventSentEvent needs a name provided.");
+        }
+
+        if (options.instanceId === undefined) {
+            throw new Error("EventSentEvent needs an instance id provided.");
+        }
+
         this.Input = options.input;
+        this.Name = options.name;
         this.InstanceId = options.instanceId;
     }
 }
diff --git a/src/history/executionstartedevent.ts b/src/history/executionstartedevent.ts
index a045315..84c80e0 100644
--- a/src/history/executionstartedevent.ts
+++ b/src/history/executionstartedevent.ts
@@ -3,7 +3,7 @@ import { HistoryEvent, HistoryEventOptions, HistoryEventType } from "../classes"
 /** @hidden */
 export class ExecutionStartedEvent extends HistoryEvent {
     public Name: string;
-    public Input: string;
+    public Input: string | undefined;
 
     constructor(
         options: HistoryEventOptions,
@@ -15,7 +15,12 @@ export class ExecutionStartedEvent extends HistoryEvent {
             options.timestamp,
         );
 
-        this.Name = options.name;
+        if (options.name === undefined) {
+            throw new Error("ExecutionStartedEvent needs a name provided.");
+        } else {
+            this.Name = options.name;
+        }
+
         this.Input = options.input;
     }
 }
diff --git a/src/history/suborchestrationinstancecompletedevent.ts b/src/history/suborchestrationinstancecompletedevent.ts
index 40c726e..5f29688 100644
--- a/src/history/suborchestrationinstancecompletedevent.ts
+++ b/src/history/suborchestrationinstancecompletedevent.ts
@@ -15,6 +15,14 @@ export class SubOrchestrationInstanceCompletedEvent extends HistoryEvent {
             options.timestamp,
         );
 
+        if (options.taskScheduledId === undefined) {
+            throw new Error("SubOrchestrationInstanceCompletedEvent needs a task scheduled id provided.");
+        }
+
+        if (options.result === undefined) {
+            throw new Error("SubOrchestrationInstanceCompletedEvent needs an result provided.");
+        }
+
         this.TaskScheduledId = options.taskScheduledId;
         this.Result = options.result;
     }
diff --git a/src/history/suborchestrationinstancecreatedevent.ts b/src/history/suborchestrationinstancecreatedevent.ts
index 0afd1c8..6550a51 100644
--- a/src/history/suborchestrationinstancecreatedevent.ts
+++ b/src/history/suborchestrationinstancecreatedevent.ts
@@ -4,7 +4,7 @@ import { HistoryEvent, HistoryEventOptions, HistoryEventType } from "../classes"
 export class SubOrchestrationInstanceCreatedEvent extends HistoryEvent {
     public Name: string;
     public InstanceId: string;
-    public Input: string;
+    public Input: string | undefined;
 
     constructor(
         options: HistoryEventOptions,
@@ -16,8 +16,16 @@ export class SubOrchestrationInstanceCreatedEvent extends HistoryEvent {
             options.timestamp,
         );
 
+        if (options.name === undefined) {
+            throw new Error("SubOrchestrationInstanceCreatedEvent needs a name provided.");
+        }
+
+        if (options.instanceId === undefined) {
+            throw new Error("SubOrchestrationInstanceCreatedEvent needs an instance id provided.");
+        }
+
+        this.Input = options.input;
         this.Name = options.name;
         this.InstanceId = options.instanceId;
-        this.Input = options.input;
     }
 }
diff --git a/src/history/suborchestrationinstancefailedevent.ts b/src/history/suborchestrationinstancefailedevent.ts
index dd94e5f..a5fabfa 100644
--- a/src/history/suborchestrationinstancefailedevent.ts
+++ b/src/history/suborchestrationinstancefailedevent.ts
@@ -3,8 +3,8 @@ import { HistoryEvent, HistoryEventOptions, HistoryEventType } from "../classes"
 /** @hidden */
 export class SubOrchestrationInstanceFailedEvent extends HistoryEvent {
     public TaskScheduledId: number;
-    public Reason: string;
-    public Details: string;
+    public Reason: string | undefined;
+    public Details: string | undefined;
 
     constructor(
         options: HistoryEventOptions,
@@ -16,6 +16,10 @@ export class SubOrchestrationInstanceFailedEvent extends HistoryEvent {
             options.timestamp,
         );
 
+        if (options.taskScheduledId === undefined) {
+            throw new Error("SubOrchestrationInstanceFailedEvent needs a task scheduled id provided.");
+        }
+
         this.TaskScheduledId = options.taskScheduledId;
         this.Reason = options.reason;
         this.Details = options.details;
diff --git a/src/history/taskcompletedevent.ts b/src/history/taskcompletedevent.ts
index 4c08d1e..ee68e1c 100644
--- a/src/history/taskcompletedevent.ts
+++ b/src/history/taskcompletedevent.ts
@@ -15,6 +15,14 @@ export class TaskCompletedEvent extends HistoryEvent {
             options.timestamp,
         );
 
+        if (options.taskScheduledId === undefined) {
+            throw new Error("TaskCompletedEvent needs a task scheduled id provided.");
+        }
+
+        if (options.result === undefined) {
+            throw new Error("TaskCompletedEvent needs a result provided.");
+        }
+
         this.TaskScheduledId = options.taskScheduledId;
         this.Result = options.result;
     }
diff --git a/src/history/taskfailedevent.ts b/src/history/taskfailedevent.ts
index 2668679..f54c54b 100644
--- a/src/history/taskfailedevent.ts
+++ b/src/history/taskfailedevent.ts
@@ -3,8 +3,8 @@ import { HistoryEvent, HistoryEventOptions, HistoryEventType } from "../classes"
 /** @hidden */
 export class TaskFailedEvent extends HistoryEvent {
     public TaskScheduledId: number;
-    public Reason: string;
-    public Details: string;
+    public Reason: string | undefined;
+    public Details: string | undefined;
 
     constructor(
         options: HistoryEventOptions,
@@ -16,6 +16,10 @@ export class TaskFailedEvent extends HistoryEvent {
             options.timestamp,
         );
 
+        if (options.taskScheduledId === undefined) {
+            throw new Error("TaskFailedEvent needs a task scheduled id provided.");
+        }
+
         this.TaskScheduledId = options.taskScheduledId;
         this.Reason = options.reason;
         this.Details = options.details;
diff --git a/src/history/taskscheduledevent.ts b/src/history/taskscheduledevent.ts
index f5f6c05..e236b37 100644
--- a/src/history/taskscheduledevent.ts
+++ b/src/history/taskscheduledevent.ts
@@ -3,7 +3,7 @@ import { HistoryEvent, HistoryEventOptions, HistoryEventType } from "../classes"
 /** @hidden */
 export class TaskScheduledEvent extends HistoryEvent {
     public Name: string;
-    public Input: string;
+    public Input: string | undefined;
 
     constructor(
         options: HistoryEventOptions,
@@ -15,7 +15,11 @@ export class TaskScheduledEvent extends HistoryEvent {
             options.timestamp,
         );
 
-        this.Name = options.name;
+        if (options.name === undefined) {
+            throw new Error("TaskScheduledEvent needs a name provided.");
+        }
+
         this.Input = options.input;
+        this.Name = options.name;
     }
 }
diff --git a/src/history/timercreatedevent.ts b/src/history/timercreatedevent.ts
index 4056f99..1444868 100644
--- a/src/history/timercreatedevent.ts
+++ b/src/history/timercreatedevent.ts
@@ -14,6 +14,10 @@ export class TimerCreatedEvent extends HistoryEvent {
             options.timestamp,
         );
 
+        if (options.fireAt === undefined) {
+            throw new Error("TimerCreatedEvent needs a fireAt time provided.");
+        }
+
         this.FireAt = options.fireAt;
     }
 }
diff --git a/src/history/timerfiredevent.ts b/src/history/timerfiredevent.ts
index fef7a3d..2c5b095 100644
--- a/src/history/timerfiredevent.ts
+++ b/src/history/timerfiredevent.ts
@@ -15,6 +15,14 @@ export class TimerFiredEvent extends HistoryEvent {
             options.timestamp,
         );
 
+        if (options.timerId === undefined) {
+            throw new Error("TimerFiredEvent needs a timer id provided.");
+        }
+
+        if (options.fireAt === undefined) {
+            throw new Error("TimerFiredEvent needs a fireAt time provided.");
+        }
+
         this.TimerId = options.timerId;
         this.FireAt = options.fireAt;
     }
diff --git a/src/orchestrator.ts b/src/orchestrator.ts
index 4dbfa59..a2c0391 100644
--- a/src/orchestrator.ts
+++ b/src/orchestrator.ts
@@ -40,11 +40,10 @@ export class Orchestrator {
         // const contextLocks: EntityId[] = orchestrationBinding.contextLocks;
 
         // Initialize currentUtcDateTime
-        let decisionStartedEvent: HistoryEvent = state.find((e) =>
-            (e.EventType === HistoryEventType.OrchestratorStarted));
-        this.currentUtcDateTime = decisionStartedEvent
-            ? new Date(decisionStartedEvent.Timestamp)
-            : undefined;
+        let decisionStartedEvent: HistoryEvent = Utils.ensureNonNull(
+            state.find((e) => e.EventType === HistoryEventType.OrchestratorStarted),
+            "The orchestrator can not execute without an OrchestratorStarted event.");
+        this.currentUtcDateTime = new Date(decisionStartedEvent.Timestamp);
 
         // Reset newGuidCounter
         this.newGuidCounter = 0;
@@ -81,7 +80,8 @@ export class Orchestrator {
         let partialResult: Task | TaskSet;
 
         try {
-            let g = gen.next(partialResult ? partialResult.result : undefined);
+            // First execution, we have not yet "yielded" any of the tasks.
+            let g = gen.next(undefined);
 
             while (true) {
                 partialResult = g.value as Task | TaskSet;
@@ -105,7 +105,10 @@ export class Orchestrator {
                 }
 
                 if ((partialResult instanceof Task || partialResult instanceof TaskSet) && partialResult.isFaulted) {
-                    g = gen.throw(partialResult.exception);
+                    if (!gen.throw) {
+                        throw new Error("Cannot properly throw the execption returned by customer code");
+                    }
+                    g = gen.throw!(partialResult.exception);
                     continue;
                 }
 
@@ -129,7 +132,7 @@ export class Orchestrator {
                 decisionStartedEvent = newDecisionStartedEvent || decisionStartedEvent;
                 context.df.currentUtcDateTime = this.currentUtcDateTime = new Date(decisionStartedEvent.Timestamp);
 
-                g = gen.next(partialResult ? partialResult.result : undefined);
+                g = gen.next(partialResult.result);
             }
         } catch (error) {
             log(`Error: ${error}`);
@@ -227,7 +230,7 @@ export class Orchestrator {
         const eventSent = this.findEventSent(state, schedulerId, "op");
         let eventRaised;
         if (eventSent) {
-            const eventSentInput = eventSent ? JSON.parse(eventSent.Input) as RequestMessage : undefined;
+            const eventSentInput = eventSent && eventSent.Input !== undefined ? JSON.parse(eventSent.Input) as RequestMessage : undefined;
             eventRaised = eventSentInput ? this.findEventRaised(state, eventSentInput.id) : undefined;
         }
         this.setProcessed([ eventSent, eventRaised ]);
@@ -419,7 +422,7 @@ export class Orchestrator {
         );
     }
 
-    private lock(state: HistoryEvent[], instanceId: string, contextLocks: EntityId[], entities: EntityId[]): DurableLock {
+    private lock(state: HistoryEvent[], instanceId: string, contextLocks: EntityId[], entities: EntityId[]): DurableLock | undefined {
         if (contextLocks) {
             throw new Error("Cannot acquire more locks when already holding some locks.");
         }
@@ -518,10 +521,13 @@ export class Orchestrator {
         }, []);
 
         const completedTasks = tasks
-            .filter((t) => t.isCompleted)
+            .filter((t) => t && t.isCompleted)
             .sort((a, b) => {
-                if (a.timestamp > b.timestamp) { return 1; }
-                if (a.timestamp < b.timestamp) { return -1; }
+                // Because we have filtered by completed tasks, all of them should have timestamps
+                if (a.timestamp && b.timestamp) {
+                    if (a.timestamp > b.timestamp) { return 1; }
+                    if (a.timestamp < b.timestamp) { return -1; }
+                }
                 return 0;
             });
 
@@ -538,7 +544,8 @@ export class Orchestrator {
 
         switch (directiveResult.EventType) {
             case (HistoryEventType.EventRaised):
-                parsedDirectiveResult = JSON.parse((directiveResult as EventRaisedEvent).Input);
+                const eventRaised = directiveResult as EventRaisedEvent;
+                parsedDirectiveResult = (eventRaised && eventRaised.Input !== undefined) ? JSON.parse(eventRaised.Input) : undefined;
                 break;
             case (HistoryEventType.SubOrchestrationInstanceCompleted):
                 parsedDirectiveResult = JSON.parse((directiveResult as SubOrchestrationInstanceCompletedEvent).Result);
@@ -594,7 +601,7 @@ export class Orchestrator {
     private findSubOrchestrationInstanceCreated(
         state: HistoryEvent[],
         name: string,
-        instanceId: string)
+        instanceId: string | undefined)
         : SubOrchestrationInstanceCreatedEvent {
         const returnValue = name
             ? state.filter((val: HistoryEvent) => {
@@ -691,8 +698,8 @@ export class Orchestrator {
         return returnValue as TimerFiredEvent;
     }
 
-    private setProcessed(events: HistoryEvent[]): void {
-        events.map((val: HistoryEvent) => {
+    private setProcessed(events: Array<HistoryEvent | undefined>): void {
+        events.map((val: HistoryEvent | undefined) => {
             if (val) { val.IsProcessed = true; }
         });
     }
diff --git a/src/utils.ts b/src/utils.ts
index 68df532..bd4009f 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -21,6 +21,14 @@ export class Utils {
             });
     }
 
+    public static ensureNonNull<T>(argument: T | undefined, message: string) {
+        if (argument === undefined) {
+            throw new TypeError(message);
+        }
+
+        return argument;
+    }
+
     public static sleep(delayInMilliseconds: number): Promise<NodeJS.Timer> {
         return new Promise((resolve) => setTimeout(resolve, delayInMilliseconds));
     }
@@ -32,8 +40,10 @@ export class Utils {
     }
 
     public static throwIfEmpty(value: unknown, name: string): void {
-        if (typeof value !== "string" || value.trim().length < 1) {
+        if (typeof value !== "string") {
             throw new TypeError(`${name}: Expected non-empty, non-whitespace string but got ${typeof value}`);
+        } else if (value.trim().length < 1) {
+            throw new Error(`${name}: Expected non-empty, non-whitespace string but got empty string`);
         }
     }
 
diff --git a/tsconfig.json b/tsconfig.json
index ba55b42..8afb6e6 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,6 +1,6 @@
 {
   "compilerOptions": {
-    "strictNullChecks": false,
+    "strictNullChecks": true,
     "declaration": true,
     "module": "commonjs",
     "moduleResolution": "node",


Test patch:
diff --git a/test/integration/entity-spec.ts b/test/integration/entity-spec.ts
index d42935c..1bc2c54 100644
--- a/test/integration/entity-spec.ts
+++ b/test/integration/entity-spec.ts
@@ -14,13 +14,17 @@ describe("Entity", () => {
         operations.push({ kind: "set", value: "hello world"});
         operations.push({ kind: "get"});
 
-        const testData = TestEntityBatches.GetStringStoreBatch(operations, null);
+        const testData = TestEntityBatches.GetStringStoreBatch(operations, undefined);
         const mockContext = new MockContext({
             context: testData.input,
         });
         entity(mockContext);
 
-        entityStateMatchesExpected(mockContext.doneValue, testData.output);
+        expect(mockContext.doneValue).to.not.equal(undefined);
+
+        if (mockContext.doneValue) {
+            entityStateMatchesExpected(mockContext.doneValue, testData.output);
+        }
     });
 
     it("StringStore entity with initial state.", async () => {
@@ -34,7 +38,11 @@ describe("Entity", () => {
         });
         entity(mockContext);
 
-        entityStateMatchesExpected(mockContext.doneValue, testData.output);
+        expect(mockContext.doneValue).to.not.equal(undefined);
+
+        if (mockContext.doneValue) {
+            entityStateMatchesExpected(mockContext.doneValue, testData.output);
+        }
     });
 });
 
diff --git a/test/integration/orchestrator-spec.ts b/test/integration/orchestrator-spec.ts
index c97953d..553dc61 100644
--- a/test/integration/orchestrator-spec.ts
+++ b/test/integration/orchestrator-spec.ts
@@ -63,9 +63,9 @@ describe("Orchestrator", () => {
                 }),
             );
             if (isNaN(falsyValue as number)) {
-                expect(isNaN(mockContext.doneValue.output as number)).to.equal(true);
+                expect(isNaN(mockContext.doneValue!.output as number)).to.equal(true);
             } else {
-                expect(mockContext.doneValue.output).to.equal(falsyValue);
+                expect(mockContext.doneValue!.output).to.equal(falsyValue);
             }
             expect(mockContext.err).to.equal(null);
         });
@@ -88,7 +88,7 @@ describe("Orchestrator", () => {
             });
             orchestrator(mockContext);
 
-            expect(mockContext.df.instanceId).to.be.equal(id);
+            expect(mockContext.df!.instanceId).to.be.equal(id);
         });
 
         it("assigns isReplaying", async () => {
@@ -110,7 +110,7 @@ describe("Orchestrator", () => {
 
             orchestrator(mockContext);
 
-            expect(mockContext.df.isReplaying).to.be.equal(replaying);
+            expect(mockContext.df!.isReplaying).to.be.equal(replaying);
         });
 
         it("assigns parentInstanceId", async () => {
@@ -133,7 +133,7 @@ describe("Orchestrator", () => {
 
             orchestrator(mockContext);
 
-            expect(mockContext.df.parentInstanceId).to.be.equal(id);
+            expect(mockContext.df!.parentInstanceId).to.be.equal(id);
         });
 
         it("updates currentUtcDateTime to the most recent OrchestratorStarted timestamp", async () => {
@@ -154,7 +154,7 @@ describe("Orchestrator", () => {
 
             orchestrator(mockContext);
 
-            expect(mockContext.df.currentUtcDateTime).to.be.deep.equal(nextTimestamp);
+            expect(mockContext.df!.currentUtcDateTime).to.be.deep.equal(nextTimestamp);
         });
 
         it("uses existing currentUtcDateTime if OrchestratorStarted events are exhausted", async () => {
@@ -170,7 +170,7 @@ describe("Orchestrator", () => {
 
             orchestrator(mockContext);
 
-            expect(mockContext.doneValue.error).to.equal(undefined);
+            expect(mockContext.doneValue!.error).to.equal(undefined);
 
             expect(mockContext.err).to.equal(null);
         });
@@ -195,9 +195,9 @@ describe("Orchestrator", () => {
                 isDone: false,
                 actions: [],
             });
-            expect(mockContext.doneValue.error).to.include(expectedErr);
+            expect(mockContext.doneValue!.error).to.include(expectedErr);
 
-            expect(mockContext.err.toString()).to.include(expectedErr);
+            expect(mockContext.err!.toString()).to.include(expectedErr);
         });
 
         it("reports an unhandled exception from activity passed through orchestrator", async () => {
@@ -220,9 +220,9 @@ describe("Orchestrator", () => {
                     [ new CallActivityAction("ThrowsErrorActivity") ],
                 ],
             });
-            expect(mockContext.doneValue.error).to.include(expectedErr);
+            expect(mockContext.doneValue!.error).to.include(expectedErr);
 
-            expect(mockContext.err.toString()).to.include(expectedErr);
+            expect(mockContext.err!.toString()).to.include(expectedErr);
         });
 
         it("schedules an activity function after orchestrator catches an exception", async () => {
@@ -442,10 +442,10 @@ describe("Orchestrator", () => {
                     actions: [],
                 }),
             );
-            expect(mockContext.doneValue.error).to
+            expect(mockContext.doneValue!.error).to
                 .include(expectedErr);
 
-            expect(mockContext.err.toString()).to.include(expectedErr);
+            expect(mockContext.err!.toString()).to.include(expectedErr);
         });
 
         it("schedules an activity function", async () => {
@@ -572,10 +572,10 @@ describe("Orchestrator", () => {
                     ],
                 ],
             });
-            expect(mockContext.doneValue.error).to
+            expect(mockContext.doneValue!.error).to
                 .include(expectedErr);
 
-            expect(mockContext.err.toString()).to.include(expectedErr);
+            expect(mockContext.err!.toString()).to.include(expectedErr);
         });
 
         it("handles a completed activity function", async () => {
@@ -902,10 +902,10 @@ describe("Orchestrator", () => {
                     [ new CallSubOrchestratorAction("SayHelloWithActivity", childId, name) ],
                 ],
             });
-            expect(mockContext.doneValue.error).to
+            expect(mockContext.doneValue!.error).to
                 .include(expectedErr);
 
-            expect(mockContext.err.toString()).to.include(expectedErr);
+            expect(mockContext.err!.toString()).to.include(expectedErr);
         });
     });
 
@@ -930,10 +930,10 @@ describe("Orchestrator", () => {
                 isDone: false,
                 actions: [],
             });
-            expect(mockContext.doneValue.error).to
+            expect(mockContext.doneValue!.error).to
                 .include(expectedErr);
 
-            expect(mockContext.err.toString()).to.include(expectedErr);
+            expect(mockContext.err!.toString()).to.include(expectedErr);
         });
 
         it("schedules a suborchestrator function", async () => {
@@ -1085,10 +1085,10 @@ describe("Orchestrator", () => {
                     ],
                 ],
             });
-            expect(mockContext.doneValue.error).to
+            expect(mockContext.doneValue!.error).to
                 .include(expectedErr);
 
-            expect(mockContext.err.toString()).to.include(expectedErr);
+            expect(mockContext.err!.toString()).to.include(expectedErr);
         });
 
         it("handles a completed suborchestrator function", async () => {
@@ -1249,7 +1249,7 @@ describe("Orchestrator", () => {
             orchestrator(mockContext1);
             orchestrator(mockContext2);
 
-            expect(mockContext1.doneValue.isDone).to.equal(true);
+            expect(mockContext1.doneValue!.isDone).to.equal(true);
             expect(mockContext1.doneValue).to.deep.equal(mockContext2.doneValue);
         });
     });
@@ -1263,7 +1263,7 @@ describe("Orchestrator", () => {
                 ),
             });
 
-            const expectedLockState = new LockState(false, undefined);
+            const expectedLockState = new LockState(false, [new EntityId("samplename", "samplekey")]);
 
             orchestrator(mockContext);
 
@@ -1311,7 +1311,7 @@ describe("Orchestrator", () => {
             orchestrator(mockContext1);
             orchestrator(mockContext2);
 
-            expect(mockContext1.doneValue.isDone).to.equal(true);
+            expect(mockContext1.doneValue!.isDone).to.equal(true);
             expect(mockContext1.doneValue).to.deep.equal(mockContext2.doneValue);
         });
     });
@@ -1546,8 +1546,8 @@ describe("Orchestrator", () => {
                     ],
                 }),
             );
-            expect(mockContext.doneValue.error).to.include(expectedErr);
-            expect(mockContext.err.toString()).to.include(expectedErr);
+            expect(mockContext.doneValue!.error).to.include(expectedErr);
+            expect(mockContext.err!.toString()).to.include(expectedErr);
         });
 
         it("Task.any proceeds if a scheduled parallel task completes in order", async () => {
diff --git a/test/testobjects/testentitybatches.ts b/test/testobjects/testentitybatches.ts
index 4f74779..e592e65 100644
--- a/test/testobjects/testentitybatches.ts
+++ b/test/testobjects/testentitybatches.ts
@@ -4,10 +4,10 @@ import { CounterOperation, EntityInputsAndOutputs, StringStoreOperation  } from
 
 export class TestEntityBatches {
 
-    public static GetStringStoreBatch(operations: StringStoreOperation[], existingState: string): EntityInputsAndOutputs {
+    public static GetStringStoreBatch(operations: StringStoreOperation[], existingState: string | undefined): EntityInputsAndOutputs {
         const id = new df.EntityId("stringstore", "stringstorekey");
 
-        const entityExists = existingState != null;
+        const entityExists = existingState !== undefined;
         const output = new EntityState([], []);
         if (entityExists)  {
             output.entityState = JSON.stringify(existingState);
@@ -52,7 +52,7 @@ export class TestEntityBatches {
 
     public static GetCounterBatch(operations: CounterOperation[], existingState: number | undefined): EntityInputsAndOutputs {
         const id = new df.EntityId("stringstore", "stringstorekey");
-        let currentState: number;
+        let currentState: number | undefined;
         if (existingState) {
             currentState = Number(existingState);
         }
@@ -128,7 +128,7 @@ export class TestEntityBatches {
             operationCount++;
         }
         return  {
-            input: new DurableEntityBindingInfo(id, entityExists, existingState != null ? existingState.toString() : undefined, batch),
+            input: new DurableEntityBindingInfo(id, entityExists, existingState !== undefined ? existingState.toString() : undefined, batch),
             output,
         };
     }
diff --git a/test/testobjects/testhistories.ts b/test/testobjects/testhistories.ts
index 92b2504..57fab89 100644
--- a/test/testobjects/testhistories.ts
+++ b/test/testobjects/testhistories.ts
@@ -588,7 +588,6 @@ export class TestHistories {
                     timestamp: firstTimestamp,
                     isPlayed: false,
                     name: "Hello",
-                    input: null,
                 },
             ),
             new OrchestratorCompletedEvent(
@@ -620,7 +619,6 @@ export class TestHistories {
                     timestamp: firstMoment.add(1, "s").toDate(),
                     isPlayed: false,
                     name: "Hello",
-                    input: null,
                 },
             ),
             new OrchestratorCompletedEvent(
@@ -652,7 +650,6 @@ export class TestHistories {
                     timestamp: firstMoment.add(2, "s").toDate(),
                     isPlayed: false,
                     name: "Hello",
-                    input: null,
                 },
             ),
             new OrchestratorCompletedEvent(
diff --git a/test/unit/guidmanager-spec.ts b/test/unit/guidmanager-spec.ts
index 357bb2b..8379a09 100644
--- a/test/unit/guidmanager-spec.ts
+++ b/test/unit/guidmanager-spec.ts
@@ -10,13 +10,13 @@ import { GuidManager } from "../../src/classes";
 describe("GuidManager", () => {
     describe("createDeterministicGuid()", async () => {
         it("throws if namespaceValue is empty", () => {
-            expect(() => GuidManager.createDeterministicGuid(undefined, "name"))
-                .to.throw("namespaceValue: Expected non-empty, non-whitespace string but got undefined");
+            expect(() => GuidManager.createDeterministicGuid("", "name"))
+                .to.throw("namespaceValue: Expected non-empty, non-whitespace string but got empty string");
         });
 
         it("throws if name is empty", () => {
-            expect(() => GuidManager.createDeterministicGuid("namespaceValue", undefined))
-                .to.throw("name: Expected non-empty, non-whitespace string but got undefined");
+            expect(() => GuidManager.createDeterministicGuid("namespaceValue", ""))
+                .to.throw("name: Expected non-empty, non-whitespace string but got empty string");
         });
 
         it("returns consistent GUID for namespace and name", () => {
diff --git a/test/unit/orchestrationclient-spec.ts b/test/unit/orchestrationclient-spec.ts
index 5662be2..07a7ee4 100644
--- a/test/unit/orchestrationclient-spec.ts
+++ b/test/unit/orchestrationclient-spec.ts
@@ -49,14 +49,6 @@ describe("Orchestration Client", () => {
 
     const requiredPostHeaders = { reqheaders: { "Content-Type": "application/json" } };
 
-    describe("Constructor", () => {
-        it("throws if clientData is undefined", async () => {
-            expect(() => {
-                const client = new DurableOrchestrationClient(undefined);
-            }).to.throw(`clientData: Expected OrchestrationClientInputData but got undefined`);
-        });
-    });
-
     describe("Properties", () => {
         it("assigns taskHubName", async () => {
             const client = new DurableOrchestrationClient(defaultClientInputData);
@@ -174,13 +166,11 @@ describe("Orchestration Client", () => {
             const expectedStatus = new DurableOrchestrationStatus(
                 defaultOrchestrationName,
                 defaultInstanceId,
-                null,
-                null,
-                null,
-                null,
-                OrchestrationRuntimeStatus.Pending,
-                null,
-                null);
+                new Date(),
+                new Date(),
+                undefined,
+                undefined,
+                OrchestrationRuntimeStatus.Pending);
             const expectedWebhookUrl = new url.URL(defaultClientInputData.managementUrls.statusQueryGetUri
                 .replace(TestConstants.idPlaceholder, defaultInstanceId));
 
@@ -191,7 +181,7 @@ describe("Orchestration Client", () => {
 
             const result = await client.getStatus(defaultInstanceId);
             expect(scope.isDone()).to.be.equal(true);
-            expect(result).to.be.deep.equal(expectedStatus);
+            expect(JSON.stringify(result)).to.be.equal(JSON.stringify(expectedStatus));
         });
 
         it("calls expected webhook when showHistory = true", async () => {
@@ -200,13 +190,11 @@ describe("Orchestration Client", () => {
             const expectedStatus = new DurableOrchestrationStatus(
                 defaultOrchestrationName,
                 defaultInstanceId,
-                null,
-                null,
-                null,
-                null,
-                OrchestrationRuntimeStatus.Pending,
-                null,
-                null);
+                new Date(),
+                new Date(),
+                undefined,
+                undefined,
+                OrchestrationRuntimeStatus.Pending);
             const expectedWebhookUrl = new url.URL(defaultClientInputData.managementUrls.statusQueryGetUri
                 .replace(TestConstants.idPlaceholder, defaultInstanceId)
                 + "&showHistory=true");
@@ -218,7 +206,7 @@ describe("Orchestration Client", () => {
 
             const result = await client.getStatus(defaultInstanceId, true);
             expect(scope.isDone()).to.be.equal(true);
-            expect(result).to.be.deep.equal(expectedStatus);
+            expect(JSON.stringify(result)).to.be.equal(JSON.stringify(expectedStatus));
         });
 
         it("calls expected webhook when showHistoryOutput = true", async () => {
@@ -227,13 +215,11 @@ describe("Orchestration Client", () => {
             const expectedStatus = new DurableOrchestrationStatus(
                 defaultOrchestrationName,
                 defaultInstanceId,
-                null,
-                null,
-                null,
-                null,
-                OrchestrationRuntimeStatus.Pending,
-                null,
-                null);
+                new Date(),
+                new Date(),
+                undefined,
+                undefined,
+                OrchestrationRuntimeStatus.Pending);
             const expectedWebhookUrl = new url.URL(defaultClientInputData.managementUrls.statusQueryGetUri
                 .replace(TestConstants.idPlaceholder, defaultInstanceId)
                 + "&showHistoryOutput=true");
@@ -245,7 +231,7 @@ describe("Orchestration Client", () => {
 
             const result = await client.getStatus(defaultInstanceId, false, true);
             expect(scope.isDone()).to.be.equal(true);
-            expect(result).to.be.deep.equal(expectedStatus);
+            expect(JSON.stringify(result)).to.be.equal(JSON.stringify(expectedStatus));
         });
 
         it("calls expected webhook when showInput = false", async () => {
@@ -254,13 +240,11 @@ describe("Orchestration Client", () => {
             const expectedStatus = new DurableOrchestrationStatus(
                 defaultOrchestrationName,
                 defaultInstanceId,
-                null,
-                null,
-                null,
-                null,
-                OrchestrationRuntimeStatus.Pending,
-                null,
-                null);
+                new Date(),
+                new Date(),
+                undefined,
+                undefined,
+                OrchestrationRuntimeStatus.Pending);
             const expectedWebhookUrl = new url.URL(defaultClientInputData.managementUrls.statusQueryGetUri
                 .replace(TestConstants.idPlaceholder, defaultInstanceId)
                 + "&showInput=false");
@@ -272,7 +256,7 @@ describe("Orchestration Client", () => {
 
             const result = await client.getStatus(defaultInstanceId, false, false, false);
             expect(scope.isDone()).to.be.equal(true);
-            expect(result).to.be.deep.equal(expectedStatus);
+            expect(JSON.stringify(result)).to.be.equal(JSON.stringify(expectedStatus));
         });
 
         // TODO: test status codes individually
@@ -297,13 +281,11 @@ describe("Orchestration Client", () => {
                 new DurableOrchestrationStatus(
                     defaultOrchestrationName,
                     defaultInstanceId,
-                    null,
-                    null,
-                    null,
-                    null,
-                    OrchestrationRuntimeStatus.Pending,
-                    null,
-                    null),
+                    new Date(),
+                    new Date(),
+                    undefined,
+                    undefined,
+                    OrchestrationRuntimeStatus.Pending),
             ];
             const scope = nock(expectedWebhookUrl.origin)
                 .get(expectedWebhookUrl.pathname)
@@ -312,7 +294,7 @@ describe("Orchestration Client", () => {
 
             const result = await client.getStatusAll();
             expect(scope.isDone()).to.be.equal(true);
-            expect(result).to.be.deep.equal(expectedStatuses);
+            expect(JSON.stringify(result)).to.be.equal(JSON.stringify(expectedStatuses));
         });
     });
 
@@ -342,13 +324,13 @@ describe("Orchestration Client", () => {
                 new DurableOrchestrationStatus(
                     defaultOrchestrationName,
                     defaultInstanceId,
-                    null,
-                    null,
-                    null,
-                    null,
+                    new Date(),
+                    new Date(),
+                    undefined,
+                    undefined,
                     OrchestrationRuntimeStatus.Terminated,
-                    null,
-                    null),
+                    undefined,
+                    undefined),
             ];
             const scope = nock(expectedWebhookUrl.origin)
                 .get(expectedWebhookUrl.pathname)
@@ -357,7 +339,7 @@ describe("Orchestration Client", () => {
 
             const result = await client.getStatusBy(createdTimeFrom, createdTimeTo, runtimeStatuses);
             expect(scope.isDone()).to.be.equal(true);
-            expect(result).to.be.deep.equal(expectedStatuses);
+            expect(JSON.stringify(result)).to.be.equal(JSON.stringify(expectedStatuses));
         });
 
         it("calls expected webhook with some filters", async () => {
@@ -438,13 +420,6 @@ describe("Orchestration Client", () => {
             nock.cleanAll();
         });
 
-        it("throws if createdTimeFrom is not a valid date", async () => {
-            const client = new DurableOrchestrationClient(defaultClientInputData);
-
-            await expect(client.purgeInstanceHistoryBy(undefined, undefined, undefined))
-                .to.be.rejectedWith("createdTimeFrom must be a valid Date");
-        });
-
         it("calls expected webhook with all filters and returns expected result when instance(s) found", async () => {
             const client = new DurableOrchestrationClient(defaultClientInputData);
 
@@ -977,13 +952,13 @@ describe("Orchestration Client", () => {
             const expectedStatus = new DurableOrchestrationStatus(
                 defaultOrchestrationName,
                 defaultInstanceId,
-                null,
-                null,
-                null,
+                new Date(),
+                new Date(),
+                undefined,
                 expectedOutput,
                 OrchestrationRuntimeStatus.Completed,
-                null,
-                null,
+                undefined,
+                undefined,
             );
             const scope = nock(Constants.DefaultLocalOrigin)
                 .get(/.*/)
@@ -1011,8 +986,8 @@ describe("Orchestration Client", () => {
             const expectedStatus = new DurableOrchestrationStatus(
                 defaultOrchestrationName,
                 defaultInstanceId,
-                undefined,
-                undefined,
+                new Date(),
+                new Date(),
                 undefined,
                 undefined,
                 OrchestrationRuntimeStatus.Canceled,
@@ -1044,8 +1019,8 @@ describe("Orchestration Client", () => {
             const expectedStatus = new DurableOrchestrationStatus(
                 defaultOrchestrationName,
                 defaultInstanceId,
-                undefined,
-                undefined,
+                new Date(),
+                new Date(),
                 undefined,
                 undefined,
                 OrchestrationRuntimeStatus.Terminated,
@@ -1077,8 +1052,8 @@ describe("Orchestration Client", () => {
             const expectedStatus = new DurableOrchestrationStatus(
                 defaultOrchestrationName,
                 defaultInstanceId,
-                undefined,
-                undefined,
+                new Date(),
+                new Date(),
                 undefined,
                 undefined,
                 OrchestrationRuntimeStatus.Failed,
@@ -1112,8 +1087,8 @@ describe("Orchestration Client", () => {
             const runningStatus = new DurableOrchestrationStatus(
                 defaultOrchestrationName,
                 defaultInstanceId,
-                undefined,
-                undefined,
+                new Date(),
+                new Date(),
                 undefined,
                 undefined,
                 OrchestrationRuntimeStatus.Running,
@@ -1121,8 +1096,8 @@ describe("Orchestration Client", () => {
             const completedStatus = new DurableOrchestrationStatus(
                 defaultOrchestrationName,
                 defaultInstanceId,
-                undefined,
-                undefined,
+                new Date(),
+                new Date(),
                 undefined,
                 expectedOutput,
                 OrchestrationRuntimeStatus.Completed,
@@ -1168,8 +1143,8 @@ describe("Orchestration Client", () => {
             const runningStatus = new DurableOrchestrationStatus(
                 defaultOrchestrationName,
                 defaultInstanceId,
-                undefined,
-                undefined,
+                new Date(),
+                new Date(),
                 undefined,
                 undefined,
                 OrchestrationRuntimeStatus.Running,
diff --git a/test/unit/timertask-spec.ts b/test/unit/timertask-spec.ts
index eab9b72..253e439 100644
--- a/test/unit/timertask-spec.ts
+++ b/test/unit/timertask-spec.ts
@@ -4,7 +4,7 @@ import { Constants, CreateTimerAction, TimerTask } from "../../src/classes";
 
 describe("TimerTask", () => {
     it ("throws cannot cancel a completed task", async () => {
-        const task = new TimerTask(true, undefined, undefined, undefined, undefined);
+        const task = new TimerTask(true, false, new CreateTimerAction(new Date(), false));
         expect(() => {
             task.cancel();
         }).to.throw("Cannot cancel a completed task.");
diff --git a/test/unit/utils-spec.ts b/test/unit/utils-spec.ts
index 9fdb01a..adb193e 100644
--- a/test/unit/utils-spec.ts
+++ b/test/unit/utils-spec.ts
@@ -9,11 +9,6 @@ describe("Utils", () => {
             return expect(result).to.be.an("array").that.is.empty;
         });
 
-        it("returns empty array when collection is undefined", async () => {
-            const result = Utils.getInstancesOf<TestType>(undefined, new TestType());
-            return expect(result).to.be.an("array").that.is.empty;
-        });
-
         it("returns empty array when typeInstance is undefined", async () => {
             const result = Utils.getInstancesOf([], undefined);
             return expect(result).to.be.an("array").that.is.empty;
@@ -139,7 +134,7 @@ describe("Utils", () => {
             expect(() => {
                 Utils.throwIfEmpty("  ", defaultName);
             }).to.throw(
-                `${defaultName}: Expected non-empty, non-whitespace string but got ${typeof "  "}`,
+                `${defaultName}: Expected non-empty, non-whitespace string but got empty string`,
             );
         });