@modernpoacher/halacious 6.2.8 → 6.3.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/examples/{api-root.js → api-root.cjs} +1 -1
- package/examples/{auto-namespace.js → auto-namespace.cjs} +1 -1
- package/index.mjs +1 -0
- package/package.json +10 -11
- package/{types/index.d.ts → src/index.d.mts} +2 -2
- package/src/index.mjs +2 -0
- package/{lib/plugin.js → src/plugin.mjs} +33 -29
- package/src/representation.mjs +272 -0
- package/test/{plugin.spec.js → plugin.spec.mjs} +17 -15
- package/test/{representation.spec.js → representation.spec.mjs} +5 -6
- package/where-am-i.mjs +9 -0
- package/index.js +0 -3
- package/lib/index.js +0 -2
- package/lib/representation.js +0 -268
- /package/examples/{array-of-links.js → array-of-links.cjs} +0 -0
- /package/examples/{functional-href.js → functional-href.cjs} +0 -0
- /package/examples/{hello-world.js → hello-world.cjs} +0 -0
- /package/examples/{manual-namespace.js → manual-namespace.cjs} +0 -0
- /package/examples/{media-types.js → media-types.cjs} +0 -0
- /package/examples/{paging.js → paging.cjs} +0 -0
- /package/examples/{programmatic-entity.js → programmatic-entity.cjs} +0 -0
- /package/examples/{programmatic-route.js → programmatic-route.cjs} +0 -0
- /package/examples/{query-string.js → query-string.cjs} +0 -0
- /package/examples/{simple-embed.js → simple-embed.cjs} +0 -0
- /package/examples/{simple-link.js → simple-link.cjs} +0 -0
- /package/{index.d.ts → index.d.mts} +0 -0
|
@@ -2,7 +2,7 @@ const hapi = require('@hapi/hapi')
|
|
|
2
2
|
const vision = require('@hapi/vision')
|
|
3
3
|
const halacious = require('#halacious')
|
|
4
4
|
|
|
5
|
-
const
|
|
5
|
+
const PLUGIN = '@modernpoacher/halacious'
|
|
6
6
|
|
|
7
7
|
async function init () {
|
|
8
8
|
const server = hapi.server({ port: 8080 })
|
|
@@ -4,7 +4,7 @@ const hapi = require('@hapi/hapi')
|
|
|
4
4
|
const vision = require('@hapi/vision')
|
|
5
5
|
const halacious = require('#halacious')
|
|
6
6
|
|
|
7
|
-
const
|
|
7
|
+
const PLUGIN = '@modernpoacher/halacious'
|
|
8
8
|
|
|
9
9
|
async function init () {
|
|
10
10
|
const server = hapi.server({ port: 8080 })
|
package/index.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * as default from './src/index.mjs'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@modernpoacher/halacious",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.3.0",
|
|
4
4
|
"description": "A HAL processor for Hapi",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"HAL",
|
|
@@ -8,11 +8,11 @@
|
|
|
8
8
|
"hypermedia",
|
|
9
9
|
"HATEOAS"
|
|
10
10
|
],
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"author": "
|
|
11
|
+
"type": "module",
|
|
12
|
+
"main": "./src/index.mjs",
|
|
13
|
+
"author": "Jonathan Perry <modernpoacher@modernpoacher.com> (Modern Poacher Limited)",
|
|
14
14
|
"contributors": [
|
|
15
|
-
"
|
|
15
|
+
"Brad Leupen"
|
|
16
16
|
],
|
|
17
17
|
"license": "MIT",
|
|
18
18
|
"engines": {
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"url": "https://github.com/modernpoacher/halacious/issues"
|
|
28
28
|
},
|
|
29
29
|
"scripts": {
|
|
30
|
-
"lint": "eslint .",
|
|
30
|
+
"lint": "eslint . --ext .mjs --ext .cjs --ext .mts --ext .cts",
|
|
31
31
|
"lint:fix": "npm run lint -- --fix",
|
|
32
32
|
"test": "cross-env NODE_ENV=test mocha test",
|
|
33
33
|
"prepare": "husky install"
|
|
@@ -69,12 +69,11 @@
|
|
|
69
69
|
"sinon-chai": "^3.7.0"
|
|
70
70
|
},
|
|
71
71
|
"imports": {
|
|
72
|
-
"#halacious
|
|
73
|
-
"#halacious
|
|
74
|
-
"#
|
|
72
|
+
"#halacious/*": "./src/*.mjs",
|
|
73
|
+
"#halacious": "./src/index.mjs",
|
|
74
|
+
"#where-am-i": "./where-am-i.mjs"
|
|
75
75
|
},
|
|
76
76
|
"exports": {
|
|
77
|
-
"
|
|
78
|
-
".": "./lib/index.js"
|
|
77
|
+
".": "./src/index.mjs"
|
|
79
78
|
}
|
|
80
79
|
}
|
package/src/index.mjs
ADDED
|
@@ -1,34 +1,34 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
import joi from '@hapi/joi'
|
|
2
|
+
import boom from '@hapi/boom'
|
|
3
|
+
import hoek from '@hapi/hoek'
|
|
4
|
+
import _ from 'lodash'
|
|
5
|
+
import fs from 'node:fs'
|
|
6
|
+
import path from 'node:path'
|
|
7
|
+
import pug from 'pug'
|
|
8
|
+
import util from 'util'
|
|
9
|
+
import async from 'async'
|
|
10
|
+
import {
|
|
11
11
|
marked
|
|
12
|
-
}
|
|
13
|
-
|
|
12
|
+
} from 'marked'
|
|
13
|
+
import {
|
|
14
14
|
mangle
|
|
15
|
-
}
|
|
16
|
-
|
|
15
|
+
} from 'marked-mangle'
|
|
16
|
+
import {
|
|
17
17
|
gfmHeadingId
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
RepresentationFactory
|
|
27
|
-
} = require('./representation')
|
|
18
|
+
} from 'marked-gfm-heading-id'
|
|
19
|
+
import urlTemplate from 'url-template'
|
|
20
|
+
import Negotiator from 'negotiator'
|
|
21
|
+
import URITemplate from 'urijs/src/URITemplate.js'
|
|
22
|
+
import url from 'url'
|
|
23
|
+
import URI from 'urijs'
|
|
24
|
+
import IAM from '#where-am-i'
|
|
25
|
+
import RepresentationFactory from './representation.mjs'
|
|
28
26
|
|
|
29
27
|
const HAL_MIME_TYPE = 'application/hal+json'
|
|
30
28
|
|
|
31
|
-
const
|
|
29
|
+
const REG = /{([^{}]+)}|([^{}]+)/g
|
|
30
|
+
|
|
31
|
+
const PKG = JSON.parse(fs.readFileSync(path.join(IAM, './package.json')))
|
|
32
32
|
|
|
33
33
|
function reach (object, path) {
|
|
34
34
|
const parts = path ? path.split('.') : []
|
|
@@ -51,7 +51,7 @@ function flattenContext (template, ctx) {
|
|
|
51
51
|
let arr
|
|
52
52
|
const result = {}
|
|
53
53
|
|
|
54
|
-
while ((arr =
|
|
54
|
+
while ((arr = REG.exec(template)) !== null) {
|
|
55
55
|
if (arr[1]) {
|
|
56
56
|
const value = reach(ctx, arr[1])
|
|
57
57
|
result[arr[1]] = value && value.toString()
|
|
@@ -92,8 +92,8 @@ const optionsSchema = {
|
|
|
92
92
|
* @param opts
|
|
93
93
|
* @param next
|
|
94
94
|
*/
|
|
95
|
-
|
|
96
|
-
pkg:
|
|
95
|
+
const plugin = {
|
|
96
|
+
pkg: PKG,
|
|
97
97
|
|
|
98
98
|
async register (server, opts) {
|
|
99
99
|
let settings = opts
|
|
@@ -964,7 +964,7 @@ exports.plugin = {
|
|
|
964
964
|
engines: {
|
|
965
965
|
jade: pug
|
|
966
966
|
},
|
|
967
|
-
path: path.join(
|
|
967
|
+
path: path.join(IAM, './views'),
|
|
968
968
|
isCached: false
|
|
969
969
|
})
|
|
970
970
|
server.route({
|
|
@@ -983,3 +983,7 @@ exports.plugin = {
|
|
|
983
983
|
server.ext('onPreStart', internals.preStart)
|
|
984
984
|
}
|
|
985
985
|
}
|
|
986
|
+
|
|
987
|
+
export {
|
|
988
|
+
plugin
|
|
989
|
+
}
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
|
|
2
|
+
import _ from 'lodash'
|
|
3
|
+
import hoek from '@hapi/hoek'
|
|
4
|
+
import URI from 'urijs'
|
|
5
|
+
|
|
6
|
+
const templatedRE = /{*}/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A HAL wrapper interface around an entity. Provides an api for adding new links and recursively embedding child
|
|
10
|
+
* entities.
|
|
11
|
+
*
|
|
12
|
+
* @param factory
|
|
13
|
+
* @param self
|
|
14
|
+
* @param entity
|
|
15
|
+
* @param root
|
|
16
|
+
* @constructor
|
|
17
|
+
*/
|
|
18
|
+
export class Representation {
|
|
19
|
+
constructor (factory, self, entity, root) {
|
|
20
|
+
this._halacious = factory._halacious
|
|
21
|
+
this.factory = factory
|
|
22
|
+
this.request = factory._request
|
|
23
|
+
this._root = root || this
|
|
24
|
+
this.self = self.href
|
|
25
|
+
this._links = { self }
|
|
26
|
+
this._embedded = {}
|
|
27
|
+
this._namespaces = {}
|
|
28
|
+
this._props = {}
|
|
29
|
+
this._ignore = {}
|
|
30
|
+
this.entity = entity
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Adds a namespace to the 'curie' link collection. all curies in a response, top level or nested, should be declared
|
|
35
|
+
* in the top level _links collection. a reference '_root' is kept to the top level representation for this purpose
|
|
36
|
+
* @param namespace
|
|
37
|
+
*/
|
|
38
|
+
curie (namespace) {
|
|
39
|
+
if (namespace && !this._root._namespaces[namespace.prefix]) {
|
|
40
|
+
this._root._namespaces[namespace.prefix] = namespace
|
|
41
|
+
this._root._links.curies = this._root._links.curies || []
|
|
42
|
+
this._root._links.curies.push({
|
|
43
|
+
name: namespace.prefix,
|
|
44
|
+
href: `${this._halacious.namespaceUrl(this.request, namespace)}/{rel}`,
|
|
45
|
+
templated: true
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Adds a custom property to the HAL payload
|
|
52
|
+
* @param {String} name the property name
|
|
53
|
+
* @param {*} value the property value
|
|
54
|
+
* @return {Representation}
|
|
55
|
+
*/
|
|
56
|
+
prop (name, value) {
|
|
57
|
+
this._props[name] = value
|
|
58
|
+
return this
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Merges an object's properties into the custom properties collection.
|
|
63
|
+
* @param prop
|
|
64
|
+
*/
|
|
65
|
+
merge (prop) {
|
|
66
|
+
hoek.merge(this._props, prop)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* @param {...String || String[]} args properties to ignore
|
|
71
|
+
* @return {Representation}
|
|
72
|
+
*/
|
|
73
|
+
ignore (...args) {
|
|
74
|
+
const props = _.isArray(args[0]) ? args[0] : args
|
|
75
|
+
props.forEach(function (prop) {
|
|
76
|
+
this._ignore[prop] = true
|
|
77
|
+
}, this)
|
|
78
|
+
return this
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Prepares the representation for JSON serialization.
|
|
83
|
+
* @return {{}}
|
|
84
|
+
*/
|
|
85
|
+
toJSON () {
|
|
86
|
+
// initialize the json entity
|
|
87
|
+
const payload = { _links: this._links }
|
|
88
|
+
const self = this
|
|
89
|
+
|
|
90
|
+
// copy all target properties in the entity using JSON.stringify(). if the entity has a .toJSON() implementation,
|
|
91
|
+
// it will be called. properties on the ignore list will not be copied
|
|
92
|
+
const { entity } = this
|
|
93
|
+
JSON.stringify(entity, (key, value) => {
|
|
94
|
+
if (!key) {
|
|
95
|
+
return value
|
|
96
|
+
}
|
|
97
|
+
if (!self._ignore[key]) {
|
|
98
|
+
payload[key] = value
|
|
99
|
+
}
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
// merge in any extra properties
|
|
103
|
+
_.assign(payload, this._props)
|
|
104
|
+
|
|
105
|
+
const embeddedKeys = _.keys(this._embedded)
|
|
106
|
+
if (embeddedKeys.length > 0) {
|
|
107
|
+
payload._embedded = {}
|
|
108
|
+
|
|
109
|
+
const self = this
|
|
110
|
+
embeddedKeys.forEach((embedKey) => {
|
|
111
|
+
if (self._embedded[embedKey] instanceof Representation) {
|
|
112
|
+
payload._embedded[embedKey] = {}
|
|
113
|
+
} else if (self._embedded[embedKey] instanceof Array) {
|
|
114
|
+
payload._embedded[embedKey] = []
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
JSON.stringify(self._embedded[embedKey], (key, value) => {
|
|
118
|
+
if (!key) {
|
|
119
|
+
return value
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
payload._embedded[embedKey][key] = value
|
|
123
|
+
})
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return payload
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Creates a new link and adds it to the _links collection
|
|
132
|
+
* @param rel
|
|
133
|
+
* @param link
|
|
134
|
+
* @return {{} || []} the new link
|
|
135
|
+
*/
|
|
136
|
+
link (originalRel, link) {
|
|
137
|
+
const rel = this._halacious.rel(originalRel)
|
|
138
|
+
const qname = rel.qname()
|
|
139
|
+
|
|
140
|
+
if (_.isArray(link)) {
|
|
141
|
+
const that = this
|
|
142
|
+
this._links[qname] = []
|
|
143
|
+
return link.map((l) => that.link(originalRel, l), this)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// adds the namespace to the top level curie list
|
|
147
|
+
this.curie(rel.namespace)
|
|
148
|
+
|
|
149
|
+
link = this._halacious.link(link, this._links.self.href)
|
|
150
|
+
link.templated = templatedRE.test(link.href) ? true : undefined
|
|
151
|
+
// e.g. 'mco:rel'
|
|
152
|
+
if (!this._links[qname]) {
|
|
153
|
+
this._links[qname] = link
|
|
154
|
+
} else if (_.isArray(this._links[qname])) {
|
|
155
|
+
this._links[qname].push(link)
|
|
156
|
+
} else {
|
|
157
|
+
this._links[qname] = [this._links[qname], link]
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return link
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Resolves a relative path against the representation's self href
|
|
165
|
+
* @param relativePath
|
|
166
|
+
* @return {*}
|
|
167
|
+
*/
|
|
168
|
+
resolve (relativePath) {
|
|
169
|
+
return new URI(relativePath)
|
|
170
|
+
.absoluteTo(`${this._links.self.href}/`)
|
|
171
|
+
.toString()
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Returns the path to a named route (specified by the plugins.hal.name configuration parameter), expanding any supplied
|
|
176
|
+
* path parameters.
|
|
177
|
+
* @param {String} routeName the route's name
|
|
178
|
+
* @param {{}=} params for expanding templated urls
|
|
179
|
+
* @return {*}
|
|
180
|
+
*/
|
|
181
|
+
route (routeName, params) {
|
|
182
|
+
return this._halacious.route(routeName, params)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Wraps an entity into a HAL representation and adds it to the _embedded collection
|
|
187
|
+
* @param {String} rel the rel name
|
|
188
|
+
* @param {String || {}} self an href or link object for the entity
|
|
189
|
+
* @param {{} || []} entity an object to wrap
|
|
190
|
+
* @return {entity || []}
|
|
191
|
+
*/
|
|
192
|
+
embed (originalRel, self, entity) {
|
|
193
|
+
const rel = this._halacious.rel(originalRel)
|
|
194
|
+
const qname = rel.qname()
|
|
195
|
+
|
|
196
|
+
this.curie(rel.namespace)
|
|
197
|
+
|
|
198
|
+
if (_.isArray(entity)) {
|
|
199
|
+
const that = this
|
|
200
|
+
this._embedded[qname] = []
|
|
201
|
+
return entity.map((e) => that.embed(originalRel, self, e), this)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
self = this._halacious.link(self, this._links.self.href)
|
|
205
|
+
|
|
206
|
+
const embedded = this.factory.create(entity, self, this._root)
|
|
207
|
+
|
|
208
|
+
if (!this._embedded[qname]) {
|
|
209
|
+
this._embedded[qname] = embedded
|
|
210
|
+
} else if (_.isArray(this._embedded[qname])) {
|
|
211
|
+
this._embedded[qname].push(embedded)
|
|
212
|
+
} else {
|
|
213
|
+
this._embedded[qname] = [this._embedded[qname], embedded]
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return embedded
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Convenience method for embedding an array of entities
|
|
221
|
+
* @param rel
|
|
222
|
+
* @param self
|
|
223
|
+
* @param entities
|
|
224
|
+
* @return {Representation}
|
|
225
|
+
*/
|
|
226
|
+
embedCollection (rel, self, entities) {
|
|
227
|
+
entities = _.isArray(entities) ? entities : [entities]
|
|
228
|
+
entities.forEach(function (entity) {
|
|
229
|
+
this.embed(rel, hoek.clone(self), entity)
|
|
230
|
+
}, this)
|
|
231
|
+
return this
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Configures a representation using a configuration object such as those found in route definitions
|
|
236
|
+
* @param config
|
|
237
|
+
* @param callback
|
|
238
|
+
*/
|
|
239
|
+
configure (config, callback) {
|
|
240
|
+
this._halacious.configureRepresentation(this, config, callback)
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Responsible for creating all hal entities, top level or embedded, needed for a hapi request
|
|
246
|
+
* @param halacious a reference to the plugin api
|
|
247
|
+
* @param request a hapi request object
|
|
248
|
+
* @constructor
|
|
249
|
+
*/
|
|
250
|
+
export class RepresentationFactory {
|
|
251
|
+
constructor (halacious, request) {
|
|
252
|
+
this._halacious = halacious
|
|
253
|
+
this._request = request
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Creates a new hal representation out of a javascript object
|
|
258
|
+
* @param {{}=} entity the entity to wrap with a representation. an empty object is created by default
|
|
259
|
+
* @param {String || {}=} self the self href or link object. The request's path is used by default
|
|
260
|
+
* @param {Representation} root a pointer to the top level representation for adding curied links
|
|
261
|
+
* should be expanded into absolute urls
|
|
262
|
+
* @return {Representation}
|
|
263
|
+
*/
|
|
264
|
+
create (entity, self, root) {
|
|
265
|
+
entity = entity || {}
|
|
266
|
+
self = self || (this._request && this._request.path)
|
|
267
|
+
self = this._halacious.link(self)
|
|
268
|
+
return new Representation(this, self, entity, root)
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export default RepresentationFactory
|
|
@@ -1,22 +1,24 @@
|
|
|
1
|
-
|
|
1
|
+
import path from 'path'
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import chai from 'chai'
|
|
4
|
+
|
|
5
|
+
import hapi from '@hapi/hapi'
|
|
6
|
+
import vision from '@hapi/vision'
|
|
7
|
+
import sinon from 'sinon'
|
|
8
|
+
import sinonChai from 'sinon-chai'
|
|
9
|
+
import chaiString from 'chai-string'
|
|
10
|
+
import halacious from '#halacious'
|
|
11
|
+
import { plugin } from '#halacious/plugin'
|
|
12
|
+
import _ from 'lodash'
|
|
13
|
+
import url from 'url'
|
|
14
|
+
import IAM from '#where-am-i'
|
|
4
15
|
|
|
5
16
|
const should = chai.should()
|
|
6
|
-
const hapi = require('@hapi/hapi')
|
|
7
|
-
const vision = require('@hapi/vision')
|
|
8
|
-
const sinon = require('sinon')
|
|
9
|
-
const sinonChai = require('sinon-chai')
|
|
10
|
-
const chaiString = require('chai-string')
|
|
11
|
-
const halacious = require('#halacious')
|
|
12
|
-
const { plugin } = require('#halacious/lib/plugin')
|
|
13
|
-
const _ = require('lodash')
|
|
14
|
-
const url = require('url')
|
|
15
17
|
|
|
16
18
|
chai.use(sinonChai)
|
|
17
19
|
chai.use(chaiString)
|
|
18
20
|
|
|
19
|
-
const
|
|
21
|
+
const PLUGIN = '@modernpoacher/halacious'
|
|
20
22
|
|
|
21
23
|
describe('Halacious Plugin', () => {
|
|
22
24
|
let server
|
|
@@ -346,7 +348,7 @@ describe('Halacious Plugin', () => {
|
|
|
346
348
|
} = server
|
|
347
349
|
|
|
348
350
|
namespaces.add({
|
|
349
|
-
dir: path.
|
|
351
|
+
dir: path.join(IAM, './test/rels/mycompany'),
|
|
350
352
|
prefix: 'mco'
|
|
351
353
|
})
|
|
352
354
|
})
|
|
@@ -373,7 +375,7 @@ describe('Halacious Plugin', () => {
|
|
|
373
375
|
} = server
|
|
374
376
|
|
|
375
377
|
const namespace = plugin.namespaces.add({
|
|
376
|
-
dir: path.
|
|
378
|
+
dir: path.join(IAM, './test/rels/mycompany'),
|
|
377
379
|
prefix: 'mco'
|
|
378
380
|
})
|
|
379
381
|
|
|
@@ -402,7 +404,7 @@ describe('Halacious Plugin', () => {
|
|
|
402
404
|
} = server
|
|
403
405
|
|
|
404
406
|
plugin.namespaces.add({
|
|
405
|
-
dir: path.
|
|
407
|
+
dir: path.join(IAM, './test/rels/mycompany'),
|
|
406
408
|
prefix: 'mco'
|
|
407
409
|
})
|
|
408
410
|
})
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
import chai from 'chai'
|
|
2
|
+
import hapi from '@hapi/hapi'
|
|
3
|
+
import halacious from '#halacious'
|
|
4
|
+
import RepresentationFactory from '#halacious/representation'
|
|
2
5
|
|
|
3
6
|
const should = chai.should()
|
|
4
|
-
const hapi = require('@hapi/hapi')
|
|
5
|
-
const halacious = require('#halacious')
|
|
6
7
|
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
const { name: PLUGIN } = require('#halacious/package.json')
|
|
8
|
+
const PLUGIN = '@modernpoacher/halacious'
|
|
10
9
|
|
|
11
10
|
describe('Representation Factory', () => {
|
|
12
11
|
let server
|
package/where-am-i.mjs
ADDED
package/index.js
DELETED
package/lib/index.js
DELETED
package/lib/representation.js
DELETED
|
@@ -1,268 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
const _ = require('lodash')
|
|
3
|
-
const hoek = require('@hapi/hoek')
|
|
4
|
-
|
|
5
|
-
const URI = require('urijs')
|
|
6
|
-
const templatedRE = /{*}/
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* A HAL wrapper interface around an entity. Provides an api for adding new links and recursively embedding child
|
|
10
|
-
* entities.
|
|
11
|
-
*
|
|
12
|
-
* @param factory
|
|
13
|
-
* @param self
|
|
14
|
-
* @param entity
|
|
15
|
-
* @param root
|
|
16
|
-
* @constructor
|
|
17
|
-
*/
|
|
18
|
-
function Representation (factory, self, entity, root) {
|
|
19
|
-
this._halacious = factory._halacious
|
|
20
|
-
this.factory = factory
|
|
21
|
-
this.request = factory._request
|
|
22
|
-
this._root = root || this
|
|
23
|
-
this.self = self.href
|
|
24
|
-
this._links = { self }
|
|
25
|
-
this._embedded = {}
|
|
26
|
-
this._namespaces = {}
|
|
27
|
-
this._props = {}
|
|
28
|
-
this._ignore = {}
|
|
29
|
-
this.entity = entity
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Adds a namespace to the 'curie' link collection. all curies in a response, top level or nested, should be declared
|
|
34
|
-
* in the top level _links collection. a reference '_root' is kept to the top level representation for this purpose
|
|
35
|
-
* @param namespace
|
|
36
|
-
*/
|
|
37
|
-
Representation.prototype.curie = function (namespace) {
|
|
38
|
-
if (namespace && !this._root._namespaces[namespace.prefix]) {
|
|
39
|
-
this._root._namespaces[namespace.prefix] = namespace
|
|
40
|
-
this._root._links.curies = this._root._links.curies || []
|
|
41
|
-
this._root._links.curies.push({
|
|
42
|
-
name: namespace.prefix,
|
|
43
|
-
href: `${this._halacious.namespaceUrl(this.request, namespace)}/{rel}`,
|
|
44
|
-
templated: true
|
|
45
|
-
})
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Adds a custom property to the HAL payload
|
|
51
|
-
* @param {String} name the property name
|
|
52
|
-
* @param {*} value the property value
|
|
53
|
-
* @return {Representation}
|
|
54
|
-
*/
|
|
55
|
-
Representation.prototype.prop = function (name, value) {
|
|
56
|
-
this._props[name] = value
|
|
57
|
-
return this
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Merges an object's properties into the custom properties collection.
|
|
62
|
-
* @param obj
|
|
63
|
-
*/
|
|
64
|
-
Representation.prototype.merge = function (obj) {
|
|
65
|
-
hoek.merge(this._props, obj)
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* @param {...String || String[]} args properties to ignore
|
|
70
|
-
* @return {Representation}
|
|
71
|
-
*/
|
|
72
|
-
Representation.prototype.ignore = function (...args) {
|
|
73
|
-
const props = _.isArray(args[0]) ? args[0] : args
|
|
74
|
-
props.forEach(function (prop) {
|
|
75
|
-
this._ignore[prop] = true
|
|
76
|
-
}, this)
|
|
77
|
-
return this
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Prepares the representation for JSON serialization.
|
|
82
|
-
* @return {{}}
|
|
83
|
-
*/
|
|
84
|
-
Representation.prototype.toJSON = function () {
|
|
85
|
-
// initialize the json entity
|
|
86
|
-
const payload = { _links: this._links }
|
|
87
|
-
const self = this
|
|
88
|
-
|
|
89
|
-
// copy all target properties in the entity using JSON.stringify(). if the entity has a .toJSON() implementation,
|
|
90
|
-
// it will be called. properties on the ignore list will not be copied
|
|
91
|
-
const { entity } = this
|
|
92
|
-
JSON.stringify(entity, (key, value) => {
|
|
93
|
-
if (!key) {
|
|
94
|
-
return value
|
|
95
|
-
}
|
|
96
|
-
if (!self._ignore[key]) {
|
|
97
|
-
payload[key] = value
|
|
98
|
-
}
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
// merge in any extra properties
|
|
102
|
-
_.assign(payload, this._props)
|
|
103
|
-
|
|
104
|
-
const embeddedKeys = _.keys(this._embedded)
|
|
105
|
-
if (embeddedKeys.length > 0) {
|
|
106
|
-
payload._embedded = {}
|
|
107
|
-
|
|
108
|
-
const self = this
|
|
109
|
-
embeddedKeys.forEach((embedKey) => {
|
|
110
|
-
if (self._embedded[embedKey] instanceof Representation) {
|
|
111
|
-
payload._embedded[embedKey] = {}
|
|
112
|
-
} else if (self._embedded[embedKey] instanceof Array) {
|
|
113
|
-
payload._embedded[embedKey] = []
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
JSON.stringify(self._embedded[embedKey], (key, value) => {
|
|
117
|
-
if (!key) {
|
|
118
|
-
return value
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
payload._embedded[embedKey][key] = value
|
|
122
|
-
})
|
|
123
|
-
})
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return payload
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Creates a new link and adds it to the _links collection
|
|
131
|
-
* @param rel
|
|
132
|
-
* @param link
|
|
133
|
-
* @return {{} || []} the new link
|
|
134
|
-
*/
|
|
135
|
-
Representation.prototype.link = function (originalRel, link) {
|
|
136
|
-
const rel = this._halacious.rel(originalRel)
|
|
137
|
-
const qname = rel.qname()
|
|
138
|
-
|
|
139
|
-
if (_.isArray(link)) {
|
|
140
|
-
const that = this
|
|
141
|
-
this._links[qname] = []
|
|
142
|
-
return link.map((l) => that.link(originalRel, l), this)
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// adds the namespace to the top level curie list
|
|
146
|
-
this.curie(rel.namespace)
|
|
147
|
-
|
|
148
|
-
link = this._halacious.link(link, this._links.self.href)
|
|
149
|
-
link.templated = templatedRE.test(link.href) ? true : undefined
|
|
150
|
-
// e.g. 'mco:rel'
|
|
151
|
-
if (!this._links[qname]) {
|
|
152
|
-
this._links[qname] = link
|
|
153
|
-
} else if (_.isArray(this._links[qname])) {
|
|
154
|
-
this._links[qname].push(link)
|
|
155
|
-
} else {
|
|
156
|
-
this._links[qname] = [this._links[qname], link]
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
return link
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Resolves a relative path against the representation's self href
|
|
164
|
-
* @param relativePath
|
|
165
|
-
* @return {*}
|
|
166
|
-
*/
|
|
167
|
-
Representation.prototype.resolve = function (relativePath) {
|
|
168
|
-
return new URI(relativePath)
|
|
169
|
-
.absoluteTo(`${this._links.self.href}/`)
|
|
170
|
-
.toString()
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Returns the path to a named route (specified by the plugins.hal.name configuration parameter), expanding any supplied
|
|
175
|
-
* path parameters.
|
|
176
|
-
* @param {String} routeName the route's name
|
|
177
|
-
* @param {{}=} params for expanding templated urls
|
|
178
|
-
* @return {*}
|
|
179
|
-
*/
|
|
180
|
-
Representation.prototype.route = function (routeName, params) {
|
|
181
|
-
return this._halacious.route(routeName, params)
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* Wraps an entity into a HAL representation and adds it to the _embedded collection
|
|
186
|
-
* @param {String} rel the rel name
|
|
187
|
-
* @param {String || {}} self an href or link object for the entity
|
|
188
|
-
* @param {{} || []} entity an object to wrap
|
|
189
|
-
* @return {entity || []}
|
|
190
|
-
*/
|
|
191
|
-
Representation.prototype.embed = function (originalRel, self, entity) {
|
|
192
|
-
const rel = this._halacious.rel(originalRel)
|
|
193
|
-
const qname = rel.qname()
|
|
194
|
-
|
|
195
|
-
this.curie(rel.namespace)
|
|
196
|
-
|
|
197
|
-
if (_.isArray(entity)) {
|
|
198
|
-
const that = this
|
|
199
|
-
this._embedded[qname] = []
|
|
200
|
-
return entity.map((e) => that.embed(originalRel, self, e), this)
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
self = this._halacious.link(self, this._links.self.href)
|
|
204
|
-
|
|
205
|
-
const embedded = this.factory.create(entity, self, this._root)
|
|
206
|
-
|
|
207
|
-
if (!this._embedded[qname]) {
|
|
208
|
-
this._embedded[qname] = embedded
|
|
209
|
-
} else if (_.isArray(this._embedded[qname])) {
|
|
210
|
-
this._embedded[qname].push(embedded)
|
|
211
|
-
} else {
|
|
212
|
-
this._embedded[qname] = [this._embedded[qname], embedded]
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
return embedded
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* Convenience method for embedding an array of entities
|
|
220
|
-
* @param rel
|
|
221
|
-
* @param self
|
|
222
|
-
* @param entities
|
|
223
|
-
* @return {Representation}
|
|
224
|
-
*/
|
|
225
|
-
Representation.prototype.embedCollection = function (rel, self, entities) {
|
|
226
|
-
entities = _.isArray(entities) ? entities : [entities]
|
|
227
|
-
entities.forEach(function (entity) {
|
|
228
|
-
this.embed(rel, hoek.clone(self), entity)
|
|
229
|
-
}, this)
|
|
230
|
-
return this
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* Configures a representation using a configuration object such as those found in route definitions
|
|
235
|
-
* @param config
|
|
236
|
-
* @param callback
|
|
237
|
-
*/
|
|
238
|
-
Representation.prototype.configure = function (config, callback) {
|
|
239
|
-
this._halacious.configureRepresentation(this, config, callback)
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* Responsible for creating all hal entities, top level or embedded, needed for a hapi request
|
|
244
|
-
* @param halacious a reference to the plugin api
|
|
245
|
-
* @param request a hapi request object
|
|
246
|
-
* @constructor
|
|
247
|
-
*/
|
|
248
|
-
function RepresentationFactory (halacious, request) {
|
|
249
|
-
this._halacious = halacious
|
|
250
|
-
this._request = request
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* Creates a new hal representation out of a javascript object
|
|
255
|
-
* @param {{}=} entity the entity to wrap with a representation. an empty object is created by default
|
|
256
|
-
* @param {String || {}=} self the self href or link object. The request's path is used by default
|
|
257
|
-
* @param {Representation} root a pointer to the top level representation for adding curied links
|
|
258
|
-
* should be expanded into absolute urls
|
|
259
|
-
* @return {Representation}
|
|
260
|
-
*/
|
|
261
|
-
RepresentationFactory.prototype.create = function (entity, self, root) {
|
|
262
|
-
entity = entity || {}
|
|
263
|
-
self = self || (this._request && this._request.path)
|
|
264
|
-
self = this._halacious.link(self)
|
|
265
|
-
return new Representation(this, self, entity, root)
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
module.exports.RepresentationFactory = RepresentationFactory
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|