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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
|
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, autoObject } from "../client/util/SerializationHelper";
import { Doc } from "../new_fields/Doc";
import { Plugins } from "./util";
import { computedFn } from "mobx-utils";
import { ProxyField } from "./Proxy";
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
});
async function deserializeScript(script: ScriptField) {
const captures: ProxyField<Doc> = (script as any).captures;
if (captures) {
const doc = (await captures.value())!;
const captured: any = {};
const keys = Object.keys(doc);
const vals = await Promise.all(keys.map(key => doc[key]) as any);
keys.forEach((key, i) => captured[key] = vals[i]);
(script.script.options as any).capturedVariables = captured;
}
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;
@serializable(autoObject())
private captures?: ProxyField<Doc>;
constructor(script: CompiledScript) {
super();
if (script && script.options.capturedVariables) {
const doc = Doc.assign(new Doc, script.options.capturedVariables);
this.captures = new ProxyField(doc);
}
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 };
}
});
}
|