@nubitio/core 0.5.11 → 0.5.13
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/index.cjs +334 -140
- package/dist/index.d.cts +73 -5
- package/dist/index.d.mts +73 -5
- package/dist/index.mjs +327 -142
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -32,7 +32,8 @@ const _coreConfig = {
|
|
|
32
32
|
locale: "es",
|
|
33
33
|
timezone: "UTC",
|
|
34
34
|
apiBaseUrl: "/api/",
|
|
35
|
-
currency: void 0
|
|
35
|
+
currency: void 0,
|
|
36
|
+
mercureTopicOrigin: void 0
|
|
36
37
|
};
|
|
37
38
|
/**
|
|
38
39
|
* Configure core runtime values (locale, timezone, apiBaseUrl).
|
|
@@ -44,6 +45,7 @@ function configureCore(config) {
|
|
|
44
45
|
if (config.timezone !== void 0) _coreConfig.timezone = config.timezone;
|
|
45
46
|
if (config.apiBaseUrl !== void 0) _coreConfig.apiBaseUrl = config.apiBaseUrl;
|
|
46
47
|
if ("currency" in config) _coreConfig.currency = config.currency;
|
|
48
|
+
if ("mercureTopicOrigin" in config) _coreConfig.mercureTopicOrigin = config.mercureTopicOrigin;
|
|
47
49
|
}
|
|
48
50
|
/**
|
|
49
51
|
* @deprecated Use configureCore() instead.
|
|
@@ -62,24 +64,30 @@ function getCoreApiBaseUrl() {
|
|
|
62
64
|
function getCoreCurrency() {
|
|
63
65
|
return _coreConfig.currency;
|
|
64
66
|
}
|
|
67
|
+
function getMercureTopicOrigin() {
|
|
68
|
+
return _coreConfig.mercureTopicOrigin;
|
|
69
|
+
}
|
|
65
70
|
const CoreConfigContext = react.default.createContext(_coreConfig);
|
|
66
|
-
const CoreConfigProvider = ({ locale, timezone, apiBaseUrl, currency, children }) => {
|
|
71
|
+
const CoreConfigProvider = ({ locale, timezone, apiBaseUrl, currency, mercureTopicOrigin, children }) => {
|
|
67
72
|
configureCore({
|
|
68
73
|
locale,
|
|
69
74
|
timezone,
|
|
70
75
|
apiBaseUrl,
|
|
71
|
-
currency
|
|
76
|
+
currency,
|
|
77
|
+
mercureTopicOrigin
|
|
72
78
|
});
|
|
73
79
|
const value = react.default.useMemo(() => ({
|
|
74
80
|
locale,
|
|
75
81
|
timezone,
|
|
76
82
|
apiBaseUrl,
|
|
77
|
-
currency
|
|
83
|
+
currency,
|
|
84
|
+
mercureTopicOrigin
|
|
78
85
|
}), [
|
|
79
86
|
locale,
|
|
80
87
|
timezone,
|
|
81
88
|
apiBaseUrl,
|
|
82
|
-
currency
|
|
89
|
+
currency,
|
|
90
|
+
mercureTopicOrigin
|
|
83
91
|
]);
|
|
84
92
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CoreConfigContext.Provider, {
|
|
85
93
|
value,
|
|
@@ -514,6 +522,230 @@ function initCoreI18n() {
|
|
|
514
522
|
i18next.default.addResourceBundle("en", "core", coreTranslationsEn, true, false);
|
|
515
523
|
}
|
|
516
524
|
//#endregion
|
|
525
|
+
//#region packages/core/mercure/MercureManager.ts
|
|
526
|
+
var MercureManager = class {
|
|
527
|
+
hubUrl = null;
|
|
528
|
+
topics = /* @__PURE__ */ new Map();
|
|
529
|
+
hubUrlListeners = /* @__PURE__ */ new Set();
|
|
530
|
+
/**
|
|
531
|
+
* Update the hub URL (called by the Axios interceptor when the `Link` header is discovered,
|
|
532
|
+
* or by MercureProvider). Notifies all registered listeners.
|
|
533
|
+
*
|
|
534
|
+
* Idempotent: if the URL is already set to the same value, listeners are NOT re-notified.
|
|
535
|
+
*/
|
|
536
|
+
setHubUrl(url) {
|
|
537
|
+
if (this.hubUrl === url) return;
|
|
538
|
+
this.disconnectAll();
|
|
539
|
+
this.hubUrl = url;
|
|
540
|
+
this.hubUrlListeners.forEach((cb) => cb(url));
|
|
541
|
+
}
|
|
542
|
+
getHubUrl() {
|
|
543
|
+
return this.hubUrl;
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Register a callback that fires whenever the hub URL changes.
|
|
547
|
+
*
|
|
548
|
+
* Returns an unsubscribe function — call it in a `useEffect` cleanup to avoid memory leaks.
|
|
549
|
+
*
|
|
550
|
+
* @example
|
|
551
|
+
* ```tsx
|
|
552
|
+
* useEffect(() => {
|
|
553
|
+
* return MercureManager.onHubUrlChange(setHubUrl);
|
|
554
|
+
* }, []);
|
|
555
|
+
* ```
|
|
556
|
+
*/
|
|
557
|
+
onHubUrlChange(callback) {
|
|
558
|
+
this.hubUrlListeners.add(callback);
|
|
559
|
+
return () => {
|
|
560
|
+
this.hubUrlListeners.delete(callback);
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* Subscribe to a Mercure topic.
|
|
565
|
+
*
|
|
566
|
+
* @param topic Full topic URI or URI Template, e.g. `https://host/api/products/{id}`.
|
|
567
|
+
* @param callback Called with the parsed JSON payload on each SSE message.
|
|
568
|
+
*
|
|
569
|
+
* If `hubUrl` is null (hub not configured), this is a no-op (graceful degradation).
|
|
570
|
+
* If an EventSource for this topic already exists, it is reused (ref-counting).
|
|
571
|
+
*/
|
|
572
|
+
subscribe(topic, callback) {
|
|
573
|
+
if (this.hubUrl === null) return;
|
|
574
|
+
const existing = this.topics.get(topic);
|
|
575
|
+
if (existing) {
|
|
576
|
+
const listener = (e) => {
|
|
577
|
+
try {
|
|
578
|
+
callback(JSON.parse(e.data));
|
|
579
|
+
} catch {
|
|
580
|
+
callback(e.data);
|
|
581
|
+
}
|
|
582
|
+
};
|
|
583
|
+
existing.eventSource.addEventListener("message", listener);
|
|
584
|
+
existing.listeners.set(callback, listener);
|
|
585
|
+
existing.count += 1;
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
const url = new URL(this.hubUrl);
|
|
589
|
+
url.searchParams.append("topic", topic);
|
|
590
|
+
const eventSource = new EventSource(url.toString(), { withCredentials: true });
|
|
591
|
+
const listener = (e) => {
|
|
592
|
+
try {
|
|
593
|
+
callback(JSON.parse(e.data));
|
|
594
|
+
} catch {
|
|
595
|
+
callback(e.data);
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
eventSource.addEventListener("message", listener);
|
|
599
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
600
|
+
listeners.set(callback, listener);
|
|
601
|
+
this.topics.set(topic, {
|
|
602
|
+
eventSource,
|
|
603
|
+
count: 1,
|
|
604
|
+
listeners
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* Unsubscribe a specific callback from a topic.
|
|
609
|
+
*
|
|
610
|
+
* Decrements the ref count. When count reaches 0, the EventSource is closed
|
|
611
|
+
* and removed from the internal map.
|
|
612
|
+
*/
|
|
613
|
+
unsubscribe(topic, callback) {
|
|
614
|
+
const entry = this.topics.get(topic);
|
|
615
|
+
if (!entry) return;
|
|
616
|
+
const listener = entry.listeners.get(callback);
|
|
617
|
+
if (listener) {
|
|
618
|
+
entry.eventSource.removeEventListener("message", listener);
|
|
619
|
+
entry.listeners.delete(callback);
|
|
620
|
+
}
|
|
621
|
+
entry.count -= 1;
|
|
622
|
+
if (entry.count <= 0) {
|
|
623
|
+
entry.eventSource.close();
|
|
624
|
+
this.topics.delete(topic);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
/**
|
|
628
|
+
* Close all active EventSource connections and clear the topic map.
|
|
629
|
+
*
|
|
630
|
+
* Called internally when the hub URL changes so stale connections
|
|
631
|
+
* bound to the previous hub are not leaked.
|
|
632
|
+
*/
|
|
633
|
+
disconnectAll() {
|
|
634
|
+
this.topics.forEach((entry) => {
|
|
635
|
+
entry.eventSource.close();
|
|
636
|
+
});
|
|
637
|
+
this.topics.clear();
|
|
638
|
+
}
|
|
639
|
+
};
|
|
640
|
+
/** Singleton instance — shared across the entire application. */
|
|
641
|
+
const mercureManager = new MercureManager();
|
|
642
|
+
//#endregion
|
|
643
|
+
//#region packages/core/mercure/mercureDiscovery.ts
|
|
644
|
+
let discoveredTopicOrigin;
|
|
645
|
+
const topicOriginListeners = /* @__PURE__ */ new Set();
|
|
646
|
+
/**
|
|
647
|
+
* Parse a `Link` response header value into `{ url, rel }` pairs.
|
|
648
|
+
* Handles API Platform's `</.well-known/mercure>; rel="mercure"` format.
|
|
649
|
+
*/
|
|
650
|
+
function parseLinkHeader(header) {
|
|
651
|
+
const links = [];
|
|
652
|
+
for (const segment of header.split(/,(?=\s*<)/)) {
|
|
653
|
+
const urlMatch = segment.match(/<([^>]+)>/);
|
|
654
|
+
const relMatch = segment.match(/rel=(?:"([^"]+)"|([^;\s,]+))/i);
|
|
655
|
+
if (urlMatch && relMatch) links.push({
|
|
656
|
+
url: urlMatch[1],
|
|
657
|
+
rel: (relMatch[1] ?? relMatch[2]).trim()
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
return links;
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* When the API advertises an absolute hub on another origin (common in Docker:
|
|
664
|
+
* `MERCURE_PUBLIC_URL=http://localhost:3000/...` while the SPA proxies the hub
|
|
665
|
+
* at `/.well-known/mercure`), prefer the same-origin path so EventSource and
|
|
666
|
+
* cookies go through the dev proxy.
|
|
667
|
+
*/
|
|
668
|
+
function normalizeMercureHubUrl(hubUrl) {
|
|
669
|
+
if (typeof window === "undefined") return hubUrl;
|
|
670
|
+
try {
|
|
671
|
+
const parsed = new URL(hubUrl, window.location.origin);
|
|
672
|
+
if (parsed.pathname === "/.well-known/mercure" && parsed.origin !== window.location.origin) return `${window.location.origin}${parsed.pathname}`;
|
|
673
|
+
return parsed.toString();
|
|
674
|
+
} catch {
|
|
675
|
+
return hubUrl;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* Extract the Mercure hub URL from response headers, if present.
|
|
680
|
+
* Relative URLs are resolved against the request URL.
|
|
681
|
+
*/
|
|
682
|
+
function extractMercureHubUrl(headers, responseUrl) {
|
|
683
|
+
const linkHeader = headers.get("link") ?? headers.get("Link");
|
|
684
|
+
if (!linkHeader) return null;
|
|
685
|
+
const mercureLink = parseLinkHeader(linkHeader).find((link) => link.rel === "mercure");
|
|
686
|
+
if (!mercureLink) return null;
|
|
687
|
+
try {
|
|
688
|
+
return normalizeMercureHubUrl(new URL(mercureLink.url, responseUrl).toString());
|
|
689
|
+
} catch {
|
|
690
|
+
return normalizeMercureHubUrl(mercureLink.url);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
function originFromAbsoluteIri(iri) {
|
|
694
|
+
if (!/^https?:\/\//i.test(iri)) return null;
|
|
695
|
+
try {
|
|
696
|
+
return new URL(iri).origin;
|
|
697
|
+
} catch {
|
|
698
|
+
return null;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
/**
|
|
702
|
+
* Infer the API origin from Hydra `@id` values (entrypoint or collection items).
|
|
703
|
+
* API Platform publishes absolute IRIs when `DEFAULT_URI` is configured.
|
|
704
|
+
*/
|
|
705
|
+
function extractTopicOriginFromPayload(data) {
|
|
706
|
+
if (!data || typeof data !== "object") return null;
|
|
707
|
+
const record = data;
|
|
708
|
+
if (typeof record["@id"] === "string") {
|
|
709
|
+
const origin = originFromAbsoluteIri(record["@id"]);
|
|
710
|
+
if (origin) return origin;
|
|
711
|
+
}
|
|
712
|
+
const members = record["hydra:member"];
|
|
713
|
+
if (Array.isArray(members)) for (const member of members) {
|
|
714
|
+
if (!member || typeof member !== "object") continue;
|
|
715
|
+
const memberId = member["@id"];
|
|
716
|
+
if (typeof memberId === "string") {
|
|
717
|
+
const origin = originFromAbsoluteIri(memberId);
|
|
718
|
+
if (origin) return origin;
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
return null;
|
|
722
|
+
}
|
|
723
|
+
function getDiscoveredMercureTopicOrigin() {
|
|
724
|
+
return discoveredTopicOrigin;
|
|
725
|
+
}
|
|
726
|
+
/** Subscribe to autodiscovered topic-origin changes (for React hooks). */
|
|
727
|
+
function onMercureTopicOriginChange(callback) {
|
|
728
|
+
topicOriginListeners.add(callback);
|
|
729
|
+
return () => {
|
|
730
|
+
topicOriginListeners.delete(callback);
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
function setDiscoveredTopicOrigin(origin) {
|
|
734
|
+
if (discoveredTopicOrigin === origin) return;
|
|
735
|
+
discoveredTopicOrigin = origin;
|
|
736
|
+
topicOriginListeners.forEach((listener) => listener(origin));
|
|
737
|
+
}
|
|
738
|
+
/**
|
|
739
|
+
* Inspect an API response and update Mercure hub URL / topic origin when found.
|
|
740
|
+
* Called from `CoreHttpClient` on every successful response.
|
|
741
|
+
*/
|
|
742
|
+
function discoverMercureFromResponse(response, data) {
|
|
743
|
+
const hubUrl = extractMercureHubUrl(response.headers, response.url);
|
|
744
|
+
if (hubUrl) mercureManager.setHubUrl(hubUrl);
|
|
745
|
+
const topicOrigin = extractTopicOriginFromPayload(data);
|
|
746
|
+
if (topicOrigin) setDiscoveredTopicOrigin(topicOrigin);
|
|
747
|
+
}
|
|
748
|
+
//#endregion
|
|
517
749
|
//#region packages/core/http/CoreHttpClient.ts
|
|
518
750
|
function joinUrl(baseUrl, url) {
|
|
519
751
|
if (/^https?:\/\//.test(url) || url.startsWith("/")) return url;
|
|
@@ -609,12 +841,16 @@ var CoreHttpClient = class {
|
|
|
609
841
|
signal: config?.signal,
|
|
610
842
|
body: body === void 0 || body === null ? void 0 : body instanceof FormData ? body : JSON.stringify(body)
|
|
611
843
|
});
|
|
612
|
-
if (response.ok)
|
|
613
|
-
response,
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
844
|
+
if (response.ok) {
|
|
845
|
+
const data = await readResponseBody(response, config?.responseType);
|
|
846
|
+
if (config?.responseType === void 0 || config.responseType === "json") discoverMercureFromResponse(response, data);
|
|
847
|
+
return {
|
|
848
|
+
response,
|
|
849
|
+
data,
|
|
850
|
+
headers: response.headers,
|
|
851
|
+
status: response.status
|
|
852
|
+
};
|
|
853
|
+
}
|
|
618
854
|
const errorData = await this.safeErrorData(response);
|
|
619
855
|
const error = createHttpError(errorData.detail ?? errorData.message ?? "HTTP request failed", response.status, errorData);
|
|
620
856
|
const loginPath = this.config.loginPath ?? "auth/login";
|
|
@@ -713,124 +949,6 @@ function CoreProvider({ children, http, httpClient, runtime }) {
|
|
|
713
949
|
});
|
|
714
950
|
}
|
|
715
951
|
//#endregion
|
|
716
|
-
//#region packages/core/mercure/MercureManager.ts
|
|
717
|
-
var MercureManager = class {
|
|
718
|
-
hubUrl = null;
|
|
719
|
-
topics = /* @__PURE__ */ new Map();
|
|
720
|
-
hubUrlListeners = /* @__PURE__ */ new Set();
|
|
721
|
-
/**
|
|
722
|
-
* Update the hub URL (called by the Axios interceptor when the `Link` header is discovered,
|
|
723
|
-
* or by MercureProvider). Notifies all registered listeners.
|
|
724
|
-
*
|
|
725
|
-
* Idempotent: if the URL is already set to the same value, listeners are NOT re-notified.
|
|
726
|
-
*/
|
|
727
|
-
setHubUrl(url) {
|
|
728
|
-
if (this.hubUrl === url) return;
|
|
729
|
-
this.disconnectAll();
|
|
730
|
-
this.hubUrl = url;
|
|
731
|
-
this.hubUrlListeners.forEach((cb) => cb(url));
|
|
732
|
-
}
|
|
733
|
-
getHubUrl() {
|
|
734
|
-
return this.hubUrl;
|
|
735
|
-
}
|
|
736
|
-
/**
|
|
737
|
-
* Register a callback that fires whenever the hub URL changes.
|
|
738
|
-
*
|
|
739
|
-
* Returns an unsubscribe function — call it in a `useEffect` cleanup to avoid memory leaks.
|
|
740
|
-
*
|
|
741
|
-
* @example
|
|
742
|
-
* ```tsx
|
|
743
|
-
* useEffect(() => {
|
|
744
|
-
* return MercureManager.onHubUrlChange(setHubUrl);
|
|
745
|
-
* }, []);
|
|
746
|
-
* ```
|
|
747
|
-
*/
|
|
748
|
-
onHubUrlChange(callback) {
|
|
749
|
-
this.hubUrlListeners.add(callback);
|
|
750
|
-
return () => {
|
|
751
|
-
this.hubUrlListeners.delete(callback);
|
|
752
|
-
};
|
|
753
|
-
}
|
|
754
|
-
/**
|
|
755
|
-
* Subscribe to a Mercure topic.
|
|
756
|
-
*
|
|
757
|
-
* @param topic Full topic URI or URI Template, e.g. `https://host/api/products/{id}`.
|
|
758
|
-
* @param callback Called with the parsed JSON payload on each SSE message.
|
|
759
|
-
*
|
|
760
|
-
* If `hubUrl` is null (hub not configured), this is a no-op (graceful degradation).
|
|
761
|
-
* If an EventSource for this topic already exists, it is reused (ref-counting).
|
|
762
|
-
*/
|
|
763
|
-
subscribe(topic, callback) {
|
|
764
|
-
if (this.hubUrl === null) return;
|
|
765
|
-
const existing = this.topics.get(topic);
|
|
766
|
-
if (existing) {
|
|
767
|
-
const listener = (e) => {
|
|
768
|
-
try {
|
|
769
|
-
callback(JSON.parse(e.data));
|
|
770
|
-
} catch {
|
|
771
|
-
callback(e.data);
|
|
772
|
-
}
|
|
773
|
-
};
|
|
774
|
-
existing.eventSource.addEventListener("message", listener);
|
|
775
|
-
existing.listeners.set(callback, listener);
|
|
776
|
-
existing.count += 1;
|
|
777
|
-
return;
|
|
778
|
-
}
|
|
779
|
-
const url = new URL(this.hubUrl);
|
|
780
|
-
url.searchParams.append("topic", topic);
|
|
781
|
-
const eventSource = new EventSource(url.toString(), { withCredentials: true });
|
|
782
|
-
const listener = (e) => {
|
|
783
|
-
try {
|
|
784
|
-
callback(JSON.parse(e.data));
|
|
785
|
-
} catch {
|
|
786
|
-
callback(e.data);
|
|
787
|
-
}
|
|
788
|
-
};
|
|
789
|
-
eventSource.addEventListener("message", listener);
|
|
790
|
-
const listeners = /* @__PURE__ */ new Map();
|
|
791
|
-
listeners.set(callback, listener);
|
|
792
|
-
this.topics.set(topic, {
|
|
793
|
-
eventSource,
|
|
794
|
-
count: 1,
|
|
795
|
-
listeners
|
|
796
|
-
});
|
|
797
|
-
}
|
|
798
|
-
/**
|
|
799
|
-
* Unsubscribe a specific callback from a topic.
|
|
800
|
-
*
|
|
801
|
-
* Decrements the ref count. When count reaches 0, the EventSource is closed
|
|
802
|
-
* and removed from the internal map.
|
|
803
|
-
*/
|
|
804
|
-
unsubscribe(topic, callback) {
|
|
805
|
-
const entry = this.topics.get(topic);
|
|
806
|
-
if (!entry) return;
|
|
807
|
-
const listener = entry.listeners.get(callback);
|
|
808
|
-
if (listener) {
|
|
809
|
-
entry.eventSource.removeEventListener("message", listener);
|
|
810
|
-
entry.listeners.delete(callback);
|
|
811
|
-
}
|
|
812
|
-
entry.count -= 1;
|
|
813
|
-
if (entry.count <= 0) {
|
|
814
|
-
entry.eventSource.close();
|
|
815
|
-
this.topics.delete(topic);
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
/**
|
|
819
|
-
* Close all active EventSource connections and clear the topic map.
|
|
820
|
-
*
|
|
821
|
-
* Called internally when the hub URL changes so stale connections
|
|
822
|
-
* bound to the previous hub are not leaked.
|
|
823
|
-
*/
|
|
824
|
-
disconnectAll() {
|
|
825
|
-
this.topics.forEach((entry) => {
|
|
826
|
-
entry.eventSource.close();
|
|
827
|
-
});
|
|
828
|
-
this.topics.clear();
|
|
829
|
-
}
|
|
830
|
-
};
|
|
831
|
-
/** Singleton instance — shared across the entire application. */
|
|
832
|
-
const mercureManager = new MercureManager();
|
|
833
|
-
//#endregion
|
|
834
952
|
//#region packages/core/mercure/MercureProvider.tsx
|
|
835
953
|
/**
|
|
836
954
|
* MercureProvider — React context provider for the Mercure hub URL.
|
|
@@ -843,14 +961,19 @@ const mercureManager = new MercureManager();
|
|
|
843
961
|
* ## Usage
|
|
844
962
|
*
|
|
845
963
|
* ```tsx
|
|
846
|
-
* //
|
|
847
|
-
* <MercureProvider
|
|
964
|
+
* // Autodiscovery (recommended): hub URL comes from API `Link` headers.
|
|
965
|
+
* <MercureProvider>
|
|
966
|
+
* <App />
|
|
967
|
+
* </MercureProvider>
|
|
968
|
+
*
|
|
969
|
+
* // Manual override (e.g. before the first API response):
|
|
970
|
+
* <MercureProvider hubUrl="/.well-known/mercure">
|
|
848
971
|
* <App />
|
|
849
972
|
* </MercureProvider>
|
|
850
973
|
* ```
|
|
851
974
|
*
|
|
852
|
-
* The
|
|
853
|
-
* (
|
|
975
|
+
* The hub URL is autodiscovered from the `Link` header of API responses
|
|
976
|
+
* (API Platform + Mercure). Pass `hubUrl` only when you need an explicit override.
|
|
854
977
|
*/
|
|
855
978
|
/** The hub URL, or null if Mercure is not configured / not yet discovered. */
|
|
856
979
|
const MercureContext = (0, react.createContext)(null);
|
|
@@ -858,10 +981,17 @@ const MercureContext = (0, react.createContext)(null);
|
|
|
858
981
|
* Provides the Mercure hub URL to the React tree and keeps `MercureManager`
|
|
859
982
|
* in sync whenever the URL changes.
|
|
860
983
|
*/
|
|
861
|
-
function MercureProvider({ hubUrl, children }) {
|
|
984
|
+
function MercureProvider({ hubUrl: hubUrlOverride, children }) {
|
|
985
|
+
const [discoveredHubUrl, setDiscoveredHubUrl] = (0, react.useState)(() => mercureManager.getHubUrl());
|
|
862
986
|
(0, react.useEffect)(() => {
|
|
863
|
-
|
|
864
|
-
|
|
987
|
+
if (hubUrlOverride !== void 0) {
|
|
988
|
+
mercureManager.setHubUrl(hubUrlOverride);
|
|
989
|
+
return;
|
|
990
|
+
}
|
|
991
|
+
setDiscoveredHubUrl(mercureManager.getHubUrl());
|
|
992
|
+
return mercureManager.onHubUrlChange(setDiscoveredHubUrl);
|
|
993
|
+
}, [hubUrlOverride]);
|
|
994
|
+
const hubUrl = hubUrlOverride !== void 0 ? hubUrlOverride : discoveredHubUrl;
|
|
865
995
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MercureContext.Provider, {
|
|
866
996
|
value: hubUrl,
|
|
867
997
|
children
|
|
@@ -878,7 +1008,8 @@ function MercureProvider({ hubUrl, children }) {
|
|
|
878
1008
|
* - Mercure is not configured in the backend.
|
|
879
1009
|
*
|
|
880
1010
|
* This hook does NOT perform any fetch or side effect — it only reads from context.
|
|
881
|
-
* Hub discovery
|
|
1011
|
+
* Hub discovery happens automatically via `CoreHttpClient` (`Link` header parsing).
|
|
1012
|
+
* Use `<MercureProvider>` without a `hubUrl` prop to expose the discovered URL.
|
|
882
1013
|
*
|
|
883
1014
|
* ## Usage
|
|
884
1015
|
*
|
|
@@ -895,6 +1026,52 @@ function useMercureHub() {
|
|
|
895
1026
|
return (0, react.useContext)(MercureContext);
|
|
896
1027
|
}
|
|
897
1028
|
//#endregion
|
|
1029
|
+
//#region packages/core/mercure/mercureTopics.ts
|
|
1030
|
+
/**
|
|
1031
|
+
* Resolves the origin used to build Mercure collection topic URIs.
|
|
1032
|
+
*
|
|
1033
|
+
* API Platform publishes item updates to IRIs such as
|
|
1034
|
+
* `http://localhost:8000/api/products/42`. The SPA may run on another origin
|
|
1035
|
+
* in dev (Vite on :5173, API on :8000). The origin is normally autodiscovered
|
|
1036
|
+
* from absolute Hydra `@id` values on API responses. Set `mercureTopicOrigin`
|
|
1037
|
+
* on CoreConfig only when autodiscovery cannot infer the correct origin.
|
|
1038
|
+
*/
|
|
1039
|
+
function resolveMercureTopicOrigin(apiBaseUrl = getCoreApiBaseUrl(), configuredOrigin, discoveredOrigin = getDiscoveredMercureTopicOrigin()) {
|
|
1040
|
+
const explicit = configuredOrigin?.trim().replace(/\/+$/, "");
|
|
1041
|
+
if (explicit) return explicit;
|
|
1042
|
+
const discovered = discoveredOrigin?.trim().replace(/\/+$/, "");
|
|
1043
|
+
if (discovered) return discovered;
|
|
1044
|
+
const trimmedBase = apiBaseUrl.trim();
|
|
1045
|
+
if (/^https?:\/\//i.test(trimmedBase)) try {
|
|
1046
|
+
return new URL(trimmedBase).origin;
|
|
1047
|
+
} catch {}
|
|
1048
|
+
if (typeof window !== "undefined") return window.location.origin;
|
|
1049
|
+
return "";
|
|
1050
|
+
}
|
|
1051
|
+
/**
|
|
1052
|
+
* Wildcard collection topic (RFC 6570 URI Template) for a resource API path.
|
|
1053
|
+
* e.g. `/api/products` → `http://localhost:8000/api/products/{id}`
|
|
1054
|
+
*/
|
|
1055
|
+
function buildMercureCollectionTopic(apiUrl, configuredOrigin, apiBaseUrl = getCoreApiBaseUrl()) {
|
|
1056
|
+
if (!apiUrl) return null;
|
|
1057
|
+
const origin = resolveMercureTopicOrigin(apiBaseUrl, configuredOrigin);
|
|
1058
|
+
if (!origin) return null;
|
|
1059
|
+
const normalizedPath = apiUrl.replace(/^\//, "").replace(/\/+$/, "");
|
|
1060
|
+
if (!normalizedPath) return null;
|
|
1061
|
+
return `${origin}/${normalizedPath}/{id}`;
|
|
1062
|
+
}
|
|
1063
|
+
//#endregion
|
|
1064
|
+
//#region packages/core/mercure/useDiscoveredMercureTopicOrigin.ts
|
|
1065
|
+
/**
|
|
1066
|
+
* Returns the Mercure topic origin autodiscovered from Hydra `@id` IRIs.
|
|
1067
|
+
* Re-renders when a new origin is discovered from a subsequent API response.
|
|
1068
|
+
*/
|
|
1069
|
+
function useDiscoveredMercureTopicOrigin() {
|
|
1070
|
+
const [origin, setOrigin] = (0, react.useState)(getDiscoveredMercureTopicOrigin);
|
|
1071
|
+
(0, react.useEffect)(() => onMercureTopicOriginChange(setOrigin), []);
|
|
1072
|
+
return origin;
|
|
1073
|
+
}
|
|
1074
|
+
//#endregion
|
|
898
1075
|
//#region packages/core/mercure/useMercureSubscription.ts
|
|
899
1076
|
/**
|
|
900
1077
|
* useMercureSubscription — Hook that subscribes to a Mercure topic and calls
|
|
@@ -904,6 +1081,9 @@ function useMercureHub() {
|
|
|
904
1081
|
* - If `hubUrl` is null (Mercure not configured) or `enabled` is false → no-op.
|
|
905
1082
|
* - Subscribes to the wildcard topic `<origin>/<apiUrl>/{id}` (URI Template RFC 6570),
|
|
906
1083
|
* which captures any item-level event (create / update / delete) for the collection.
|
|
1084
|
+
* - `<origin>` comes from `CoreConfig.mercureTopicOrigin`, autodiscovered Hydra
|
|
1085
|
+
* `@id`, an absolute `apiBaseUrl`, or `window.location.origin`
|
|
1086
|
+
* (see `buildMercureCollectionTopic`).
|
|
907
1087
|
* - Cleanup: unsubscribes on unmount or when dependencies change (no memory leaks).
|
|
908
1088
|
*
|
|
909
1089
|
* ## Usage
|
|
@@ -923,10 +1103,12 @@ function useMercureHub() {
|
|
|
923
1103
|
*/
|
|
924
1104
|
function useMercureSubscription(apiUrl, onUpdate, enabled = true) {
|
|
925
1105
|
const hubUrl = useMercureHub();
|
|
1106
|
+
const { mercureTopicOrigin, apiBaseUrl } = useCoreConfig();
|
|
1107
|
+
const discoveredTopicOrigin = useDiscoveredMercureTopicOrigin();
|
|
926
1108
|
(0, react.useEffect)(() => {
|
|
927
1109
|
if (!hubUrl || !enabled || !apiUrl) return;
|
|
928
|
-
const
|
|
929
|
-
|
|
1110
|
+
const topic = buildMercureCollectionTopic(apiUrl, mercureTopicOrigin ?? discoveredTopicOrigin, apiBaseUrl);
|
|
1111
|
+
if (!topic) return;
|
|
930
1112
|
const handler = (_data) => {
|
|
931
1113
|
onUpdate();
|
|
932
1114
|
};
|
|
@@ -937,7 +1119,10 @@ function useMercureSubscription(apiUrl, onUpdate, enabled = true) {
|
|
|
937
1119
|
}, [
|
|
938
1120
|
hubUrl,
|
|
939
1121
|
enabled,
|
|
940
|
-
apiUrl
|
|
1122
|
+
apiUrl,
|
|
1123
|
+
mercureTopicOrigin,
|
|
1124
|
+
discoveredTopicOrigin,
|
|
1125
|
+
apiBaseUrl
|
|
941
1126
|
]);
|
|
942
1127
|
}
|
|
943
1128
|
//#endregion
|
|
@@ -950,6 +1135,7 @@ exports.DEFAULT_TIMEZONE = DEFAULT_TIMEZONE;
|
|
|
950
1135
|
exports.DateUtils = DateUtils;
|
|
951
1136
|
exports.MercureManager = mercureManager;
|
|
952
1137
|
exports.MercureProvider = MercureProvider;
|
|
1138
|
+
exports.buildMercureCollectionTopic = buildMercureCollectionTopic;
|
|
953
1139
|
exports.configureCore = configureCore;
|
|
954
1140
|
exports.configureCoreDate = configureCoreDate;
|
|
955
1141
|
exports.coreTranslationsEn = coreTranslationsEn;
|
|
@@ -957,16 +1143,24 @@ exports.coreTranslationsEs = coreTranslationsEs;
|
|
|
957
1143
|
exports.createCoreHttpClient = createCoreHttpClient;
|
|
958
1144
|
exports.createCrudEvents = createCrudEvents;
|
|
959
1145
|
exports.createScopedEventBus = createScopedEventBus;
|
|
1146
|
+
exports.discoverMercureFromResponse = discoverMercureFromResponse;
|
|
960
1147
|
exports.dispatch = dispatch;
|
|
1148
|
+
exports.extractMercureHubUrl = extractMercureHubUrl;
|
|
1149
|
+
exports.extractTopicOriginFromPayload = extractTopicOriginFromPayload;
|
|
961
1150
|
exports.getCoreApiBaseUrl = getCoreApiBaseUrl;
|
|
962
1151
|
exports.getCoreCurrency = getCoreCurrency;
|
|
963
1152
|
exports.getCoreLocale = getCoreLocale;
|
|
964
1153
|
exports.getCoreTimezone = getCoreTimezone;
|
|
1154
|
+
exports.getDiscoveredMercureTopicOrigin = getDiscoveredMercureTopicOrigin;
|
|
1155
|
+
exports.getMercureTopicOrigin = getMercureTopicOrigin;
|
|
965
1156
|
exports.initCoreI18n = initCoreI18n;
|
|
1157
|
+
exports.parseLinkHeader = parseLinkHeader;
|
|
1158
|
+
exports.resolveMercureTopicOrigin = resolveMercureTopicOrigin;
|
|
966
1159
|
exports.useCoreConfig = useCoreConfig;
|
|
967
1160
|
exports.useCoreHttpClient = useCoreHttpClient;
|
|
968
1161
|
exports.useCoreRuntime = useCoreRuntime;
|
|
969
1162
|
exports.useCoreTranslation = useCoreTranslation;
|
|
1163
|
+
exports.useDiscoveredMercureTopicOrigin = useDiscoveredMercureTopicOrigin;
|
|
970
1164
|
exports.useEvents = useEvents;
|
|
971
1165
|
exports.useMercureHub = useMercureHub;
|
|
972
1166
|
exports.useMercureSubscription = useMercureSubscription;
|