druxt 0.25.0-dev.20260921034536 → 0.25.0-dev.20260927110413
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/dist/druxt.esm.js +166 -9
- package/dist/druxt.ssr.js +166 -9
- package/package.json +2 -2
package/dist/druxt.esm.js
CHANGED
|
@@ -8,7 +8,113 @@ import merge from 'deepmerge';
|
|
|
8
8
|
import md5 from 'md5';
|
|
9
9
|
import Vue from 'vue';
|
|
10
10
|
|
|
11
|
+
const SCOPES = Symbol.for("druxt.processCache");
|
|
12
|
+
const scopes = globalThis[SCOPES] || (globalThis[SCOPES] = new Map());
|
|
13
|
+
const credentialed = new WeakSet();
|
|
14
|
+
const watched = new WeakSet();
|
|
15
|
+
const runtime = { isServer: () => typeof window === "undefined" };
|
|
16
|
+
const DRUPAL_SESSION_COOKIE = "S?SESS[0-9a-f]+";
|
|
17
|
+
const consumerId = (axios) => {
|
|
18
|
+
const headers = ((axios || {}).defaults || {}).headers || {};
|
|
19
|
+
for (const set of [headers, headers.common, headers.get]) {
|
|
20
|
+
const match = Object.entries(set || {}).find(([name]) => name.toLowerCase() === "x-consumer-id");
|
|
21
|
+
if (match && match[1])
|
|
22
|
+
return String(match[1]);
|
|
23
|
+
}
|
|
24
|
+
return "";
|
|
25
|
+
};
|
|
26
|
+
const headersHaveCredentials = (headers, sessionCookie) => {
|
|
27
|
+
const session = new RegExp(`(?:^|;\\s*)(?:${sessionCookie})=`, "i");
|
|
28
|
+
return Object.entries(headers || {}).some(([name, value]) => {
|
|
29
|
+
if (!value || typeof value === "object")
|
|
30
|
+
return false;
|
|
31
|
+
const header = name.toLowerCase();
|
|
32
|
+
return header === "authorization" || header === "cookie" && session.test(String(value));
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
const configHasCredentials = (config, sessionCookie = DRUPAL_SESSION_COOKIE) => {
|
|
36
|
+
if (!config)
|
|
37
|
+
return false;
|
|
38
|
+
if (config.auth)
|
|
39
|
+
return true;
|
|
40
|
+
const headers = config.headers || {};
|
|
41
|
+
return headersHaveCredentials(headers, sessionCookie) || headersHaveCredentials(headers.common, sessionCookie) || headersHaveCredentials(headers.get, sessionCookie);
|
|
42
|
+
};
|
|
43
|
+
const hasCredentials = (axios, sessionCookie = DRUPAL_SESSION_COOKIE) => {
|
|
44
|
+
if (!axios)
|
|
45
|
+
return false;
|
|
46
|
+
if (credentialed.has(axios))
|
|
47
|
+
return true;
|
|
48
|
+
return configHasCredentials(axios.defaults, sessionCookie);
|
|
49
|
+
};
|
|
50
|
+
const watchCredentials = (axios, sessionCookie = DRUPAL_SESSION_COOKIE) => {
|
|
51
|
+
const interceptors = ((axios || {}).interceptors || {}).response;
|
|
52
|
+
if (!interceptors || typeof interceptors.use !== "function" || watched.has(axios))
|
|
53
|
+
return;
|
|
54
|
+
watched.add(axios);
|
|
55
|
+
const mark = (config) => {
|
|
56
|
+
if (configHasCredentials(config, sessionCookie))
|
|
57
|
+
credentialed.add(axios);
|
|
58
|
+
};
|
|
59
|
+
interceptors.use((response) => {
|
|
60
|
+
mark((response || {}).config);
|
|
61
|
+
return response;
|
|
62
|
+
}, (error) => {
|
|
63
|
+
mark((error || {}).config);
|
|
64
|
+
return Promise.reject(error);
|
|
65
|
+
});
|
|
66
|
+
};
|
|
67
|
+
const parseCacheLifetime = (headers) => {
|
|
68
|
+
const header = (name) => Object.entries(headers || {}).filter(([key]) => key.toLowerCase() === name).map(([, value]) => String(value)).join(", ");
|
|
69
|
+
const directives = header("cache-control").toLowerCase().split(",").map((d) => d.trim());
|
|
70
|
+
if (directives.some((d) => ["private", "no-store", "no-cache"].includes(d)))
|
|
71
|
+
return 0;
|
|
72
|
+
const vary = header("vary").toLowerCase().split(",").map((v) => v.trim()).filter(Boolean);
|
|
73
|
+
if (vary.some((v) => !["cookie", "x-consumer-id", "accept-encoding"].includes(v)))
|
|
74
|
+
return 0;
|
|
75
|
+
const seconds = (name) => {
|
|
76
|
+
const directive = directives.find((d) => d.startsWith(`${name}=`));
|
|
77
|
+
const value = directive ? directive.slice(name.length + 1) : "";
|
|
78
|
+
return /^\d+$/.test(value) ? Number(value) : NaN;
|
|
79
|
+
};
|
|
80
|
+
const lifetime = directives.some((d) => d.startsWith("s-maxage=")) ? seconds("s-maxage") : seconds("max-age");
|
|
81
|
+
if (Number.isNaN(lifetime))
|
|
82
|
+
return 0;
|
|
83
|
+
const age = parseInt(header("age"), 10) || 0;
|
|
84
|
+
return Math.max(0, lifetime - age);
|
|
85
|
+
};
|
|
86
|
+
const processCache = (scope, { axios, ttl, sessionCookie } = {}) => {
|
|
87
|
+
if (!runtime.isServer() || hasCredentials(axios, sessionCookie))
|
|
88
|
+
return null;
|
|
89
|
+
if (!scopes.has(scope))
|
|
90
|
+
scopes.set(scope, new Map());
|
|
91
|
+
const consumers = scopes.get(scope);
|
|
92
|
+
const consumer = consumerId(axios);
|
|
93
|
+
if (!consumers.has(consumer))
|
|
94
|
+
consumers.set(consumer, new Map());
|
|
95
|
+
const entries = consumers.get(consumer);
|
|
96
|
+
const cap = (seconds) => typeof ttl === "number" ? Math.min(seconds, ttl) : seconds;
|
|
97
|
+
return {
|
|
98
|
+
get(key) {
|
|
99
|
+
const entry = entries.get(key);
|
|
100
|
+
if (!entry)
|
|
101
|
+
return void 0;
|
|
102
|
+
if (Date.now() - entry.created >= cap(entry.seconds) * 1e3)
|
|
103
|
+
return void 0;
|
|
104
|
+
return entry.value;
|
|
105
|
+
},
|
|
106
|
+
set(key, value, seconds) {
|
|
107
|
+
if (cap(seconds) > 0)
|
|
108
|
+
entries.set(key, { value, seconds: cap(seconds), created: Date.now() });
|
|
109
|
+
else
|
|
110
|
+
entries.delete(key);
|
|
111
|
+
return value;
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
|
|
11
116
|
const indexCache = new WeakMap();
|
|
117
|
+
const lifetimes = new WeakMap();
|
|
12
118
|
class DruxtClient {
|
|
13
119
|
constructor(baseUrl, options = {}) {
|
|
14
120
|
this.log = consola.create({ defaults: {
|
|
@@ -41,6 +147,8 @@ class DruxtClient {
|
|
|
41
147
|
jsonapiResourceConfig: "jsonapi_resource_config--jsonapi_resource_config",
|
|
42
148
|
...options
|
|
43
149
|
};
|
|
150
|
+
if (this.options.cache)
|
|
151
|
+
watchCredentials(this.axios, this.options.cache.sessionCookie);
|
|
44
152
|
const indexKey = JSON.stringify([baseUrl, this.options.endpoint, this.options.jsonapiResourceConfig]);
|
|
45
153
|
this.indexKey = indexKey;
|
|
46
154
|
if (!indexCache.has(this.axios))
|
|
@@ -49,6 +157,18 @@ class DruxtClient {
|
|
|
49
157
|
this.indexRequests = cache.requests;
|
|
50
158
|
this.index = cache.index[indexKey] || (cache.index[indexKey] = {});
|
|
51
159
|
}
|
|
160
|
+
processCache(scope) {
|
|
161
|
+
if (!this.options.cache)
|
|
162
|
+
return null;
|
|
163
|
+
const { ttl, sessionCookie } = this.options.cache;
|
|
164
|
+
return processCache(scope, { axios: this.axios, ttl, sessionCookie });
|
|
165
|
+
}
|
|
166
|
+
cacheLifetime(document) {
|
|
167
|
+
const record = document && typeof document === "object" && lifetimes.get(document);
|
|
168
|
+
if (!record)
|
|
169
|
+
return 0;
|
|
170
|
+
return Math.max(0, record.seconds - Math.floor((Date.now() - record.at) / 1e3));
|
|
171
|
+
}
|
|
52
172
|
addHeaders(headers) {
|
|
53
173
|
if (typeof headers === "undefined") {
|
|
54
174
|
return false;
|
|
@@ -142,6 +262,8 @@ class DruxtClient {
|
|
|
142
262
|
async get(url, options) {
|
|
143
263
|
try {
|
|
144
264
|
const res = await this.axios.get(url, options);
|
|
265
|
+
if (res && res.data && typeof res.data === "object")
|
|
266
|
+
lifetimes.set(res.data, { seconds: parseCacheLifetime(res.headers), at: Date.now() });
|
|
145
267
|
return res;
|
|
146
268
|
} catch (err) {
|
|
147
269
|
this.error(err, { url });
|
|
@@ -168,15 +290,26 @@ class DruxtClient {
|
|
|
168
290
|
return collections;
|
|
169
291
|
}
|
|
170
292
|
async getIndex(resource, prefix) {
|
|
293
|
+
if (!(this.index || {})[prefix]) {
|
|
294
|
+
const shared = this.processCache("index");
|
|
295
|
+
const sharedKey = [this.indexKey, prefix || ""].join(":");
|
|
296
|
+
const stored = shared && shared.get(sharedKey);
|
|
297
|
+
if (stored)
|
|
298
|
+
this.index[prefix] = stored;
|
|
299
|
+
}
|
|
171
300
|
if (!(this.index || {})[prefix]) {
|
|
172
301
|
const key = [this.indexKey, prefix || ""].join(":");
|
|
173
302
|
const request = this.indexRequests[key] || (this.indexRequests[key] = this.fetchIndex(prefix));
|
|
303
|
+
let lifetime;
|
|
174
304
|
try {
|
|
175
|
-
await request;
|
|
305
|
+
lifetime = await request;
|
|
176
306
|
} finally {
|
|
177
307
|
if (this.indexRequests[key] === request)
|
|
178
308
|
delete this.indexRequests[key];
|
|
179
309
|
}
|
|
310
|
+
const shared = this.processCache("index");
|
|
311
|
+
if (shared && this.index[prefix])
|
|
312
|
+
shared.set(key, this.index[prefix], lifetime);
|
|
180
313
|
}
|
|
181
314
|
return resource ? this.index[prefix][resource] || false : this.index[prefix];
|
|
182
315
|
}
|
|
@@ -193,10 +326,13 @@ class DruxtClient {
|
|
|
193
326
|
value.href = value.href.replace(baseUrl, "");
|
|
194
327
|
return [key, value];
|
|
195
328
|
}));
|
|
329
|
+
const documents = [data];
|
|
196
330
|
if (index[this.options.jsonapiResourceConfig]) {
|
|
197
331
|
let resources = [];
|
|
198
332
|
try {
|
|
199
|
-
|
|
333
|
+
const config = (await this.get(index[this.options.jsonapiResourceConfig].href)).data;
|
|
334
|
+
resources = config.data;
|
|
335
|
+
documents.push(config);
|
|
200
336
|
} catch (err) {
|
|
201
337
|
this.log.warn(err.message);
|
|
202
338
|
}
|
|
@@ -217,6 +353,7 @@ class DruxtClient {
|
|
|
217
353
|
}
|
|
218
354
|
}
|
|
219
355
|
this.index[prefix] = index;
|
|
356
|
+
return Math.min(...documents.map((document) => this.cacheLifetime(document)));
|
|
220
357
|
}
|
|
221
358
|
async getRelated(type, id, related, query, prefix = "") {
|
|
222
359
|
if (!id || !type || !related) {
|
|
@@ -263,7 +400,7 @@ class DruxtClient {
|
|
|
263
400
|
}
|
|
264
401
|
|
|
265
402
|
var name = "druxt";
|
|
266
|
-
var version = "0.25.0-dev.
|
|
403
|
+
var version = "0.25.0-dev.20260927110413";
|
|
267
404
|
var description = "The Fully Decoupled Drupal Framework for Nuxt.js.";
|
|
268
405
|
var keywords = [
|
|
269
406
|
"cms",
|
|
@@ -322,7 +459,7 @@ var dependencies = {
|
|
|
322
459
|
};
|
|
323
460
|
var devDependencies = {
|
|
324
461
|
"@storybook/addon-docs": "^6.5.16",
|
|
325
|
-
"druxt-test-utils": "1.0.0-dev.
|
|
462
|
+
"druxt-test-utils": "1.0.0-dev.20260927110413"
|
|
326
463
|
};
|
|
327
464
|
var peerDependencies = {
|
|
328
465
|
axios: "0.28.0",
|
|
@@ -365,6 +502,9 @@ const DruxtNuxtModule = async function(moduleOptions = {}) {
|
|
|
365
502
|
...moduleOptions,
|
|
366
503
|
...(this.options || {}).druxt
|
|
367
504
|
};
|
|
505
|
+
if (options.cache !== false) {
|
|
506
|
+
options.cache = this.options.dev ? false : { ...options.cache };
|
|
507
|
+
}
|
|
368
508
|
this.options.baseUrl = options.baseUrl = options.baseUrl.endsWith("/") ? options.baseUrl.slice(0, -1) : options.baseUrl;
|
|
369
509
|
this.options.endpoint = options.endpoint = options.endpoint.startsWith("/") ? options.endpoint : `/${options.endpoint}`;
|
|
370
510
|
const druxt = new DruxtClient(options.baseUrl, {
|
|
@@ -501,6 +641,16 @@ const DruxtStore = ({ store }) => {
|
|
|
501
641
|
throw new TypeError("Vuex store not found.");
|
|
502
642
|
}
|
|
503
643
|
const namespace = "druxt";
|
|
644
|
+
const inFlight = new Map();
|
|
645
|
+
const share = (key, request) => {
|
|
646
|
+
if (!inFlight.has(key)) {
|
|
647
|
+
const clear = () => inFlight.delete(key);
|
|
648
|
+
const promise = request();
|
|
649
|
+
inFlight.set(key, promise);
|
|
650
|
+
promise.then(clear, clear);
|
|
651
|
+
}
|
|
652
|
+
return inFlight.get(key);
|
|
653
|
+
};
|
|
504
654
|
const module = {
|
|
505
655
|
namespaced: true,
|
|
506
656
|
state: () => ({
|
|
@@ -588,9 +738,12 @@ const DruxtStore = ({ store }) => {
|
|
|
588
738
|
};
|
|
589
739
|
}
|
|
590
740
|
}
|
|
591
|
-
const
|
|
592
|
-
|
|
593
|
-
|
|
741
|
+
const key = JSON.stringify(["collection", prefix, type, hash, queryObject]);
|
|
742
|
+
return share(key, async () => {
|
|
743
|
+
const collection = await this.$druxt.getCollection(type, query, prefix);
|
|
744
|
+
commit("addCollection", { collection: { ...collection }, type, hash, prefix });
|
|
745
|
+
return collection;
|
|
746
|
+
});
|
|
594
747
|
},
|
|
595
748
|
async getResource({ commit, dispatch, state }, { type, id, query, prefix, bypassCache = false }) {
|
|
596
749
|
const storedResource = ((state.resources[type] || {})[id] || {})[prefix] ? { ...state.resources[type][id][prefix] } : null;
|
|
@@ -639,8 +792,12 @@ const DruxtStore = ({ store }) => {
|
|
|
639
792
|
let resource;
|
|
640
793
|
if (bypassCache || !storedResource || fields) {
|
|
641
794
|
try {
|
|
642
|
-
|
|
643
|
-
|
|
795
|
+
const key = JSON.stringify(["resource", prefix, type, id, queryObject]);
|
|
796
|
+
resource = await share(key, async () => {
|
|
797
|
+
const response = await this.$druxt.getResource(type, id, getDrupalJsonApiParams(queryObject), prefix);
|
|
798
|
+
commit("addResource", { prefix, resource: { ...response } });
|
|
799
|
+
return response;
|
|
800
|
+
});
|
|
644
801
|
} catch (e) {
|
|
645
802
|
}
|
|
646
803
|
}
|
package/dist/druxt.ssr.js
CHANGED
|
@@ -21,7 +21,113 @@ const merge__default = /*#__PURE__*/_interopDefaultLegacy(merge);
|
|
|
21
21
|
const md5__default = /*#__PURE__*/_interopDefaultLegacy(md5);
|
|
22
22
|
const Vue__default = /*#__PURE__*/_interopDefaultLegacy(Vue);
|
|
23
23
|
|
|
24
|
+
const SCOPES = Symbol.for("druxt.processCache");
|
|
25
|
+
const scopes = globalThis[SCOPES] || (globalThis[SCOPES] = new Map());
|
|
26
|
+
const credentialed = new WeakSet();
|
|
27
|
+
const watched = new WeakSet();
|
|
28
|
+
const runtime = { isServer: () => typeof window === "undefined" };
|
|
29
|
+
const DRUPAL_SESSION_COOKIE = "S?SESS[0-9a-f]+";
|
|
30
|
+
const consumerId = (axios) => {
|
|
31
|
+
const headers = ((axios || {}).defaults || {}).headers || {};
|
|
32
|
+
for (const set of [headers, headers.common, headers.get]) {
|
|
33
|
+
const match = Object.entries(set || {}).find(([name]) => name.toLowerCase() === "x-consumer-id");
|
|
34
|
+
if (match && match[1])
|
|
35
|
+
return String(match[1]);
|
|
36
|
+
}
|
|
37
|
+
return "";
|
|
38
|
+
};
|
|
39
|
+
const headersHaveCredentials = (headers, sessionCookie) => {
|
|
40
|
+
const session = new RegExp(`(?:^|;\\s*)(?:${sessionCookie})=`, "i");
|
|
41
|
+
return Object.entries(headers || {}).some(([name, value]) => {
|
|
42
|
+
if (!value || typeof value === "object")
|
|
43
|
+
return false;
|
|
44
|
+
const header = name.toLowerCase();
|
|
45
|
+
return header === "authorization" || header === "cookie" && session.test(String(value));
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
const configHasCredentials = (config, sessionCookie = DRUPAL_SESSION_COOKIE) => {
|
|
49
|
+
if (!config)
|
|
50
|
+
return false;
|
|
51
|
+
if (config.auth)
|
|
52
|
+
return true;
|
|
53
|
+
const headers = config.headers || {};
|
|
54
|
+
return headersHaveCredentials(headers, sessionCookie) || headersHaveCredentials(headers.common, sessionCookie) || headersHaveCredentials(headers.get, sessionCookie);
|
|
55
|
+
};
|
|
56
|
+
const hasCredentials = (axios, sessionCookie = DRUPAL_SESSION_COOKIE) => {
|
|
57
|
+
if (!axios)
|
|
58
|
+
return false;
|
|
59
|
+
if (credentialed.has(axios))
|
|
60
|
+
return true;
|
|
61
|
+
return configHasCredentials(axios.defaults, sessionCookie);
|
|
62
|
+
};
|
|
63
|
+
const watchCredentials = (axios, sessionCookie = DRUPAL_SESSION_COOKIE) => {
|
|
64
|
+
const interceptors = ((axios || {}).interceptors || {}).response;
|
|
65
|
+
if (!interceptors || typeof interceptors.use !== "function" || watched.has(axios))
|
|
66
|
+
return;
|
|
67
|
+
watched.add(axios);
|
|
68
|
+
const mark = (config) => {
|
|
69
|
+
if (configHasCredentials(config, sessionCookie))
|
|
70
|
+
credentialed.add(axios);
|
|
71
|
+
};
|
|
72
|
+
interceptors.use((response) => {
|
|
73
|
+
mark((response || {}).config);
|
|
74
|
+
return response;
|
|
75
|
+
}, (error) => {
|
|
76
|
+
mark((error || {}).config);
|
|
77
|
+
return Promise.reject(error);
|
|
78
|
+
});
|
|
79
|
+
};
|
|
80
|
+
const parseCacheLifetime = (headers) => {
|
|
81
|
+
const header = (name) => Object.entries(headers || {}).filter(([key]) => key.toLowerCase() === name).map(([, value]) => String(value)).join(", ");
|
|
82
|
+
const directives = header("cache-control").toLowerCase().split(",").map((d) => d.trim());
|
|
83
|
+
if (directives.some((d) => ["private", "no-store", "no-cache"].includes(d)))
|
|
84
|
+
return 0;
|
|
85
|
+
const vary = header("vary").toLowerCase().split(",").map((v) => v.trim()).filter(Boolean);
|
|
86
|
+
if (vary.some((v) => !["cookie", "x-consumer-id", "accept-encoding"].includes(v)))
|
|
87
|
+
return 0;
|
|
88
|
+
const seconds = (name) => {
|
|
89
|
+
const directive = directives.find((d) => d.startsWith(`${name}=`));
|
|
90
|
+
const value = directive ? directive.slice(name.length + 1) : "";
|
|
91
|
+
return /^\d+$/.test(value) ? Number(value) : NaN;
|
|
92
|
+
};
|
|
93
|
+
const lifetime = directives.some((d) => d.startsWith("s-maxage=")) ? seconds("s-maxage") : seconds("max-age");
|
|
94
|
+
if (Number.isNaN(lifetime))
|
|
95
|
+
return 0;
|
|
96
|
+
const age = parseInt(header("age"), 10) || 0;
|
|
97
|
+
return Math.max(0, lifetime - age);
|
|
98
|
+
};
|
|
99
|
+
const processCache = (scope, { axios, ttl, sessionCookie } = {}) => {
|
|
100
|
+
if (!runtime.isServer() || hasCredentials(axios, sessionCookie))
|
|
101
|
+
return null;
|
|
102
|
+
if (!scopes.has(scope))
|
|
103
|
+
scopes.set(scope, new Map());
|
|
104
|
+
const consumers = scopes.get(scope);
|
|
105
|
+
const consumer = consumerId(axios);
|
|
106
|
+
if (!consumers.has(consumer))
|
|
107
|
+
consumers.set(consumer, new Map());
|
|
108
|
+
const entries = consumers.get(consumer);
|
|
109
|
+
const cap = (seconds) => typeof ttl === "number" ? Math.min(seconds, ttl) : seconds;
|
|
110
|
+
return {
|
|
111
|
+
get(key) {
|
|
112
|
+
const entry = entries.get(key);
|
|
113
|
+
if (!entry)
|
|
114
|
+
return void 0;
|
|
115
|
+
if (Date.now() - entry.created >= cap(entry.seconds) * 1e3)
|
|
116
|
+
return void 0;
|
|
117
|
+
return entry.value;
|
|
118
|
+
},
|
|
119
|
+
set(key, value, seconds) {
|
|
120
|
+
if (cap(seconds) > 0)
|
|
121
|
+
entries.set(key, { value, seconds: cap(seconds), created: Date.now() });
|
|
122
|
+
else
|
|
123
|
+
entries.delete(key);
|
|
124
|
+
return value;
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
|
|
24
129
|
const indexCache = new WeakMap();
|
|
130
|
+
const lifetimes = new WeakMap();
|
|
25
131
|
class DruxtClient {
|
|
26
132
|
constructor(baseUrl, options = {}) {
|
|
27
133
|
this.log = consola__default["default"].create({ defaults: {
|
|
@@ -54,6 +160,8 @@ class DruxtClient {
|
|
|
54
160
|
jsonapiResourceConfig: "jsonapi_resource_config--jsonapi_resource_config",
|
|
55
161
|
...options
|
|
56
162
|
};
|
|
163
|
+
if (this.options.cache)
|
|
164
|
+
watchCredentials(this.axios, this.options.cache.sessionCookie);
|
|
57
165
|
const indexKey = JSON.stringify([baseUrl, this.options.endpoint, this.options.jsonapiResourceConfig]);
|
|
58
166
|
this.indexKey = indexKey;
|
|
59
167
|
if (!indexCache.has(this.axios))
|
|
@@ -62,6 +170,18 @@ class DruxtClient {
|
|
|
62
170
|
this.indexRequests = cache.requests;
|
|
63
171
|
this.index = cache.index[indexKey] || (cache.index[indexKey] = {});
|
|
64
172
|
}
|
|
173
|
+
processCache(scope) {
|
|
174
|
+
if (!this.options.cache)
|
|
175
|
+
return null;
|
|
176
|
+
const { ttl, sessionCookie } = this.options.cache;
|
|
177
|
+
return processCache(scope, { axios: this.axios, ttl, sessionCookie });
|
|
178
|
+
}
|
|
179
|
+
cacheLifetime(document) {
|
|
180
|
+
const record = document && typeof document === "object" && lifetimes.get(document);
|
|
181
|
+
if (!record)
|
|
182
|
+
return 0;
|
|
183
|
+
return Math.max(0, record.seconds - Math.floor((Date.now() - record.at) / 1e3));
|
|
184
|
+
}
|
|
65
185
|
addHeaders(headers) {
|
|
66
186
|
if (typeof headers === "undefined") {
|
|
67
187
|
return false;
|
|
@@ -155,6 +275,8 @@ class DruxtClient {
|
|
|
155
275
|
async get(url, options) {
|
|
156
276
|
try {
|
|
157
277
|
const res = await this.axios.get(url, options);
|
|
278
|
+
if (res && res.data && typeof res.data === "object")
|
|
279
|
+
lifetimes.set(res.data, { seconds: parseCacheLifetime(res.headers), at: Date.now() });
|
|
158
280
|
return res;
|
|
159
281
|
} catch (err) {
|
|
160
282
|
this.error(err, { url });
|
|
@@ -181,15 +303,26 @@ class DruxtClient {
|
|
|
181
303
|
return collections;
|
|
182
304
|
}
|
|
183
305
|
async getIndex(resource, prefix) {
|
|
306
|
+
if (!(this.index || {})[prefix]) {
|
|
307
|
+
const shared = this.processCache("index");
|
|
308
|
+
const sharedKey = [this.indexKey, prefix || ""].join(":");
|
|
309
|
+
const stored = shared && shared.get(sharedKey);
|
|
310
|
+
if (stored)
|
|
311
|
+
this.index[prefix] = stored;
|
|
312
|
+
}
|
|
184
313
|
if (!(this.index || {})[prefix]) {
|
|
185
314
|
const key = [this.indexKey, prefix || ""].join(":");
|
|
186
315
|
const request = this.indexRequests[key] || (this.indexRequests[key] = this.fetchIndex(prefix));
|
|
316
|
+
let lifetime;
|
|
187
317
|
try {
|
|
188
|
-
await request;
|
|
318
|
+
lifetime = await request;
|
|
189
319
|
} finally {
|
|
190
320
|
if (this.indexRequests[key] === request)
|
|
191
321
|
delete this.indexRequests[key];
|
|
192
322
|
}
|
|
323
|
+
const shared = this.processCache("index");
|
|
324
|
+
if (shared && this.index[prefix])
|
|
325
|
+
shared.set(key, this.index[prefix], lifetime);
|
|
193
326
|
}
|
|
194
327
|
return resource ? this.index[prefix][resource] || false : this.index[prefix];
|
|
195
328
|
}
|
|
@@ -206,10 +339,13 @@ class DruxtClient {
|
|
|
206
339
|
value.href = value.href.replace(baseUrl, "");
|
|
207
340
|
return [key, value];
|
|
208
341
|
}));
|
|
342
|
+
const documents = [data];
|
|
209
343
|
if (index[this.options.jsonapiResourceConfig]) {
|
|
210
344
|
let resources = [];
|
|
211
345
|
try {
|
|
212
|
-
|
|
346
|
+
const config = (await this.get(index[this.options.jsonapiResourceConfig].href)).data;
|
|
347
|
+
resources = config.data;
|
|
348
|
+
documents.push(config);
|
|
213
349
|
} catch (err) {
|
|
214
350
|
this.log.warn(err.message);
|
|
215
351
|
}
|
|
@@ -230,6 +366,7 @@ class DruxtClient {
|
|
|
230
366
|
}
|
|
231
367
|
}
|
|
232
368
|
this.index[prefix] = index;
|
|
369
|
+
return Math.min(...documents.map((document) => this.cacheLifetime(document)));
|
|
233
370
|
}
|
|
234
371
|
async getRelated(type, id, related, query, prefix = "") {
|
|
235
372
|
if (!id || !type || !related) {
|
|
@@ -276,7 +413,7 @@ class DruxtClient {
|
|
|
276
413
|
}
|
|
277
414
|
|
|
278
415
|
var name = "druxt";
|
|
279
|
-
var version = "0.25.0-dev.
|
|
416
|
+
var version = "0.25.0-dev.20260927110413";
|
|
280
417
|
var description = "The Fully Decoupled Drupal Framework for Nuxt.js.";
|
|
281
418
|
var keywords = [
|
|
282
419
|
"cms",
|
|
@@ -335,7 +472,7 @@ var dependencies = {
|
|
|
335
472
|
};
|
|
336
473
|
var devDependencies = {
|
|
337
474
|
"@storybook/addon-docs": "^6.5.16",
|
|
338
|
-
"druxt-test-utils": "1.0.0-dev.
|
|
475
|
+
"druxt-test-utils": "1.0.0-dev.20260927110413"
|
|
339
476
|
};
|
|
340
477
|
var peerDependencies = {
|
|
341
478
|
axios: "0.28.0",
|
|
@@ -378,6 +515,9 @@ const DruxtNuxtModule = async function(moduleOptions = {}) {
|
|
|
378
515
|
...moduleOptions,
|
|
379
516
|
...(this.options || {}).druxt
|
|
380
517
|
};
|
|
518
|
+
if (options.cache !== false) {
|
|
519
|
+
options.cache = this.options.dev ? false : { ...options.cache };
|
|
520
|
+
}
|
|
381
521
|
this.options.baseUrl = options.baseUrl = options.baseUrl.endsWith("/") ? options.baseUrl.slice(0, -1) : options.baseUrl;
|
|
382
522
|
this.options.endpoint = options.endpoint = options.endpoint.startsWith("/") ? options.endpoint : `/${options.endpoint}`;
|
|
383
523
|
const druxt = new DruxtClient(options.baseUrl, {
|
|
@@ -514,6 +654,16 @@ const DruxtStore = ({ store }) => {
|
|
|
514
654
|
throw new TypeError("Vuex store not found.");
|
|
515
655
|
}
|
|
516
656
|
const namespace = "druxt";
|
|
657
|
+
const inFlight = new Map();
|
|
658
|
+
const share = (key, request) => {
|
|
659
|
+
if (!inFlight.has(key)) {
|
|
660
|
+
const clear = () => inFlight.delete(key);
|
|
661
|
+
const promise = request();
|
|
662
|
+
inFlight.set(key, promise);
|
|
663
|
+
promise.then(clear, clear);
|
|
664
|
+
}
|
|
665
|
+
return inFlight.get(key);
|
|
666
|
+
};
|
|
517
667
|
const module = {
|
|
518
668
|
namespaced: true,
|
|
519
669
|
state: () => ({
|
|
@@ -601,9 +751,12 @@ const DruxtStore = ({ store }) => {
|
|
|
601
751
|
};
|
|
602
752
|
}
|
|
603
753
|
}
|
|
604
|
-
const
|
|
605
|
-
|
|
606
|
-
|
|
754
|
+
const key = JSON.stringify(["collection", prefix, type, hash, queryObject]);
|
|
755
|
+
return share(key, async () => {
|
|
756
|
+
const collection = await this.$druxt.getCollection(type, query, prefix);
|
|
757
|
+
commit("addCollection", { collection: { ...collection }, type, hash, prefix });
|
|
758
|
+
return collection;
|
|
759
|
+
});
|
|
607
760
|
},
|
|
608
761
|
async getResource({ commit, dispatch, state }, { type, id, query, prefix, bypassCache = false }) {
|
|
609
762
|
const storedResource = ((state.resources[type] || {})[id] || {})[prefix] ? { ...state.resources[type][id][prefix] } : null;
|
|
@@ -652,8 +805,12 @@ const DruxtStore = ({ store }) => {
|
|
|
652
805
|
let resource;
|
|
653
806
|
if (bypassCache || !storedResource || fields) {
|
|
654
807
|
try {
|
|
655
|
-
|
|
656
|
-
|
|
808
|
+
const key = JSON.stringify(["resource", prefix, type, id, queryObject]);
|
|
809
|
+
resource = await share(key, async () => {
|
|
810
|
+
const response = await this.$druxt.getResource(type, id, getDrupalJsonApiParams(queryObject), prefix);
|
|
811
|
+
commit("addResource", { prefix, resource: { ...response } });
|
|
812
|
+
return response;
|
|
813
|
+
});
|
|
657
814
|
} catch (e) {
|
|
658
815
|
}
|
|
659
816
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "druxt",
|
|
3
|
-
"version": "0.25.0-dev.
|
|
3
|
+
"version": "0.25.0-dev.20260927110413",
|
|
4
4
|
"description": "The Fully Decoupled Drupal Framework for Nuxt.js.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cms",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
61
|
"@storybook/addon-docs": "^6.5.16",
|
|
62
|
-
"druxt-test-utils": "1.0.0-dev.
|
|
62
|
+
"druxt-test-utils": "1.0.0-dev.20260927110413"
|
|
63
63
|
},
|
|
64
64
|
"peerDependencies": {
|
|
65
65
|
"axios": "0.28.0",
|