<commit_before>import * as _ from "lodash";
import {Observable} from "rxjs/Observable";
import {Subject} from "rxjs/Subject";
import {input, InputState} from "./InputState";
import {enableReactiveStatesLogging} from "./log";
import {LogEvent, logInvalidDataChangeInsideAction, logInvalidStateChangeOutsideAction, logStoreEvent} from "./StoreLog";

let developmentMode = false;

let invalidDataModificationComparator: <T>(v1: T, v2: T) => boolean
        = (v1: any, v2: any) => _.isEqual(v1, v2);

export type StateMembers<T> = { [P in keyof T]: InputState<T[P]>; };

export interface ActionOptions<T> {
    deepCloneFields?: (keyof T)[];
    afterAction?: (store: Store<T>, data: T, modifiedFields: Set<string>, newFields: Set<string>) => void;
    log?: boolean;
}

export class SelectEvent<T> {

    constructor(public readonly data: T,
                public readonly fields: Set<keyof T>) {
    }

    allSelectedFieldsNonNil(): boolean {
        return _.every(Array.from(this.fields), f => !_.isNil((this.data as any)[f]));
    }

}

export function setInvalidDataModificationComparator<T>(fn: (v1: T, v2: T) => boolean) {
    invalidDataModificationComparator = fn;
}

export function enableDevelopmentMode(enable: boolean = true) {
    developmentMode = enable;
    enableReactiveStatesLogging(enable);
}

function createInputState(name: string) {
    const is = input<any>();
    is.name = name;
    is.logEnabled = false;
    return is;
}


export abstract class Store<T> {

    readonly states: StateMembers<T>;

    private dataState: T;

    private actionDataState: T | null = null;

    private actionCompleted = new Subject<Set<keyof T>>();

    private actionStackCompletedTrigger = new Subject<any>();

    private actionCompletedBuffered: Observable<Set<keyof T>> = this.actionCompleted
            .bufferWhen(() => this.actionStackCompletedTrigger)
            .map(sets => {
                const all = new Set<any>();
                sets.forEach(s => s.forEach(k => all.add(k)));
                return all;
            })
    ;

    // for debugging in dev mode --------------------------------
    private dataAfterLastAction: T | null = null;

    private nameOfLastAction: string | null = null;
    // ----------------------------------------------------------

    /**
     * @param data
     */
    constructor(data: T) {
        this.dataState = data;

        // init inputStates
        const states: any = {};
        _.forIn(this.dataState, (value: any, key: string) => {
            let inputState = createInputState(key);
            if (value !== undefined) {
                inputState.putValue(value);
            }
            states[key] = inputState;
        });
        this.states = states;
    }

    get data(): T {
        return this.dataState;
    }

    select<K extends keyof T>(...fields: K[]): Observable<SelectEvent<T>> {
        return this.actionCompletedBuffered
                .filter(changedFields => {
                    return _.some(fields, f => changedFields.has(f));
                })
                .startWith(new Set(fields))
                .map(fields => new SelectEvent(this.data, new Set(fields)));
    }

    selectNonNil<K extends keyof T>(...fields: K[]): Observable<SelectEvent<T>> {
        return this.select(...fields)
                .filter(s => s.allSelectedFieldsNonNil());
    }

    stateField<M extends keyof T>(name: M): InputState<T[M]> {
        if (!_.has(this.states, name)) {
            this.states[name] = input<any>();
        }
        return this.states[name];
    }

    protected defaultActionOptions(): ActionOptions<T> {
        return {};
    }

