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
|
import { action, computed, observable, runInAction } from 'mobx';
import { primitive, serializable } from 'serializr';
import { DocServer } from '../client/DocServer';
import { scriptingGlobal } from '../client/util/ScriptingGlobals';
import { Deserializable } from '../client/util/SerializationHelper';
import { Doc, Field, FieldWaiting, Opt } from './Doc';
import { Copy, Id, ToJavascriptString, ToScriptString, ToString, ToValue } from './FieldSymbols';
import { ObjectField } from './ObjectField';
type serializedProxyType = { cache: { field: unknown; p: undefined | Promise<unknown> }; fieldId: string };
function deserializeProxy(field: serializedProxyType) {
if (!field.cache.field) {
field.cache = { field: DocServer.GetCachedRefField(field.fieldId), p: undefined };
}
}
@Deserializable('proxy', (obj: unknown) => deserializeProxy(obj as serializedProxyType))
export class ProxyField<T extends Doc> extends ObjectField {
@serializable(primitive())
readonly fieldId: string = '';
private failed = false;
// This getter/setter and nested object thing is
// because mobx doesn't play well with observable proxies
@observable.ref
private _cache: { readonly field: T | undefined; p: FieldWaiting<T> | undefined } = { field: undefined, p: undefined };
private get cache(): { field: T | undefined; p: FieldWaiting<T> | undefined } {
return this._cache;
}
private set cache(val: { field: T | undefined; p: FieldWaiting<T> | undefined }) {
runInAction(() => (this._cache = { ...val }));
}
constructor();
constructor(value: T);
constructor(fieldId: string);
constructor(value: T | string);
constructor(value?: T | string) {
super();
if (typeof value === 'string') {
// this.cache = DocServer.GetCachedRefField(value) as any;
this.fieldId = value;
} else if (value) {
this.cache = { field: value, p: undefined };
this.fieldId = value[Id];
}
}
[ToValue]() {
return ProxyField.toValue(this);
}
[Copy]() {
return new ProxyField<T>(this.cache.field ?? this.fieldId);
}
[ToJavascriptString]() {
return Field.toScriptString(this[ToValue]()?.value as T);
}
[ToScriptString]() {
return Field.toScriptString(this[ToValue]()?.value as T); // not sure this is quite right since it doesn't recreate a proxy field, but better than 'invalid' ?
}
[ToString]() {
return Field.toString(this[ToValue]()?.value as T);
}
@computed get value(): T | undefined | FieldWaiting<T> {
if (this.cache.field) return this.cache.field;
if (this.failed) return undefined;
this.cache.field = DocServer.GetCachedRefField(this.fieldId) as T;
if (!this.cache.field && !this.cache.p) {
this.cache = {
field: undefined,
p: DocServer.GetRefField(this.fieldId).then(val => this.setValue(val as T)) as FieldWaiting<T>,
};
}
return this.cache.field ?? this.cache.p;
}
@computed get needsRequesting(): boolean {
return !!(!this.cache.field && !this.failed && !this._cache.p && !DocServer.GetCachedRefField(this.fieldId));
}
setExternalValuePromise(externalValuePromise: Promise<unknown>) {
this.cache.p = externalValuePromise.then(() => this.value) as FieldWaiting<T>;
}
@action
setValue(field: Opt<T>) {
this.cache = { field, p: undefined };
this.failed = field === undefined;
return field;
}
}
export namespace ProxyField {
let useProxy = true;
export function DisableDereference<T>(fn: () => T) {
useProxy = false;
try {
return fn();
} finally {
useProxy = true;
}
}
export function toValue(value: { value: unknown }) {
return useProxy ? { value: value.value } : undefined;
}
}
// eslint-disable-next-line no-use-before-define
function prefetchValue(proxy: PrefetchProxy<Doc>) {
return proxy.value as Promise<Doc>;
}
@scriptingGlobal
// eslint-disable-next-line no-use-before-define
@Deserializable('prefetch_proxy', (obj: unknown) => prefetchValue(obj as PrefetchProxy<Doc>))
export class PrefetchProxy<T extends Doc> extends ProxyField<T> {}
|