Skip to content

Commit a7d33f6

Browse files
committed
[fix] Fixes API Merging
- Fixes an Issue where the API defined in the app.api wouldn't overwrite a spool.api namespace conflict. - Fixes an Issue where spool.api was not being properly merged within a namespace conflict.
1 parent 524a256 commit a7d33f6

File tree

7 files changed

+247
-31
lines changed

7 files changed

+247
-31
lines changed

lib/Core.ts

+96-26
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { union } from 'lodash'
1+
import { union, defaultsDeep } from 'lodash'
22
import { FabrixApp } from './'
33
import * as mkdirp from 'mkdirp'
44
import { Templates } from './'
@@ -115,8 +115,8 @@ export const Core = {
115115
/**
116116
* Instantiate resource classes and bind resource methods
117117
*/
118-
bindResourceMethods(app: FabrixApp, defaults: string[]): void {
119-
defaults.forEach(resource => {
118+
bindResourceMethods(app: FabrixApp, resources: string[]): void {
119+
resources.forEach(resource => {
120120
try {
121121
app[resource] = Core.bindMethods(app, resource)
122122
}
@@ -148,42 +148,112 @@ export const Core = {
148148
return props
149149
},
150150

151+
/**
152+
* Get the property names of an Object
153+
*/
154+
getPropertyNames(obj) {
155+
return Object.getOwnPropertyNames(obj.prototype)
156+
},
157+
158+
/**
159+
* If the object has a prototype property
160+
*/
161+
hasPrototypeProperty(obj, proto) {
162+
return obj.prototype.hasOwnProperty(proto)
163+
},
164+
165+
/**
166+
* Merge the Prototype of uninitiated classes
167+
*/
168+
mergePrototype(obj, next, proto) {
169+
obj.prototype[proto] = next.prototype[proto]
170+
},
171+
151172
/**
152173
* Merge the app api resources with the ones provided by the spools
153174
* Given that they are allowed by app.config.main.resources
154175
*/
155-
mergeApi (app: FabrixApp, spool: Spool) {
156-
// Use the setter to see if any new api resources from the spool can be applied
157-
app.resources = union(app.resources, Object.keys(app.api), Object.keys(spool.api))
158-
// Foreach resource, bind the spool.api into the app.api
159-
app.resources.forEach( resource => {
160-
// If there is a conflict, resolve it at the resource level
161-
if (app.api.hasOwnProperty(resource) && spool.api.hasOwnProperty(resource)) {
162-
Core.mergeApiResource(app, spool, resource)
163-
}
164-
// Else define the resource in the app.api and merge the spool.api resource into it
165-
else if (!app.api.hasOwnProperty(resource) && spool.api.hasOwnProperty(resource)) {
166-
app.api[resource] = {}
167-
Object.assign(
168-
(app.api[resource] || {}),
169-
(spool.api[resource] || {})
170-
)
171-
}
176+
mergeApi (app: FabrixApp) {
177+
const spools = Object.keys(app.spools).reverse()
178+
app.resources.forEach(resource => {
179+
spools.forEach(s => {
180+
// Add the defaults from the spools Apis
181+
defaultsDeep(app.api, app.spools[s].api)
182+
// Deep merge the Api
183+
Core.mergeApiResource(app, app.spools[s], resource)
184+
})
172185
})
173186
},
174187

175188
/**
176-
* Merge the Spool Api Resource if not already defined by App Api
189+
* Adds Api resources that were not merged by default
177190
*/
178191
mergeApiResource (app: FabrixApp, spool: Spool, resource: string) {
179-
const spoolApiResources = Object.keys(spool.api[resource])
180-
spoolApiResources.forEach(k => {
181-
if (!app.api[resource].hasOwnProperty(k)) {
182-
app.api[resource][k] = spool.api[resource][k]
183-
}
192+
if (app.api.hasOwnProperty(resource) && spool.api.hasOwnProperty(resource)) {
193+
Object.keys(spool.api[resource]).forEach(method => {
194+
Core.mergeApiResourceMethod(app, spool, resource, method)
195+
})
196+
}
197+
},
198+
199+
/**
200+
* Adds Api resource methods that were not merged by default
201+
*/
202+
mergeApiResourceMethod (app: FabrixApp, spool: Spool, resource: string, method: string) {
203+
if (spool.api[resource].hasOwnProperty(method) && app.api[resource].hasOwnProperty(method)) {
204+
const spoolProto = Core.getPropertyNames(spool.api[resource][method])
205+
spoolProto.forEach(proto => {
206+
if (!Core.hasPrototypeProperty(app.api[resource][method], proto)) {
207+
Core.mergePrototype(app.api[resource][method], spool.api[resource][method], proto)
208+
app.log.silly(`${spool.name}.api.${resource}.${method}.${proto} extending app.api.${resource}.${method}.${proto}`)
209+
}
210+
})
211+
}
212+
},
213+
214+
/**
215+
* Merge the spool api resources with the ones provided by other spools
216+
* Given that they are allowed by app.config.main.resources
217+
*/
218+
mergeSpoolApi (app: FabrixApp, spool: Spool) {
219+
app.resources = union(app.resources, Object.keys(app.api), Object.keys(spool.api))
220+
221+
const spools = Object.keys(app.spools)
222+
.filter(s => s !== spool.name)
223+
224+
app.resources.forEach(resource => {
225+
spools.forEach(s => {
226+
Core.mergeSpoolApiResource(app, app.spools[s], spool, resource)
227+
})
184228
})
185229
},
186230

231+
/**
232+
* Merge two Spools Api Resources's in order of their load
233+
*/
234+
mergeSpoolApiResource (app: FabrixApp, spool: Spool, next: Spool, resource: string) {
235+
if (spool.api.hasOwnProperty(resource) && next.api.hasOwnProperty(resource)) {
236+
Object.keys(next.api[resource]).forEach(method => {
237+
Core.mergeSpoolApiResourceMethod(app, spool, next, resource, method)
238+
})
239+
}
240+
},
241+
242+
/**
243+
* Merge two Spools Api Resources Method's in order of their load
244+
*/
245+
mergeSpoolApiResourceMethod (app: FabrixApp, spool: Spool, next: Spool, resource: string, method: string) {
246+
if (spool.api[resource].hasOwnProperty(method) && next.api[resource].hasOwnProperty(method)) {
247+
const spoolProto = Core.getPropertyNames(spool.api[resource][method])
248+
spoolProto.forEach(proto => {
249+
if (!Core.hasPrototypeProperty(next.api[resource][method], proto)) {
250+
Core.mergePrototype(next.api[resource][method], spool.api[resource][method], proto)
251+
app.log.silly(`${spool.name}.api.${resource}.${method}.${proto} extending ${next.name}.api.${resource}.${method}.${proto}`)
252+
}
253+
})
254+
}
255+
},
256+
187257
/**
188258
* Merge extensions provided by spools into the app
189259
*/

lib/Fabrix.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ export class FabrixApp extends EventEmitter {
115115
// Merge extensions into app.<ext>
116116
Core.mergeExtensions(this, spool)
117117
// Merge the spool.api with app.api
118-
Core.mergeApi(this, spool)
118+
Core.mergeSpoolApi(this, spool)
119119
// Bind the Spool Listeners to app.emit
120120
Core.bindSpoolMethodListeners(this, spool)
121121
}
@@ -125,6 +125,8 @@ export class FabrixApp extends EventEmitter {
125125
}
126126
})
127127

128+
// Merge the API from the spools
129+
Core.mergeApi(this)
128130
// Instantiate resource classes and bind resource methods
129131
Core.bindResourceMethods(this, this.resources)
130132
// Bind Application Listeners

package-lock.json

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@fabrix/fabrix",
3-
"version": "1.0.0",
3+
"version": "1.0.1",
44
"description": "Strongly Typed Modern Web Application Framework for Node.js",
55
"keywords": [
66
"framework",

test/integration/fabrixapp.test.js

+88-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const Controller = require('../../dist/common').FabrixController
77
const Testspool = require('./testspool')
88
const Testspool2 = require('./testspool2')
99
const Testspool3 = require('./testspool3')
10+
const Testspool4 = require('./testspool4')
1011
const testAppDefinition = require('./testapp')
1112
const lib = require('../../dist/index')
1213

@@ -271,7 +272,93 @@ describe('Fabrix', () => {
271272
}
272273
}
273274
const app = new FabrixApp(def)
274-
assert.equal(app.controllers.Test2Controller.foo(), 'bar')
275+
assert.equal(app.controllers.TestController.foo(), 'bar')
276+
assert.equal(app.controllers.Test2Controller.foo(), 'baz')
277+
})
278+
279+
it('(sanity) should combine api resources in the same name space from different spools in order', () => {
280+
const def = {
281+
pkg: { },
282+
api: { },
283+
config: {
284+
main: {
285+
spools: [
286+
Testspool3,
287+
Testspool4
288+
]
289+
}
290+
}
291+
}
292+
const app = new FabrixApp(def)
293+
294+
assert.equal(app.controllers.TestController.foo(), 'barf')
295+
assert.equal(app.controllers.Test2Controller.foo(), 'barf')
296+
assert.equal(app.controllers.Test3Controller.foo(), 'baz')
297+
assert.equal(app.controllers.Test4Controller.foo(), 'barf')
298+
299+
assert.equal(app.controllers.TestController.kaz(), 'ba')
300+
assert.equal(app.controllers.Test2Controller.kaz(), 'baf')
301+
assert.equal(app.controllers.Test3Controller.kaz(), 'ba')
302+
assert.equal(app.controllers.Test4Controller.kaz(), 'baf')
303+
})
304+
305+
it('(sanity) should combine root api resources in the same name space from different spools in order', () => {
306+
const def = {
307+
pkg: { },
308+
api: {
309+
controllers: {
310+
TestController: class TestController extends Controller {
311+
foo() {
312+
return 'bar'
313+
}
314+
}
315+
}
316+
},
317+
config: {
318+
main: {
319+
spools: [
320+
Testspool3,
321+
Testspool4
322+
]
323+
}
324+
}
325+
}
326+
const app = new FabrixApp(def)
327+
328+
assert.equal(app.controllers.TestController.foo(), 'bar')
329+
assert.equal(app.controllers.Test2Controller.foo(), 'barf')
330+
assert.equal(app.controllers.Test3Controller.foo(), 'baz')
331+
assert.equal(app.controllers.Test4Controller.foo(), 'barf')
332+
333+
assert.equal(app.controllers.TestController.kaz(), 'ba')
334+
assert.equal(app.controllers.Test2Controller.kaz(), 'baf')
335+
assert.equal(app.controllers.Test3Controller.kaz(), 'ba')
336+
assert.equal(app.controllers.Test4Controller.kaz(), 'baf')
337+
})
338+
339+
it('(sanity) should ignore spool resource method becacuse it\'s defined in the app', () => {
340+
const def = {
341+
pkg: { },
342+
api: {
343+
controllers: {
344+
TestController: class TestController extends Controller {
345+
foo() {
346+
return 'bar'
347+
}
348+
}
349+
}
350+
},
351+
config: {
352+
main: {
353+
spools: [
354+
Testspool3,
355+
Testspool4,
356+
]
357+
}
358+
}
359+
}
360+
const app = new FabrixApp(def)
361+
assert.equal(app.controllers.TestController.foo(), 'bar')
275362
})
276363

277364
it('should have default resources', () => {

test/integration/testspool3.js

+15-1
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,24 @@ module.exports = class Testspool3 extends Spool {
1919
foo() {
2020
return 'baz'
2121
}
22+
kaz() {
23+
return 'ba'
24+
}
2225
},
2326
Test2Controller: class Test2Controller extends Controller {
2427
foo() {
25-
return 'bar'
28+
return 'baz'
29+
}
30+
kaz() {
31+
return 'ba'
32+
}
33+
},
34+
Test3Controller: class Test3Controller extends Controller {
35+
foo() {
36+
return 'baz'
37+
}
38+
kaz() {
39+
return 'ba'
2640
}
2741
}
2842
}

test/integration/testspool4.js

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
const Spool = require('../../dist/common').Spool
2+
const Controller = require('../../dist/common').FabrixController
3+
4+
module.exports = class Testspool4 extends Spool {
5+
constructor (app) {
6+
super(app, {
7+
pkg: {
8+
name: 'spool-test4'
9+
},
10+
config: {
11+
test: {
12+
val: 0,
13+
otherval: 1
14+
}
15+
},
16+
api: {
17+
controllers: {
18+
TestController: class TestController extends Controller {
19+
foo() {
20+
return 'barf'
21+
}
22+
},
23+
Test2Controller: class Test2Controller extends Controller {
24+
foo() {
25+
return 'barf'
26+
}
27+
kaz() {
28+
return 'baf'
29+
}
30+
},
31+
Test4Controller: class Test4Controller extends Controller {
32+
foo() {
33+
return 'barf'
34+
}
35+
kaz() {
36+
return 'baf'
37+
}
38+
}
39+
}
40+
}
41+
})
42+
}
43+
}

0 commit comments

Comments
 (0)