@coderich/autograph 0.11.1 → 0.13.0

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.
Files changed (56) hide show
  1. package/index.js +4 -6
  2. package/package.json +30 -44
  3. package/src/data/DataLoader.js +77 -70
  4. package/src/data/Emitter.js +89 -0
  5. package/src/data/Loader.js +33 -0
  6. package/src/data/Pipeline.js +88 -96
  7. package/src/data/Resolver.js +304 -0
  8. package/src/data/Transaction.js +49 -0
  9. package/src/query/Query.js +159 -334
  10. package/src/query/QueryBuilder.js +228 -114
  11. package/src/query/QueryResolver.js +110 -216
  12. package/src/query/QueryResolverTransaction.js +16 -0
  13. package/src/schema/Schema.js +593 -0
  14. package/src/service/AppService.js +38 -0
  15. package/src/service/ErrorService.js +7 -0
  16. package/CHANGELOG.md +0 -41
  17. package/LICENSE +0 -21
  18. package/README.md +0 -76
  19. package/src/.DS_Store +0 -0
  20. package/src/core/.DS_Store +0 -0
  21. package/src/core/Boom.js +0 -9
  22. package/src/core/EventEmitter.js +0 -95
  23. package/src/core/Resolver.js +0 -124
  24. package/src/core/Schema.js +0 -55
  25. package/src/core/ServerResolver.js +0 -15
  26. package/src/data/.DS_Store +0 -0
  27. package/src/data/DataService.js +0 -120
  28. package/src/data/DataTransaction.js +0 -96
  29. package/src/data/Field.js +0 -83
  30. package/src/data/Model.js +0 -223
  31. package/src/data/TreeMap.js +0 -78
  32. package/src/data/Type.js +0 -50
  33. package/src/driver/.DS_Store +0 -0
  34. package/src/driver/MongoDriver.js +0 -227
  35. package/src/driver/index.js +0 -11
  36. package/src/graphql/.DS_Store +0 -0
  37. package/src/graphql/ast/.DS_Store +0 -0
  38. package/src/graphql/ast/Field.js +0 -206
  39. package/src/graphql/ast/Model.js +0 -145
  40. package/src/graphql/ast/Node.js +0 -291
  41. package/src/graphql/ast/Schema.js +0 -133
  42. package/src/graphql/ast/Type.js +0 -26
  43. package/src/graphql/ast/TypeDefApi.js +0 -93
  44. package/src/graphql/extension/.DS_Store +0 -0
  45. package/src/graphql/extension/api.js +0 -193
  46. package/src/graphql/extension/framework.js +0 -71
  47. package/src/graphql/extension/type.js +0 -34
  48. package/src/query/.DS_Store +0 -0
  49. package/src/query/QueryBuilderTransaction.js +0 -26
  50. package/src/query/QueryService.js +0 -111
  51. package/src/service/.DS_Store +0 -0
  52. package/src/service/app.service.js +0 -319
  53. package/src/service/decorator.service.js +0 -114
  54. package/src/service/event.service.js +0 -66
  55. package/src/service/graphql.service.js +0 -92
  56. package/src/service/schema.service.js +0 -95
