@coderich/autograph 0.12.0 → 0.13.1

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 +84 -101
  7. package/src/data/Resolver.js +304 -0
  8. package/src/data/Transaction.js +49 -0
  9. package/src/query/Query.js +159 -335
  10. package/src/query/QueryBuilder.js +228 -114
  11. package/src/query/QueryResolver.js +110 -205
  12. package/src/query/QueryResolverTransaction.js +16 -0
  13. package/src/schema/Schema.js +602 -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 -161
  29. package/src/data/Field.js +0 -83
  30. package/src/data/Model.js +0 -214
  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,214 +0,0 @@
1
- const Stream = require('stream');
2
- const Field = require('./Field');
3
- const Model = require('../graphql/ast/Model');
4
- const { eventEmitter } = require('../service/event.service');
5
- const { finalizeResults } = require('./DataService');
6
- const { map, mapPromise, seek, deseek } = require('../service/app.service');
7
-
8
- module.exports = class extends Model {
9
- constructor(schema, model, driver) {
10
- super(schema, JSON.parse(JSON.stringify((model.getAST()))));
11
- this.driver = driver;
12
- this.fields = super.getFields().map(field => new Field(this, field));
13
- this.namedQueries = {};
14
- this.shapesCache = new Map();
15
- }
16
-
17
- raw() {
18
- return this.driver.dao.raw(this.getKey());
19
- }
20
-
21
- drop() {
22
- return this.driver.dao.dropModel(this.getKey());
23
- }
24
-
25
- idValue(id) {
26
- return this.driver.idValue(id);
27
- }
28
-
29
- idKey() {
30
- return this.getDirectiveArg('model', 'id', this.driver.idKey());
31
- }
32
-
33
- getDriver() {
34
- return this.driver.dao;
35
- }
36
-
37
- createNamedQuery(name, fn) {
38
- this.namedQueries[name] = fn;
39
- }
40
-
41
- getNamedQueries(name) {
42
- return this.namedQueries;
43
- }
44
-
45
- referentialIntegrity(refs) {
46
- if (refs) this.referentials = refs;
47
- return this.referentials;
48
- }
49
-
50
- /**
51
- * Convenience method to deserialize data from a data source (such as a database)
52
- */
53
- deserialize(mixed, query) {
54
- const shape = this.getShape();
55
-
56
- return new Promise((resolve, reject) => {
57
- if (!(mixed instanceof Stream)) {
58
- resolve(this.shapeObject(shape, mixed, query));
59
- } else {
60
- const results = [];
61
- mixed.on('data', (data) => { results.push(this.shapeObject(shape, data, query)); });
62
- mixed.on('end', () => { resolve(results); });
63
- mixed.on('error', reject);
64
- }
65
- }).then(rs => finalizeResults(rs, query));
66
- }
67
-
68
- getShape(crud = 'read', target = 'doc', paths = [], depth = 0) {
69
- if (depth++ > 10) return {}; // Prevent infinite circular references
70
-
71
- // Cache check
72
- const cacheKey = `${crud}:${target}`;
73
- if (this.shapesCache.has(cacheKey)) return this.shapesCache.get(cacheKey);
74
-
75
- const serdes = crud === 'read' ? 'deserialize' : 'serialize';
76
- const fields = serdes === 'deserialize' ? this.getSelectFields() : this.getPersistableFields();
77
- const crudMap = { create: ['constructs'], update: ['restructs'], delete: ['destructs'], remove: ['destructs'] };
78
- const sortKeys = ['isIdField', 'isBasicType', 'isEmbedded'];
79
- const crudKeys = crudMap[crud] || [];
80
-
81
- // Define target mapping
82
- const targetMap = {
83
- doc: ['defaultValue', 'castValue', 'ensureArrayValue', 'normalizers', 'instructs', ...crudKeys, `$${serdes}rs`, `${serdes}rs`, 'transforms'],
84
- input: ['defaultValue', 'castValue', 'ensureArrayValue', 'normalizers', 'instructs', ...crudKeys, `$${serdes}rs`, `${serdes}rs`, 'transforms'],
85
- // input: ['defaultValue', 'castValue', 'ensureArrayValue'],
86
- where: ['castValue', 'instructs', `$${serdes}rs`],
87
- };
88
-
89
- const structureKeys = targetMap[target] || ['castValue'];
90
-
91
- // Create sorted shape, recursive
92
- const shape = fields.sort((a, b) => {
93
- const aObject = a.toObject();
94
- const bObject = b.toObject();
95
-
96
- // PK first
97
- if (aObject.isPrimaryKeyId) return -1;
98
- if (bObject.isPrimaryKeyId) return 1;
99
-
100
- // Arrays last
101
- if (aObject.isArray && !bObject.isArray) return 1;
102
- if (bObject.isArray && !aObject.isArray) return -1;
103
-
104
- // Now, follow sort keys
105
- const aNum = sortKeys.findIndex(key => aObject[key]);
106
- const bNum = sortKeys.findIndex(key => bObject[key]);
107
- if (aNum < bNum) return -1;
108
- if (aNum > bNum) return 1;
109
- return 0;
110
- }).map((field) => {
111
- let instructed = false;
112
- const structures = field.getStructures();
113
- const { key, name, type, isArray, isEmbedded, modelRef } = field.toObject();
114
- const [from, to] = serdes === 'serialize' ? [name, key] : [key, name];
115
- const actualTo = target === 'input' || target === 'splice' ? from : to;
116
- const path = paths.concat(actualTo);
117
- const subCrud = crud === 'update' && isArray ? 'create' : crud; // Due to limitation to update embedded array
118
- const subShape = isEmbedded ? modelRef.getShape(subCrud, target, path, depth) : null;
119
- const transformers = structureKeys.reduce((prev, struct) => {
120
- const structs = structures[struct];
121
- if (struct === 'instructs' && structs.length) instructed = true;
122
- return prev.concat(structs);
123
- }, []).filter(Boolean);
124
- return { instructed, field, path, from, to: actualTo, type, isArray, transformers, validators: structures.validators, shape: subShape };
125
- });
126
-
127
- // Adding useful shape info
128
- shape.crud = crud;
129
- shape.model = this;
130
- shape.serdes = serdes;
131
- shape.target = target;
132
-
133
- // Cache and return
134
- this.shapesCache.set(cacheKey, shape);
135
- return shape;
136
- }
137
-
138
- shapeObject(shape, obj, query, root, base) {
139
- const { serdes, model } = shape;
140
- const { context, resolver, doc = {}, flags = {} } = query.toObject();
141
- const { pipeline } = flags;
142
-
143
- if (!pipeline) return obj;
144
- // const filters = pipeline === true ? [] : Object.entries(pipeline).map(([k, v]) => (v === false ? k : null)).filter(Boolean);
145
-
146
- // base is the base model
147
- base = base || model;
148
-
149
- return map(obj, (parent) => {
150
- // root is the base data object
151
- root = root || parent;
152
-
153
- // Lookup helper functions
154
- const docPath = (p, hint) => seek(doc, p, hint); // doc is already serialized; so always a seek
155
- const rootPath = (p, hint) => (serdes === 'serialize' ? seek(root, p, hint) : deseek(shape, root, p, hint));
156
- const parentPath = (p, hint) => (serdes === 'serialize' ? seek(parent, p, hint) : deseek(shape, parent, p, hint));
157
-
158
- return shape.reduce((prev, { instructed, field, from, to, path, type, isArray, defaultValue, transformers = [], shape: subShape }) => {
159
- const startValue = parent[from];
160
- // transformers = filters.length ? transformers.filter() : transformers;
161
-
162
- // Transform value
163
- const transformedValue = transformers.reduce((value, t) => {
164
- const v = t({ base, model, field, path, docPath, rootPath, parentPath, startValue, value, resolver, context });
165
- return v === undefined ? value : v;
166
- }, startValue);
167
-
168
- // Determine if key should stay or be removed
169
- if (!instructed && transformedValue === undefined && !Object.prototype.hasOwnProperty.call(parent, from)) return prev;
170
- if (!instructed && subShape && typeof transformedValue !== 'object') return prev;
171
-
172
- // Rename key & assign value
173
- prev[to] = (!subShape || transformedValue == null) ? transformedValue : this.shapeObject(subShape, transformedValue, query, root, base);
174
-
175
- return prev;
176
- }, {});
177
- });
178
- }
179
-
180
- validateObject(shape, obj, query, root, base, silent = false) {
181
- const { model } = shape;
182
- const { context, resolver, doc = {}, flags = {} } = query.toObject();
183
- const { validate = true } = flags;
184
-
185
- if (!validate) return Promise.resolve();
186
-
187
- // base is the base model
188
- base = base || model;
189
-
190
- return mapPromise(obj, (parent) => {
191
- // root is the base data object
192
- root = root || parent;
193
-
194
- // Lookup helper functions
195
- const docPath = (p, hint) => seek(doc, p, hint);
196
- const rootPath = (p, hint) => seek(root, p, hint);
197
- const parentPath = (p, hint) => seek(parent, p, hint);
198
-
199
- return Promise.all(shape.map(({ field, from, path, validators, shape: subShape }) => {
200
- const value = parent[from]; // It hasn't been shaped yet
201
-
202
- return Promise.all(validators.map((v) => {
203
- return new Promise((resolve, reject) => {
204
- return Promise.resolve(v({ base, model, field, path, docPath, rootPath, parentPath, startValue: value, value, resolver, context })).then(resolve).catch(reject);
205
- });
206
- })).then(() => {
207
- return subShape ? this.validateObject(subShape, value, query, root, base, true) : Promise.resolve();
208
- });
209
- }));
210
- }).then(() => {
211
- return silent ? Promise.resolve() : eventEmitter.emit('validate', query.toObject());
212
- });
213
- }
214
- };
@@ -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 Flat = require('flat');
3
- const { get } = require('lodash');
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.connection = this.connect();
11
- this.getDirectives = () => get(config, 'directives', {});
12
- }
13
-
14
- connect() {
15
- const { uri, options = {} } = this.config;
16
- options.ignoreUndefined = false;
17
- return MongoClient.connect(uri, options);
18
- }
19
-
20
- disconnect() {
21
- return this.connection.then(client => client.close());
22
- }
23
-
24
- raw(collection) {
25
- return proxyPromise(this.connection.then(client => client.db().collection(collection)));
26
- }
27
-
28
- query(collection, method, ...args) {
29
- if (get(args[args.length - 1], 'debug') === true) console.log(collection, method, Util.inspect(args, { depth: null, showHidden: false, colors: true }));
30
- if (method === 'aggregate') args.splice(2);
31
- return this.raw(collection)[method](...args);
32
- }
33
-
34
- resolve(query) {
35
- const { isNative } = query;
36
- if (!isNative) query.where = MongoDriver.normalizeWhere(query.where);
37
- return this[query.method](query);
38
- }
39
-
40
- findOne(query) {
41
- return this.findMany(Object.assign(query, { first: 1 })).then((stream) => {
42
- return new Promise((resolve, reject) => {
43
- stream.on('data', resolve);
44
- stream.on('error', reject);
45
- stream.on('end', resolve);
46
- });
47
- });
48
- }
49
-
50
- findMany(query) {
51
- const { model, options = {}, flags } = query;
52
- Object.assign(options, this.config.query || {});
53
- return this.query(model, 'aggregate', MongoDriver.aggregateQuery(query), options, flags).then(cursor => cursor.stream());
54
- }
55
-
56
- count(query) {
57
- const { model, options = {}, flags } = query;
58
- Object.assign(options, this.config.query || {});
59
-
60
- return this.query(model, 'aggregate', MongoDriver.aggregateQuery(query, true), options, flags).then((cursor) => {
61
- return cursor.next().then((doc) => {
62
- return doc ? doc.count : 0;
63
- });
64
- });
65
- }
66
-
67
- createOne({ model, input, options, flags }) {
68
- return this.query(model, 'insertOne', input, options, flags).then(result => Object.assign(input, { id: result.insertedId }));
69
- }
70
-
71
- updateOne({ model, where, $doc, options, flags }) {
72
- const $update = { $set: Flat.flatten($doc, { safe: true }) };
73
- return this.query(model, 'updateOne', where, $update, options, flags).then(() => $doc);
74
- }
75
-
76
- deleteOne({ model, where, options, flags }) {
77
- return this.query(model, 'deleteOne', where, options, flags).then(() => true);
78
- }
79
-
80
- dropModel(model) {
81
- return this.query(model, 'deleteMany');
82
- }
83
-
84
- createCollection(model) {
85
- return this.connection.then(client => client.db().createCollection(model)).catch(e => null);
86
- }
87
-
88
- createIndexes(model, indexes) {
89
- return Promise.all(indexes.map(({ name, type, on }) => {
90
- const $fields = on.reduce((prev, field) => Object.assign(prev, { [field]: 1 }), {});
91
-
92
- switch (type) {
93
- case 'unique': return this.query(model, 'createIndex', $fields, { name, unique: true });
94
- default: return null;
95
- }
96
- }));
97
- }
98
-
99
- transaction(ops) {
100
- const promise = async () => {
101
- // Create session and start transaction
102
- const session = await this.connection.then(client => client.startSession({ readPreference: { mode: 'primary' } }));
103
- session.startTransaction({ readConcern: { level: 'snapshot' }, writeConcern: { w: 'majority' } });
104
- const close = () => { session.endSession(); };
105
-
106
- // Execute each operation with session
107
- return Promise.all(ops.map(op => op.exec({ session }))).then((results) => {
108
- results.$commit = () => session.commitTransaction().then(close);
109
- results.$rollback = () => session.abortTransaction().then(close);
110
- return results;
111
- }).catch((e) => {
112
- close();
113
- throw e;
114
- });
115
- };
116
-
117
- // Retry promise conditionally
118
- return promiseRetry(promise, 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