aboutsummaryrefslogtreecommitdiff
path: root/src/new_fields/ScriptField.ts
blob: e8a1ea28a3c98813741bab66d7c927b82f33a481 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
import { ObjectField } from "./ObjectField";
import { CompiledScript, CompileScript, scriptingGlobal } from "../client/util/Scripting";
import { Copy, ToScriptString, Parent, SelfProxy } from "./FieldSymbols";
import { serializable, createSimpleSchema, map, primitive, object, deserialize, PropSchema, custom, SKIP } from "serializr";
import { Deserializable } from "../client/util/SerializationHelper";
import { Doc } from "../new_fields/Doc";
import { Plugins } from "./util";
import { computedFn } from "mobx-utils";

function optional(propSchema: PropSchema) {
    return custom(value => {
        if (value !== undefined) {
            return propSchema.serializer(value);
        }
        return SKIP;
    }, (jsonValue: any, context: any, oldValue: any, callback: (err: any, result: any) => void) => {
        if (jsonValue !== undefined) {
            return propSchema.deserializer(jsonValue, callback, context, oldValue);
        }
        return SKIP;
    });
}

const optionsSchema = createSimpleSchema({
    requiredType: true,
    addReturn: true,
    typecheck: true,
    readonly: true,
    params: optional(map(primitive()))
});

const scriptSchema = createSimpleSchema({
    options: object(optionsSchema),
    originalScript: true
});

function deserializeScript(script: ScriptField) {
    const comp = CompileScript(script.script.originalScript, script.script.options);
    if (!comp.compiled) {
        throw new Error("Couldn't compile loaded script");
    }
    (script as any).script = comp;
}

@scriptingGlobal
@Deserializable("script", deserializeScript)
export class ScriptField extends ObjectField {
    @serializable(object(scriptSchema))
    readonly script: CompiledScript;

    constructor(script: CompiledScript) {
        super();

        this.script = script;
    }

    //     init(callback: (res: Field) => any) {
    //         const options = this.options!;
    //         const keys = Object.keys(options.options.capturedIds);
    //         Server.GetFields(keys).then(fields => {
    //             let captured: { [name: string]: Field } = {};
    //             keys.forEach(key => captured[options.options.capturedIds[key]] = fields[key]);
    //             const opts: ScriptOptions = {
    //                 addReturn: options.options.addReturn,
    //                 params: options.options.params,
    //                 requiredType: options.options.requiredType,
    //                 capturedVariables: captured
    //             };
    //             const script = CompileScript(options.script, opts);
    //             if (!script.compiled) {
    //                 throw new Error("Can't compile script");
    //             }
    //             this._script = script;
    //             callback(this);
    //         });
    //     }

    [Copy](): ObjectField {
        return new ScriptField(this.script);
    }

    [ToScriptString]() {
        return "script field";
    }
}

@scriptingGlobal
@Deserializable("computed", deserializeScript)
export class ComputedField extends ScriptField {
    //TODO maybe add an observable cache based on what is passed in for doc, considering there shouldn't really be that many possible values for doc
    value = computedFn((doc: Doc) => {
        const val = this.script.run({ this: doc });
        if (val.success) {
            return val.result;
        }
        return undefined;
    });
}

export namespace ComputedField {
    let useComputed = true;
    export function DisableComputedFields() {
        useComputed = false;
    }

    export function EnableComputedFields() {
        useComputed = true;
    }

    export function WithoutComputed<T>(fn: () => T) {
        DisableComputedFields();
        try {
            return fn();
        } finally {
            EnableComputedFields();
        }
    }

    Plugins.addGetterPlugin((doc, _, value) => {
        if (useComputed && value instanceof ComputedField) {
            return { value: value.value(doc), shouldReturn: true };
        }
    });
}