@coderich/autograph 0.13.108 → 0.14.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/package.json +2 -3
- package/src/data/DataLoader.js +28 -19
- package/src/data/Emitter.js +55 -14
- package/src/data/Pipeline.js +0 -1
- package/src/data/Resolver.js +4 -22
- package/src/data/Transformer.js +4 -2
- package/src/query/Query.js +25 -6
- package/src/schema/Schema.js +23 -39
- package/src/service/AppService.js +1 -1
- package/src/data/__mocks__/Emitter.js +0 -11
- package/src/data/__mocks__/Pipeline.js +0 -32
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coderich/autograph",
|
|
3
3
|
"main": "index.js",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.14.0",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"clinic": "clinic flame -- node ./test/server"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@coderich/util": "
|
|
19
|
+
"@coderich/util": "2.2.0",
|
|
20
20
|
"@graphql-tools/merge": "9.0.0",
|
|
21
21
|
"@graphql-tools/resolvers-composition": "7.0.0",
|
|
22
22
|
"@hapi/boom": "10.0.1",
|
|
@@ -38,7 +38,6 @@
|
|
|
38
38
|
"@graphql-tools/schema": "10.0.0",
|
|
39
39
|
"clinic": "13.0.0",
|
|
40
40
|
"graphql": "16.8.1",
|
|
41
|
-
"mongodb": "5.9.2",
|
|
42
41
|
"mongodb-memory-server": "8.13.0"
|
|
43
42
|
},
|
|
44
43
|
"peerDependencies": {
|
package/src/data/DataLoader.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
const get = require('lodash.get');
|
|
2
2
|
const Util = require('@coderich/util');
|
|
3
3
|
const DataLoader = require('dataloader');
|
|
4
|
-
const { hashObject } = require('../service/AppService');
|
|
5
4
|
|
|
6
5
|
module.exports = class Loader {
|
|
7
6
|
#model;
|
|
@@ -11,7 +10,7 @@ module.exports = class Loader {
|
|
|
11
10
|
constructor(model, resolver) {
|
|
12
11
|
this.#model = model;
|
|
13
12
|
this.#resolver = resolver;
|
|
14
|
-
this.#model.loader.cacheKeyFn ??= query =>
|
|
13
|
+
this.#model.loader.cacheKeyFn ??= query => query.toCacheKey();
|
|
15
14
|
this.#loader = new DataLoader(keys => this.#resolve(keys), this.#model.loader);
|
|
16
15
|
}
|
|
17
16
|
|
|
@@ -33,10 +32,9 @@ module.exports = class Loader {
|
|
|
33
32
|
const $query = query.toDriver().toObject();
|
|
34
33
|
const key = $query.batch ?? '__default__';
|
|
35
34
|
let [values] = key === '__default__' ? [] : Object.values(Util.flatten($query.where, { safe: true }));
|
|
36
|
-
values =
|
|
37
|
-
const $values = values.map(value => (value instanceof RegExp ? value : new RegExp(`${value}`, 'i')));
|
|
35
|
+
values = Loader.#dedup(Util.ensureArray(values));
|
|
38
36
|
prev[key] = prev[key] || [];
|
|
39
|
-
prev[key].push({ query, $query, values,
|
|
37
|
+
prev[key].push({ query, $query, values, i });
|
|
40
38
|
return prev;
|
|
41
39
|
}, {});
|
|
42
40
|
|
|
@@ -47,36 +45,37 @@ module.exports = class Loader {
|
|
|
47
45
|
}
|
|
48
46
|
default: {
|
|
49
47
|
// Collect all the values for the where clause
|
|
50
|
-
const values =
|
|
48
|
+
const values = Loader.#dedup(batches.map(batch => batch.values).flat());
|
|
51
49
|
const $query = { ...batches[0].$query, op: 'findMany', where: { [key]: values } };
|
|
52
50
|
|
|
53
|
-
//
|
|
54
51
|
if (values.length < 3) {
|
|
55
52
|
return batches.map(batch => this.#model.source.client.resolve(batch.$query).then(data => ({ data, ...batch })));
|
|
56
53
|
}
|
|
57
54
|
|
|
58
|
-
// Collect all the $values (Regular Expressions) to match doc (result) data by
|
|
59
|
-
const $values = Array.from(new Set(batches.map(batch => batch.$values).flat()));
|
|
60
|
-
const docsByRegExpKey = $values.reduce((map, re) => map.set(re, []), new Map());
|
|
61
|
-
|
|
62
55
|
// Now we perform 1 query, instead of many smaller ones
|
|
63
56
|
return this.#model.source.client.resolve($query).then((docs) => {
|
|
64
|
-
|
|
57
|
+
const docsByKey = new Map();
|
|
58
|
+
|
|
65
59
|
docs.forEach((doc) => {
|
|
66
60
|
Util.pathmap(key, doc, (value) => {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
});
|
|
61
|
+
Util.ensureArray(value).forEach((v) => {
|
|
62
|
+
const k = `${v}`;
|
|
63
|
+
if (!docsByKey.has(k)) docsByKey.set(k, []);
|
|
64
|
+
docsByKey.get(k).push(doc);
|
|
73
65
|
});
|
|
74
66
|
return value;
|
|
75
67
|
});
|
|
76
68
|
});
|
|
77
69
|
|
|
78
70
|
return batches.map((batch) => {
|
|
79
|
-
const matches = Array.from(new Set(batch
|
|
71
|
+
const matches = Array.from(new Set(batch.values.flatMap((v) => {
|
|
72
|
+
if (v instanceof RegExp) {
|
|
73
|
+
const result = [];
|
|
74
|
+
docsByKey.forEach((d, k) => { if (v.test(k)) result.push(...d); });
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
return docsByKey.get(`${v}`) || [];
|
|
78
|
+
})));
|
|
80
79
|
const data = batch.$query.op === 'findOne' ? matches[0] : matches;
|
|
81
80
|
return { data, ...batch };
|
|
82
81
|
});
|
|
@@ -103,6 +102,16 @@ module.exports = class Loader {
|
|
|
103
102
|
// }));
|
|
104
103
|
}
|
|
105
104
|
|
|
105
|
+
// Deduplicate an array of values that may contain RegExp objects.
|
|
106
|
+
// new Set() compares by reference, so two RegExp literals with identical patterns
|
|
107
|
+
// are treated as distinct. Using toString() as the Map key handles this correctly
|
|
108
|
+
// while preserving the actual RegExp instance as the value.
|
|
109
|
+
static #dedup(arr) {
|
|
110
|
+
const seen = new Map();
|
|
111
|
+
arr.forEach(v => seen.set(v instanceof RegExp ? v.toString() : v, v));
|
|
112
|
+
return Array.from(seen.values());
|
|
113
|
+
}
|
|
114
|
+
|
|
106
115
|
static #paginateResults(rs, query) {
|
|
107
116
|
let hasNextPage = false;
|
|
108
117
|
let hasPreviousPage = false;
|
package/src/data/Emitter.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const EventEmitter = require('events');
|
|
1
|
+
const EventEmitter = require('node:events');
|
|
2
2
|
const Util = require('@coderich/util');
|
|
3
3
|
const { AbortEarlyError } = require('../service/ErrorService');
|
|
4
4
|
|
|
@@ -9,28 +9,36 @@ const { AbortEarlyError } = require('../service/ErrorService');
|
|
|
9
9
|
* If it expects more than 1 we block and wait for it to finish.
|
|
10
10
|
*/
|
|
11
11
|
class Emitter extends EventEmitter {
|
|
12
|
+
#cache = new Map();
|
|
13
|
+
|
|
14
|
+
#invalidate(event) {
|
|
15
|
+
this.#cache.delete(event);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
#getListeners(event) {
|
|
19
|
+
if (!this.#cache.has(event)) {
|
|
20
|
+
const [basicFuncs, nextFuncs] = this.rawListeners(event).reduce((prev, wrapper) => {
|
|
21
|
+
const { listener = wrapper } = wrapper;
|
|
22
|
+
wrapper.priority = listener.priority ?? 0;
|
|
23
|
+
return prev[listener.length < 2 ? 0 : 1].push(wrapper) && prev;
|
|
24
|
+
}, [[], []]);
|
|
25
|
+
this.#cache.set(event, { basicFuncs: basicFuncs.sort(Emitter.sort), nextFuncs: nextFuncs.sort(Emitter.sort) });
|
|
26
|
+
}
|
|
27
|
+
return this.#cache.get(event);
|
|
28
|
+
}
|
|
29
|
+
|
|
12
30
|
emit(event, data) {
|
|
13
|
-
|
|
14
|
-
const [basicFuncs, nextFuncs] = this.rawListeners(event).reduce((prev, wrapper) => {
|
|
15
|
-
const { listener = wrapper } = wrapper;
|
|
16
|
-
const isBasic = listener.length < 2;
|
|
17
|
-
wrapper.priority = listener.priority ?? 0;
|
|
18
|
-
return prev[isBasic ? 0 : 1].push(wrapper) && prev;
|
|
19
|
-
}, [[], []]);
|
|
20
|
-
|
|
21
|
-
// // Basic functions are not designed to be bound to the query execution so we need an isolated resolver from any transactions
|
|
22
|
-
// const resolver = data?.resolver?.clone();
|
|
23
|
-
// const basicData = { ...data, resolver };
|
|
31
|
+
const { basicFuncs, nextFuncs } = this.#getListeners(event);
|
|
24
32
|
|
|
25
33
|
return new Promise((resolve, reject) => {
|
|
26
34
|
// Basic functions run first; if they return a value they abort the flow of execution
|
|
27
|
-
basicFuncs.
|
|
35
|
+
basicFuncs.forEach((fn) => {
|
|
28
36
|
const value = fn(data);
|
|
29
37
|
if (value !== undefined && !(value instanceof Promise)) throw new AbortEarlyError(value);
|
|
30
38
|
});
|
|
31
39
|
|
|
32
40
|
// Next functions are async and control the timing of the next phase
|
|
33
|
-
Promise.all(nextFuncs.
|
|
41
|
+
Promise.all(nextFuncs.map((fn) => {
|
|
34
42
|
return new Promise((next, err) => {
|
|
35
43
|
Promise.resolve().then(() => fn(data, next)).catch(err);
|
|
36
44
|
}).then((result) => {
|
|
@@ -45,14 +53,47 @@ class Emitter extends EventEmitter {
|
|
|
45
53
|
|
|
46
54
|
on(event, listener, priority = 0) {
|
|
47
55
|
listener.priority = priority;
|
|
56
|
+
this.#invalidate(event);
|
|
48
57
|
return super.on(event, listener);
|
|
49
58
|
}
|
|
50
59
|
|
|
60
|
+
addListener(event, listener, priority = 0) {
|
|
61
|
+
return this.on(event, listener, priority);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
once(event, listener, priority = 0) {
|
|
65
|
+
listener.priority = priority;
|
|
66
|
+
this.#invalidate(event);
|
|
67
|
+
return super.once(event, listener);
|
|
68
|
+
}
|
|
69
|
+
|
|
51
70
|
prependListener(event, listener, priority = 0) {
|
|
52
71
|
listener.priority = priority;
|
|
72
|
+
this.#invalidate(event);
|
|
53
73
|
return super.prependListener(event, listener);
|
|
54
74
|
}
|
|
55
75
|
|
|
76
|
+
prependOnceListener(event, listener, priority = 0) {
|
|
77
|
+
listener.priority = priority;
|
|
78
|
+
this.#invalidate(event);
|
|
79
|
+
return super.prependOnceListener(event, listener);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
removeListener(event, listener) {
|
|
83
|
+
this.#invalidate(event);
|
|
84
|
+
return super.removeListener(event, listener);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
off(event, listener) {
|
|
88
|
+
return this.removeListener(event, listener);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
removeAllListeners(event) {
|
|
92
|
+
if (event) this.#invalidate(event);
|
|
93
|
+
else this.#cache.clear();
|
|
94
|
+
return super.removeAllListeners(event);
|
|
95
|
+
}
|
|
96
|
+
|
|
56
97
|
/**
|
|
57
98
|
* Syntactic sugar to listen on query keys
|
|
58
99
|
*/
|
package/src/data/Pipeline.js
CHANGED
|
@@ -63,7 +63,6 @@ module.exports = class Pipeline {
|
|
|
63
63
|
Pipeline.define('$construct', params => Pipeline.resolve(params, 'construct'), { ignoreNull: false });
|
|
64
64
|
Pipeline.define('$restruct', params => Pipeline.resolve(params, 'restruct'), { ignoreNull: false });
|
|
65
65
|
Pipeline.define('$serialize', params => Pipeline.resolve(params, 'serialize'), { ignoreNull: false });
|
|
66
|
-
Pipeline.define('$deserialize', params => Pipeline.resolve(params, 'deserialize'), { ignoreNull: false });
|
|
67
66
|
Pipeline.define('$validate', params => Pipeline.resolve(params, 'validate'), { ignoreNull: false });
|
|
68
67
|
|
|
69
68
|
//
|
package/src/data/Resolver.js
CHANGED
|
@@ -245,7 +245,7 @@ module.exports = class Resolver {
|
|
|
245
245
|
model = this.#schema.models[model];
|
|
246
246
|
|
|
247
247
|
return Object.defineProperties(Util.map(result, (doc) => {
|
|
248
|
-
const $doc = model.
|
|
248
|
+
const $doc = model.docTransform(doc);
|
|
249
249
|
|
|
250
250
|
// Assign useful/needed meta data
|
|
251
251
|
return Object.defineProperties($doc, {
|
|
@@ -307,44 +307,26 @@ module.exports = class Resolver {
|
|
|
307
307
|
const tquery = $query.transform(false);
|
|
308
308
|
const query = tquery.toObject();
|
|
309
309
|
const type = query.isMutation ? 'Mutation' : 'Query';
|
|
310
|
-
const event = this.#
|
|
310
|
+
const event = { schema: this.#schema, context: this.#context, resolver: this, query };
|
|
311
311
|
|
|
312
312
|
return Emitter.emit(`pre${type}`, event).then(async (resultEarly) => {
|
|
313
|
-
if (resultEarly !== undefined) return resultEarly;
|
|
314
|
-
// if (query.crud === 'update' && Util.isEqual({ added: {}, updated: {}, deleted: {} }, Util.changeset(query.doc, query.input))) return query.doc;
|
|
313
|
+
if (resultEarly !== undefined) return resultEarly;
|
|
315
314
|
|
|
316
315
|
if (['create', 'update'].includes(query.crud)) {
|
|
317
|
-
tquery.validate(); //
|
|
316
|
+
tquery.validate(); // sets async $thunks (e.g. ensureFK)
|
|
318
317
|
await Promise.all([...query.input.$thunks]);
|
|
319
318
|
await Emitter.emit('validate', event);
|
|
320
319
|
}
|
|
321
320
|
|
|
322
321
|
return thunk(tquery);
|
|
323
322
|
}).then((result) => {
|
|
324
|
-
event.result = result; // backwards compat
|
|
325
323
|
query.result = result;
|
|
326
324
|
return Emitter.emit(`post${type}`, event);
|
|
327
325
|
}).then((result = query.result) => result).catch((e) => {
|
|
328
326
|
throw Boom.boomify(e);
|
|
329
|
-
// const { data = {} } = e;
|
|
330
|
-
// throw Boom.boomify(e, { data: { ...event, ...data } });
|
|
331
327
|
});
|
|
332
328
|
}
|
|
333
329
|
|
|
334
|
-
#createEvent(query) {
|
|
335
|
-
const event = { schema: this.#schema, context: this.#context, resolver: this, query };
|
|
336
|
-
|
|
337
|
-
// Backwards compat
|
|
338
|
-
Object.assign(event, query);
|
|
339
|
-
query.match = event.args.where;
|
|
340
|
-
query.toObject = () => query;
|
|
341
|
-
event.merged = event.input;
|
|
342
|
-
event.input = Util.unflatten(event.args?.input, { safe: true });
|
|
343
|
-
event.doc ??= {};
|
|
344
|
-
|
|
345
|
-
return event;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
330
|
static $loader(name, resolver, config) {
|
|
349
331
|
if (!name) return loaders;
|
|
350
332
|
if (!resolver) return loaders[name];
|
package/src/data/Transformer.js
CHANGED
|
@@ -9,6 +9,8 @@ module.exports = class Transformer {
|
|
|
9
9
|
keepUndefined: false, // If true, will preserve undefined values
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
+
#callArgs = {}; // Ephemeral per-call merge of #config.args + transform() args; never persisted
|
|
13
|
+
|
|
12
14
|
#operation = {
|
|
13
15
|
set: (target, prop, startValue, proxy) => {
|
|
14
16
|
if (this.#config.shape[prop]) {
|
|
@@ -16,7 +18,7 @@ module.exports = class Transformer {
|
|
|
16
18
|
|
|
17
19
|
const result = this.#config.shape[prop].reduce((value, t) => {
|
|
18
20
|
previousValue = value;
|
|
19
|
-
if (typeof t === 'function') return Util.uvl(t({ startValue, value, ...this.#
|
|
21
|
+
if (typeof t === 'function') return Util.uvl(t({ startValue, value, ...this.#callArgs }), value);
|
|
20
22
|
prop = t; // rename key
|
|
21
23
|
return value;
|
|
22
24
|
}, startValue);
|
|
@@ -64,7 +66,7 @@ module.exports = class Transformer {
|
|
|
64
66
|
|
|
65
67
|
transform(mixed, args = {}) {
|
|
66
68
|
args.thunks ??= [];
|
|
67
|
-
this.args
|
|
69
|
+
this.#callArgs = { ...this.#config.args, ...args };
|
|
68
70
|
|
|
69
71
|
const transformed = Util.map(mixed, (data) => {
|
|
70
72
|
const thunks = Object.defineProperty({}, '$thunks', { value: args.thunks });
|
package/src/query/Query.js
CHANGED
|
@@ -39,19 +39,18 @@ module.exports = class Query {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
toCacheKey() {
|
|
42
|
-
return {
|
|
42
|
+
return JSON.stringify({
|
|
43
43
|
op: this.#query.op,
|
|
44
44
|
select: this.#query.select,
|
|
45
45
|
where: this.#query.where,
|
|
46
46
|
sort: this.#query.sort,
|
|
47
|
-
joins: this.#query.joins,
|
|
48
47
|
skip: this.#query.skip,
|
|
49
48
|
limit: this.#query.limit,
|
|
50
49
|
before: this.#query.before,
|
|
51
50
|
after: this.#query.after,
|
|
52
51
|
first: this.#query.first,
|
|
53
52
|
last: this.#query.last,
|
|
54
|
-
};
|
|
53
|
+
});
|
|
55
54
|
}
|
|
56
55
|
|
|
57
56
|
/**
|
|
@@ -134,14 +133,25 @@ module.exports = class Query {
|
|
|
134
133
|
query.batch = (op === 'findOne' || op === 'findMany') && Object.keys(query.where).length === 1 ? Object.keys(query.where)[0] : '__default__';
|
|
135
134
|
|
|
136
135
|
// Construct joins
|
|
136
|
+
const joinsByPath = {};
|
|
137
137
|
query.joins = [];
|
|
138
138
|
|
|
139
|
+
// Recursively search a join tree for the first join matching a target model key
|
|
140
|
+
const findJoin = (joins, modelKey) => {
|
|
141
|
+
for (const j of joins) {
|
|
142
|
+
if (j.to === modelKey) return j;
|
|
143
|
+
const found = findJoin(j.children, modelKey);
|
|
144
|
+
if (found) return found;
|
|
145
|
+
}
|
|
146
|
+
return null;
|
|
147
|
+
};
|
|
148
|
+
|
|
139
149
|
this.#model.walk(joinData, (node) => {
|
|
140
150
|
const { model, field, key, value, isLeaf, path, run } = node;
|
|
141
151
|
|
|
142
152
|
if (field.join) {
|
|
143
153
|
let isArray;
|
|
144
|
-
const join = { ...field.join, where: {} };
|
|
154
|
+
const join = { ...field.join, where: {}, children: [] };
|
|
145
155
|
|
|
146
156
|
if (run.length > 1) {
|
|
147
157
|
join.from = path.reduce((prev, curr, i) => {
|
|
@@ -153,12 +163,21 @@ module.exports = class Query {
|
|
|
153
163
|
|
|
154
164
|
join.isArray = isArray || model.resolvePath(join.from).isArray;
|
|
155
165
|
|
|
156
|
-
|
|
166
|
+
// Find the nearest ancestor FK join by scanning ancestor paths from closest to farthest.
|
|
167
|
+
// Joins reached through embedded fields have no FK ancestor and stay at the root level.
|
|
168
|
+
const parentJoin = path.slice(0, -1).reduceRight((found, _, i) => found || joinsByPath[path.slice(0, i + 1).join('.')], null);
|
|
169
|
+
|
|
170
|
+
if (parentJoin) {
|
|
171
|
+
parentJoin.children.push(join);
|
|
172
|
+
} else {
|
|
173
|
+
query.joins.push(join);
|
|
174
|
+
}
|
|
175
|
+
joinsByPath[path.join('.')] = join;
|
|
157
176
|
}
|
|
158
177
|
|
|
159
178
|
if (isLeaf) {
|
|
160
179
|
const $model = field.model || model;
|
|
161
|
-
const join = query.joins
|
|
180
|
+
const join = findJoin(query.joins, $model.key);
|
|
162
181
|
const $value = Util.map(value, el => (isGlob(el) ? globToRegex(el) : el));
|
|
163
182
|
const $$value = Array.isArray($value) ? { $in: $value } : $value;
|
|
164
183
|
const from = field.model ? join.from : key;
|
package/src/schema/Schema.js
CHANGED
|
@@ -16,7 +16,7 @@ const scalarKinds = [Kind.SCALAR_TYPE_DEFINITION, Kind.SCALAR_TYPE_EXTENSION];
|
|
|
16
16
|
const fieldKinds = [Kind.FIELD_DEFINITION];
|
|
17
17
|
const modelKinds = [Kind.OBJECT_TYPE_DEFINITION, Kind.OBJECT_TYPE_EXTENSION].concat(interfaceKinds);
|
|
18
18
|
const allowedKinds = modelKinds.concat(fieldKinds).concat(Kind.DOCUMENT, Kind.NON_NULL_TYPE, Kind.NAMED_TYPE, Kind.LIST_TYPE, Kind.DIRECTIVE).concat(scalarKinds).concat(enumKinds);
|
|
19
|
-
const pipelines = ['validate', 'construct', 'restruct', 'instruct', 'normalize', 'serialize'
|
|
19
|
+
const pipelines = ['validate', 'construct', 'restruct', 'instruct', 'normalize', 'serialize'];
|
|
20
20
|
const createPipelines = ['validate', 'construct', 'instruct', 'normalize', 'serialize'];
|
|
21
21
|
const updatePipelines = ['validate', 'restruct', 'instruct', 'normalize', 'serialize'];
|
|
22
22
|
// const validatePipelines = ['validate', 'instruct', 'normalize', 'serialize'];
|
|
@@ -97,7 +97,7 @@ module.exports = class Schema {
|
|
|
97
97
|
try {
|
|
98
98
|
const $td = typeof td === 'string' ? parse(td) : td;
|
|
99
99
|
return $td;
|
|
100
|
-
} catch
|
|
100
|
+
} catch {
|
|
101
101
|
console.log(`Unable to parse typeDef (being ignored):\n${td}`); // eslint-disable-line
|
|
102
102
|
return null;
|
|
103
103
|
}
|
|
@@ -153,7 +153,6 @@ module.exports = class Schema {
|
|
|
153
153
|
create: new Transformer({ args: { schema: this.#schema, path: [] } }),
|
|
154
154
|
update: new Transformer({ args: { schema: this.#schema, path: [] } }),
|
|
155
155
|
where: new Transformer({ args: { schema: this.#schema, path: [] } }),
|
|
156
|
-
doc: new Transformer({ args: { schema: this.#schema, path: [] } }),
|
|
157
156
|
},
|
|
158
157
|
directives: {},
|
|
159
158
|
ignorePaths: [],
|
|
@@ -205,6 +204,7 @@ module.exports = class Schema {
|
|
|
205
204
|
target.directives[name] = target.directives[name] || {};
|
|
206
205
|
|
|
207
206
|
if (name === directives.model) {
|
|
207
|
+
model.isEntity = true;
|
|
208
208
|
model.isMarkedModel = true;
|
|
209
209
|
model.isEmbedded = false;
|
|
210
210
|
} else if (name === directives.index) {
|
|
@@ -234,6 +234,7 @@ module.exports = class Schema {
|
|
|
234
234
|
}
|
|
235
235
|
case `${directives.model}-embed`: {
|
|
236
236
|
model.isEmbedded = value;
|
|
237
|
+
model.isEntity = !value;
|
|
237
238
|
break;
|
|
238
239
|
}
|
|
239
240
|
// Field specific directives
|
|
@@ -312,8 +313,6 @@ module.exports = class Schema {
|
|
|
312
313
|
|
|
313
314
|
// Model resolution after field resolution (push)
|
|
314
315
|
thunks.push(($schema) => {
|
|
315
|
-
$model.isEntity = Boolean($model.isMarkedModel && !$model.isEmbedded);
|
|
316
|
-
|
|
317
316
|
$model.resolvePath = (path, prop = 'name') => this.#schema.resolvePath(`${$model[prop]}.${path}`, prop);
|
|
318
317
|
|
|
319
318
|
$model.isJoinPath = (path, prop = 'name') => {
|
|
@@ -452,33 +451,6 @@ module.exports = class Schema {
|
|
|
452
451
|
|
|
453
452
|
$model.transformers.sort = $model.transformers.where.clone({ defaults: {} });
|
|
454
453
|
|
|
455
|
-
$model.transformers.doc.config({
|
|
456
|
-
shape: Object.values($model.fields).reduce((prev, curr) => {
|
|
457
|
-
const args = { model: $model, field: curr };
|
|
458
|
-
|
|
459
|
-
const rules = [
|
|
460
|
-
curr.name, // Rename key
|
|
461
|
-
a => Pipeline.$deserialize({ ...a, ...args, path: a.path.concat(curr.name) }),
|
|
462
|
-
];
|
|
463
|
-
|
|
464
|
-
if (curr.isArray) rules.unshift(({ value }) => (value == null ? value : Util.ensureArray(value)));
|
|
465
|
-
|
|
466
|
-
if (curr.isEmbedded) {
|
|
467
|
-
rules.unshift(a => Util.map(a.value, (value, i) => {
|
|
468
|
-
const path = a.path.concat(curr.name);
|
|
469
|
-
if (curr.isArray) path.push(i);
|
|
470
|
-
return curr.model.transformers.doc.transform(value, { ...args, query: a.query, context: a.context, path });
|
|
471
|
-
}));
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
return Object.assign(prev, { [curr.key]: rules });
|
|
475
|
-
}, {}),
|
|
476
|
-
defaults: Object.values($model.fields).reduce((prev, curr) => {
|
|
477
|
-
if (curr.defaultValue === undefined) return prev;
|
|
478
|
-
return Object.assign(prev, { [curr.key]: curr.defaultValue });
|
|
479
|
-
}, {}),
|
|
480
|
-
});
|
|
481
|
-
|
|
482
454
|
$model.transformers.validate.config({
|
|
483
455
|
strictSchema: true,
|
|
484
456
|
shape: Object.values($model.fields).reduce((prev, curr) => {
|
|
@@ -499,6 +471,21 @@ module.exports = class Schema {
|
|
|
499
471
|
}, {}),
|
|
500
472
|
});
|
|
501
473
|
|
|
474
|
+
// Deserialize/docs special case handling for performance
|
|
475
|
+
const docFields = Object.values($model.fields);
|
|
476
|
+
$model.docTransform = (doc) => {
|
|
477
|
+
if (doc == null) return doc;
|
|
478
|
+
const out = {};
|
|
479
|
+
for (const docField of docFields) {
|
|
480
|
+
let value = docField.key in doc ? doc[docField.key] : docField.defaultValue;
|
|
481
|
+
if (value === undefined) continue; // eslint-disable-line
|
|
482
|
+
if (docField.isArray) value = value == null ? value : Util.ensureArray(value);
|
|
483
|
+
if (docField.isEmbedded) value = Util.map(value, v => docField.model.docTransform(v));
|
|
484
|
+
out[docField.name] = value;
|
|
485
|
+
}
|
|
486
|
+
return out;
|
|
487
|
+
};
|
|
488
|
+
|
|
502
489
|
Util.traverse(Object.values($model.fields), (f, info) => {
|
|
503
490
|
const path = info.path.concat(f.name);
|
|
504
491
|
if (f.isEmbedded) return { value: Object.values(f.model.fields), info: { path } };
|
|
@@ -611,8 +598,6 @@ module.exports = class Schema {
|
|
|
611
598
|
},
|
|
612
599
|
});
|
|
613
600
|
|
|
614
|
-
// console.log(this.#schema.models.Person.referentialIntegrity);
|
|
615
|
-
|
|
616
601
|
// Return schema
|
|
617
602
|
return this.#schema;
|
|
618
603
|
}
|
|
@@ -646,11 +631,11 @@ module.exports = class Schema {
|
|
|
646
631
|
|
|
647
632
|
const arr = [];
|
|
648
633
|
|
|
649
|
-
Object.values(this.#schema.models).forEach((m) => {
|
|
634
|
+
Object.values(this.#schema.models).filter(m => m.isEntity).forEach((m) => {
|
|
650
635
|
Util.traverse(Object.values(m.fields), (f, info) => {
|
|
651
636
|
const path = info.path.concat(f.name);
|
|
652
|
-
if (f.isEmbedded) return { value: Object.values(f.model.fields), info: { path, isArray: info.isArray || f.isArray } };
|
|
653
637
|
if (f.type === model.name) arr.push({ model: m, field, path: path.concat(`${field}`), isArray: info.isArray || field.isArray || f.isArray });
|
|
638
|
+
else if (f.isEmbedded) return { value: Object.values(f.model.fields), info: { path, isArray: info.isArray || f.isArray } };
|
|
654
639
|
return null;
|
|
655
640
|
}, { path: [], isArray: false });
|
|
656
641
|
});
|
|
@@ -722,7 +707,6 @@ module.exports = class Schema {
|
|
|
722
707
|
construct: [AutoGraphPipelineEnum!]
|
|
723
708
|
restruct: [AutoGraphPipelineEnum!]
|
|
724
709
|
serialize: [AutoGraphPipelineEnum!]
|
|
725
|
-
deserialize: [AutoGraphPipelineEnum!]
|
|
726
710
|
validate: [AutoGraphPipelineEnum!]
|
|
727
711
|
|
|
728
712
|
# TEMP TO APPEASE TRANSITION
|
|
@@ -905,7 +889,7 @@ module.exports = class Schema {
|
|
|
905
889
|
`,
|
|
906
890
|
resolvers: {
|
|
907
891
|
Node: {
|
|
908
|
-
__resolveType: (doc, args, context, info) => doc.__typename,
|
|
892
|
+
__resolveType: (doc, args, context, info) => doc.__typename,
|
|
909
893
|
},
|
|
910
894
|
...queryModels.reduce((prev, model) => {
|
|
911
895
|
return Object.assign(prev, {
|
|
@@ -928,7 +912,7 @@ module.exports = class Schema {
|
|
|
928
912
|
const model = schema.models[modelName];
|
|
929
913
|
return context[schema.namespace].resolver.match(model).id(id).info(info).one().then((result) => {
|
|
930
914
|
if (result == null) return result;
|
|
931
|
-
result.__typename = modelName;
|
|
915
|
+
result.__typename = modelName;
|
|
932
916
|
return result;
|
|
933
917
|
});
|
|
934
918
|
},
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
const API = jest.requireActual('../Emitter');
|
|
2
|
-
const { mergeDeep } = require('../../service/AppService');
|
|
3
|
-
|
|
4
|
-
const { emit } = API;
|
|
5
|
-
|
|
6
|
-
API.emit = (eventName, data) => {
|
|
7
|
-
if (API.cloneData) data = { ...data, query: mergeDeep({}, data.query) };
|
|
8
|
-
return emit.call(API, eventName, data);
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
module.exports = API;
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* We need to export Pipeline as a POJO so jest can spy on it
|
|
3
|
-
* This is because all the methods are defined using Object.defineProperty
|
|
4
|
-
*/
|
|
5
|
-
const Pipeline = jest.requireActual('../Pipeline');
|
|
6
|
-
const Util = require('@coderich/util');
|
|
7
|
-
|
|
8
|
-
const API = {};
|
|
9
|
-
|
|
10
|
-
Pipeline.resolve = (params, pipeline) => {
|
|
11
|
-
const transformers = params.field.pipelines[pipeline] || [];
|
|
12
|
-
|
|
13
|
-
return transformers.reduce((value, t) => {
|
|
14
|
-
return Util.uvl(API[t]({ ...params, value }), value);
|
|
15
|
-
}, params.value);
|
|
16
|
-
|
|
17
|
-
// return Util.pipeline(transformers.map(t => (value) => {
|
|
18
|
-
// return API[t]({ ...params, value });
|
|
19
|
-
// }), params.value);
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
Object.getOwnPropertyNames(Pipeline).reduce((prev, key) => {
|
|
23
|
-
return Object.assign(prev, { [key]: Pipeline[key] });
|
|
24
|
-
}, API);
|
|
25
|
-
|
|
26
|
-
// For those defined outside of Pipeline.js itself
|
|
27
|
-
API.define = (key, ...args) => {
|
|
28
|
-
Pipeline.define(key, ...args);
|
|
29
|
-
API[key] = Pipeline[key];
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
module.exports = API;
|