introspectron 0.2.4 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +12 -0
- package/esm/gql.js +297 -0
- package/esm/index.js +6 -0
- package/esm/introspect.js +85 -0
- package/{module → esm}/introspectGql.js +2 -2
- package/esm/process.js +278 -0
- package/{module → esm}/query.js +16 -12
- package/esm/utils.js +42 -0
- package/gql.d.ts +4 -0
- package/gql.js +301 -0
- package/{module/index.js → index.d.ts} +1 -1
- package/index.js +22 -0
- package/introspect.d.ts +16 -0
- package/introspect.js +89 -0
- package/introspectGql.d.ts +1 -0
- package/introspectGql.js +105 -0
- package/package.json +19 -51
- package/process.d.ts +1 -0
- package/process.js +282 -0
- package/query.d.ts +1 -0
- package/query.js +425 -0
- package/utils.d.ts +2 -0
- package/utils.js +47 -0
- package/CHANGELOG.md +0 -99
- package/main/gql.js +0 -376
- package/main/index.js +0 -70
- package/main/introspect.js +0 -194
- package/main/introspectGql.js +0 -25
- package/main/process.js +0 -410
- package/main/query.js +0 -26
- package/main/utils.js +0 -64
- package/module/gql.js +0 -330
- package/module/introspect.js +0 -75
- package/module/process.js +0 -321
- package/module/utils.js +0 -46
package/module/process.js
DELETED
|
@@ -1,321 +0,0 @@
|
|
|
1
|
-
import { deepClone, parseTags } from './utils';
|
|
2
|
-
|
|
3
|
-
const removeQuotes = str => {
|
|
4
|
-
const trimmed = str.trim();
|
|
5
|
-
|
|
6
|
-
if (trimmed[0] === '"') {
|
|
7
|
-
if (trimmed[trimmed.length - 1] !== '"') {
|
|
8
|
-
throw new Error(`We failed to parse a quoted identifier '${str}'. Please avoid putting quotes or commas in smart comment identifiers (or file a PR to fix the parser).`);
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
return trimmed.substr(1, trimmed.length - 2);
|
|
12
|
-
} else {
|
|
13
|
-
// PostgreSQL lower-cases unquoted columns, so we should too.
|
|
14
|
-
return trimmed.toLowerCase();
|
|
15
|
-
}
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
const parseSqlColumnArray = str => {
|
|
19
|
-
if (!str) {
|
|
20
|
-
throw new Error(`Cannot parse '${str}'`);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const parts = str.split(',');
|
|
24
|
-
return parts.map(removeQuotes);
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
const parseSqlColumnString = str => {
|
|
28
|
-
if (!str) {
|
|
29
|
-
throw new Error(`Cannot parse '${str}'`);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
return removeQuotes(str);
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
function parseConstraintSpec(rawSpec) {
|
|
36
|
-
const [spec, ...tagComponents] = rawSpec.split(/\|/);
|
|
37
|
-
const parsed = parseTags(tagComponents.join('\n'));
|
|
38
|
-
return {
|
|
39
|
-
spec,
|
|
40
|
-
tags: parsed.tags,
|
|
41
|
-
description: parsed.text
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function smartCommentConstraints(introspectionResults) {
|
|
46
|
-
const attributesByNames = (tbl, cols, debugStr) => {
|
|
47
|
-
const attributes = introspectionResults.attribute.filter(a => a.classId === tbl.id).sort((a, b) => a.num - b.num);
|
|
48
|
-
|
|
49
|
-
if (!cols) {
|
|
50
|
-
const pk = introspectionResults.constraint.find(c => c.classId == tbl.id && c.type === 'p');
|
|
51
|
-
|
|
52
|
-
if (pk) {
|
|
53
|
-
return pk.keyAttributeNums.map(n => attributes.find(a => a.num === n));
|
|
54
|
-
} else {
|
|
55
|
-
throw new Error(`No columns specified for '${tbl.namespaceName}.${tbl.name}' (oid: ${tbl.id}) and no PK found (${debugStr}).`);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return cols.map(colName => {
|
|
60
|
-
const attr = attributes.find(a => a.name === colName);
|
|
61
|
-
|
|
62
|
-
if (!attr) {
|
|
63
|
-
throw new Error(`Could not find attribute '${colName}' in '${tbl.namespaceName}.${tbl.name}'`);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return attr;
|
|
67
|
-
});
|
|
68
|
-
}; // First: primary keys
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
introspectionResults.class.forEach(klass => {
|
|
72
|
-
const namespace = introspectionResults.namespace.find(n => n.id === klass.namespaceId);
|
|
73
|
-
|
|
74
|
-
if (!namespace) {
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
if (klass.tags.primaryKey) {
|
|
79
|
-
if (typeof klass.tags.primaryKey !== 'string') {
|
|
80
|
-
throw new Error(`@primaryKey configuration of '${klass.namespaceName}.${klass.name}' is invalid; please specify just once "@primaryKey col1,col2"`);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const {
|
|
84
|
-
spec: pkSpec,
|
|
85
|
-
tags,
|
|
86
|
-
description
|
|
87
|
-
} = parseConstraintSpec(klass.tags.primaryKey);
|
|
88
|
-
const columns = parseSqlColumnArray(pkSpec);
|
|
89
|
-
const attributes = attributesByNames(klass, columns, `@primaryKey ${klass.tags.primaryKey}`);
|
|
90
|
-
attributes.forEach(attr => {
|
|
91
|
-
attr.tags.notNull = true;
|
|
92
|
-
});
|
|
93
|
-
const keyAttributeNums = attributes.map(a => a.num); // Now we need to fake a constraint for this:
|
|
94
|
-
|
|
95
|
-
const fakeConstraint = {
|
|
96
|
-
kind: 'constraint',
|
|
97
|
-
isFake: true,
|
|
98
|
-
isIndexed: true,
|
|
99
|
-
// otherwise it gets ignored by ignoreIndexes
|
|
100
|
-
id: Math.random(),
|
|
101
|
-
name: `FAKE_${klass.namespaceName}_${klass.name}_primaryKey`,
|
|
102
|
-
type: 'p',
|
|
103
|
-
// primary key
|
|
104
|
-
classId: klass.id,
|
|
105
|
-
foreignClassId: null,
|
|
106
|
-
comment: null,
|
|
107
|
-
description,
|
|
108
|
-
keyAttributeNums,
|
|
109
|
-
foreignKeyAttributeNums: null,
|
|
110
|
-
tags
|
|
111
|
-
};
|
|
112
|
-
introspectionResults.constraint.push(fakeConstraint);
|
|
113
|
-
}
|
|
114
|
-
}); // Now primary keys are in place, we can apply foreign keys
|
|
115
|
-
|
|
116
|
-
introspectionResults.class.forEach(klass => {
|
|
117
|
-
const namespace = introspectionResults.namespace.find(n => n.id === klass.namespaceId);
|
|
118
|
-
|
|
119
|
-
if (!namespace) {
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const getType = () => introspectionResults.type.find(t => t.id === klass.typeId);
|
|
124
|
-
|
|
125
|
-
const foreignKey = klass.tags.foreignKey || getType().tags.foreignKey;
|
|
126
|
-
|
|
127
|
-
if (foreignKey) {
|
|
128
|
-
const foreignKeys = typeof foreignKey === 'string' ? [foreignKey] : foreignKey;
|
|
129
|
-
|
|
130
|
-
if (!Array.isArray(foreignKeys)) {
|
|
131
|
-
throw new Error(`Invalid foreign key smart comment specified on '${klass.namespaceName}.${klass.name}'`);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
foreignKeys.forEach((fkSpecRaw, index) => {
|
|
135
|
-
if (typeof fkSpecRaw !== 'string') {
|
|
136
|
-
throw new Error(`Invalid foreign key spec (${index}) on '${klass.namespaceName}.${klass.name}'`);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const {
|
|
140
|
-
spec: fkSpec,
|
|
141
|
-
tags,
|
|
142
|
-
description
|
|
143
|
-
} = parseConstraintSpec(fkSpecRaw);
|
|
144
|
-
const matches = fkSpec.match(/^\(([^()]+)\) references ([^().]+)(?:\.([^().]+))?(?:\s*\(([^()]+)\))?$/i);
|
|
145
|
-
|
|
146
|
-
if (!matches) {
|
|
147
|
-
throw new Error(`Invalid foreignKey syntax for '${klass.namespaceName}.${klass.name}'; expected something like "(col1,col2) references schema.table (c1, c2)", you passed '${fkSpecRaw}'`);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const [, rawColumns, rawSchemaOrTable, rawTableOnly, rawForeignColumns] = matches;
|
|
151
|
-
const rawSchema = rawTableOnly ? rawSchemaOrTable : `"${klass.namespaceName}"`;
|
|
152
|
-
const rawTable = rawTableOnly || rawSchemaOrTable;
|
|
153
|
-
const columns = parseSqlColumnArray(rawColumns);
|
|
154
|
-
const foreignSchema = parseSqlColumnString(rawSchema);
|
|
155
|
-
const foreignTable = parseSqlColumnString(rawTable);
|
|
156
|
-
const foreignColumns = rawForeignColumns ? parseSqlColumnArray(rawForeignColumns) : null;
|
|
157
|
-
const foreignKlass = introspectionResults.class.find(k => k.name === foreignTable && k.namespaceName === foreignSchema);
|
|
158
|
-
|
|
159
|
-
if (!foreignKlass) {
|
|
160
|
-
throw new Error(`@foreignKey smart comment referenced non-existant table/view '${foreignSchema}'.'${foreignTable}'. Note that this reference must use *database names* (i.e. it does not respect @name). (${fkSpecRaw})`);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const foreignNamespace = introspectionResults.namespace.find(n => n.id === foreignKlass.namespaceId);
|
|
164
|
-
|
|
165
|
-
if (!foreignNamespace) {
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const keyAttributeNums = attributesByNames(klass, columns, `@foreignKey ${fkSpecRaw}`).map(a => a.num);
|
|
170
|
-
const foreignKeyAttributeNums = attributesByNames(foreignKlass, foreignColumns, `@foreignKey ${fkSpecRaw}`).map(a => a.num); // Now we need to fake a constraint for this:
|
|
171
|
-
|
|
172
|
-
const fakeConstraint = {
|
|
173
|
-
kind: 'constraint',
|
|
174
|
-
isFake: true,
|
|
175
|
-
isIndexed: true,
|
|
176
|
-
// otherwise it gets ignored by ignoreIndexes
|
|
177
|
-
id: Math.random(),
|
|
178
|
-
name: `FAKE_${klass.namespaceName}_${klass.name}_foreignKey_${index}`,
|
|
179
|
-
type: 'f',
|
|
180
|
-
// foreign key
|
|
181
|
-
classId: klass.id,
|
|
182
|
-
foreignClassId: foreignKlass.id,
|
|
183
|
-
comment: null,
|
|
184
|
-
description,
|
|
185
|
-
keyAttributeNums,
|
|
186
|
-
foreignKeyAttributeNums,
|
|
187
|
-
tags
|
|
188
|
-
};
|
|
189
|
-
introspectionResults.constraint.push(fakeConstraint);
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
export const introspectionResultsFromRaw = (rawResults, pgAugmentIntrospectionResults) => {
|
|
196
|
-
const introspectionResultsByKind = deepClone(rawResults);
|
|
197
|
-
|
|
198
|
-
const xByY = (arrayOfX, attrKey) => arrayOfX.reduce((memo, x) => {
|
|
199
|
-
memo[x[attrKey]] = x;
|
|
200
|
-
return memo;
|
|
201
|
-
}, {});
|
|
202
|
-
|
|
203
|
-
const xByYAndZ = (arrayOfX, attrKey, attrKey2) => arrayOfX.reduce((memo, x) => {
|
|
204
|
-
if (!memo[x[attrKey]]) memo[x[attrKey]] = {};
|
|
205
|
-
memo[x[attrKey]][x[attrKey2]] = x;
|
|
206
|
-
return memo;
|
|
207
|
-
}, {});
|
|
208
|
-
|
|
209
|
-
introspectionResultsByKind.namespaceById = xByY(introspectionResultsByKind.namespace, 'id');
|
|
210
|
-
introspectionResultsByKind.classById = xByY(introspectionResultsByKind.class, 'id');
|
|
211
|
-
introspectionResultsByKind.typeById = xByY(introspectionResultsByKind.type, 'id');
|
|
212
|
-
introspectionResultsByKind.attributeByClassIdAndNum = xByYAndZ(introspectionResultsByKind.attribute, 'classId', 'num');
|
|
213
|
-
introspectionResultsByKind.extensionById = xByY(introspectionResultsByKind.extension, 'id');
|
|
214
|
-
|
|
215
|
-
const relate = (array, newAttr, lookupAttr, lookup, missingOk = false) => {
|
|
216
|
-
array.forEach(entry => {
|
|
217
|
-
const key = entry[lookupAttr];
|
|
218
|
-
|
|
219
|
-
if (Array.isArray(key)) {
|
|
220
|
-
entry[newAttr] = key.map(innerKey => {
|
|
221
|
-
const result = lookup[innerKey];
|
|
222
|
-
|
|
223
|
-
if (innerKey && !result) {
|
|
224
|
-
if (missingOk) {
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
throw new Error(`Could not look up '${newAttr}' by '${lookupAttr}' ('${innerKey}') on '${JSON.stringify(entry)}'`);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
return result;
|
|
232
|
-
}).filter(_ => _);
|
|
233
|
-
} else {
|
|
234
|
-
const result = lookup[key];
|
|
235
|
-
|
|
236
|
-
if (key && !result) {
|
|
237
|
-
if (missingOk) {
|
|
238
|
-
return;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
throw new Error(`Could not look up '${newAttr}' by '${lookupAttr}' on '${JSON.stringify(entry)}'`);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
entry[newAttr] = result;
|
|
245
|
-
}
|
|
246
|
-
});
|
|
247
|
-
};
|
|
248
|
-
|
|
249
|
-
const augment = introspectionResults => {
|
|
250
|
-
[pgAugmentIntrospectionResults, smartCommentConstraints].forEach(fn => fn ? fn(introspectionResults) : null);
|
|
251
|
-
};
|
|
252
|
-
|
|
253
|
-
augment(introspectionResultsByKind);
|
|
254
|
-
relate(introspectionResultsByKind.class, 'namespace', 'namespaceId', introspectionResultsByKind.namespaceById, true // Because it could be a type defined in a different namespace - which is fine so long as we don't allow querying it directly
|
|
255
|
-
);
|
|
256
|
-
relate(introspectionResultsByKind.class, 'type', 'typeId', introspectionResultsByKind.typeById);
|
|
257
|
-
relate(introspectionResultsByKind.attribute, 'class', 'classId', introspectionResultsByKind.classById);
|
|
258
|
-
relate(introspectionResultsByKind.attribute, 'type', 'typeId', introspectionResultsByKind.typeById);
|
|
259
|
-
relate(introspectionResultsByKind.procedure, 'namespace', 'namespaceId', introspectionResultsByKind.namespaceById);
|
|
260
|
-
relate(introspectionResultsByKind.type, 'class', 'classId', introspectionResultsByKind.classById, true);
|
|
261
|
-
relate(introspectionResultsByKind.type, 'domainBaseType', 'domainBaseTypeId', introspectionResultsByKind.typeById, true // Because not all types are domains
|
|
262
|
-
);
|
|
263
|
-
relate(introspectionResultsByKind.type, 'arrayItemType', 'arrayItemTypeId', introspectionResultsByKind.typeById, true // Because not all types are arrays
|
|
264
|
-
);
|
|
265
|
-
relate(introspectionResultsByKind.constraint, 'class', 'classId', introspectionResultsByKind.classById);
|
|
266
|
-
relate(introspectionResultsByKind.constraint, 'foreignClass', 'foreignClassId', introspectionResultsByKind.classById, true // Because many constraints don't apply to foreign classes
|
|
267
|
-
);
|
|
268
|
-
relate(introspectionResultsByKind.extension, 'namespace', 'namespaceId', introspectionResultsByKind.namespaceById, true // Because the extension could be a defined in a different namespace
|
|
269
|
-
);
|
|
270
|
-
relate(introspectionResultsByKind.extension, 'configurationClasses', 'configurationClassIds', introspectionResultsByKind.classById, true // Because the configuration table could be a defined in a different namespace
|
|
271
|
-
);
|
|
272
|
-
relate(introspectionResultsByKind.index, 'class', 'classId', introspectionResultsByKind.classById); // Reverse arrayItemType -> arrayType
|
|
273
|
-
|
|
274
|
-
introspectionResultsByKind.type.forEach(type => {
|
|
275
|
-
if (type.arrayItemType) {
|
|
276
|
-
type.arrayItemType.arrayType = type;
|
|
277
|
-
}
|
|
278
|
-
}); // Table/type columns / constraints
|
|
279
|
-
|
|
280
|
-
introspectionResultsByKind.class.forEach(klass => {
|
|
281
|
-
klass.attributes = introspectionResultsByKind.attribute.filter(attr => attr.classId === klass.id);
|
|
282
|
-
klass.canUseAsterisk = !klass.attributes.some(attr => attr.columnLevelSelectGrant);
|
|
283
|
-
klass.constraints = introspectionResultsByKind.constraint.filter(constraint => constraint.classId === klass.id);
|
|
284
|
-
klass.foreignConstraints = introspectionResultsByKind.constraint.filter(constraint => constraint.foreignClassId === klass.id);
|
|
285
|
-
klass.primaryKeyConstraint = klass.constraints.find(constraint => constraint.type === 'p');
|
|
286
|
-
}); // Constraint attributes
|
|
287
|
-
|
|
288
|
-
introspectionResultsByKind.constraint.forEach(constraint => {
|
|
289
|
-
if (constraint.keyAttributeNums && constraint.class) {
|
|
290
|
-
constraint.keyAttributes = constraint.keyAttributeNums.map(nr => constraint.class.attributes.find(attr => attr.num === nr));
|
|
291
|
-
} else {
|
|
292
|
-
constraint.keyAttributes = [];
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
if (constraint.foreignKeyAttributeNums && constraint.foreignClass) {
|
|
296
|
-
constraint.foreignKeyAttributes = constraint.foreignKeyAttributeNums.map(nr => constraint.foreignClass.attributes.find(attr => attr.num === nr));
|
|
297
|
-
} else {
|
|
298
|
-
constraint.foreignKeyAttributes = [];
|
|
299
|
-
}
|
|
300
|
-
}); // Detect which columns and constraints are indexed
|
|
301
|
-
|
|
302
|
-
introspectionResultsByKind.index.forEach(index => {
|
|
303
|
-
const columns = index.attributeNums.map(nr => index.class.attributes.find(attr => attr.num === nr)); // Indexed column (for orderBy / filter):
|
|
304
|
-
|
|
305
|
-
if (columns[0]) {
|
|
306
|
-
columns[0].isIndexed = true;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
if (columns[0] && columns.length === 1 && index.isUnique) {
|
|
310
|
-
columns[0].isUnique = true;
|
|
311
|
-
} // Indexed constraints (for reverse relations):
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
index.class.constraints.filter(constraint => constraint.type === 'f').forEach(constraint => {
|
|
315
|
-
if (constraint.keyAttributeNums.every((nr, idx) => index.attributeNums[idx] === nr)) {
|
|
316
|
-
constraint.isIndexed = true;
|
|
317
|
-
}
|
|
318
|
-
});
|
|
319
|
-
});
|
|
320
|
-
return introspectionResultsByKind;
|
|
321
|
-
};
|
package/module/utils.js
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import _defineProperty from "@babel/runtime/helpers/esm/defineProperty";
|
|
2
|
-
|
|
3
|
-
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
|
|
4
|
-
|
|
5
|
-
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
|
|
6
|
-
|
|
7
|
-
export const parseTags = str => {
|
|
8
|
-
return str.split(/\r?\n/).reduce((prev, curr) => {
|
|
9
|
-
if (prev.text !== '') {
|
|
10
|
-
return _objectSpread(_objectSpread({}, prev), {}, {
|
|
11
|
-
text: `${prev.text}\n${curr}`
|
|
12
|
-
});
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const match = curr.match(/^@[a-zA-Z][a-zA-Z0-9_]*($|\s)/);
|
|
16
|
-
|
|
17
|
-
if (!match) {
|
|
18
|
-
return _objectSpread(_objectSpread({}, prev), {}, {
|
|
19
|
-
text: curr
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const key = match[0].substr(1).trim();
|
|
24
|
-
const value = match[0] === curr ? true : curr.replace(match[0], '');
|
|
25
|
-
return _objectSpread(_objectSpread({}, prev), {}, {
|
|
26
|
-
tags: _objectSpread(_objectSpread({}, prev.tags), {}, {
|
|
27
|
-
[key]: !Object.prototype.hasOwnProperty.call(prev.tags, key) ? value : Array.isArray(prev.tags[key]) ? [...prev.tags[key], value] : [prev.tags[key], value]
|
|
28
|
-
})
|
|
29
|
-
});
|
|
30
|
-
}, {
|
|
31
|
-
tags: {},
|
|
32
|
-
text: ''
|
|
33
|
-
});
|
|
34
|
-
};
|
|
35
|
-
export const deepClone = value => {
|
|
36
|
-
if (Array.isArray(value)) {
|
|
37
|
-
return value.map(val => deepClone(val));
|
|
38
|
-
} else if (typeof value === 'object' && value) {
|
|
39
|
-
return Object.keys(value).reduce((memo, k) => {
|
|
40
|
-
memo[k] = deepClone(value[k]);
|
|
41
|
-
return memo;
|
|
42
|
-
}, {});
|
|
43
|
-
} else {
|
|
44
|
-
return value;
|
|
45
|
-
}
|
|
46
|
-
};
|