druxt 0.12.0 → 0.16.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/CHANGELOG.md CHANGED
@@ -1,5 +1,82 @@
1
1
  # druxt
2
2
 
3
+ ## 0.16.0
4
+
5
+ ### Minor Changes
6
+
7
+ - dc226c2: Made the \$druxt plugin first to be available to all Druxt module plugins.
8
+ - 7b749bd: Added improved error handling.
9
+
10
+ ## 0.15.0
11
+
12
+ ### Minor Changes
13
+
14
+ - 2ae1d6d: Reduced Fetch based UI flicker in the DruxtModule
15
+
16
+ ## 0.14.0
17
+
18
+ ### Minor Changes
19
+
20
+ - 85fff42: Added debug mode and Axios request logging.
21
+
22
+ ```
23
+ export default {
24
+ druxt: {
25
+ // Enable debug log messages.
26
+ debug: true,
27
+ }
28
+ }
29
+ ```
30
+
31
+ - 39e2e2e: Added ability to render readable JSON with the DruxtDebug component
32
+
33
+ ```jsx
34
+ <DruxtDebug :json="{ data: [{ one: true, two: false }] }" />
35
+ ```
36
+
37
+ - 9c33d82: Made summary prop optional on DruxtDebug component
38
+ - 45bc0b9: Added Druxt version to Nuxt badge
39
+
40
+ ## 0.13.0
41
+
42
+ ### Minor Changes
43
+
44
+ - 77ab204: Added ability to proxy the API
45
+
46
+ ```js
47
+ export default {
48
+ druxt: {
49
+ proxy: {
50
+ api: true
51
+ }
52
+ }
53
+ };
54
+ ```
55
+
56
+ Creates two proxy entries:
57
+
58
+ - The JSON:API: `${ENDPOINT}` -> `${BASEURL}${ENDPOINT}`
59
+ - The Decoupled Router:`/router/translate-path` -> `${BASEURL}/router/translate-path`
60
+
61
+ - 77ab204: Added ability to proxy the Drupal file system
62
+
63
+ ```js
64
+ export default {
65
+ druxt: {
66
+ proxy: {
67
+ files: "default"
68
+ }
69
+ }
70
+ };
71
+ ```
72
+
73
+ Creates a proxy entry:
74
+
75
+ - `/sites/${PATH}/files` -> `${BASEURL}/site/${PATH}/files`
76
+
77
+ - c4457e1: Changed template injected module components to not use a DruxtWrapper component by default.
78
+ - e7b1533: Refactored DruxtModule fetch hooks
79
+
3
80
  ## 0.12.0
4
81
 
5
82
  ### Minor Changes
@@ -1,6 +1,9 @@
1
1
  <template>
2
2
  <details v-if="$nuxt.context.isDev">
3
3
  <summary v-text="title" />
4
+ <pre v-if="json"><!--
5
+ --><code>{{ JSON.stringify(json, null, ' ') }}</code><!--
6
+ --></pre>
4
7
  <slot />
5
8
  </details>
6
9
 
@@ -10,19 +13,36 @@
10
13
  <script>
11
14
 
12
15
  /**
13
- * Debug component for use by Druxt modules.
16
+ * Renders Debug data only when Nuxt is in development mode.
17
+ *
18
+ * @example @lang vue
19
+ * <DruxtDebug :json="entity" />
14
20
  */
15
21
  export default {
16
22
  /** */
17
23
  props: {
18
24
  /**
19
- * Text to display in summary.
25
+ * JSON data to format for readability.
26
+ *
27
+ * @type {*}
28
+ * @example @lang vue
29
+ * <DruxtDebug :json="{ data: [{ one: true, two: false }] }" />
30
+ */
31
+ json: {
32
+ type: [Array, Boolean, Object, Number, String],
33
+ default: undefined
34
+ },
35
+
36
+ /**
37
+ * Text to use for debug summary.
20
38
  *
21
39
  * @type {string}
40
+ * @example @lang vue
41
+ * <DruxtDebug summary="Foo bar" />
22
42
  */
23
43
  summary: {
24
44
  type: String,
25
- required: true,
45
+ default: 'Debug',
26
46
  },
27
47
  },
28
48
 
@@ -43,7 +63,9 @@ export default {
43
63
  *
44
64
  * @return {string}
45
65
  */
46
- title: ({ module, summary }) => `[${module.$options._componentTag}] ${summary}`,
66
+ title: ({ module, summary }) => module.$options._componentTag
67
+ ? `[${module.$options._componentTag}] ${summary}`
68
+ : summary,
47
69
  },
48
70
  };
49
71
  </script>
@@ -38,12 +38,25 @@ export default {
38
38
  default: null,
39
39
  },
40
40
 
41
+ /**
42
+ * The wrapper component configuration.
43
+ *
44
+ * Used to set the wrapper component, class, style and propsData.
45
+ *
46
+ * @example
47
+ * <DruxtModule
48
+ * :wrapper="{
49
+ * component: 'MyWrapper',
50
+ * class: 'wrapper',
51
+ * propsData: { foo: 'bar' }
52
+ * }"
53
+ * />
54
+ *
55
+ * @type {(Boolean|Object)}
56
+ */
41
57
  wrapper: {
42
- type: Object,
43
- default: () => ({
44
- component: 'div',
45
- propsData: {},
46
- })
58
+ type: [Boolean, Object],
59
+ default: () => undefined
47
60
  },
