1 1
import { Buffer } from 'buffer';
2 1
import CryptoJS from 'crypto-js/core';
3 1
import { monotonicFactory, ULID } from 'ulid';
4 1
import { v4 as uuid } from 'uuid';
5
import { ModelInstanceCreator } from './datastore/datastore';
6 1
import {
7
	AllOperators,
8
	isPredicateGroup,
9
	isPredicateObj,
10
	ModelInstanceMetadata,
11
	PersistentModel,
12
	PersistentModelConstructor,
13
	PredicateGroups,
14
	PredicateObject,
15
	PredicatesGroup,
16
	RelationshipType,
17
	RelationType,
18
	SchemaNamespace,
19
	SortPredicatesGroup,
20
	SortDirection,
21
} from './types';
22

23 1
export const exhaustiveCheck = (obj: never, throwOnError: boolean = true) => {
24 1
	if (throwOnError) {
25 0
		throw new Error(`Invalid ${obj}`);
26
	}
27
};
28

29 1
export const isNullOrUndefined = (val: any): boolean => {
30 1
	return typeof val === 'undefined' || val === undefined || val === null;
31
};
32

33 1
export const validatePredicate = <T extends PersistentModel>(
34
	model: T,
35
	groupType: keyof PredicateGroups<T>,
36
	predicatesOrGroups: (PredicateObject<T> | PredicatesGroup<T>)[]
37
) => {
38
	let filterType: keyof Pick<any[], 'every' | 'some'>;
39 1
	let isNegation = false;
40

41 1
	if (predicatesOrGroups.length === 0) {
42 1
		return true;
43
	}
44

45 1
	switch (groupType) {
46 1
		case 'not':
47 0
			filterType = 'every';
48 0
			isNegation = true;
49 0
			break;
50
		case 'and':
51 1
			filterType = 'every';
52 1
			break;
53
		case 'or':
54 0
			filterType = 'some';
55 0
			break;
56
		default:
57 0
			exhaustiveCheck(groupType);
58
	}
59

60 1
	const result: boolean = predicatesOrGroups[filterType](predicateOrGroup => {
61 1
		if (isPredicateObj(predicateOrGroup)) {
62 1
			const { field, operator, operand } = predicateOrGroup;
63 1
			const value = model[field];
64

65 1
			return validatePredicateField(value, operator, operand);
66
		}
67

68 1
		if (isPredicateGroup(predicateOrGroup)) {
69 0
			const { type, predicates } = predicateOrGroup;
70 0
			return validatePredicate(model, type, predicates);
71
		}
72

73 0
		throw new Error('Not a predicate or group');
74
	});
75

76 1
	return isNegation ? !result : result;
77
};
78

79 1
const validatePredicateField = <T>(
80
	value: T,
81
	operator: keyof AllOperators,
82
	operand: T | [T, T]
83
) => {
84 1
	switch (operator) {
85 1
		case 'ne':
86 1
			return value !== operand;
87
		case 'eq':
88 1
			return value === operand;
89
		case 'le':
90 0
			return value <= operand;
91
		case 'lt':
92 0
			return value < operand;
93
		case 'ge':
94 0
			return value >= operand;
95
		case 'gt':
96 0
			return value > operand;
97
		case 'between':
98 0
			const [min, max] = <[T, T]>operand;
99 1
			return value >= min && value <= max;
100
		case 'beginsWith':
101 1
			return (<string>(<unknown>value)).startsWith(<string>(<unknown>operand));
102
		case 'contains':
103 0
			return (
104
				(<string>(<unknown>value)).indexOf(<string>(<unknown>operand)) > -1
105
			);
106
		case 'notContains':
107 0
			return (
108
				(<string>(<unknown>value)).indexOf(<string>(<unknown>operand)) === -1
109
			);
110
		default:
111 0
			exhaustiveCheck(operator, false);
112 0
			return false;
113
	}
114
};
115

116 1
export const isModelConstructor = <T extends PersistentModel>(
117
	obj: any
118
): obj is PersistentModelConstructor<T> => {
119 1
	return (
120 1
		obj && typeof (<PersistentModelConstructor<T>>obj).copyOf === 'function'
121
	);
122
};
123