    private checkForInvalidStateChangeBetweenActions(currentActionName: string) {
        if (developmentMode) {
            const invalidDataChange = this.dataAfterLastAction !== null
                    && !invalidDataModificationComparator(this.dataState, this.dataAfterLastAction);
            if (invalidDataChange) {
                this.dataAfterLastAction = this.data; // avoid repeating error messages
                throw logInvalidStateChangeOutsideAction(
                        this.nameOfLastAction!, currentActionName, this.dataAfterLastAction, this.dataState);
            }
        }<commit_msg>new release<commit_after>import * as _ from "lodash";
import {Observable} from "rxjs/Observable";
import {Subject} from "rxjs/Subject";
import {input, InputState} from "./InputState";
import {enableReactiveStatesLogging} from "./log";
import {LogEvent, logInvalidDataChangeInsideAction, logInvalidStateChangeOutsideAction, logStoreEvent} from "./StoreLog";

let developmentMode = false;

let invalidDataModificationComparator: <T>(v1: T, v2: T) => boolean
        = (v1: any, v2: any) => _.isEqual(v1, v2);

export type StateMembers<T> = { [P in keyof T]: InputState<T[P]>; };

export interface ActionOptions<T> {
    deepCloneFields?: (keyof T)[];
    afterAction?: (store: Store<T>, data: T, modifiedFields: Set<string>, newFields: Set<string>) => void;
    log?: boolean;
}

export class SelectEvent<T> {

    constructor(public readonly data: T,
                public readonly fields: Set<keyof T>) {
    }

    allSelectedFieldsNonNil(): boolean {
        return _.every(Array.from(this.fields), f => !_.isNil((this.data as any)[f]));
    }

}

export function setInvalidDataModificationComparator<T>(fn: (v1: T, v2: T) => boolean) {
    invalidDataModificationComparator = fn;
}

export function enableDevelopmentMode(enable: boolean = true) {
    developmentMode = enable;
    enableReactiveStatesLogging(enable);
}

function createInputState(name: string) {
    const is = input<any>();
    is.name = name;
    is.logEnabled = false;
    return is;
}


export abstract class Store<T> {

    readonly states: StateMembers<T>;

    private dataState: T;

    private actionDataState: T | null = null;

    private actionCompleted = new Subject<Set<keyof T>>();

    private actionStackCompletedTrigger = new Subject<any>();

    private actionCompletedBuffered: Observable<Set<keyof T>> = this.actionCompleted
            .bufferWhen(() => this.actionStackCompletedTrigger)
            .map(sets => {
                const all = new Set<any>();
                sets.forEach(s => s.forEach(k => all.add(k)));
                return all;
            })
    ;

    // for debugging in dev mode --------------------------------
    private dataAfterLastAction: T | null = null;

    private nameOfLastAction: string | null = null;
    // ----------------------------------------------------------

    /**
     * @param data
     */
    constructor(data: T) {
        this.dataState = data;

        // init inputStates
        const states: any = {};
        _.forIn(this.dataState, (value: any, key: string) => {
            let inputState = createInputState(key);
            if (value !== undefined) {
                inputState.putValue(value);
            }
            states[key] = inputState;
        });
        this.states = states;
    }

    get data(): T {
        return this.dataState;
    }

    select<K extends keyof T>(...fields: K[]): Observable<SelectEvent<T>> {
        return this.actionCompletedBuffered
                .filter(changedFields => {
                    return _.some(fields, f => changedFields.has(f));
                })
                .startWith(new Set(fields))
                .map(fields => new SelectEvent(this.data, new Set(fields)));
    }

    selectNonNil<K extends keyof T>(...fields: K[]): Observable<SelectEvent<T>> {
        return this.select(...fields)
                .filter(s => s.allSelectedFieldsNonNil());
    }

    stateField<M extends keyof T>(name: M): InputState<T[M]> {
        if (!_.has(this.states, name)) {
            this.states[name] = input<any>();
        }
        return this.states[name];
    }

    protected defaultActionOptions(): ActionOptions<T> {
        return {};
    }

    private checkForInvalidStateChangeBetweenActions(currentActionName: string) {
        if (developmentMode) {
            const invalidDataChange = this.dataAfterLastAction !== null
                    && !invalidDataModificationComparator(this.dataState, this.dataAfterLastAction);
            if (invalidDataChange) {
                this.dataAfterLastAction = this.dataState; // avoid repeating error messages
                throw logInvalidStateChangeOutsideAction(
                        this.nameOfLastAction!, currentActionName, this.dataAfterLastAction, this.dataState);
            }
        }
{'fraction_non_alphanumeric': 0.14294083186877563, 'fraction_numerical': 0.0018746338605741067, 'mean_word_length': 2.3846153846153846, 'pattern_counts': {'":': 0, '<': 61, '<?xml version=': 0, '>': 89, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '50773', 'n_tokens_mistral': 2401, 'n_tokens_neox': 2315, 'n_words': 561}