@nextage/nx-frame-be 1.0.46 → 1.0.48
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/build/common/base/__test__/tenancy-opt-out.test.js +12 -0
- package/build/common/base/base.model.d.ts +7 -0
- package/build/common/base/base.model.js +9 -0
- package/build/common/graphql/__test__/generator.overrides.test.d.ts +1 -0
- package/build/common/graphql/__test__/generator.overrides.test.js +116 -0
- package/build/common/graphql/generator.js +62 -21
- package/build/common/graphql/interfaces.d.ts +24 -0
- package/package.json +4 -1
|
@@ -32,6 +32,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
32
32
|
const constants_1 = require("../../constants");
|
|
33
33
|
const base_model_1 = require("../base.model");
|
|
34
34
|
const mongo_utils_1 = require("../mongo-utils");
|
|
35
|
+
const mongo_utils_2 = require("../mongo-utils");
|
|
35
36
|
const enums_1 = require("../../enums");
|
|
36
37
|
/** Marks a document every tenant shares, as an application would mark it. */
|
|
37
38
|
const SHARED = '__GLOBAL__';
|
|
@@ -125,6 +126,17 @@ describe('a model that overrides nothing is scoped anyway', () => {
|
|
|
125
126
|
yield expect(unaware.remove(doc.id, outsider)).rejects.toBeDefined();
|
|
126
127
|
expect(yield unaware.mgModel.collection.countDocuments({})).toBe(1);
|
|
127
128
|
}));
|
|
129
|
+
// A refusal that only happens when the document EXISTS is an oracle: delete an id at
|
|
130
|
+
// random and the answer tells you whether it names something in somebody else's tenant.
|
|
131
|
+
// The two outcomes must therefore be indistinguishable - the existence of another
|
|
132
|
+
// tenant's document is not a fact this caller is entitled to learn.
|
|
133
|
+
it('refuses a delete the same way whether the document is missing or another tenant\'s', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
134
|
+
const doc = yield unaware.create({ code: 'A' }, owner);
|
|
135
|
+
const missing = (0, mongo_utils_2.genObjectId)().toHexString();
|
|
136
|
+
const foreign = yield unaware.remove(doc.id, outsider).then(() => null, (e) => e.constructor.name);
|
|
137
|
+
const absent = yield unaware.remove(missing, outsider).then(() => null, (e) => e.constructor.name);
|
|
138
|
+
expect(absent).toBe(foreign);
|
|
139
|
+
}));
|
|
128
140
|
it('refuses to update another tenant document', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
129
141
|
const doc = yield unaware.create({ code: 'A' }, owner);
|
|
130
142
|
yield expect(unaware.update(doc.id, { code: 'B' }, outsider)).rejects.toBeDefined();
|
|
@@ -214,6 +214,13 @@ export declare class BaseModel<TAttrs, TDoc extends BaseMongoDoc, TMongoModel ex
|
|
|
214
214
|
* id. The document is re-read unscoped on purpose - the point is to judge its real
|
|
215
215
|
* owner, and a scoped read would hide the very case being guarded against.
|
|
216
216
|
*
|
|
217
|
+
* A MISSING document is refused in exactly the same way as a foreign one, and that is
|
|
218
|
+
* not defensive coding: refusing only when the document exists turns this hook into an
|
|
219
|
+
* oracle. Delete an id at random and the outcome answers a question the caller was
|
|
220
|
+
* never entitled to ask - whether that id names something in somebody else's tenant.
|
|
221
|
+
* The two cases are therefore made indistinguishable, which costs nothing: deleting an
|
|
222
|
+
* id that matches nothing had no effect to begin with.
|
|
223
|
+
*
|
|
217
224
|
* @param {*} id
|
|
218
225
|
*/
|
|
219
226
|
beforeRemove(id: string, context: NxContext): Promise<string>;
|
|
@@ -410,12 +410,21 @@ class BaseModel extends events_1.EventEmitter {
|
|
|
410
410
|
* id. The document is re-read unscoped on purpose - the point is to judge its real
|
|
411
411
|
* owner, and a scoped read would hide the very case being guarded against.
|
|
412
412
|
*
|
|
413
|
+
* A MISSING document is refused in exactly the same way as a foreign one, and that is
|
|
414
|
+
* not defensive coding: refusing only when the document exists turns this hook into an
|
|
415
|
+
* oracle. Delete an id at random and the outcome answers a question the caller was
|
|
416
|
+
* never entitled to ask - whether that id names something in somebody else's tenant.
|
|
417
|
+
* The two cases are therefore made indistinguishable, which costs nothing: deleting an
|
|
418
|
+
* id that matches nothing had no effect to begin with.
|
|
419
|
+
*
|
|
413
420
|
* @param {*} id
|
|
414
421
|
*/
|
|
415
422
|
beforeRemove(id, context) {
|
|
416
423
|
return __awaiter(this, void 0, void 0, function* () {
|
|
417
424
|
if (this.tenancy && id) {
|
|
418
425
|
const doc = yield this.mgModel.findOne({ _id: (0, mongo_utils_1.toObjectId)(id) });
|
|
426
|
+
if (!doc)
|
|
427
|
+
throw new errors_1.ForbiddenError();
|
|
419
428
|
this.assertWritable(doc, context);
|
|
420
429
|
}
|
|
421
430
|
return id;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/* -------------------------------------------------------------------------- */
|
|
3
|
+
/* Per-operation @auth role overrides */
|
|
4
|
+
/* */
|
|
5
|
+
/* These tests assert the SDL emitted by the generator, not a running server: */
|
|
6
|
+
/* the point is WHICH directive is attached to WHICH generated operation. */
|
|
7
|
+
/* Mounting Apollo here is impossible anyway, since registering the same */
|
|
8
|
+
/* controller twice collides on `type Mutation`. */
|
|
9
|
+
/* -------------------------------------------------------------------------- */
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
const coded_entity_controller_1 = require("../../models/coded-entity.controller");
|
|
12
|
+
const generator_1 = require("../generator");
|
|
13
|
+
const loader_1 = require("../loader");
|
|
14
|
+
const READ = 'READ_ROLE';
|
|
15
|
+
const WRITE = 'WRITE_ROLE';
|
|
16
|
+
const NARROW = 'CROSS_TENANT_ROLE';
|
|
17
|
+
/**
|
|
18
|
+
* `loadSchema` APPENDS to the process-wide `mainSchema`, so every scenario has to
|
|
19
|
+
* start from an empty accumulator or it would read the previous scenario's SDL.
|
|
20
|
+
*/
|
|
21
|
+
function generate(roles) {
|
|
22
|
+
loader_1.mainSchema.typeDefs.query = '';
|
|
23
|
+
loader_1.mainSchema.typeDefs.mutation = '';
|
|
24
|
+
loader_1.mainSchema.typeDefs.subscription = '';
|
|
25
|
+
loader_1.mainSchema.typeDefs.type = '';
|
|
26
|
+
loader_1.mainSchema.resolvers = {};
|
|
27
|
+
(0, generator_1.generateSchema)(coded_entity_controller_1.ceCtrl, roles ? { roles } : {});
|
|
28
|
+
return {
|
|
29
|
+
query: loader_1.mainSchema.typeDefs.query,
|
|
30
|
+
mutation: loader_1.mainSchema.typeDefs.mutation,
|
|
31
|
+
subscription: loader_1.mainSchema.typeDefs.subscription
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Return the `@auth` directive argument attached to one generated operation, or
|
|
36
|
+
* `null` when the operation is absent from the SDL.
|
|
37
|
+
*
|
|
38
|
+
* IMPORTANT: the match is anchored on the FIELD (`name(`), never on the bare name:
|
|
39
|
+
* `codedEntity` is a prefix of `codedEntityList`, and asserting on the bare name
|
|
40
|
+
* would silently read the wrong line.
|
|
41
|
+
*/
|
|
42
|
+
function authOf(sdl, operation) {
|
|
43
|
+
var _a;
|
|
44
|
+
const line = sdl.split('\n').find(l => l.trim().startsWith(`${operation}(`));
|
|
45
|
+
if (!line)
|
|
46
|
+
return null;
|
|
47
|
+
const match = line.match(/@auth(\(requires: "[^"]*"\))?/);
|
|
48
|
+
return match ? ((_a = match[1]) !== null && _a !== void 0 ? _a : '') : null;
|
|
49
|
+
}
|
|
50
|
+
const requires = (roles) => `(requires: "${roles}")`;
|
|
51
|
+
/* -------------------------------------------------------------------------- */
|
|
52
|
+
/* Default behaviour (non-regression) */
|
|
53
|
+
/* -------------------------------------------------------------------------- */
|
|
54
|
+
it('keeps read on queries and write on mutations when no override is given', () => {
|
|
55
|
+
const sdl = generate({ read: READ, write: WRITE });
|
|
56
|
+
expect(authOf(sdl.query, 'codedEntityCount')).toEqual(requires(READ));
|
|
57
|
+
expect(authOf(sdl.query, 'codedEntityList')).toEqual(requires(READ));
|
|
58
|
+
expect(authOf(sdl.query, 'codedEntity')).toEqual(requires(READ));
|
|
59
|
+
expect(authOf(sdl.query, 'findOneCodedEntity')).toEqual(requires(READ));
|
|
60
|
+
expect(authOf(sdl.mutation, 'createCodedEntity')).toEqual(requires(WRITE));
|
|
61
|
+
expect(authOf(sdl.mutation, 'updateCodedEntity')).toEqual(requires(WRITE));
|
|
62
|
+
expect(authOf(sdl.mutation, 'removeCodedEntity')).toEqual(requires(WRITE));
|
|
63
|
+
expect(authOf(sdl.mutation, 'copyCodedEntity')).toEqual(requires(WRITE));
|
|
64
|
+
expect(authOf(sdl.mutation, 'bulkRemoveCodedEntity')).toEqual(requires(WRITE));
|
|
65
|
+
});
|
|
66
|
+
it('emits a bare @auth when a role list is not configured', () => {
|
|
67
|
+
const sdl = generate({ write: WRITE });
|
|
68
|
+
expect(authOf(sdl.query, 'codedEntityList')).toEqual('');
|
|
69
|
+
expect(authOf(sdl.mutation, 'removeCodedEntity')).toEqual(requires(WRITE));
|
|
70
|
+
});
|
|
71
|
+
it('still accepts a plain role list as read and write', () => {
|
|
72
|
+
const sdl = generate([READ, WRITE]);
|
|
73
|
+
expect(authOf(sdl.query, 'codedEntityList')).toEqual(requires(`${READ},${WRITE}`));
|
|
74
|
+
expect(authOf(sdl.mutation, 'removeCodedEntity')).toEqual(requires(`${READ},${WRITE}`));
|
|
75
|
+
});
|
|
76
|
+
/* -------------------------------------------------------------------------- */
|
|
77
|
+
/* Overrides */
|
|
78
|
+
/* -------------------------------------------------------------------------- */
|
|
79
|
+
it('narrows a single mutation and leaves its siblings on write', () => {
|
|
80
|
+
const sdl = generate({ read: READ, write: WRITE, overrides: { remove: NARROW } });
|
|
81
|
+
expect(authOf(sdl.mutation, 'removeCodedEntity')).toEqual(requires(NARROW));
|
|
82
|
+
expect(authOf(sdl.mutation, 'createCodedEntity')).toEqual(requires(WRITE));
|
|
83
|
+
expect(authOf(sdl.mutation, 'updateCodedEntity')).toEqual(requires(WRITE));
|
|
84
|
+
expect(authOf(sdl.mutation, 'copyCodedEntity')).toEqual(requires(WRITE));
|
|
85
|
+
expect(authOf(sdl.mutation, 'bulkRemoveCodedEntity')).toEqual(requires(WRITE));
|
|
86
|
+
expect(authOf(sdl.mutation, 'removeCodedEntityArrayItem')).toEqual(requires(WRITE));
|
|
87
|
+
});
|
|
88
|
+
it('narrows a single query and leaves its siblings on read', () => {
|
|
89
|
+
const sdl = generate({ read: READ, write: WRITE, overrides: { list: NARROW } });
|
|
90
|
+
expect(authOf(sdl.query, 'codedEntityList')).toEqual(requires(NARROW));
|
|
91
|
+
expect(authOf(sdl.query, 'codedEntityCount')).toEqual(requires(READ));
|
|
92
|
+
expect(authOf(sdl.query, 'codedEntity')).toEqual(requires(READ));
|
|
93
|
+
expect(authOf(sdl.query, 'findOneCodedEntity')).toEqual(requires(READ));
|
|
94
|
+
});
|
|
95
|
+
it('joins a multi-role override into a single requires argument', () => {
|
|
96
|
+
const sdl = generate({ read: READ, write: WRITE, overrides: { remove: [NARROW, 'ADMIN'] } });
|
|
97
|
+
expect(authOf(sdl.mutation, 'removeCodedEntity')).toEqual(requires(`${NARROW},ADMIN`));
|
|
98
|
+
});
|
|
99
|
+
/* -------------------------------------------------------------------------- */
|
|
100
|
+
/* Fail-safe */
|
|
101
|
+
/* -------------------------------------------------------------------------- */
|
|
102
|
+
it('falls back to the default instead of publishing the operation when an override is empty', () => {
|
|
103
|
+
const empty = generate({ read: READ, write: WRITE, overrides: { remove: [], list: '' } });
|
|
104
|
+
expect(authOf(empty.mutation, 'removeCodedEntity')).toEqual(requires(WRITE));
|
|
105
|
+
expect(authOf(empty.query, 'codedEntityList')).toEqual(requires(READ));
|
|
106
|
+
const missing = generate({ read: READ, write: WRITE, overrides: { remove: undefined } });
|
|
107
|
+
expect(authOf(missing.mutation, 'removeCodedEntity')).toEqual(requires(WRITE));
|
|
108
|
+
});
|
|
109
|
+
/* -------------------------------------------------------------------------- */
|
|
110
|
+
/* Subscriptions */
|
|
111
|
+
/* -------------------------------------------------------------------------- */
|
|
112
|
+
it('does not let an override reach the subscriptions, which always follow read', () => {
|
|
113
|
+
const sdl = generate({ read: READ, write: WRITE, overrides: { remove: NARROW } });
|
|
114
|
+
expect(sdl.subscription.match(new RegExp(`@auth\\(requires: "${READ}"\\)`, 'g'))).toHaveLength(3);
|
|
115
|
+
expect(sdl.subscription).not.toContain(NARROW);
|
|
116
|
+
});
|
|
@@ -79,6 +79,19 @@ exports.foldedEntityFields = {
|
|
|
79
79
|
`
|
|
80
80
|
};
|
|
81
81
|
exports.mainSubChannel = 'mainCH';
|
|
82
|
+
/**
|
|
83
|
+
* Every base operation generated by generateTypeDefs, in declaration order.
|
|
84
|
+
*/
|
|
85
|
+
const baseMethodNames = [
|
|
86
|
+
'count', 'list', 'get', 'findOne',
|
|
87
|
+
'create', 'update', 'remove', 'copy',
|
|
88
|
+
'addArrayItem', 'updateArrayItem', 'removeArrayItem',
|
|
89
|
+
'bulkUpdate', 'bulkRemove'
|
|
90
|
+
];
|
|
91
|
+
/**
|
|
92
|
+
* Base operations that default to the `read` roles; every other one defaults to `write`.
|
|
93
|
+
*/
|
|
94
|
+
const readMethodNames = ['count', 'list', 'get', 'findOne'];
|
|
82
95
|
/**
|
|
83
96
|
*
|
|
84
97
|
* @param modelName
|
|
@@ -119,24 +132,52 @@ function getSubscriptionNames(modelName) {
|
|
|
119
132
|
};
|
|
120
133
|
}
|
|
121
134
|
/**
|
|
135
|
+
* Formats a role list as an `@auth` directive argument list.
|
|
136
|
+
*
|
|
137
|
+
* @param roles
|
|
138
|
+
* @returns `(requires: "ROLE_A,ROLE_B")`, or an empty string when no role is required
|
|
139
|
+
*/
|
|
140
|
+
function toAuthDirective(roles) {
|
|
141
|
+
if (!roles)
|
|
142
|
+
return '';
|
|
143
|
+
return `(requires: "${Array.isArray(roles) ? roles.join(',') : roles}")`;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Resolves the `@auth` directive of every generated operation.
|
|
147
|
+
*
|
|
148
|
+
* Read operations default to `roles.read` and the remaining ones to `roles.write`; a per-operation
|
|
149
|
+
* entry in `roles.overrides` replaces that default for that operation only.
|
|
150
|
+
*
|
|
151
|
+
* IMPORTANT: an override is applied only when it carries roles. An entry whose value is empty or
|
|
152
|
+
* undefined is treated as ABSENT and the default stands, because the alternative fails OPEN: a
|
|
153
|
+
* mistyped constant, or one that did not resolve, would silently strip the directive and publish
|
|
154
|
+
* the operation to everyone. An operation meant to require no role is expressed by leaving the
|
|
155
|
+
* module's `roles` out, not by an empty override.
|
|
122
156
|
*
|
|
123
157
|
* @param extension
|
|
124
|
-
* @returns
|
|
158
|
+
* @returns the directive suffix of each base operation, empty string when unconstrained
|
|
125
159
|
*/
|
|
126
160
|
function getAuth(extension = {}) {
|
|
127
|
-
|
|
161
|
+
var _a;
|
|
162
|
+
const methods = {};
|
|
163
|
+
const auth = { read: '', write: '', methods };
|
|
164
|
+
for (const method of baseMethodNames)
|
|
165
|
+
methods[method] = '';
|
|
128
166
|
if (!extension.roles)
|
|
129
|
-
return
|
|
167
|
+
return auth;
|
|
130
168
|
if ((0, utils_1.isString)(extension.roles))
|
|
131
169
|
extension.roles = extension.roles.split(',');
|
|
132
170
|
if (Array.isArray(extension.roles))
|
|
133
171
|
extension.roles = { read: extension.roles, write: extension.roles };
|
|
134
172
|
const sr = extension.roles;
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
173
|
+
auth.read = toAuthDirective(sr.read);
|
|
174
|
+
auth.write = toAuthDirective(sr.write);
|
|
175
|
+
for (const method of baseMethodNames) {
|
|
176
|
+
const override = (_a = sr.overrides) === null || _a === void 0 ? void 0 : _a[method];
|
|
177
|
+
const fallback = readMethodNames.includes(method) ? auth.read : auth.write;
|
|
178
|
+
methods[method] = (override === null || override === void 0 ? void 0 : override.length) ? toAuthDirective(override) : fallback;
|
|
179
|
+
}
|
|
180
|
+
return auth;
|
|
140
181
|
}
|
|
141
182
|
/**
|
|
142
183
|
*
|
|
@@ -163,23 +204,23 @@ function generateTypeDefs(ctrl, schemaExtension = {}, mergeCodedEntity, readOnly
|
|
|
163
204
|
dataFields = exports.codedEntityFields;
|
|
164
205
|
const baseTypeDefs = {
|
|
165
206
|
query: `
|
|
166
|
-
${methods.count}(params: ${modelName}Query) : Int! @auth${roles.
|
|
167
|
-
${methods.list}(params: ${modelName}Query, pagination: OffsetPagination): [${modelName}]! @auth${roles.
|
|
168
|
-
${methods.get}(id: ID!) : ${modelName}! @auth${roles.
|
|
169
|
-
${methods.findOne}(params: ${modelName}Query) : ${modelName} @auth${roles.
|
|
207
|
+
${methods.count}(params: ${modelName}Query) : Int! @auth${roles.methods.count}
|
|
208
|
+
${methods.list}(params: ${modelName}Query, pagination: OffsetPagination): [${modelName}]! @auth${roles.methods.list}
|
|
209
|
+
${methods.get}(id: ID!) : ${modelName}! @auth${roles.methods.get}
|
|
210
|
+
${methods.findOne}(params: ${modelName}Query) : ${modelName} @auth${roles.methods.findOne}
|
|
170
211
|
`,
|
|
171
212
|
mutation: `
|
|
172
|
-
${methods.create}(item: ${modelName}Input!) : ${modelName}! @auth${roles.
|
|
173
|
-
${methods.update}(id: ID!, item: ${modelName}Input!) : ${modelName}! @auth${roles.
|
|
174
|
-
${methods.remove}(id: ID!) : ${modelName}! @auth${roles.
|
|
175
|
-
${methods.copy}(id: ID!, item: ${modelName}Input!) : ${modelName}! @auth${roles.
|
|
213
|
+
${methods.create}(item: ${modelName}Input!) : ${modelName}! @auth${roles.methods.create}
|
|
214
|
+
${methods.update}(id: ID!, item: ${modelName}Input!) : ${modelName}! @auth${roles.methods.update}
|
|
215
|
+
${methods.remove}(id: ID!) : ${modelName}! @auth${roles.methods.remove}
|
|
216
|
+
${methods.copy}(id: ID!, item: ${modelName}Input!) : ${modelName}! @auth${roles.methods.copy}
|
|
176
217
|
|
|
177
|
-
${methods.addArrayItem}(id: ID!, item: AddArrayElemItem!) : ${modelName}! @auth${roles.
|
|
178
|
-
${methods.updateArrayItem}(id: ID!, item: UpdateArrayElemItem!) : ${modelName}! @auth${roles.
|
|
179
|
-
${methods.removeArrayItem}(id: ID!, item: RemoveArrayElemItem!) : ${modelName}! @auth${roles.
|
|
218
|
+
${methods.addArrayItem}(id: ID!, item: AddArrayElemItem!) : ${modelName}! @auth${roles.methods.addArrayItem}
|
|
219
|
+
${methods.updateArrayItem}(id: ID!, item: UpdateArrayElemItem!) : ${modelName}! @auth${roles.methods.updateArrayItem}
|
|
220
|
+
${methods.removeArrayItem}(id: ID!, item: RemoveArrayElemItem!) : ${modelName}! @auth${roles.methods.removeArrayItem}
|
|
180
221
|
|
|
181
|
-
${methods.bulkUpdate}(ids: [ID]!, items: [${modelName}Input]!) : [${modelName}]! @auth${roles.
|
|
182
|
-
${methods.bulkRemove}(ids: [ID]!) : [${modelName}]! @auth${roles.
|
|
222
|
+
${methods.bulkUpdate}(ids: [ID]!, items: [${modelName}Input]!) : [${modelName}]! @auth${roles.methods.bulkUpdate}
|
|
223
|
+
${methods.bulkRemove}(ids: [ID]!) : [${modelName}]! @auth${roles.methods.bulkRemove}
|
|
183
224
|
`,
|
|
184
225
|
subscription: `
|
|
185
226
|
${subscriptions.create.id}(channelId: String!, filter: ${modelName}Query): ${modelName}! @auth${roles.read}
|
|
@@ -64,4 +64,28 @@ export interface NxGraphQLSchemaExtension extends NxGraphQLSchemaData {
|
|
|
64
64
|
export interface NxGraphQLSchemaRole {
|
|
65
65
|
read?: string | string[];
|
|
66
66
|
write?: string | string[];
|
|
67
|
+
overrides?: NxGraphQLSchemaRoleOverrides;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Per-operation role overrides.
|
|
71
|
+
*
|
|
72
|
+
* Each key is one of the generated base methods (see NxGraphQLBaseMethodNames) and its value
|
|
73
|
+
* replaces the roles required by that single operation. Operations left out keep the default
|
|
74
|
+
* behaviour: `read` for count/list/get/findOne, `write` for every other operation.
|
|
75
|
+
*
|
|
76
|
+
* IMPORTANT: an empty or undefined value does NOT remove the role constraint, it falls back to
|
|
77
|
+
* the default. Publishing an operation without any constraint must stay an explicit choice made
|
|
78
|
+
* by omitting `read`/`write`, never an accident of a half-filled override map.
|
|
79
|
+
*
|
|
80
|
+
* Subscriptions are not overridable: they always follow `read`.
|
|
81
|
+
*/
|
|
82
|
+
export type NxGraphQLSchemaRoleOverrides = Partial<Record<keyof NxGraphQLBaseMethodNames, string | string[]>>;
|
|
83
|
+
/**
|
|
84
|
+
* Resolved `@auth` directive suffixes, ready to be interpolated into the generated SDL.
|
|
85
|
+
* Each value is either an empty string (no role constraint) or `(requires: "ROLE_A,ROLE_B")`.
|
|
86
|
+
*/
|
|
87
|
+
export interface NxGraphQLAuthDirectives {
|
|
88
|
+
read: string;
|
|
89
|
+
write: string;
|
|
90
|
+
methods: Record<keyof NxGraphQLBaseMethodNames, string>;
|
|
67
91
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextage/nx-frame-be",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.48",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "./build/index.js",
|
|
6
6
|
"types": "./build/index.d.ts",
|
|
@@ -16,6 +16,9 @@
|
|
|
16
16
|
"jest": {
|
|
17
17
|
"preset": "ts-jest",
|
|
18
18
|
"testEnvironment": "node",
|
|
19
|
+
"testPathIgnorePatterns": [
|
|
20
|
+
"<rootDir>/build/"
|
|
21
|
+
],
|
|
19
22
|
"setupFilesAfterEnv": [
|
|
20
23
|
"./src/test/setup.ts"
|
|
21
24
|
]
|