-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathtypes-lib.js
247 lines (231 loc) · 7.47 KB
/
types-lib.js
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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
/**
* @flow
*/
import * as Immutable from 'immutable'
export type Updater<T> = (t: T) => T;
const registeredConstructors = {};
/**
* A "lens", as defined here, is a path down an existing object with the ability
* to do an immutable replacement deep in the object tree. All generated value
* types know how to create a type-specific lens to assist in navigating through
* them, and for other types, we
*/
export class Lens<T, Result> {
value: T;
replace: (t: T) => Result;
constructor(value: T, replace: (t: T) => Result) {
this.value = value;
this.replace = replace;
}
update(updater: Updater<T>): Result {
return this.replace(updater(this.value));
}
}
export const makeLens = function<T, Result>(
value: T, replace: (t: T) => Result): Lens<T, Result> {
if (value != null && typeof value.makeLens === 'function') {
return value.makeLens(replace);
}
return new Lens(value, replace);
};
const withName = (name) => {
return 'with' + name[0].toUpperCase() + name.slice(1);
};
const updateName = (name) => {
return 'update' + name[0].toUpperCase() + name.slice(1);
};
/**
* Construct a class with properly-named fields.
*/
export const buildValueClass = (
className: string, mixinClass: any,
fieldNames: Array<string>): any => {
class CustomLens extends Lens {}
const customLensPrototype: any = CustomLens.prototype;
for (const name of fieldNames) {
customLensPrototype[name] = function() {
const replaceChild = newChildVal =>
this.replace(this.value[withName(name)](newChildVal));
return makeLens(this.value[name], replaceChild);
};
}
class ValueClass {
constructor(fields) {
// Attach the class name for easier debugging.
(this: any).className = className;
for (const fieldName of fieldNames) {
(this: any)[fieldName] = fields[fieldName];
}
}
static make(...args) {
const constructorArg = {};
for (let i = 0; i < fieldNames.length; i++) {
constructorArg[fieldNames[i]] = args[i];
}
return new ValueClass(constructorArg);
}
serialize() {
const result = {};
result.__SERIALIZED_CLASS = className;
for (const name of fieldNames) {
result[name] = serialize((this: any)[name]);
}
return result;
}
lens() {
return this.makeLens(newValue => newValue);
}
makeLens(replace) {
return new CustomLens(this, replace);
}
equals(other) {
if (!(other instanceof ValueClass)) {
return false;
}
for (const name of fieldNames) {
if (!Immutable.is((this: any)[name], other[name])) {
return false;
}
}
return true;
}
hashCode() {
return Immutable.Map(this).hashCode();
}
toString() {
return Immutable.Map(this).toString();
}
}
const valuePrototype = (ValueClass: any).prototype;
for (const name of fieldNames) {
valuePrototype[withName(name)] = function(newVal) {
const newArgs = {};
for (const copyName of fieldNames) {
newArgs[copyName] = this[copyName];
}
newArgs[name] = newVal;
return new ValueClass(newArgs);
};
valuePrototype[updateName(name)] = function(updater) {
return this[withName(name)](updater(this[name]));
};
}
if (mixinClass) {
for (const methodName of
Object.getOwnPropertyNames(mixinClass.prototype)) {
if (methodName === 'constructor') {
continue;
}
valuePrototype[methodName] = mixinClass.prototype[methodName];
}
}
registeredConstructors[className] = ValueClass;
return ValueClass;
};
export const buildUnionCaseClass = (
caseName: string, fieldNames: Array<string>): any => {
const defaults: any = {};
for (const name of fieldNames) {
defaults[name] = undefined;
}
defaults.type = undefined;
class UnionCaseClass {
constructor(fields) {
(this: any).type = fields.type;
for (const fieldName of fieldNames) {
(this: any)[fieldName] = fields[fieldName];
}
}
static make(...args) {
const constructorArg = {};
constructorArg.type = caseName;
for (let i = 0; i < fieldNames.length; i++) {
constructorArg[fieldNames[i]] = args[i];
}
return new UnionCaseClass(constructorArg);
}
match(visitor) {
return visitor[caseName](this);
}
serialize() {
const result = {};
result.__SERIALIZED_CLASS = caseName;
for (const name of fieldNames) {
result[name] = serialize((this: any)[name]);
}
result.type = (this: any).type;
return result;
}
equals(other) {
if (!(other instanceof UnionCaseClass)) {
return false;
}
if (!Immutable.is((this: any).type, other.type)) {
return false;
}
for (const name of fieldNames) {
if (!Immutable.is((this: any)[name], other[name])) {
return false;
}
}
return true;
}
hashCode() {
return Immutable.Map(this).hashCode();
}
toString() {
return Immutable.Map(this).toString();
}
}
for (const name of fieldNames) {
(UnionCaseClass: any).prototype[withName(name)] = function(newVal) {
const newArgs = {};
for (const copyName of fieldNames) {
newArgs[copyName] = this[copyName];
}
newArgs.type = this.type;
newArgs[name] = newVal;
return new UnionCaseClass(newArgs);
};
(UnionCaseClass: any).prototype[updateName(name)] = function(updater) {
return this[withName(name)](updater(this[name]));
};
}
registeredConstructors[caseName] = UnionCaseClass;
return UnionCaseClass;
};
const serialize = (obj: any): any => {
if (obj == null || typeof obj !== 'object') {
return obj;
}
if (typeof obj.serialize === 'function') {
return obj.serialize();
}
return obj;
};
// TODO: Handle immutable maps. Currently it just doesn't serialize them.
export const deserialize = (obj: any): any => {
if (obj == null || typeof obj !== 'object') {
return obj;
}
const className = obj.__SERIALIZED_CLASS;
if (className == null) {
return obj;
}
const constructorArg = {};
for (const name of Object.keys(obj)) {
if (name !== '__SERIALIZED_CLASS') {
constructorArg[name] = deserialize(obj[name]);
}
}
const constructor = registeredConstructors[className];
return new constructor(constructorArg);
};
export const serializeActionsMiddleware = (store: any) => (next: any) => (action: any) => {
if (action != null &&
typeof action === 'object' &&
typeof action.serialize === 'function') {
action = action.serialize();
}
return next(action);
};