124 1
export const establishRelation = (
125
	namespace: SchemaNamespace
126
): RelationshipType => {
127 1
	const relationship: RelationshipType = {};
128

129 1
	Object.keys(namespace.models).forEach((mKey: string) => {
130 1
		relationship[mKey] = { indexes: [], relationTypes: [] };
131

132 1
		const model = namespace.models[mKey];
133 1
		Object.keys(model.fields).forEach((attr: string) => {
134 1
			const fieldAttribute = model.fields[attr];
135 1
			if (
136 1
				typeof fieldAttribute.type === 'object' &&
137
				'model' in fieldAttribute.type
138
			) {
139 1
				const connectionType = fieldAttribute.association.connectionType;
140 1
				relationship[mKey].relationTypes.push({
141
					fieldName: fieldAttribute.name,
142
					modelName: fieldAttribute.type.model,
143
					relationType: connectionType,
144
					targetName: fieldAttribute.association['targetName'],
145
					associatedWith: fieldAttribute.association['associatedWith'],
146
				});
147

148 1
				if (connectionType === 'BELONGS_TO') {
149 1
					relationship[mKey].indexes.push(
150
						fieldAttribute.association['targetName']
151
					);
152
				}
153
			}
154
		});
155

156
		// create indexes from key fields
157 1
		if (model.attributes) {
158 1
			model.attributes.forEach(attribute => {
159 1
				if (attribute.type === 'key') {
160 1
					const { fields } = attribute.properties;
161 1
					if (fields) {
162 1
						fields.forEach(field => {
163
							// only add index if it hasn't already been added
164 1
							const exists = relationship[mKey].indexes.includes(field);
165 1
							if (!exists) {
166 0
								relationship[mKey].indexes.push(field);
167
							}
168
						});
169
					}
170
				}
171
			});
172
		}
173
	});
174

175 1
	return relationship;
176
};
177

178 1
const topologicallySortedModels = new WeakMap<SchemaNamespace, string[]>();
179

180 1
export const traverseModel = <T extends PersistentModel>(
181
	srcModelName: string,
182
	instance: T,
183
	namespace: SchemaNamespace,
184
	modelInstanceCreator: ModelInstanceCreator,
185
	getModelConstructorByModelName: (
186
		namsespaceName: string,
187
		modelName: string
188
	) => PersistentModelConstructor<any>
189
) => {
190 1
	const relationships = namespace.relationships;
191 1
	const modelConstructor = getModelConstructorByModelName(
192
		namespace.name,
193
		srcModelName
194
	);
195

196 1
	const relation = relationships[srcModelName];
197
	const result: {
198
		modelName: string;
199
		item: T;
200
		instance: T;
201 1
	}[] = [];
202

203 1
	const newInstance = modelConstructor.copyOf(instance, draftInstance => {
204 1
		relation.relationTypes.forEach((rItem: RelationType) => {
205 1
			const modelConstructor = getModelConstructorByModelName(
206
				namespace.name,
207
				rItem.modelName
208
			);
209

210 1
			switch (rItem.relationType) {
211 1
				case 'HAS_ONE':
212 1
					if (instance[rItem.fieldName]) {
213 0
						let modelInstance: T;
214 0
						try {
215 0
							modelInstance = modelInstanceCreator(
216
								modelConstructor,
217
								instance[rItem.fieldName]
218
							);
219
						} catch (error) {
220
							// Do nothing
221
						}
222

223 0
						result.push({
224
							modelName: rItem.modelName,
225
							item: instance[rItem.fieldName],
226
							instance: modelInstance,
227
						});
228

229 0
						(<any>draftInstance)[rItem.fieldName] = (<PersistentModel>(
230
							draftInstance[rItem.fieldName]
231
						)).id;
232
					}
233

234 1
					break;
235
				case 'BELONGS_TO':
236 1
					if (instance[rItem.fieldName]) {
237 1
						let modelInstance: T;
238 1
						try {
239 1
							modelInstance = modelInstanceCreator(
240
								modelConstructor,
241
								instance[rItem.fieldName]
242
							);
243
						} catch (error) {
244
							// Do nothing
245
						}
246

247 1
						const isDeleted = (<ModelInstanceMetadata>(
248
							draftInstance[rItem.fieldName]
249
						))._deleted;
250

251 1
						if (!isDeleted) {
252 1
							result.push({
253
								modelName: rItem.modelName,
254
								item: instance[rItem.fieldName],
255
								instance: modelInstance,
256
							});
257
						}
258
					}
259

260 1
					(<any>draftInstance)[rItem.targetName] = draftInstance[
261
						rItem.fieldName
262
					]
263 1
						? (<PersistentModel>draftInstance[rItem.fieldName]).id
264
						: null;
265

266 1
					delete draftInstance[rItem.fieldName];
267

268 1
					break;
269
				case 'HAS_MANY':
270
					// Intentionally blank
271 1
					break;
272
				default:
273 0
					exhaustiveCheck(rItem.relationType);
274 0
					break;
275
			}
276
		});
277
	});
278

279 1
	result.unshift({
280
		modelName: srcModelName,
281
		item: newInstance,
282
		instance: newInstance,
283
	});
284

285 1
	if (!topologicallySortedModels.has(namespace)) {
286 1
		topologicallySortedModels.set(
287
			namespace,
288
			Array.from(namespace.modelTopologicalOrdering.keys())
289
		);
290
	}
291

292 1
	const sortedModels = topologicallySortedModels.get(namespace);
293

294 1
	result.sort((a, b) => {
295 1
		return (
296
			sortedModels.indexOf(a.modelName) - sortedModels.indexOf(b.modelName)
297
		);
298
	});
299

300 1
	return result;
301
};
302

