@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.
- package/index.js +4 -6
- package/package.json +30 -44
- package/src/data/DataLoader.js +77 -70
- package/src/data/Emitter.js +89 -0
- package/src/data/Loader.js +33 -0
- package/src/data/Pipeline.js +88 -96
- package/src/data/Resolver.js +304 -0
- package/src/data/Transaction.js +49 -0
- package/src/query/Query.js +159 -334
- package/src/query/QueryBuilder.js +228 -114
- package/src/query/QueryResolver.js +110 -216
- package/src/query/QueryResolverTransaction.js +16 -0
- package/src/schema/Schema.js +593 -0
- package/src/service/AppService.js +38 -0
- package/src/service/ErrorService.js +7 -0
- package/CHANGELOG.md +0 -41
- package/LICENSE +0 -21
- package/README.md +0 -76
- package/src/.DS_Store +0 -0
- package/src/core/.DS_Store +0 -0
- package/src/core/Boom.js +0 -9
- package/src/core/EventEmitter.js +0 -95
- package/src/core/Resolver.js +0 -124
- package/src/core/Schema.js +0 -55
- package/src/core/ServerResolver.js +0 -15
- package/src/data/.DS_Store +0 -0
- package/src/data/DataService.js +0 -120
- package/src/data/DataTransaction.js +0 -96
- package/src/data/Field.js +0 -83
- package/src/data/Model.js +0 -223
- package/src/data/TreeMap.js +0 -78
- package/src/data/Type.js +0 -50
- package/src/driver/.DS_Store +0 -0
- package/src/driver/MongoDriver.js +0 -227
- package/src/driver/index.js +0 -11
- package/src/graphql/.DS_Store +0 -0
- package/src/graphql/ast/.DS_Store +0 -0
- package/src/graphql/ast/Field.js +0 -206
- package/src/graphql/ast/Model.js +0 -145
- package/src/graphql/ast/Node.js +0 -291
- package/src/graphql/ast/Schema.js +0 -133
- package/src/graphql/ast/Type.js +0 -26
- package/src/graphql/ast/TypeDefApi.js +0 -93
- package/src/graphql/extension/.DS_Store +0 -0
- package/src/graphql/extension/api.js +0 -193
- package/src/graphql/extension/framework.js +0 -71
- package/src/graphql/extension/type.js +0 -34
- package/src/query/.DS_Store +0 -0
- package/src/query/QueryBuilderTransaction.js +0 -26
- package/src/query/QueryService.js +0 -111
- package/src/service/.DS_Store +0 -0
- package/src/service/app.service.js +0 -319
- package/src/service/decorator.service.js +0 -114
- package/src/service/event.service.js +0 -66
- package/src/service/graphql.service.js +0 -92
- package/src/service/schema.service.js +0 -95
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2020 coderich
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
package/README.md
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
# AutoGraph
|
|
2
|
-
### Instantly Build [Relay-Compliant](https://relay.dev/docs/en/graphql-server-specification.html) GraphQL APIs
|
|
3
|
-
|
|
4
|
-
**AutoGraph** is a powerful **framework** to *instantly* build **Relay-Compliant GraphQL APIs** that adhere to *standards and best practice*. It provides a robust set of **directives** that encapsulates *data access*, *validation*, *transformations*, and so much more!
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
#### Features
|
|
8
|
-
- Instantly Connected Relay-Compliant GraphQL APIs
|
|
9
|
-
- Extensible Validation & Transformation Logic
|
|
10
|
-
- Unified Data Access / Abstraction Layer
|
|
11
|
-
- Memoized Caching (via [DataLoader](https://www.npmjs.com/package/dataloader))
|
|
12
|
-
- Transactions, Pagination, and more!
|
|
13
|
-
|
|
14
|
-
#### Installation
|
|
15
|
-
|
|
16
|
-
```sh
|
|
17
|
-
npm i @coderich/autograph --save
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
## Schema API
|
|
21
|
-
#### Custom Types
|
|
22
|
-
```gql
|
|
23
|
-
scalar AutoGraphMixed
|
|
24
|
-
enum AutoGraphScopeEnum { private protected public restricted }
|
|
25
|
-
enum AutoGraphOnDeleteEnum { cascade nullify restrict }
|
|
26
|
-
enum AutoGraphIndexEnum { unique }
|
|
27
|
-
```
|
|
28
|
-
#### Custom Directives
|
|
29
|
-
##### @model
|
|
30
|
-
| arg | type | description |
|
|
31
|
-
| :--- | :--- | :--- |
|
|
32
|
-
| `id` | `String` | Specify database id field name (default: `id`)
|
|
33
|
-
| `meta` | `String` | Define a model
|
|
34
|
-
| `alias` | `String` | Specify database table name (default models's name)
|
|
35
|
-
| `scope` | `AutoGraphScopeEnum` | Access scope for this model (default `protected`)
|
|
36
|
-
| `driver` | `String` | Specify database driver (default `default`)
|
|
37
|
-
| `namespace` | `String` | Define a custom namespace
|
|
38
|
-
| `createdAt` | `String` | TBD
|
|
39
|
-
| `updatedAt` | `String` | TBD
|
|
40
|
-
|
|
41
|
-
##### @field
|
|
42
|
-
| arg | type | description |
|
|
43
|
-
| :--- | :--- | :--- |
|
|
44
|
-
| `alias` | `String` | Specify database field name (default field's name)
|
|
45
|
-
| `scope` | `AutoGraphScopeEnum` | Access scope for this field (default `protected`)
|
|
46
|
-
| `enforce` | `[AutoGraphEnforceEnum!]` | List of `Rules` to enforce
|
|
47
|
-
| `noRepeat` | `Boolean` | TBD
|
|
48
|
-
| `onDelete` | `AutoGraphOnDeleteEnum` | Specify *onDelete* behavior
|
|
49
|
-
| `transform` | `[AutoGraphTransformEnum!]` | List of `Transformers` to apply
|
|
50
|
-
| `materializeBy` | `String` | Define a virtual field
|
|
51
|
-
|
|
52
|
-
##### @index
|
|
53
|
-
| arg | type | description |
|
|
54
|
-
| :--- | :--- | :--- |
|
|
55
|
-
| `on` | `[String!]!` | The field names to use for this index
|
|
56
|
-
| `type` | `AutoGraphIndexEnum!` | The type of index to create
|
|
57
|
-
| `name` | `String` | The name of the index
|
|
58
|
-
|
|
59
|
-
## Data API
|
|
60
|
-
#### Data Access
|
|
61
|
-
#### Data Validation
|
|
62
|
-
#### Data Transformation
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
Each `Resolver` treats your schema definition as a *graph of connected nodes*. To begin a *query* or *mutation*, you must first identify a node in the graph as your starting point.
|
|
66
|
-
|
|
67
|
-
##### .match
|
|
68
|
-
Identify a node, returns a `QueryBuilder`.
|
|
69
|
-
```
|
|
70
|
-
const queryBuilder = resolver.match('Person');
|
|
71
|
-
```
|
|
72
|
-
##### .transaction
|
|
73
|
-
Identify a node, returns a `Transaction`.
|
|
74
|
-
```
|
|
75
|
-
const txn = resolver.transaction('Person');
|
|
76
|
-
```
|
package/src/.DS_Store
DELETED
|
Binary file
|
package/src/core/.DS_Store
DELETED
|
Binary file
|
package/src/core/Boom.js
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
const Boom = require('@hapi/boom');
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Boom.
|
|
5
|
-
*
|
|
6
|
-
* Standardizing how known/expected errors are thrown (such as Rule validation errors). This allows
|
|
7
|
-
* upstream applications to more easily decide what to show to end-users and what to hide.
|
|
8
|
-
*/
|
|
9
|
-
module.exports = Boom;
|
package/src/core/EventEmitter.js
DELETED
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
const EventEmitter = require('events');
|
|
2
|
-
const { ensureArray } = require('../service/app.service');
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* EventEmitter.
|
|
6
|
-
*
|
|
7
|
-
* The difference is that I'm looking at each raw listeners to determine how many arguments it's expecting.
|
|
8
|
-
* If it expects more than 1 we block and wait for it to finish.
|
|
9
|
-
*/
|
|
10
|
-
module.exports = class extends EventEmitter {
|
|
11
|
-
emit(event, data) {
|
|
12
|
-
return Promise.all(this.rawListeners(event).map((wrapper) => {
|
|
13
|
-
return new Promise((resolve, reject) => {
|
|
14
|
-
const next = result => resolve(result); // If a result is passed this will bypass middleware thunk()
|
|
15
|
-
const numArgs = (wrapper.listener || wrapper).length;
|
|
16
|
-
Promise.resolve(wrapper(data, next)).catch(e => reject(e));
|
|
17
|
-
if (numArgs < 2) next();
|
|
18
|
-
});
|
|
19
|
-
})).then((results) => {
|
|
20
|
-
return results.find(r => r !== undefined); // There can be only one (result)
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Syntactic sugar to listen on keys
|
|
26
|
-
*/
|
|
27
|
-
onKeys(on, keys, fn) {
|
|
28
|
-
const numArgs = fn.length;
|
|
29
|
-
|
|
30
|
-
return super.on(on, async (event, next) => {
|
|
31
|
-
const { key } = event;
|
|
32
|
-
|
|
33
|
-
if (ensureArray(keys).indexOf(key) > -1) {
|
|
34
|
-
if (numArgs < 2) next();
|
|
35
|
-
await fn(event, next);
|
|
36
|
-
} else {
|
|
37
|
-
next();
|
|
38
|
-
}
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Syntactic sugar to listen once keys
|
|
44
|
-
*/
|
|
45
|
-
onceKeys(once, keys, fn) {
|
|
46
|
-
const numArgs = fn.length;
|
|
47
|
-
|
|
48
|
-
return super.once(once, async (event, next) => {
|
|
49
|
-
const { key } = event;
|
|
50
|
-
|
|
51
|
-
if (ensureArray(keys).indexOf(key) > -1) {
|
|
52
|
-
if (numArgs < 2) next();
|
|
53
|
-
await fn(event, next);
|
|
54
|
-
} else {
|
|
55
|
-
next();
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Syntactic sugar to listen on models
|
|
62
|
-
*/
|
|
63
|
-
onModels(on, models, fn) {
|
|
64
|
-
const numArgs = fn.length;
|
|
65
|
-
|
|
66
|
-
return super.on(on, async (event, next) => {
|
|
67
|
-
const { model } = event;
|
|
68
|
-
|
|
69
|
-
if (ensureArray(models).indexOf(`${model}`) > -1) {
|
|
70
|
-
if (numArgs < 2) next();
|
|
71
|
-
await fn(event, next);
|
|
72
|
-
} else {
|
|
73
|
-
next();
|
|
74
|
-
}
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Syntactic sugar to listen once models
|
|
80
|
-
*/
|
|
81
|
-
onceModels(once, models, fn) {
|
|
82
|
-
const numArgs = fn.length;
|
|
83
|
-
|
|
84
|
-
return super.once(once, async (event, next) => {
|
|
85
|
-
const { model } = event;
|
|
86
|
-
|
|
87
|
-
if (ensureArray(models).indexOf(`${model}`) > -1) {
|
|
88
|
-
if (numArgs < 2) next();
|
|
89
|
-
await fn(event, next);
|
|
90
|
-
} else {
|
|
91
|
-
next();
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
};
|
package/src/core/Resolver.js
DELETED
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
const Model = require('../data/Model');
|
|
2
|
-
const DataLoader = require('../data/DataLoader');
|
|
3
|
-
const DataTransaction = require('../data/DataTransaction');
|
|
4
|
-
const Query = require('../query/Query');
|
|
5
|
-
const QueryBuilder = require('../query/QueryBuilder');
|
|
6
|
-
const { finalizeResults } = require('../data/DataService');
|
|
7
|
-
const { createSystemEvent } = require('../service/event.service');
|
|
8
|
-
|
|
9
|
-
module.exports = class Resolver {
|
|
10
|
-
constructor(schema, context = {}) {
|
|
11
|
-
this.schema = schema;
|
|
12
|
-
this.context = context;
|
|
13
|
-
this.models = schema.getModels();
|
|
14
|
-
this.loaders = this.models.reduce((prev, model) => prev.set(`${model}`, new DataLoader(this, model)), new Map());
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
getSchema() {
|
|
18
|
-
return this.schema;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
getContext() {
|
|
22
|
-
return this.context;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Creates and returns a QueryBuilder for a given model
|
|
27
|
-
*/
|
|
28
|
-
match(model) {
|
|
29
|
-
return new QueryBuilder(this, this.toModelEntity(model));
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Returns a user-defined Map (repository) of custom named queries.
|
|
34
|
-
*/
|
|
35
|
-
named(model) {
|
|
36
|
-
return this.toModel(model).getNamedQueries();
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Returns the raw client driver associated with the model.
|
|
41
|
-
*/
|
|
42
|
-
raw(model) {
|
|
43
|
-
return this.toModelEntity(model).raw();
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Creates and returns a Transaction to run multiple queries
|
|
48
|
-
*/
|
|
49
|
-
transaction(parentTxn) {
|
|
50
|
-
return new DataTransaction(this, parentTxn);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
disconnect(model) {
|
|
54
|
-
return this.toModelEntity(model).getDriver().disconnect();
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
resolve(query) {
|
|
58
|
-
const { model, crud, method } = query.toObject();
|
|
59
|
-
|
|
60
|
-
switch (crud) {
|
|
61
|
-
case 'create': case 'update': case 'delete': {
|
|
62
|
-
return model.getDriver().resolve(query.toDriver()).then((data) => {
|
|
63
|
-
this.clear(model);
|
|
64
|
-
const rs = model.shapeObject(model.getShape(), data, query);
|
|
65
|
-
return finalizeResults(rs, query);
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
default: {
|
|
69
|
-
const key = model.idKey();
|
|
70
|
-
const { where } = query.toDriver();
|
|
71
|
-
const lookupValue = where[key];
|
|
72
|
-
|
|
73
|
-
// This is a shortcut to prevent making unnecessary query
|
|
74
|
-
if (Object.prototype.hasOwnProperty.call(where, key) && (lookupValue == null || (Array.isArray(lookupValue) && lookupValue.length === 0))) {
|
|
75
|
-
switch (method) {
|
|
76
|
-
case 'count': return Promise.resolve(0);
|
|
77
|
-
case 'findMany': return Promise.resolve([]);
|
|
78
|
-
default: return Promise.resolve(null);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Go through DataLoader to cache results
|
|
83
|
-
return this.loaders.get(`${model}`).load(query);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
toModel(model) {
|
|
89
|
-
return model instanceof Model ? model : this.schema.getModel(model);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
toModelMarked(model) {
|
|
93
|
-
const marked = this.toModel(model);
|
|
94
|
-
if (!marked) throw new Error(`${model} is not defined in schema`);
|
|
95
|
-
if (!marked.isMarkedModel()) throw new Error(`${model} is not a marked model`);
|
|
96
|
-
return marked;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
toModelEntity(model) {
|
|
100
|
-
const entity = this.toModel(model);
|
|
101
|
-
if (!entity) throw new Error(`${model} is not defined in schema`);
|
|
102
|
-
if (!entity.isEntity()) throw new Error(`${model} is not an entity`);
|
|
103
|
-
return entity;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
toResultSet(model, data, method) {
|
|
107
|
-
model = this.toModel(model);
|
|
108
|
-
const query = new Query({ model, resolver: this, context: this.context, method });
|
|
109
|
-
const result = model.deserialize(data, query);
|
|
110
|
-
const event = { result, query, ...query.doc(result).merged(result).toObject() };
|
|
111
|
-
return createSystemEvent('Response', event, () => result);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// DataLoader Proxy Methods
|
|
115
|
-
clear(model) {
|
|
116
|
-
this.loaders.get(`${model}`).clearAll();
|
|
117
|
-
return this;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
clearAll() {
|
|
121
|
-
this.models.forEach(model => this.clear(model));
|
|
122
|
-
return this;
|
|
123
|
-
}
|
|
124
|
-
};
|
package/src/core/Schema.js
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
const Model = require('../data/Model');
|
|
2
|
-
const Schema = require('../graphql/ast/Schema');
|
|
3
|
-
const { identifyOnDeletes } = require('../service/schema.service');
|
|
4
|
-
const { eventEmitter } = require('../service/event.service');
|
|
5
|
-
|
|
6
|
-
// Export class
|
|
7
|
-
module.exports = class extends Schema {
|
|
8
|
-
constructor(schema, stores) {
|
|
9
|
-
super(schema);
|
|
10
|
-
|
|
11
|
-
// Create drivers
|
|
12
|
-
this.drivers = Object.entries(stores).reduce((prev, [key, value]) => {
|
|
13
|
-
const { Driver } = value;
|
|
14
|
-
|
|
15
|
-
return Object.assign(prev, {
|
|
16
|
-
[key]: {
|
|
17
|
-
dao: new Driver(value, this),
|
|
18
|
-
idKey: Driver.idKey,
|
|
19
|
-
idValue: Driver.idValue,
|
|
20
|
-
},
|
|
21
|
-
});
|
|
22
|
-
}, {});
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
setup() {
|
|
26
|
-
return eventEmitter.emit('setup', this).then(() => {
|
|
27
|
-
const entities = this.models.filter(m => m.isEntity());
|
|
28
|
-
|
|
29
|
-
// Create model indexes
|
|
30
|
-
return Promise.all(entities.map(async (model) => {
|
|
31
|
-
const key = model.getKey();
|
|
32
|
-
const indexes = model.getIndexes();
|
|
33
|
-
const driver = model.getDriver();
|
|
34
|
-
if (driver.createCollection) await driver.createCollection(key);
|
|
35
|
-
return driver.createIndexes(key, indexes);
|
|
36
|
-
}));
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
disconnect() {
|
|
41
|
-
return Promise.all(Object.values(this.drivers).map(({ dao }) => dao.disconnect()));
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
initialize() {
|
|
45
|
-
super.initialize();
|
|
46
|
-
this.models = super.getModels().map(model => new Model(this, model, this.drivers[model.getDriverName()]));
|
|
47
|
-
return this;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
finalize() {
|
|
51
|
-
super.finalize();
|
|
52
|
-
this.models.forEach(model => model.referentialIntegrity(identifyOnDeletes(this.models, model)));
|
|
53
|
-
return this;
|
|
54
|
-
}
|
|
55
|
-
};
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
const { unrollGuid, guidToId } = require('../service/app.service');
|
|
2
|
-
|
|
3
|
-
module.exports = class ServerResolver {
|
|
4
|
-
constructor() {
|
|
5
|
-
// Queries
|
|
6
|
-
this.get = ({ autograph }, model, { id: guid }, required = false, query) => autograph.resolver.match(model).id(guidToId(autograph, guid)).select(query.fields).one({ required });
|
|
7
|
-
this.query = ({ autograph }, model, args, query) => autograph.resolver.match(model).select(query.fields).merge(args).many();
|
|
8
|
-
this.count = ({ autograph }, model, args, query) => autograph.resolver.match(model).merge(args).count();
|
|
9
|
-
|
|
10
|
-
// Mutations
|
|
11
|
-
this.create = ({ autograph }, model, { input, meta }, query) => autograph.resolver.match(model).select(query.fields).meta(meta).save(unrollGuid(autograph, model, input));
|
|
12
|
-
this.delete = ({ autograph }, model, { id: guid, meta }, query) => autograph.resolver.match(model).id(guidToId(autograph, guid)).select(query.fields).meta(meta).remove();
|
|
13
|
-
this.update = ({ autograph }, model, { id: guid, meta, input }, query) => autograph.resolver.match(model).id(guidToId(autograph, guid)).select(query.fields).meta(meta).save(unrollGuid(autograph, model, input));
|
|
14
|
-
}
|
|
15
|
-
};
|
package/src/data/.DS_Store
DELETED
|
Binary file
|
package/src/data/DataService.js
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
const { get, remove } = require('lodash');
|
|
2
|
-
const { map, isPlainObject, objectContaining, mergeDeep, ensureArray, keyPaths } = require('../service/app.service');
|
|
3
|
-
|
|
4
|
-
exports.finalizeResults = (rs, query) => {
|
|
5
|
-
const { model, resolver } = query.toObject();
|
|
6
|
-
|
|
7
|
-
return map(exports.paginateResults(rs, query), (doc) => {
|
|
8
|
-
return Object.defineProperties(doc, {
|
|
9
|
-
$model: { value: model },
|
|
10
|
-
$save: { value: input => resolver.match(model).id(doc.id).save({ ...doc, ...input }) },
|
|
11
|
-
$remove: { value: (...args) => resolver.match(model).id(doc.id).remove(...args) },
|
|
12
|
-
$delete: { value: (...args) => resolver.match(model).id(doc.id).delete(...args) },
|
|
13
|
-
$lookup: { value: (fieldName, args) => model.getFieldByName(fieldName).resolve(resolver, doc, args) },
|
|
14
|
-
// $resolve: { value: (fieldName, args) => model.getFieldByName(fieldName).resolve(resolver, doc, args) },
|
|
15
|
-
});
|
|
16
|
-
});
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* This is cursor-style pagination only
|
|
21
|
-
* You add 2 extra records to the result in order to determine previous/next
|
|
22
|
-
*/
|
|
23
|
-
exports.paginateResults = (rs, query) => {
|
|
24
|
-
const { first, after, last, before, sort } = query.toObject();
|
|
25
|
-
const isPaginating = Boolean(first || last || after || before);
|
|
26
|
-
|
|
27
|
-
// Return right away if not paginating
|
|
28
|
-
if (!isPaginating) return rs;
|
|
29
|
-
|
|
30
|
-
let hasNextPage = false;
|
|
31
|
-
let hasPreviousPage = false;
|
|
32
|
-
const limiter = first || last;
|
|
33
|
-
const sortPaths = keyPaths(sort);
|
|
34
|
-
|
|
35
|
-
// Add $cursor data
|
|
36
|
-
map(rs, (doc) => {
|
|
37
|
-
const sortValues = sortPaths.reduce((prv, path) => Object.assign(prv, { [path]: get(doc, path) }), {});
|
|
38
|
-
Object.defineProperty(doc, '$cursor', { get() { return Buffer.from(JSON.stringify(sortValues)).toString('base64'); } });
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
// First try to take off the "bookends" ($gte | $lte)
|
|
42
|
-
if (rs.length && rs[0].$cursor === after) {
|
|
43
|
-
rs.shift();
|
|
44
|
-
hasPreviousPage = true;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (rs.length && rs[rs.length - 1].$cursor === before) {
|
|
48
|
-
rs.pop();
|
|
49
|
-
hasNextPage = true;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Next, remove any overage
|
|
53
|
-
const overage = rs.length - (limiter - 2);
|
|
54
|
-
|
|
55
|
-
if (overage > 0) {
|
|
56
|
-
if (first) {
|
|
57
|
-
rs.splice(-overage);
|
|
58
|
-
hasNextPage = true;
|
|
59
|
-
} else if (last) {
|
|
60
|
-
rs.splice(0, overage);
|
|
61
|
-
hasPreviousPage = true;
|
|
62
|
-
} else {
|
|
63
|
-
rs.splice(-overage);
|
|
64
|
-
hasNextPage = true;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Add $pageInfo
|
|
69
|
-
return Object.defineProperties(rs, {
|
|
70
|
-
$pageInfo: {
|
|
71
|
-
get() {
|
|
72
|
-
return {
|
|
73
|
-
startCursor: get(rs, '0.$cursor', ''),
|
|
74
|
-
endCursor: get(rs, `${rs.length - 1}.$cursor`, ''),
|
|
75
|
-
hasPreviousPage,
|
|
76
|
-
hasNextPage,
|
|
77
|
-
};
|
|
78
|
-
},
|
|
79
|
-
},
|
|
80
|
-
});
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
exports.spliceEmbeddedArray = (array, from, to) => {
|
|
84
|
-
const op = from && to ? 'edit' : (from ? 'pull' : 'push'); // eslint-disable-line no-nested-ternary
|
|
85
|
-
|
|
86
|
-
// Convenience so the user does not have to explicity type out the same value over and over to replace
|
|
87
|
-
if (from && from.length > 1 && to && to.length === 1) to = Array.from(from).fill(to[0]);
|
|
88
|
-
|
|
89
|
-
switch (op) {
|
|
90
|
-
case 'edit': {
|
|
91
|
-
array.forEach((el, j) => {
|
|
92
|
-
ensureArray(from).forEach((val, k) => {
|
|
93
|
-
if (objectContaining(el, val)) array[j] = isPlainObject(el) ? mergeDeep(el, ensureArray(to)[k]) : ensureArray(to)[k];
|
|
94
|
-
});
|
|
95
|
-
});
|
|
96
|
-
break;
|
|
97
|
-
}
|
|
98
|
-
// case 'edit': {
|
|
99
|
-
// ensureArray(from).forEach((f, i) => {
|
|
100
|
-
// const t = ensureArray(to)[i];
|
|
101
|
-
// const indexes = array.map((el, j) => (el === f ? j : -1)).filter(index => index !== -1);
|
|
102
|
-
// indexes.forEach(index => (array[index] = t));
|
|
103
|
-
// });
|
|
104
|
-
// break;
|
|
105
|
-
// }
|
|
106
|
-
case 'push': {
|
|
107
|
-
array.push(...to);
|
|
108
|
-
break;
|
|
109
|
-
}
|
|
110
|
-
case 'pull': {
|
|
111
|
-
remove(array, el => from.find(val => objectContaining(el, val)));
|
|
112
|
-
break;
|
|
113
|
-
}
|
|
114
|
-
default: {
|
|
115
|
-
break;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
return array;
|
|
120
|
-
};
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
const { get } = require('lodash');
|
|
2
|
-
const TreeMap = require('./TreeMap');
|
|
3
|
-
const QueryBuilderTransaction = require('../query/QueryBuilderTransaction');
|
|
4
|
-
|
|
5
|
-
const makeMap = (resolver) => {
|
|
6
|
-
let resolve, reject;
|
|
7
|
-
const map = new TreeMap();
|
|
8
|
-
map.promise = new Promise((good, bad) => { resolve = good; reject = bad; });
|
|
9
|
-
map.resolve = resolve;
|
|
10
|
-
map.reject = reject;
|
|
11
|
-
|
|
12
|
-
map.ready = () => {
|
|
13
|
-
const elements = map.elements();
|
|
14
|
-
const notReady = elements.filter(el => !el.marker);
|
|
15
|
-
if (notReady.length) return [undefined, undefined];
|
|
16
|
-
let rollbackIndex = elements.findIndex(el => el.marker === 'rollback');
|
|
17
|
-
if (rollbackIndex === -1) rollbackIndex = Infinity;
|
|
18
|
-
return [elements.slice(0, rollbackIndex), elements.slice(rollbackIndex)];
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
map.perform = () => {
|
|
22
|
-
const [commits, rollbacks] = map.ready();
|
|
23
|
-
|
|
24
|
-
if (commits && rollbacks) {
|
|
25
|
-
const rollbackData = rollbacks.map(tnx => tnx.data).flat();
|
|
26
|
-
const commitData = commits.map(tnx => tnx.data).flat();
|
|
27
|
-
|
|
28
|
-
Promise.all(rollbackData.map(rbd => rbd.$rollback())).then(() => {
|
|
29
|
-
if (commits.length) resolver.clearAll();
|
|
30
|
-
Promise.all(commitData.map(cd => cd.$commit())).then(d => map.resolve(d));
|
|
31
|
-
}).catch(e => map.reject(e));
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return map.promise;
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
return map;
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
module.exports = class DataTransaction {
|
|
41
|
-
constructor(resolver, parentTxn) {
|
|
42
|
-
this.data = [];
|
|
43
|
-
this.resolver = resolver;
|
|
44
|
-
this.driverMap = new Map();
|
|
45
|
-
this.txnMap = get(parentTxn, 'txnMap') || makeMap(resolver);
|
|
46
|
-
this.txnMap.add(parentTxn, this);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
match(modelish) {
|
|
50
|
-
const model = this.resolver.toModelMarked(modelish);
|
|
51
|
-
const driver = model.getDriver();
|
|
52
|
-
if (!this.driverMap.has(driver)) this.driverMap.set(driver, []);
|
|
53
|
-
const op = new QueryBuilderTransaction(this.resolver, model, this);
|
|
54
|
-
this.driverMap.get(driver).push(op);
|
|
55
|
-
return op;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
exec() {
|
|
59
|
-
return Promise.all(Array.from(this.driverMap.entries()).map(([driver, ops]) => {
|
|
60
|
-
if (driver.getDirectives().transactions === false) {
|
|
61
|
-
return Promise.all(ops.map(op => op.exec())).then((results) => {
|
|
62
|
-
results.$commit = () => this.resolver.clearAll();
|
|
63
|
-
results.$rollback = () => this.resolver.clearAll();
|
|
64
|
-
return results;
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return driver.transaction(ops);
|
|
69
|
-
})).then((results) => {
|
|
70
|
-
this.data = results;
|
|
71
|
-
return results.flat();
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
run() {
|
|
76
|
-
return this.exec().then((results) => {
|
|
77
|
-
if (this.txnMap.root(this) === this) return this.commit().then(() => results);
|
|
78
|
-
this.commit();
|
|
79
|
-
return results;
|
|
80
|
-
}).catch((e) => {
|
|
81
|
-
if (this.txnMap.root(this) === this) return this.rollback().then(() => Promise.reject(e));
|
|
82
|
-
this.rollback();
|
|
83
|
-
throw e;
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
commit() {
|
|
88
|
-
if (this.marker !== 'rollback') this.marker = 'commit';
|
|
89
|
-
return this.txnMap.perform();
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
rollback() {
|
|
93
|
-
this.marker = 'rollback';
|
|
94
|
-
return this.txnMap.perform();
|
|
95
|
-
}
|
|
96
|
-
};
|