package/src/data/Field.js DELETED
@@ -1,83 +0,0 @@
1
- const { isEmpty } = require('lodash');
2
- const Type = require('./Type');
3
- const Field = require('../graphql/ast/Field');
4
- const Pipeline = require('./Pipeline');
5
-
6
- module.exports = class extends Field {
7
- constructor(model, field) {
8
- super(model, JSON.parse(JSON.stringify((field.getAST()))));
9
- this.type = new Type(field);
10
- this.model = model;
11
- }
12
-
13
- getStructures() {
14
- // Grab structures from the underlying type
15
- const structures = this.type.getStructures();
16
- const { type, isPrimaryKeyId, isIdField, isRequired, isPersistable, isVirtual, isEmbedded, modelRef } = this.props;
17
-
18
- // Structures defined on the field
19
- const $structures = Object.entries(this.getDirectiveArgs('field', {})).reduce((prev, [key, value]) => {
20
- if (!Array.isArray(value)) value = [value];
21
- if (key === 'validate') prev.validators.push(...value.map(t => Pipeline[t]));
22
- if (key === 'instruct') prev.instructs.push(...value.map(t => Pipeline[t]));
23
- if (key === 'restruct') prev.restructs.push(...value.map(t => Pipeline[t]));
24
- if (key === 'destruct') prev.destructs.push(...value.map(t => Pipeline[t]));
25
- if (key === 'construct') prev.constructs.push(...value.map(t => Pipeline[t]));
26
- if (key === 'transform') prev.transforms.push(...value.map(t => Pipeline[t]));
27
- if (key === 'normalize') prev.normalizers.push(...value.map(t => Pipeline[t]));
28
- if (key === 'serialize') prev.serializers.push(...value.map(t => Pipeline[t]));
29
- if (key === 'deserialize') prev.deserializers.push(...value.map(t => Pipeline[t]));
30
- return prev;
31
- }, structures);
32
-
33
- // IDs (first - shift)
34
- if (isPrimaryKeyId && type === 'ID') $structures.serializers.unshift(Pipeline.idKey);
35
- if (isIdField) $structures.$serializers.unshift(Pipeline.idField);
36
-
37
- // Required (last - push)
38
- if (isRequired && isPersistable && !isVirtual) $structures.validators.push(Pipeline.required);
39
- if (modelRef && !isEmbedded) $structures.validators.push(Pipeline.ensureId);
40
-
41
- return $structures;
42
- }
43
-
44
- resolve(resolver, doc, args = {}) {
45
- const { name, isArray, isScalar, isVirtual, isRequired, isEmbedded, modelRef, virtualField } = this.props;
46
- const value = doc[name];
47
-
48
- // Default resolver return immediately!
49
- if (isScalar || isEmbedded) return value;
50
-
51
- // Ensure where clause for DB lookup
52
- args.where = args.where || {};
53
-
54
- if (isArray) {
55
- if (isVirtual) {
56
- if (isEmpty(args.where)) args.batch = `${virtualField}`;
57
- args.where[virtualField] = doc.id;
58
- return resolver.match(modelRef).merge(args).many();
59
- }
60
-
61
- // Not a "required" query + strip out nulls
62
- if (isEmpty(args.where)) args.batch = 'id';
63
- args.where.id = value;
64
- return resolver.match(modelRef).merge(args).many();
65
- }
66
-
67
- if (isVirtual) {
68
- if (isEmpty(args.where)) args.batch = `${virtualField}`;
69
- args.where[virtualField] = doc.id;
70
- return resolver.match(modelRef).merge(args).one();
71
- }
72
-
73
- return resolver.match(modelRef).id(value).one({ required: isRequired });
74
- }
75
-
76
- count(resolver, doc, args = {}) {
77
- const { name, isVirtual, modelRef, virtualField } = this.props;
78
- args.where = args.where || {};
79
- if (isVirtual) args.where[virtualField] = doc.id;
80
- else args.where.id = doc[name];
81
- return resolver.match(modelRef).merge(args).count();
82
- }
83
- };
package/src/data/Model.js DELETED
@@ -1,223 +0,0 @@
1
- const Stream = require('stream');
2
- const { get } = require('lodash');
3
- const { flatten } = require('@coderich/util');
4
- const Field = require('./Field');
5
- const Model = require('../graphql/ast/Model');
6
- const { eventEmitter } = require('../service/event.service');
7
- const { finalizeResults } = require('./DataService');
8
- const { map, mapPromise, seek, deseek } = require('../service/app.service');
9
-
10
- module.exports = class extends Model {
11
- constructor(schema, model, driver) {
12
- super(schema, JSON.parse(JSON.stringify((model.getAST()))));
13
- this.driver = driver;
14
- this.fields = super.getFields().map(field => new Field(this, field));
15
- this.namedQueries = {};
16
- this.shapesCache = new Map();
17
- }
18
-
19
- raw() {
20
- return this.driver.dao.raw(this.getKey());
21
- }
22
-
23
- drop() {
24
- return this.driver.dao.dropModel(this.getKey());
25
- }
26
-
27
- idValue(id) {
28
- return this.driver.idValue(id);
29
- }
30
-
31
- idKey() {
32
- return this.getDirectiveArg('model', 'id', this.driver.idKey());
33
- }
34
-
35
- getDriver() {
36
- return this.driver.dao;
37
- }
38
-
39
- createNamedQuery(name, fn) {
40
- this.namedQueries[name] = fn;
41
- }
42
-
43
- getNamedQueries(name) {
44
- return this.namedQueries;
45
- }
46
-
47
- referentialIntegrity(refs) {
48
- if (refs) this.referentials = refs;
49
- return this.referentials;
50
- }
51
-
52
- /**
53
- * Convenience method to deserialize data from a data source (such as a database)
54
- */
55
- deserialize(mixed, query) {
56
- const shape = this.getShape();
57
-
58
- return new Promise((resolve, reject) => {
59
- if (!(mixed instanceof Stream)) {
60
- resolve(this.shapeObject(shape, mixed, query));
61
- } else {
62
- const results = [];
63
- mixed.on('data', (data) => { results.push(this.shapeObject(shape, data, query)); });
64
- mixed.on('end', () => { resolve(results); });
65
- mixed.on('error', reject);
66
- }
67
- }).then(rs => finalizeResults(rs, query));
68
- }
69
-
70
- getShape(crud = 'read', target = 'doc', paths = [], depth = 0) {
71
- if (depth++ > 10) return {}; // Prevent infinite circular references
72
-
73
- // Cache check
74
- const cacheKey = `${crud}:${target}`;
75
- if (this.shapesCache.has(cacheKey)) return this.shapesCache.get(cacheKey);
76
-
77
- const serdes = crud === 'read' ? 'deserialize' : 'serialize';
78
- const fields = serdes === 'deserialize' ? this.getSelectFields() : this.getPersistableFields();
79
- const crudMap = { create: ['constructs'], update: ['restructs'], delete: ['destructs'], remove: ['destructs'] };
80
- const sortKeys = ['isIdField', 'isBasicType', 'isEmbedded'];
81
- const crudKeys = crudMap[crud] || [];
82
-
83
- // Define target mapping
84
- const targetMap = {
85
- doc: [], // Do nothing...
86
- // doc: ['defaultValue', 'castValue', 'ensureArrayValue', 'normalizers', 'instructs', ...crudKeys, `$${serdes}rs`, `${serdes}rs`, 'transforms'],
87
- input: ['defaultValue', 'castValue', 'ensureArrayValue', 'normalizers', 'instructs', ...crudKeys, `$${serdes}rs`, `${serdes}rs`, 'transforms'],
88
- where: ['castValue', 'instructs', `$${serdes}rs`],
89
- };
90
-
91
- const structureKeys = targetMap[target] || ['castValue'];
92
-
93
- // Create sorted shape, recursive
94
- const shape = fields.sort((a, b) => {
95
- const aObject = a.toObject();
96
- const bObject = b.toObject();
97
-
98
- // PK first
99
- if (aObject.isPrimaryKeyId) return -1;
100
- if (bObject.isPrimaryKeyId) return 1;
101
-
102
- // Arrays last
103
- if (aObject.isArray && !bObject.isArray) return 1;
104
- if (bObject.isArray && !aObject.isArray) return -1;
105
-
106
- // Now, follow sort keys
107
- const aNum = sortKeys.findIndex(key => aObject[key]);
108
- const bNum = sortKeys.findIndex(key => bObject[key]);
109
- if (aNum < bNum) return -1;
110
- if (aNum > bNum) return 1;
111
- return 0;
112
- }).map((field) => {
113
- let instructed = false;
114
- const structures = field.getStructures();
115
- const { key, name, type, isArray, isEmbedded, modelRef } = field.toObject();
116
- const [from, to] = serdes === 'serialize' ? [name, key] : [key, name];
117
- const actualTo = target === 'input' || target === 'splice' ? from : to;
118
- const path = paths.concat(actualTo);
119
- const subCrud = crud === 'update' && isArray ? 'create' : crud; // Due to limitation to update embedded array
120
- const subShape = isEmbedded ? modelRef.getShape(subCrud, target, path, depth) : null;
121
- const transformers = structureKeys.reduce((prev, struct) => {
122
- const structs = structures[struct];
123
- if (struct === 'instructs' && structs.length) instructed = true;
124
- return prev.concat(structs);
125
- }, []).filter(Boolean);
126
- return { instructed, field, path, from, to: actualTo, type, isArray, transformers, validators: structures.validators, shape: subShape };
127
- });
128
-
129
- // Adding useful shape info
130
- shape.crud = crud;
131
- shape.model = this;
132
- shape.serdes = serdes;
133
- shape.target = target;
134
- // console.log(shape.modelRef);
135
-
136
- // Cache and return
137
- this.shapesCache.set(cacheKey, shape);
138
- return shape;
139
- }
140
-
141
- shapeObject(shape, obj, query, root, base, toFlat = false) {
142
- const { serdes, model } = shape;
143
- const { context, resolver, doc = {}, flags = {} } = query.toObject();
144
- const { pipeline } = flags;
145
-
146
- if (!pipeline) return obj;
147
- // const filters = pipeline === true ? [] : Object.entries(pipeline).map(([k, v]) => (v === false ? k : null)).filter(Boolean);
148
-
149
- // base is the base model
150
- base = base || model;
151
-
152
- return map(obj, (parent) => {
153
- // root is the base data object
154
- root = root || parent;
155
-
156
- // Lookup helper functions
157
- const docPath = (p, hint) => seek(doc, p, hint); // doc is already serialized; so always a seek
158
- const rootPath = (p, hint) => (serdes === 'serialize' ? seek(root, p, hint) : deseek(shape, root, p, hint));
159
- const parentPath = (p, hint) => (serdes === 'serialize' ? seek(parent, p, hint) : deseek(shape, parent, p, hint));
160
-
161
- return shape.reduce((prev, { instructed, field, from, to, path, type, isArray, defaultValue, transformers = [], shape: subShape }) => {
162
- const startValue = parent[from];
163
- // transformers = filters.length ? transformers.filter() : transformers;
164
-
165
- // Transform value
166
- const transformedValue = transformers.reduce((value, t) => {
167
- const v = t({ base, model, field, path, docPath, rootPath, parentPath, startValue, value, resolver, context });
168
- return v === undefined ? value : v;
169
- }, startValue);
170
-
171
- // Determine if key should stay or be removed
172
- if (!instructed && transformedValue === undefined && !Object.prototype.hasOwnProperty.call(parent, from)) return prev;
173
- if (!instructed && subShape && typeof transformedValue !== 'object') return prev;
174
-
175
- // Rename key & assign value
176
- prev[to] = (!subShape || transformedValue == null) ? transformedValue : this.shapeObject(subShape, transformedValue, query, root, base, toFlat);
177
-
178
- if (toFlat && get(doc, to) && field.getModelRef()) {
179
- const val = prev[to];
180
- delete prev[to];
181
- Object.assign(prev, flatten({ [to]: val }, { safe: true, depth: 1 }));
182
- }
183
-
184
- return prev;
185
- }, {});
186
- });
187
- }
188
-
189
- validateObject(shape, obj, query, root, base, silent = false) {
190
- const { model } = shape;
191
- const { context, resolver, doc = {}, flags = {} } = query.toObject();
192
- const { validate = true } = flags;
193
-
194
- if (!validate) return Promise.resolve();
195
-
196
- // base is the base model
197
- base = base || model;
198
-
199
- return mapPromise(obj, (parent) => {
200
- // root is the base data object
201
- root = root || parent;
202
-
203
- // Lookup helper functions
204
- const docPath = (p, hint) => seek(doc, p, hint);
205
- const rootPath = (p, hint) => seek(root, p, hint);
206
- const parentPath = (p, hint) => seek(parent, p, hint);
207
-
208
- return Promise.all(shape.map(({ field, from, path, validators, shape: subShape }) => {
209
- const value = parent[from]; // It hasn't been shaped yet
210
-
211
- return Promise.all(validators.map((v) => {
212
- return new Promise((resolve, reject) => {
213
- return Promise.resolve(v({ base, model, field, path, docPath, rootPath, parentPath, startValue: value, value, resolver, context })).then(resolve).catch(reject);
214
- });
215
- })).then(() => {
216
- return subShape ? this.validateObject(subShape, value, query, root, base, true) : Promise.resolve();
217
- });
218
- }));
219
- }).then(() => {
220
- return silent ? Promise.resolve() : eventEmitter.emit('validate', query.toObject());
221
- });
222
- }
223
- };
@@ -1,78 +0,0 @@
1
- /**
2
- * TreeMap.
3
- *
4
- * A utility class to help build and access elements in a tree-like structure. Used
5
- * primarly when handling nested transactions.
6
- */
7
- module.exports = class TreeMap {
8
- constructor() {
9
- this.map = new Map();
10
- this.throw = (e = 'Parent not found') => { throw new Error(e); };
11
- }
12
-
13
- add(parent, child) {
14
- if (parent && child) {
15
- const [, map] = this.get(parent) || this.throw();
16
- map.set(child, new Map());
17
- } else {
18
- this.map.set(parent || child, new Map());
19
- }
20
- }
21
-
22
- get(parent, map = this.map) {
23
- if (map.has(parent)) return [map, map.get(parent)];
24
-
25
- return Array.from(map.values()).reduce((value, child) => {
26
- if (value) return value;
27
- return this.get(parent, child);
28
- }, undefined);
29
- }
30
-
31
- remove(parent) {
32
- const [map] = this.get(parent) || this.throw();
33
- const el = map.get(parent);
34
- map.delete(parent);
35
- return el;
36
- }
37
-
38
- elements(map = this.map) {
39
- return Array.from(map.entries()).reduce((prev, [key, value]) => {
40
- return prev.concat(key).concat(this.elements(value));
41
- }, []);
42
- }
43
-
44
- root(parent) {
45
- const [root = parent] = this.ascendants(parent);
46
- return root;
47
- }
48
-
49
- ascendants(parent) {
50
- if (!this.get(parent)) this.throw();
51
-
52
- const traverse = (e) => {
53
- return Array.from(e.entries()).reduce((prev, [key, value]) => {
54
- const descendants = this.descendants(key);
55
- if (descendants.indexOf(parent) > -1) prev.push(key);
56
- return prev.concat(traverse(value));
57
- }, []);
58
- };
59
-
60
- return traverse(this.map);
61
- }
62
-
63
- descendants(parent) {
64
- const [, map] = this.get(parent) || this.throw();
65
- return this.elements(map);
66
- }
67
-
68
- siblings(parent) {
69
- const [map] = this.get(parent) || this.throw();
70
- if (map === this.map) return [];
71
- return Array.from(map.keys()).filter(node => node !== parent);
72
- }
73
-
74
- lineage(parent) {
75
- const root = this.root(parent);
76
- return [root].concat(this.descendants(root));
77
- }
78
- };
package/src/data/Type.js DELETED
@@ -1,50 +0,0 @@
1
- const Type = require('../graphql/ast/Type');
2
- const Pipeline = require('./Pipeline');
3
-
4
- module.exports = class extends Type {
5
- constructor(field) {
6
- super(field.getAST());
7
- this.field = field;
8
- }
9
-
10
- getStructures() {
11
- const type = this.field.getType();
12
- const enumType = this.field.getEnumRef();
13
- const scalarType = this.field.getScalarRef();
14
- const structures = {
15
- validators: [],
16
- instructs: [],
17
- restructs: [],
18
- destructs: [],
19
- constructs: [],
20
- normalizers: [],
21
- $serializers: [],
22
- $deserializers: [],
23
- serializers: [],
24
- deserializers: [],
25
- transforms: [],
26
- };
27
-
28
- // Built-in pipelines
29
- structures.castValue = Pipeline.castValue;
30
- structures.defaultValue = Pipeline.defaultValue;
31
- structures.ensureArrayValue = Pipeline.ensureArrayValue;
32
-
33
- if (enumType) structures.validators.push(Pipeline.define(`allow${type}`, Pipeline.Allow(...enumType.getValue()), { configurable: true }));
34
- if (!scalarType) return structures;
35
-
36
- return Object.entries(scalarType.getDirectiveArgs('field', {})).reduce((prev, [key, value]) => {
37
- if (!Array.isArray(value)) value = [value];
38
- if (key === 'validate') prev.validators.push(...value.map(t => Pipeline[t]));
39
- if (key === 'instruct') prev.instructs.push(...value.map(t => Pipeline[t]));
40
- if (key === 'restruct') prev.restructs.push(...value.map(t => Pipeline[t]));
41
- if (key === 'destruct') prev.destructs.push(...value.map(t => Pipeline[t]));
42
- if (key === 'construct') prev.constructs.push(...value.map(t => Pipeline[t]));
43
- if (key === 'transform') prev.transforms.push(...value.map(t => Pipeline[t]));
44
- if (key === 'normalize') prev.normalizers.push(...value.map(t => Pipeline[t]));
45
- if (key === 'serialize') prev.serializers.push(...value.map(t => Pipeline[t]));
46
- if (key === 'deserialize') prev.deserializers.push(...value.map(t => Pipeline[t]));
47
- return prev;
48
- }, structures);
49
- }
50
- };
Binary file
@@ -1,227 +0,0 @@
1
- const Util = require('util');
2
- const { get } = require('lodash');
3
- const { unflatten } = require('@coderich/util');
4
- const { MongoClient, ObjectId } = require('mongodb');
5
- const { map, ensureArray, proxyDeep, toKeyObj, globToRegex, proxyPromise, isScalarDataType, promiseRetry } = require('../service/app.service');
6
-
7
- module.exports = class MongoDriver {
8
- constructor(config) {
9
- this.config = config;
10
- this.config.query = this.config.query || {};
11
- this.connection = this.connect();
12
- this.getDirectives = () => get(config, 'directives', {});
13
- }
14
-
15
- connect() {
16
- const { uri, options = {} } = this.config;
17
- options.ignoreUndefined = false;
18
- return MongoClient.connect(uri, options);
19
- }
20
-
21
- disconnect() {
22
- return this.connection.then(client => client.close());
23
- }
24
-
25
- raw(collection) {
26
- return proxyPromise(this.connection.then(client => client.db().collection(collection)));
27
- }
28
-
29
- query(collection, method, ...args) {
30
- if (get(args[args.length - 1], 'debug') === true) console.log(collection, method, Util.inspect(args, { depth: null, showHidden: false, colors: true }));
31
- if (method === 'aggregate') args.splice(2);
32
- return this.raw(collection)[method](...args);
33
- }
34
-
35
- resolve(query) {
36
- const { isNative } = query;
37
- if (!isNative) query.where = MongoDriver.normalizeWhere(query.where);
38
- return this[query.method](query);
39
- }
40
-
41
- findOne(query) {
42
- return this.findMany(Object.assign(query, { first: 1 })).then((stream) => {
43
- return new Promise((resolve, reject) => {
44
- stream.on('data', resolve);
45
- stream.on('error', reject);
46
- stream.on('end', resolve);
47
- });
48
- });
49
- }
50
-
51
- findMany(query) {
52
- const { model, options = {}, flags } = query;
53
- const $options = { ...this.config.query, ...options };
54
- return this.query(model, 'aggregate', MongoDriver.aggregateQuery(query), $options, flags).then(cursor => cursor.stream());
55
- }
56
-
57
- count(query) {
58
- const { model, options = {}, flags } = query;
59
- const $options = { ...this.config.query, ...options };
60
-
61
- return this.query(model, 'aggregate', MongoDriver.aggregateQuery(query, true), $options, flags).then((cursor) => {
62
- return cursor.next().then((doc) => {
63
- return doc ? doc.count : 0;
64
- });
65
- });
66
- }
67
-
68
- createOne({ model, input, options, flags }) {
69
- // console.log(JSON.stringify(input, null, 2));
70
- return this.query(model, 'insertOne', input, options, flags).then(result => Object.assign(input, { id: result.insertedId }));
71
- }
72
-
73
- updateOne({ model, where, $doc, options, flags }) {
74
- const $update = { $set: $doc };
75
- return this.query(model, 'updateOne', where, $update, options, flags).then(() => unflatten($doc, { safe: true }));
76
- }
77
-
78
- deleteOne({ model, where, options, flags }) {
79
- return this.query(model, 'deleteOne', where, options, flags).then(() => true);
80
- }
81
-
82
- dropModel(model) {
83
- return this.query(model, 'deleteMany');
84
- }
85
-
86
- createCollection(model) {
87
- return this.connection.then(client => client.db().createCollection(model)).catch(e => null);
88
- }
89
-
90
- createIndexes(model, indexes) {
91
- return Promise.all(indexes.map(({ name, type, on }) => {
92
- const $fields = on.reduce((prev, field) => Object.assign(prev, { [field]: 1 }), {});
93
-
94
- switch (type) {
95
- case 'unique': return this.query(model, 'createIndex', $fields, { name, unique: true });
96
- default: return null;
97
- }
98
- }));
99
- }
100
-
101
- transaction(ops) {
102
- return promiseRetry(() => {
103
- // Create session and start transaction
104
- return this.connection.then(client => client.startSession({ readPreference: { mode: 'primary' } })).then((session) => {
105
- session.startTransaction({ readConcern: { level: 'snapshot' }, writeConcern: { w: 'majority' } });
106
- const close = () => { session.endSession(); };
107
-
108
- // Execute each operation with session
109
- return Promise.all(ops.map(op => op.exec({ session }))).then((results) => {
110
- results.$commit = () => session.commitTransaction().then(close);
111
- results.$rollback = () => session.abortTransaction().then(close);
112
- return results;
113
- }).catch((e) => {
114
- close();
115
- throw e;
116
- });
117
- });
118
- }, 200, 5, e => e.errorLabels && e.errorLabels.indexOf('TransientTransactionError') > -1);
119
- }
120
-
121
- static idKey() {
122
- return '_id';
123
- }
124
-
125
- static idValue(value) {
126
- if (value instanceof ObjectId) return value;
127
-
128
- try {
129
- const id = ObjectId(value);
130
- return id;
131
- } catch (e) {
132
- return value;
133
- }
134
- }
135
-
136
- static normalizeWhere(where) {
137
- return proxyDeep(toKeyObj(where), {
138
- get(target, prop, rec) {
139
- const value = Reflect.get(target, prop, rec);
140
- if (typeof value === 'function') return value.bind(target);
141
- const $value = map(value, v => (typeof v === 'string' ? globToRegex(v, { nocase: true, regex: true }) : v));
142
- if (Array.isArray($value)) {
143
- // console.log(Util.inspect({ value, $value }, { depth: null, showHidden: false, colors: true }));
144
- return { $in: $value };
145
- }
146
- return $value;
147
- },
148
- }).toObject();
149
- }
150
-
151
- static getAddFields(query) {
152
- const { shape, where } = query;
153
-
154
- return shape.reduce((prev, { from, type, isArray }) => {
155
- // Basic checks to see if worth converting for regex
156
- let value = where[from];
157
- if (value === undefined) return prev;
158
- if (!isScalarDataType(type)) return prev;
159
-
160
- // Do regex conversion
161
- if (isArray) value = value.$in || value; // Where clause does not always use $in
162
- if (!ensureArray(value).some(el => el instanceof RegExp)) return prev;
163
- const conversion = isArray ? { $map: { input: `$${from}`, as: 'el', in: { $toString: '$$el' } } } : { $toString: `$${from}` };
164
- return Object.assign(prev, { [from]: conversion });
165
- }, {});
166
- }
167
-
168
- static getProjectFields(parentShape, currentShape = { _id: 0, id: '$_id' }, isEmbedded, isEmbeddedArray, path = []) {
169
- return parentShape.reduce((project, value) => {
170
- const { from, to, shape: subShape, isArray } = value;
171
- const $key = isEmbedded && isEmbeddedArray ? `$$embedded.${from}` : `$${path.concat(from).join('.')}`;
172
-
173
- if (subShape) {
174
- const $project = MongoDriver.getProjectFields(subShape, {}, true, isArray, path.concat(from));
175
- Object.assign(project, { [to]: isArray ? { $map: { input: $key, as: 'embedded', in: $project } } : $project });
176
- } else if (isEmbedded) {
177
- Object.assign(project, { [to]: $key });
178
- } else {
179
- Object.assign(project, { [to]: from === to ? 1 : $key });
180
- }
181
-
182
- return project;
183
- }, currentShape);
184
- }
185
-
186
- static aggregateQuery(query, count = false) {
187
- const { where: $match, sort = {}, skip, limit, joins, after, before, first } = query;
188
- const $aggregate = [{ $match }];
189
-
190
- // Used for $regex matching
191
- const $addFields = MongoDriver.getAddFields(query);
192
- if (Object.keys($addFields).length) $aggregate.unshift({ $addFields });
193
-
194
- if (count) {
195
- $aggregate.push({ $count: 'count' });
196
- } else {
197
- // This is needed to return FK references as an array in the correct order
198
- // http://www.kamsky.org/stupid-tricks-with-mongodb/using-34-aggregation-to-return-documents-in-same-order-as-in-expression
199
- // https://jira.mongodb.org/browse/SERVER-7528
200
- const idKey = MongoDriver.idKey();
201
- const idMatch = $match[idKey];
202
- if (typeof idMatch === 'object' && idMatch.$in) {
203
- $aggregate.push({ $addFields: { __order: { $indexOfArray: [idMatch.$in, `$${idKey}`] } } });
204
- $aggregate.push({ $sort: { __order: 1 } });
205
- }
206
-
207
- // Joins
208
- if (joins) $aggregate.push(...joins.map(({ to: from, by: foreignField, from: localField, as }) => ({ $lookup: { from, foreignField, localField, as } })));
209
-
210
- // Sort, Skip, Limit documents
211
- if (sort && Object.keys(sort).length) $aggregate.push({ $sort: toKeyObj(sort) });
212
- if (skip) $aggregate.push({ $skip: skip });
213
- if (limit) $aggregate.push({ $limit: limit });
214
-
215
- // Pagination
216
- if (after) $aggregate.push({ $match: { $or: Object.entries(after).reduce((prev, [key, value]) => prev.concat({ [key]: { [sort[key] === 1 ? '$gte' : '$lte']: value } }), []) } });
217
- if (before) $aggregate.push({ $match: { $or: Object.entries(before).reduce((prev, [key, value]) => prev.concat({ [key]: { [sort[key] === 1 ? '$lte' : '$gte']: value } }), []) } });
218
- if (first) $aggregate.push({ $limit: first });
219
-
220
- // // Projection
221
- // const $project = MongoDriver.getProjectFields(shape);
222
- // $aggregate.push({ $project });
223
- }
224
-
225
- return $aggregate;
226
- }
227
- };
@@ -1,11 +0,0 @@
1
- /* eslint-disable global-require */
2
- module.exports = class Driver {
3
- constructor(name) {
4
- switch (name) {
5
- case 'Mongo': return require('./MongoDriver');
6
- // case 'Neo4jDriver': return require('./Neo4jDriver').Neo4jDriver;
7
- // case 'Neo4jRestDriver': return require('./Neo4jDriver').Neo4jRestDriver;
8
- default: throw new Error(`Unknown driver ${name}`);
9
- }
10
- }
11
- };
Binary file
Binary file