303 1
export const getIndex = (rel: RelationType[], src: string): string => {
304 1
	let index = '';
305 1
	rel.some((relItem: RelationType) => {
306 1
		if (relItem.modelName === src) {
307 1
			index = relItem.targetName;
308
		}
309
	});
310 1
	return index;
311
};
312

313 1
export const getIndexFromAssociation = (
314
	indexes: string[],
315
	src: string
316
): string => {
317 1
	const index = indexes.find(idx => idx === src);
318 1
	return index;
319
};
320

321 1
export enum NAMESPACES {
322 1
	DATASTORE = 'datastore',
323 1
	USER = 'user',
324 1
	SYNC = 'sync',
325 1
	STORAGE = 'storage',
326
}
327

328 1
const DATASTORE = NAMESPACES.DATASTORE;
329 1
const USER = NAMESPACES.USER;
330 1
const SYNC = NAMESPACES.SYNC;
331 1
const STORAGE = NAMESPACES.STORAGE;
332

333 1
export { USER, SYNC, STORAGE, DATASTORE };
334

335
let privateModeCheckResult;
336

337 1
export const isPrivateMode = () => {
338 1
	return new Promise(resolve => {
339 1
		const dbname = uuid();
340
		let db;
341

342 1
		const isPrivate = () => {
343 0
			privateModeCheckResult = false;
344

345 0
			resolve(true);
346
		};
347

348 1
		const isNotPrivate = async () => {
349 1
			if (db && db.result && typeof db.result.close === 'function') {
350 1
				await db.result.close();
351
			}
352

353 1
			await indexedDB.deleteDatabase(dbname);
354

355 1
			privateModeCheckResult = true;
356

357 1
			return resolve(false);
358
		};
359

360 1
		if (privateModeCheckResult === true) {
361 1
			return isNotPrivate();
362
		}
363

364 1
		if (privateModeCheckResult === false) {
365 0
			return isPrivate();
366
		}
367

368 1
		if (indexedDB === null) return isPrivate();
369

370 1
		db = indexedDB.open(dbname);
371 1
		db.onerror = isPrivate;
372 1
		db.onsuccess = isNotPrivate;
373
	});
374
};
375

376 1
const randomBytes = function(nBytes: number): Buffer {
377 1
	return Buffer.from(CryptoJS.lib.WordArray.random(nBytes).toString(), 'hex');
378
};
379 1
const prng = () => randomBytes(1).readUInt8(0) / 0xff;
380 1
export function monotonicUlidFactory(seed?: number): ULID {
381 1
	const ulid = monotonicFactory(prng);
382

383 1
	return () => {
384 1
		return ulid(seed);
385
	};
386
}
387

388
/**
389
 * Uses performance.now() if available, otherwise, uses Date.now() (e.g. react native without a polyfill)
390
 *
391
 * The values returned by performance.now() always increase at a constant rate,
392
 * independent of the system clock (which might be adjusted manually or skewed
393
 * by software like NTP).
394
 *
395
 * Otherwise, performance.timing.navigationStart + performance.now() will be
396
 * approximately equal to Date.now()
397
 *
398
 * See: https://developer.mozilla.org/en-US/docs/Web/API/Performance/now#Example
399
 */
400 1
export function getNow() {
401 1
	if (
402 1
		typeof performance !== 'undefined' &&
403
		performance &&
404
		typeof performance.now === 'function'
405
	) {
406 0
		return performance.now() | 0; // convert to integer
407
	} else {
408 0
		return Date.now();
409
	}
410
}
411

412 1
export function sortCompareFunction<T extends PersistentModel>(
413
	sortPredicates: SortPredicatesGroup<T>
414
) {
415 1
	return function compareFunction(a, b) {
416
		// enable multi-field sort by iterating over predicates until
417
		// a comparison returns -1 or 1
418 1
		for (const predicate of sortPredicates) {
419 1
			const { field, sortDirection } = predicate;
420

421
			// reverse result when direction is descending
422 1
			const sortMultiplier = sortDirection === SortDirection.ASCENDING ? 1 : -1;
423

424 1
			if (a[field] < b[field]) {
425 1
				return -1 * sortMultiplier;
426
			}
427

428 1
			if (a[field] > b[field]) {
429 1
				return 1 * sortMultiplier;
430
			}
431
		}
432

433 0
		return 0;
434
	};
435
}

Read our documentation on viewing source code .

Loading