48
61
  },
49
62
 
@@ -92,19 +105,52 @@ export default {
92
105
  return false
93
106
  }
94
107
 
108
+ // Fetch configuration.
109
+ if ((this.$options.druxt || {}).fetchConfig) {
110
+ try {
111
+ await this.$options.druxt.fetchConfig.call(this)
112
+ } catch(err) {
113
+ return this.error(err)
114
+ }
115
+ }
116
+
95
117
  // Build wrapper component object.
96
- const options = this.getModuleComponents()
118
+ let options = []
119
+ const hasDefaultTemplate = !!(((this.$vnode || {}).data || {}).scopedSlots || {}).default
120
+ // Load wrapper components if:
121
+ if (
122
+ // No default template and wrapper isn't false OR
123
+ (!hasDefaultTemplate && this.wrapper !== false) ||
124
+ // Default tempalte and wrapper is set
125
+ (hasDefaultTemplate && this.wrapper)
126
+ ) {
127
+ options = this.getModuleComponents()
128
+ }
97
129
  let component = {
98
130
  is: (((options.filter(o => o.global) || [])[0] || {}).name || 'DruxtWrapper'),
99
131
  options: options.map(o => o.name) || [],
100
132
  }
101
133
 
102
- // Get scoped slots.
103
- component.slots = Object.keys(this.getScopedSlots())
104
-
105
134
  // Get wrapper data.
106
135
  const wrapperData = await this.getWrapperData(component.is)
136
+
137
+ // Build module settings.
107
138
  component.settings = wrapperData.druxt || {}
139
+ if ((this.$options.druxt || {}).settings) {
140
+ component.settings = this.$options.druxt.settings(this, component.settings)
141
+ }
142
+
143
+ // Fetch resource.
144
+ if ((this.$options.druxt || {}).fetchData) {
145
+ try {
146
+ await this.$options.druxt.fetchData.call(this, component.settings)
147
+ } catch(err) {
148
+ return this.error(err, { component })
149
+ }
150
+ }
151
+
152
+ // Get scoped slots.
153
+ component.slots = Object.keys(this.getScopedSlots())
108
154
 
109
155
  // Build wrapper component propsData.
110
156
  component = { ...component, ...this.getModulePropsData(wrapperData.props) }
@@ -115,7 +161,7 @@ export default {
115
161
 
116
162
  watch: {
117
163
  model() {
118
- if (this.component.props.value !== this.model) {
164
+ if (this.component.props && this.component.props.value !== this.model) {
119
165
  this.component.props.value = this.model
120
166
 
121
167
  // Only emit 'input' if using the default 'DruxtWrapper' component.
@@ -134,18 +180,45 @@ export default {
134
180
 
135
181
  /** */
136
182
  methods: {
183
+ /**
184
+ * Sets the component to render a DruxtDebug error message.
185
+ */
186
+ error(err, context = {}) {
187
+ // Build error details.
188
+ const { url } = err.druxt || {}
189
+ const title = (err.response || {}).statusText || ((((err.response || {}).data || {}).errors || [])[0] || {}).title
190
+ const summary = (err.response || {}).status
191
+ ? [(err.response || {}).status, title].filter((s) => s).join(': ')
192
+ : err.message
193
+
194
+ // Set the component to a Debug component with error details.
195
+ this.component = {
196
+ ...context.component || {},
197
+ is: 'DruxtDebug',
198
+ props: {
199
+ json: {
200
+ url,
201
+ errors: ((err.response || {}).data || {}).errors
202
+ },
203
+ summary
204
+ }
205
+ }
206
+ },
207
+
137
208
  /**
138
209
  * Get list of module wrapper components.
139
210
  *
140
211
  * @returns {Components}
141
212
  */
142
213
  getModuleComponents() {
214
+ // Ensure that the Druxt module component has `druxt.componentOptions`.
143
215
  if (!(this.$options.druxt || {}).componentOptions) {
144
216
  return []
145
217
  }
146
-
147
218
  const options = this.$options.druxt.componentOptions.call(this, this)
148
- if (!options || !options.length) {
219
+
220
+ // Ensure that there available component options are returned.
221
+ if (!(options || []).length) {
149
222
  return []
150
223
  }
151
224
 
@@ -290,20 +363,27 @@ export default {
290
363
  const self = this
291
364
 
292
365
  const wrapperData = {
293
- class: this.wrapper.class || undefined,
294
- style: this.wrapper.style || undefined,
295
- props: this.wrapper.propsData,
366
+ class: (this.wrapper || {}).class || undefined,
367
+ style: (this.wrapper || {}).style || undefined,
368
+ props: (this.wrapper || {}).propsData || undefined,
296
369
  }
297
370
 
298
371
  // Return only wrapper if fetch state is still pending.
299
- if (this.$fetchState.pending) {
300
- return h(this.wrapper.component, wrapperData)
372
+ if (this.$fetchState.pending && this.component.is === 'DruxtWrapper') {
373
+ return h((this.wrapper || {}).component || 'div', wrapperData)
301
374
  }
302
375
 
303
- // Return wrapped component.
376
+ // Prepare attributes.
304
377
  const attrs = { ...this.component.$attrs, ...this.$attrs }
305
378
  delete attrs['data-fetch-key']
306
- return h(this.wrapper.component, wrapperData, [
379
+
380
+ // Unwrap default template based component if required.
381
+ if ((this.$scopedSlots.default && !this.wrapper) || this.wrapper === false) {
382
+ this.component.is = 'DruxtWrapper'
383
+ }
384
+
385
+ // Return component.
386
+ return h((this.wrapper || {}).component || 'div', wrapperData, [
307
387
  h(this.component.is, {
308
388
  attrs,
309
389
  on: {
package/dist/druxt.esm.js CHANGED
@@ -2,11 +2,89 @@ import chalk from 'chalk';
2
2
  import { join, resolve } from 'path';
3
3
  import axios from 'axios';
4
4
  import { stringify } from 'querystring';
5
+ import consola from 'consola';
5
6
  import merge from 'deepmerge';
6
7
  import md5 from 'md5';
7
8
  import Vue from 'vue';
8
9
  import { DrupalJsonApiParams } from 'drupal-jsonapi-params';
9
10
 
11
+ var name = "druxt";
12
+ var version = "0.16.0";
13
+ var description = "The Fully Decoupled Drupal Framework for Nuxt.js.";
14
+ var keywords = [
15
+ "cms",
16
+ "decoupled",
17
+ "drupal",
18
+ "druxt",
19
+ "headless",
20
+ "jsonapi",
21
+ "json:api",
22
+ "nuxt",
23
+ "module",
24
+ "vue"
25
+ ];
26
+ var homepage = "https://druxtjs.org";
27
+ var bugs = {
28
+ url: "https://github.com/druxt/druxt.js/issues"
29
+ };
30
+ var repository = {
31
+ type: "git",
32
+ url: "git+https://github.com/druxt/druxt.js"
33
+ };
34
+ var license = "MIT";
35
+ var exports = {
36
+ ".": {
37
+ require: "./dist/druxt.ssr.js",
38
+ "import": "./dist/druxt.esm.js"
39
+ },
40
+ "./components/*": "./dist/components/*"
41
+ };
42
+ var main = "dist/druxt.ssr.js";
43
+ var module = "dist/druxt.esm.js";
44
+ var files = [
45
+ "dist",
46
+ "templates"
47
+ ];
48
+ var dependencies = {
49
+ "@nuxtjs/proxy": "^2.1.0",
50
+ axios: "^0.21.1",
51
+ chalk: "^4.1.2",
52
+ deepmerge: "^4.2.2",
53
+ "drupal-jsonapi-params": "^1.2.1",
54
+ md5: "^2.3.0",
55
+ querystring: "^0.2.0",
56
+ scule: "^0.2.0"
57
+ };
58
+ var peerDependencies = {
59
+ consola: "*"
60
+ };
61
+ var optionalDependencies = {
62
+ "core-js": "^3.16.1",
63
+ vue: "^2.6.14",
64
+ vuex: "^3.6.2"
65
+ };
66
+ var publishConfig = {
67
+ access: "public"
68
+ };
69
+ var meta = {
70
+ name: name,
71
+ version: version,
72
+ description: description,
73
+ keywords: keywords,
74
+ homepage: homepage,
75
+ bugs: bugs,
76
+ repository: repository,
77
+ license: license,
78
+ exports: exports,
79
+ main: main,
80
+ module: module,
81
+ files: files,
82
+ dependencies: dependencies,
83
+ peerDependencies: peerDependencies,
84
+ optionalDependencies: optionalDependencies,
85
+ publishConfig: publishConfig
86
+ };
87
+
10
88
  const DruxtNuxtModule = function(moduleOptions = {}) {
11
89
  var _a;
12
90
  const options = {
@@ -14,6 +92,33 @@ const DruxtNuxtModule = function(moduleOptions = {}) {
14
92
  ...moduleOptions,
15
93
  ...(this.options || {}).druxt
16
94
  };
95
+ if (options.proxy) {
96
+ const proxies = {};
97
+ if ((options.proxy || {}).api) {
98
+ proxies[options.endpoint] = options.baseUrl;
99
+ proxies["/router/translate-path"] = options.baseUrl;
100
+ }
101
+ if ((options.proxy || {}).files) {
102
+ const filesPath = typeof options.proxy.files === "string" ? options.proxy.files : "default";
103
+ proxies[`/sites/${filesPath}/files`] = options.baseUrl;
104
+ }
105
+ if (this.options.proxy) {
106
+ if (Array.isArray(this.options.proxy)) {
107
+ this.options.proxy = [
108
+ ...Object.keys(proxies).map((path) => `${options.baseUrl}${path}`),
109
+ ...this.options.proxy
110
+ ];
111
+ } else {
112
+ this.options.proxy = {
113
+ ...proxies,
114
+ ...this.options.proxy
115
+ };
116
+ }
117
+ } else {
118
+ this.options.proxy = proxies;
119
+ }
120
+ this.addModule("@nuxtjs/proxy");
121
+ }
17
122
  this.nuxt.hook("components:dirs", (dirs) => {
18
123
  dirs.push({ path: join(__dirname, "components") });
19
124
  });
@@ -22,6 +127,15 @@ const DruxtNuxtModule = function(moduleOptions = {}) {
22
127
  fileName: "druxt.js",
23
128
  options
24
129
  });
130
+ const extendPlugins = this.options.extendPlugins;
131
+ this.options.extendPlugins = (plugins) => {
132
+ plugins = typeof extendPlugins === "function" ? extendPlugins(plugins) : plugins;
133
+ const index = plugins.findIndex(({ src }) => src === `${this.options.buildDir}/druxt.js`);
134
+ const plugin = plugins[index];
135
+ plugins.splice(index, 1);
136
+ plugins.unshift(plugin);
137
+ return plugins;
138
+ };
25
139
  this.addPlugin({
26
140
  src: resolve(__dirname, "../templates/store.js"),
27
141
  fileName: "store/druxt.js",
@@ -29,20 +143,36 @@ const DruxtNuxtModule = function(moduleOptions = {}) {
29
143
  });
30
144
  this.options.store = true;
31
145
  this.options.components = (_a = this.options.components) != null ? _a : true;
32
- this.options.cli.badgeMessages.push(`${chalk.bold("Druxt API:")} ${chalk.blue.underline(options.baseUrl + options.endpoint)}`);
146
+ this.options.cli.badgeMessages.push(`${chalk.blue.bold("Druxt")} @ v${meta.version}`);
147
+ this.options.cli.badgeMessages.push(`${chalk.bold("API:")} ${chalk.blue.underline(options.baseUrl + options.endpoint)}`);
33
148
  };
34
- DruxtNuxtModule.meta = require("../package.json");
35
149
 
36
150
  class DruxtClient {
37
151
  constructor(baseUrl, options = {}) {
152
+ this.log = consola.create({ defaults: {
153
+ tag: "DruxtClient"
154
+ } });
38
155
  if (!baseUrl) {
39
156
  throw new Error("The 'baseUrl' parameter is required.");
40
157
  }
41
- let axiosSettings = { baseURL: baseUrl };
158
+ let axiosSettings = {};
159
+ if (baseUrl && !(options.proxy || {}).api) {
160
+ axiosSettings.baseURL = baseUrl;
161
+ }
42
162
  if (typeof options.axios === "object") {
43
163
  axiosSettings = Object.assign(axiosSettings, options.axios);
44
164
  }
45
165
  this.axios = axios.create(axiosSettings);
166
+ if (options.debug) {
167
+ const log = this.log;
168
+ this.axios.interceptors.request.use((config) => {
169
+ log.info(config.url);
170
+ return config;
171
+ }, (error) => {
172
+ log.error(error);
173
+ return Promise.reject(error);
174
+ });
175
+ }
46
176
  this.options = {
47
177
  endpoint: "/jsonapi",
48
178
  jsonapiResourceConfig: "jsonapi_resource_config--jsonapi_resource_config",
@@ -84,9 +214,16 @@ class DruxtClient {
84
214
  }
85
215
  }
86
216
  if (Object.keys(permissions).length) {
87
- throw new TypeError(`${res.data.meta.omitted.detail}
88
-
89
- Required permissions: ${Object.keys(permissions).join(", ")}.`);
217
+ const err = {
218
+ response: {
219
+ statusText: res.data.meta.omitted.detail,
220
+ data: {
221
+ errors: [{ detail: `Required permissions:
222
+ - ${Object.keys(permissions).join("\n - ")}` }]
223
+ }
224
+ }
225
+ };
226
+ throw err;
90
227
  }
91
228
  }
92
229
  }
@@ -106,19 +243,49 @@ class DruxtClient {
106
243
  }
107
244
  });
108
245
  } catch (err) {
109
- response = (err.response || {}).data || err.message;
246
+ this.error(err, { url: href });
110
247
  }
111
248
  return response;
112
249
  }
250
+ error(err, context = {}) {
251
+ let { url } = context;
252
+ if (!url && ((err.response || {}).config || {}).url) {
253
+ url = err.response.config.url;
254
+ }
255
+ const title = [
256
+ (err.response || {}).status,
257
+ (err.response || {}).statusText || err.message
258
+ ].filter((s) => s).join(": ");
259
+ const meta = { url: url && [this.options.baseUrl, url].join("") };
260
+ let message = [title];
261
+ if (Object.values(meta).filter((o) => o).length) {
262
+ message.push(Object.entries(meta).filter(([, v]) => v).map(([key, value]) => `${key.toUpperCase()}: ${value}`).join("\n"));
263
+ }
264
+ if (((((err.response || {}).data || {}).errors || [])[0] || {}).detail) {
265
+ message.push(err.response.data.errors[0].detail);
266
+ }
267
+ const error = Error(message.join("\n\n"));
268
+ error.response = err.response;
269
+ error.druxt = context;
270
+ throw error;
271
+ }
272
+ async get(url, options) {
273
+ try {
274
+ const res = await this.axios.get(url, options);
275
+ this.checkPermissions(res);
276
+ return res;
277
+ } catch (err) {
278
+ this.error(err, { url });
279
+ }
280
+ }
113
281
  async getCollection(type, query) {
114
282
  const { href } = await this.getIndex(type);
115
283
  if (!href) {
116
284
  return false;
117
285
  }
118
286
  const url = this.buildQueryUrl(href, query);
119
- const res = await this.axios.get(url);
120
- this.checkPermissions(res);
121
- return res.data;
287
+ const { data } = await this.get(url);
288
+ return data;
122
289
  }
123
290
  async getCollectionAll(type, query) {
124
291
  const collections = [];
@@ -138,12 +305,27 @@ class DruxtClient {
138
305
  if (this.index && resource) {
139
306
  return this.index[resource] ? this.index[resource] : false;
140
307
  }
141
- const index = await this.axios.get(this.options.endpoint);
142
- this.index = index.data.links;
143
- if (this.index[this.options.jsonapiResourceConfig]) {
144
- const resources = await this.axios.get(this.index[this.options.jsonapiResourceConfig].href);
145
- for (const resourceType in resources.data.data) {
146
- const resource2 = resources.data.data[resourceType];
308
+ const url = this.options.endpoint;
309
+ const { data } = await this.get(url);
310
+ let index = data.links;
311
+ if (typeof index !== "object") {
312
+ const err = { response: { statusText: "Invalid JSON:API endpoint" } };
313
+ this.error(err, { url });
314
+ }
315
+ const baseUrl = this.options.baseUrl;
316
+ index = Object.fromEntries(Object.entries(index).map(([key, value]) => {
317
+ value.href = value.href.replace(baseUrl, "");
318
+ return [key, value];
319
+ }));
320
+ if (index[this.options.jsonapiResourceConfig]) {
321
+ let resources = [];
322
+ try {
323
+ resources = (await this.get(index[this.options.jsonapiResourceConfig].href)).data.data;
324
+ } catch (err) {
325
+ this.log.warn(err.message);
326
+ }
327
+ for (const resourceType in resources) {
328
+ const resource2 = resources[resourceType];
147
329
  const internal = resource2.attributes.drupal_internal__id.split("--");
148
330
  const item = {
149
331
  resourceType: resource2.attributes.resourceType,
@@ -152,16 +334,14 @@ class DruxtClient {
152
334
  resourceFields: resource2.attributes.resourceFields
153
335
  };
154
336
  const id = [item.entityType, item.bundle].join("--");
155
- this.index[id] = {
337
+ index[id] = {
156
338
  ...item,
157
- ...this.index[id]
339
+ ...index[id]
158
340
  };
159
341
  }
160
342
  }
161
- if (resource) {
162
- return this.index[resource] ? this.index[resource] : false;
163
- }
164
- return this.index;
343
+ this.index = index;
344
+ return resource ? this.index[resource] || false : this.index;
165
345
  }
166
346
  async getRelated(type, id, related, query) {
167
347
  if (!id || !type || !related) {
@@ -172,12 +352,8 @@ class DruxtClient {
172
352
  href = this.options.endpoint + "/" + type.replace("--", "/");
173
353
  }
174
354
  const url = this.buildQueryUrl(`${href}/${id}/${related}`, query);
175
- try {
176
- const related2 = await this.axios.get(url);
177
- return related2.data;
178
- } catch (e) {
179
- return false;
180
- }
355
+ const { data } = await this.get(url);
356
+ return data;
181
357
  }
182
358
  async getResource(type, id, query) {
183
359
  if (!id || !type) {
@@ -187,13 +363,9 @@ class DruxtClient {
187
363
  if (!href) {
188
364
  href = this.options.endpoint + "/" + type.replace("--", "/");
189
365
  }
190
- const url = this.buildQueryUrl(`${href}/${id}`, query);
191
- try {
192
- const resource = await this.axios.get(url);
193
- return resource.data;
194
- } catch (e) {
195
- return false;
196
- }
366
+ const url = this.buildQueryUrl([href, id].join("/"), query);
367
+ const { data } = await this.get(url);
368
+ return data;
197
369
  }
198
370
  async updateResource(resource) {
199
371
  const { href } = await this.getIndex(resource.type);
@@ -209,7 +381,7 @@ class DruxtClient {
209
381
  }
210
382
  });
211
383
  } catch (err) {
212
- response = (err.response || {}).data || err.message;
384
+ this.error(err);
213
385
  }
214
386
  return response;
215
387
  }
@@ -220,7 +392,7 @@ const getDrupalJsonApiParams = (query) => {
220
392
  };
221
393
 
222
394
  const dehydrateResources = ({ commit, queryObject, resources }) => {
223
- return resources.map((data) => {
395
+ return (resources || []).map((data) => {
224
396
  const link = decodeURI(((data.links || {}).self || {}).href || "");
225
397
  const href = typeof (queryObject.fields || {})[data.type] === "string" ? [link.split("?")[0], `fields[${data.type}]=${queryObject.fields[data.type]}`].join("?") : link;
226
398
  commit("druxt/addResource", {
@@ -411,5 +583,7 @@ class DruxtClass {
411
583
  }
412
584
  }
413
585
 
586
+ DruxtNuxtModule.meta = require("../package.json");
587
+
414
588
  export default DruxtNuxtModule;
415
589
  export { DruxtClass, DruxtClient, DruxtStore };
package/dist/druxt.ssr.js CHANGED
@@ -6,6 +6,7 @@ const chalk = require('chalk');
6
6
  const path = require('path');
7
7
  const axios = require('axios');
8
8
  const querystring = require('querystring');
9
+ const consola = require('consola');
9
10
  const merge = require('deepmerge');
10
11
  const md5 = require('md5');
11
12
  const Vue = require('vue');
@@ -15,10 +16,88 @@ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'defau
15
16
 
16
17
  const chalk__default = /*#__PURE__*/_interopDefaultLegacy(chalk);
17
18
  const axios__default = /*#__PURE__*/_interopDefaultLegacy(axios);
19
+ const consola__default = /*#__PURE__*/_interopDefaultLegacy(consola);
18
20
  const merge__default = /*#__PURE__*/_interopDefaultLegacy(merge);
19
21
  const md5__default = /*#__PURE__*/_interopDefaultLegacy(md5);
20
22
  const Vue__default = /*#__PURE__*/_interopDefaultLegacy(Vue);
21
23
 
24
+ var name = "druxt";
25
+ var version = "0.16.0";
26
+ var description = "The Fully Decoupled Drupal Framework for Nuxt.js.";
27
+ var keywords = [
28
+ "cms",
29
+ "decoupled",
30
+ "drupal",
31
+ "druxt",
32
+ "headless",
33
+ "jsonapi",
34
+ "json:api",
35
+ "nuxt",
36
+ "module",
37
+ "vue"
38
+ ];
39
+ var homepage = "https://druxtjs.org";
40
+ var bugs = {
41
+ url: "https://github.com/druxt/druxt.js/issues"
42
+ };
43
+ var repository = {
44
+ type: "git",
45
+ url: "git+https://github.com/druxt/druxt.js"
46
+ };
47
+ var license = "MIT";
48
+ var exports$1 = {
49
+ ".": {
50
+ require: "./dist/druxt.ssr.js",
51
+ "import": "./dist/druxt.esm.js"
52
+ },
53
+ "./components/*": "./dist/components/*"
54
+ };
55
+ var main = "dist/druxt.ssr.js";
56
+ var module$1 = "dist/druxt.esm.js";
57
+ var files = [
58
+ "dist",
59
+ "templates"
60
+ ];
61
+ var dependencies = {
62
+ "@nuxtjs/proxy": "^2.1.0",
63
+ axios: "^0.21.1",
64
+ chalk: "^4.1.2",
65
+ deepmerge: "^4.2.2",
66
+ "drupal-jsonapi-params": "^1.2.1",
67
+ md5: "^2.3.0",
68
+ querystring: "^0.2.0",
69
+ scule: "^0.2.0"
70
+ };
71
+ var peerDependencies = {
72
+ consola: "*"
73
+ };
74
+ var optionalDependencies = {
75
+ "core-js": "^3.16.1",
76
+ vue: "^2.6.14",
77
+ vuex: "^3.6.2"
78
+ };
79
+ var publishConfig = {
80
+ access: "public"
81
+ };
82
+ const meta = {
83
+ name: name,
84
+ version: version,
85
+ description: description,
86
+ keywords: keywords,
87
+ homepage: homepage,
88
+ bugs: bugs,
89
+ repository: repository,
90
+ license: license,
91
+ exports: exports$1,
92
+ main: main,
93
+ module: module$1,
94
+ files: files,
95
+ dependencies: dependencies,
96
+ peerDependencies: peerDependencies,
97
+ optionalDependencies: optionalDependencies,
98
+ publishConfig: publishConfig
99
+ };
100
+
22
101
  const DruxtNuxtModule = function(moduleOptions = {}) {
23
102
  var _a;
24
103
  const options = {
@@ -26,6 +105,33 @@ const DruxtNuxtModule = function(moduleOptions = {}) {
26
105
  ...moduleOptions,
27
106
  ...(this.options || {}).druxt
28
107
  };
108
+ if (options.proxy) {
109
+ const proxies = {};
110
+ if ((options.proxy || {}).api) {
111
+ proxies[options.endpoint] = options.baseUrl;
112
+ proxies["/router/translate-path"] = options.baseUrl;
113
+ }
114
+ if ((options.proxy || {}).files) {
115
+ const filesPath = typeof options.proxy.files === "string" ? options.proxy.files : "default";
116
+ proxies[`/sites/${filesPath}/files`] = options.baseUrl;
117
+ }
118
+ if (this.options.proxy) {
119
+ if (Array.isArray(this.options.proxy)) {
120
+ this.options.proxy = [
121
+ ...Object.keys(proxies).map((path) => `${options.baseUrl}${path}`),
122
+ ...this.options.proxy
123
+ ];
124
+ } else {
125
+ this.options.proxy = {
126
+ ...proxies,
127
+ ...this.options.proxy
128
+ };
129
+ }
130
+ } else {
131
+ this.options.proxy = proxies;
132
+ }
133
+ this.addModule("@nuxtjs/proxy");
134
+ }
29
135
  this.nuxt.hook("components:dirs", (dirs) => {
30
136
  dirs.push({ path: path.join(__dirname, "components") });
31
137
  });
@@ -34,6 +140,15 @@ const DruxtNuxtModule = function(moduleOptions = {}) {
34
140
  fileName: "druxt.js",
35
141
  options
36
142
  });
143
+ const extendPlugins = this.options.extendPlugins;
144
+ this.options.extendPlugins = (plugins) => {
145
+ plugins = typeof extendPlugins === "function" ? extendPlugins(plugins) : plugins;
146
+ const index = plugins.findIndex(({ src }) => src === `${this.options.buildDir}/druxt.js`);
147
+ const plugin = plugins[index];
148
+ plugins.splice(index, 1);
149
+ plugins.unshift(plugin);
150
+ return plugins;
151
+ };
37
152
  this.addPlugin({
38
153
  src: path.resolve(__dirname, "../templates/store.js"),
39
154
  fileName: "store/druxt.js",
@@ -41,20 +156,36 @@ const DruxtNuxtModule = function(moduleOptions = {}) {
41
156
  });
42
157
  this.options.store = true;
43
158
  this.options.components = (_a = this.options.components) != null ? _a : true;
44
- this.options.cli.badgeMessages.push(`${chalk__default['default'].bold("Druxt API:")} ${chalk__default['default'].blue.underline(options.baseUrl + options.endpoint)}`);
159
+ this.options.cli.badgeMessages.push(`${chalk__default['default'].blue.bold("Druxt")} @ v${meta.version}`);
160
+ this.options.cli.badgeMessages.push(`${chalk__default['default'].bold("API:")} ${chalk__default['default'].blue.underline(options.baseUrl + options.endpoint)}`);
45
161
  };
46
- DruxtNuxtModule.meta = require("../package.json");
47
162
 
48
163
  class DruxtClient {
49
164
  constructor(baseUrl, options = {}) {
165
+ this.log = consola__default['default'].create({ defaults: {
166
+ tag: "DruxtClient"
167
+ } });
50
168
  if (!baseUrl) {
51
169
  throw new Error("The 'baseUrl' parameter is required.");
52
170
  }
53
- let axiosSettings = { baseURL: baseUrl };
171
+ let axiosSettings = {};
172
+ if (baseUrl && !(options.proxy || {}).api) {
173
+ axiosSettings.baseURL = baseUrl;
174
+ }
54
175
  if (typeof options.axios === "object") {
55
176
  axiosSettings = Object.assign(axiosSettings, options.axios);
56
177
  }
57
178
  this.axios = axios__default['default'].create(axiosSettings);
179
+ if (options.debug) {
180
+ const log = this.log;
181
+ this.axios.interceptors.request.use((config) => {
182
+ log.info(config.url);
183
+ return config;
184
+ }, (error) => {
185
+ log.error(error);
186
+ return Promise.reject(error);
187
+ });
188
+ }
58
189
  this.options = {
59
190
  endpoint: "/jsonapi",
60
191
  jsonapiResourceConfig: "jsonapi_resource_config--jsonapi_resource_config",
@@ -96,9 +227,16 @@ class DruxtClient {
96
227
  }
97
228
  }
98
229
  if (Object.keys(permissions).length) {
99
- throw new TypeError(`${res.data.meta.omitted.detail}
100
-
101
- Required permissions: ${Object.keys(permissions).join(", ")}.`);
230
+ const err = {
231
+ response: {
232
+ statusText: res.data.meta.omitted.detail,
233
+ data: {
234
+ errors: [{ detail: `Required permissions:
235
+ - ${Object.keys(permissions).join("\n - ")}` }]
236
+ }
237
+ }
238
+ };
239
+ throw err;
102
240
  }
103
241
  }
104
242
  }
@@ -118,19 +256,49 @@ class DruxtClient {
118
256
  }
119
257
  });
120
258
  } catch (err) {
121
- response = (err.response || {}).data || err.message;
259
+ this.error(err, { url: href });
122
260
  }
123
261
  return response;
124
262
  }
263
+ error(err, context = {}) {
264
+ let { url } = context;
265
+ if (!url && ((err.response || {}).config || {}).url) {
266
+ url = err.response.config.url;
267
+ }
268
+ const title = [
269
+ (err.response || {}).status,
270
+ (err.response || {}).statusText || err.message
271
+ ].filter((s) => s).join(": ");
272
+ const meta = { url: url && [this.options.baseUrl, url].join("") };
273
+ let message = [title];
274
+ if (Object.values(meta).filter((o) => o).length) {
275
+ message.push(Object.entries(meta).filter(([, v]) => v).map(([key, value]) => `${key.toUpperCase()}: ${value}`).join("\n"));
276
+ }
277
+ if (((((err.response || {}).data || {}).errors || [])[0] || {}).detail) {
278
+ message.push(err.response.data.errors[0].detail);
279
+ }
280
+ const error = Error(message.join("\n\n"));
281
+ error.response = err.response;
282
+ error.druxt = context;
283
+ throw error;
284
+ }
285
+ async get(url, options) {
286
+ try {
287
+ const res = await this.axios.get(url, options);
288
+ this.checkPermissions(res);
289
+ return res;
290
+ } catch (err) {
291
+ this.error(err, { url });
292
+ }
293
+ }
125
294
  async getCollection(type, query) {
126
295
  const { href } = await this.getIndex(type);
127
296
  if (!href) {
128
297
  return false;
129
298
  }
130
299
  const url = this.buildQueryUrl(href, query);
131
- const res = await this.axios.get(url);
132
- this.checkPermissions(res);
133
- return res.data;
300
+ const { data } = await this.get(url);
301
+ return data;
134
302
  }
135
303
  async getCollectionAll(type, query) {
136
304
  const collections = [];
@@ -150,12 +318,27 @@ class DruxtClient {
150
318
  if (this.index && resource) {
151
319
  return this.index[resource] ? this.index[resource] : false;
152
320
  }
153
- const index = await this.axios.get(this.options.endpoint);
154
- this.index = index.data.links;
155
- if (this.index[this.options.jsonapiResourceConfig]) {
156
- const resources = await this.axios.get(this.index[this.options.jsonapiResourceConfig].href);
157
- for (const resourceType in resources.data.data) {
158
- const resource2 = resources.data.data[resourceType];
321
+ const url = this.options.endpoint;
322
+ const { data } = await this.get(url);
323
+ let index = data.links;
324
+ if (typeof index !== "object") {
325
+ const err = { response: { statusText: "Invalid JSON:API endpoint" } };
326
+ this.error(err, { url });
327
+ }
328
+ const baseUrl = this.options.baseUrl;
329
+ index = Object.fromEntries(Object.entries(index).map(([key, value]) => {
330
+ value.href = value.href.replace(baseUrl, "");
331
+ return [key, value];
332
+ }));
333
+ if (index[this.options.jsonapiResourceConfig]) {
334
+ let resources = [];
335
+ try {
336
+ resources = (await this.get(index[this.options.jsonapiResourceConfig].href)).data.data;
337
+ } catch (err) {
338
+ this.log.warn(err.message);
339
+ }
340
+ for (const resourceType in resources) {
341
+ const resource2 = resources[resourceType];
159
342
  const internal = resource2.attributes.drupal_internal__id.split("--");
160
343
  const item = {
161
344
  resourceType: resource2.attributes.resourceType,
@@ -164,16 +347,14 @@ class DruxtClient {
164
347
  resourceFields: resource2.attributes.resourceFields
165
348
  };
166
349
  const id = [item.entityType, item.bundle].join("--");
167
- this.index[id] = {
350
+ index[id] = {
168
351
  ...item,
169
- ...this.index[id]
352
+ ...index[id]
170
353
  };
171
354
  }
172
355
  }
173
- if (resource) {
174
- return this.index[resource] ? this.index[resource] : false;
175
- }
176
- return this.index;
356
+ this.index = index;
357
+ return resource ? this.index[resource] || false : this.index;
177
358
  }
178
359
  async getRelated(type, id, related, query) {
179
360
  if (!id || !type || !related) {
@@ -184,12 +365,8 @@ class DruxtClient {
184
365
  href = this.options.endpoint + "/" + type.replace("--", "/");
185
366
  }
186
367
  const url = this.buildQueryUrl(`${href}/${id}/${related}`, query);
187
- try {
188
- const related2 = await this.axios.get(url);
189
- return related2.data;
190
- } catch (e) {
191
- return false;
192
- }
368
+ const { data } = await this.get(url);
369
+ return data;
193
370
  }
194
371
  async getResource(type, id, query) {
195
372
  if (!id || !type) {
@@ -199,13 +376,9 @@ class DruxtClient {
199
376
  if (!href) {
200
377
  href = this.options.endpoint + "/" + type.replace("--", "/");
201
378
  }
202
- const url = this.buildQueryUrl(`${href}/${id}`, query);
203
- try {
204
- const resource = await this.axios.get(url);
205
- return resource.data;
206
- } catch (e) {
207
- return false;
208
- }
379
+ const url = this.buildQueryUrl([href, id].join("/"), query);
380
+ const { data } = await this.get(url);
381
+ return data;
209
382
  }
210
383
  async updateResource(resource) {
211
384
  const { href } = await this.getIndex(resource.type);
@@ -221,7 +394,7 @@ class DruxtClient {
221
394
  }
222
395
  });
223
396
  } catch (err) {
224
- response = (err.response || {}).data || err.message;
397
+ this.error(err);
225
398
  }
226
399
  return response;
227
400
  }
@@ -232,7 +405,7 @@ const getDrupalJsonApiParams = (query) => {
232
405
  };
233
406
 
234
407
  const dehydrateResources = ({ commit, queryObject, resources }) => {
235
- return resources.map((data) => {
408
+ return (resources || []).map((data) => {
236
409
  const link = decodeURI(((data.links || {}).self || {}).href || "");
237
410
  const href = typeof (queryObject.fields || {})[data.type] === "string" ? [link.split("?")[0], `fields[${data.type}]=${queryObject.fields[data.type]}`].join("?") : link;
238
411
  commit("druxt/addResource", {
@@ -423,6 +596,8 @@ class DruxtClass {
423
596
  }
424
597
  }
425
598
 
599
+ DruxtNuxtModule.meta = require("../package.json");
600
+
426
601
  exports.DruxtClass = DruxtClass;
427
602
  exports.DruxtClient = DruxtClient;
428
603
  exports.DruxtStore = DruxtStore;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "druxt",
3
- "version": "0.12.0",
3
+ "version": "0.16.0",
4
4
  "description": "The Fully Decoupled Drupal Framework for Nuxt.js.",
5
5
  "keywords": [
6
6
  "cms",
@@ -37,6 +37,7 @@
37
37
  "templates"
38
38
  ],
39
39
  "dependencies": {
40
+ "@nuxtjs/proxy": "^2.1.0",
40
41
  "axios": "^0.21.1",
41
42
  "chalk": "^4.1.2",
42
43
  "deepmerge": "^4.2.2",
@@ -45,6 +46,9 @@
45
46
  "querystring": "^0.2.0",
46
47
  "scule": "^0.2.0"
47
48
  },
49
+ "peerDependencies": {
50
+ "consola": "*"
51
+ },
48
52
  "optionalDependencies": {
49
53
  "core-js": "^3.16.1",
50
54
  "vue": "^2.6.14",
@@ -1,7 +1,12 @@
1
1
  import { DruxtClient } from 'druxt'
2
2
 
3
3
  export default (context, inject) => {
4
- const options = <%= devalue(options) %>
4
+ const options = <%= JSON.stringify(options) %>
5
+
6
+ // Disable the proxy for server side requests.
7
+ if (!process.client && (options.proxy || {}).api) {
8
+ options.proxy.api = false
9
+ }
5
10
 
6
11
  const druxt = new DruxtClient(options.baseUrl, options)
7
12
  druxt.settings = options