@tailorkit/react 0.1.0-beta.19 → 0.1.0-beta.21

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.js CHANGED
@@ -1,6 +1,5 @@
1
1
  // src/components/slot.tsx
2
- import { createSessionProvider as createSessionProvider2 } from "@tailorkit/app/client";
3
- import { useCallback as useCallback3, useEffect as useEffect3, useId as useId2, useMemo as useMemo4, useRef as useRef4, useSyncExternalStore as useSyncExternalStore3 } from "react";
2
+ import { useCallback as useCallback4, useEffect as useEffect4, useId as useId2, useMemo as useMemo5, useRef as useRef3, useSyncExternalStore as useSyncExternalStore4 } from "react";
4
3
 
5
4
  // src/hooks/use-stable-context.ts
6
5
  import { useRef } from "react";
@@ -14,32 +13,8 @@ function useStableContext(value) {
14
13
  }
15
14
 
16
15
  // src/hooks/use-slot-instances.ts
17
- import { createClient, createSessionProvider, reference } from "@tailorkit/app/client";
18
-
19
- // src/slot-view.ts
20
- import { composeViewContext, getViewHierarchy } from "@tailorkit/core/views";
21
- function selectSlotView(views, slot, activePath, schema) {
22
- const supported = schema.slots[slot]?.views ?? [];
23
- const path = getViewHierarchy(activePath).find(
24
- (path2) => supported.includes(path2) && views.some((view2) => view2.slot === slot && view2.path === path2)
25
- );
26
- const view = views.find((view2) => view2.slot === slot && view2.path === path);
27
- return !view || view.disabled ? null : view;
28
- }
29
- function resolveSlotView(views, slot, activeView, schema) {
30
- const view = selectSlotView(views, slot, activeView.view, schema);
31
- if (!view) return null;
32
- return {
33
- ...composeViewContext(view.path, {
34
- layers: activeView.layers,
35
- declaredViews: Object.keys(schema.views)
36
- }),
37
- instances: Boolean(view.instances)
38
- };
39
- }
40
-
41
- // src/hooks/use-slot-instances.ts
42
- import { useCallback, useEffect, useMemo, useRef as useRef2, useState, useSyncExternalStore } from "react";
16
+ import { createSlotInstancesStore } from "@tailorkit/client-core";
17
+ import { useCallback as useCallback2, useEffect as useEffect2, useMemo as useMemo2, useSyncExternalStore as useSyncExternalStore2 } from "react";
43
18
 
44
19
  // src/components/context.ts
45
20
  import { createContext, useContext } from "react";
@@ -57,114 +32,115 @@ function useTailorRootContext(component, expectedClient) {
57
32
  return context;
58
33
  }
59
34
 
35
+ // src/hooks/use-apps.ts
36
+ import { useCallback, useEffect, useMemo, useSyncExternalStore } from "react";
37
+ import { matchesApp } from "@tailorkit/client-core";
38
+
39
+ // src/scope-query.ts
40
+ import { appendScopeSelection, normalizeScopeSelection } from "@tailorkit/client-core";
41
+
42
+ // src/hooks/use-apps.ts
43
+ import { matchesApp as matchesApp2 } from "@tailorkit/client-core";
44
+ function useApps(options = {}) {
45
+ return useAppsStore(useTailorRootContext("useApps").store, options);
46
+ }
47
+ function useAppsStore(store, options = {}) {
48
+ const snapshot = useAppsSnapshot(store, options);
49
+ const scopes = normalizeScopeSelection(options.scopes);
50
+ const appIds = normalizeScopeSelection(options.appIds);
51
+ const data = useMemo(
52
+ () => snapshot.status === "ready" ? snapshot.apps.filter((app) => matchesApp(app, scopes.scopes, appIds.scopes)) : void 0,
53
+ [snapshot, scopes.key, appIds.key]
54
+ );
55
+ const refetch = useCallback(
56
+ () => store.fetchApps({ force: true, staleTime: options.staleTime }),
57
+ [store, options.staleTime]
58
+ );
59
+ return { ...toUseAppsResult(snapshot, refetch), data };
60
+ }
61
+ function useAppsSnapshot(store, options = {}) {
62
+ const snapshot = useSyncExternalStore(
63
+ store.subscribeApps,
64
+ store.getAppsSnapshot,
65
+ store.getAppsSnapshot
66
+ );
67
+ useEffect(() => {
68
+ void store.fetchApps(options);
69
+ }, [store, options.staleTime]);
70
+ return snapshot;
71
+ }
72
+ function toUseAppsResult(snapshot, refetch) {
73
+ return {
74
+ data: snapshot.status === "ready" ? snapshot.apps : void 0,
75
+ error: snapshot.error,
76
+ isFetching: snapshot.isFetching,
77
+ isError: snapshot.status === "error",
78
+ isLoading: snapshot.status === "loading",
79
+ isPending: snapshot.status === "idle" || snapshot.status === "loading",
80
+ isSuccess: snapshot.status === "ready",
81
+ refetch,
82
+ status: snapshot.status
83
+ };
84
+ }
85
+
60
86
  // src/hooks/use-slot-instances.ts
61
- var resolveInstances = reference("_tailorkit.instances.resolve", "action");
62
- function useSlotInstances({ app, slot }) {
87
+ function useSlotInstances({
88
+ slot,
89
+ staleTime,
90
+ gcTime
91
+ }) {
92
+ const apps = useApps();
93
+ const instances = useResolvedSlotInstances(slot, apps.data, apps.status, apps.error, {
94
+ staleTime,
95
+ gcTime
96
+ });
97
+ const refetch = useCallback2(async () => {
98
+ if (apps.isSuccess) await instances.refetch();
99
+ else await apps.refetch();
100
+ }, [apps.isSuccess, apps.refetch, instances.refetch]);
101
+ return { ...instances, isFetching: apps.isFetching || instances.isFetching, refetch };
102
+ }
103
+ function useAppSlotInstances(app, slot) {
104
+ return useResolvedSlotInstances(slot, [app], "ready", null);
105
+ }
106
+ function useResolvedSlotInstances(slot, apps, appsStatus, appsError, { staleTime, gcTime } = {}) {
63
107
  const { store } = useTailorRootContext("useSlotInstances");
64
- const activeView = useSyncExternalStore(
108
+ const activeView = useSyncExternalStore2(
65
109
  store.views.subscribe,
66
110
  store.views.getSnapshot,
67
111
  store.views.getSnapshot
68
112
  );
69
- const request = useStableContext({
70
- baseUrl: store.baseUrl.toString(),
71
- appId: app.id,
72
- deploymentId: app.currentDeployment?.id,
73
- views: app.views ?? [],
74
- slot,
75
- activeView
76
- });
77
- const getSession = useMemo(
78
- () => createSessionProvider({ baseUrl: store.baseUrl, appId: app.id }),
79
- [store, app.id, app.currentDeployment?.id]
113
+ const options = useStableContext({ apps: apps ?? [], slot, activeView });
114
+ const query = useMemo2(
115
+ () => createSlotInstancesStore(store.client, {
116
+ ...options,
117
+ appsStatus,
118
+ appsError,
119
+ staleTime,
120
+ gcTime
121
+ }),
122
+ [store.client, options, appsStatus, appsError, staleTime, gcTime]
80
123
  );
81
- const [snapshot, setSnapshot] = useState({
82
- request: null,
83
- data: void 0,
84
- error: null,
85
- status: "idle"
86
- });
87
- const generation = useRef2(0);
88
- const backend = useRef2(null);
89
- const refetch = useCallback(async () => {
90
- const id = ++generation.current;
91
- backend.current?.close();
92
- backend.current = null;
93
- const publish = (status2, data, error = null) => {
94
- if (id === generation.current) {
95
- setSnapshot({
96
- request,
97
- status: status2,
98
- data,
99
- error
100
- });
101
- }
102
- };
103
- if (!request.activeView) {
104
- publish("idle");
105
- return;
106
- }
107
- if (!request.views.some((view) => view.slot === slot && view.instances && !view.disabled)) {
108
- publish("ready", []);
109
- return;
110
- }
111
- publish("loading");
112
- let client;
113
- try {
114
- await store.fetchMeta({ force: store.getMetaSnapshot().status === "error" });
115
- if (id !== generation.current) return;
116
- const meta = store.getMetaSnapshot();
117
- if (meta.error) throw meta.error;
118
- if (!meta.schema) throw new Error("TailorKit metadata is unavailable.");
119
- const resolved = resolveSlotView(request.views, slot, request.activeView, meta.schema);
120
- if (!resolved?.instances) {
121
- publish("ready", []);
122
- return;
123
- }
124
- if (resolved.status !== "ready") {
125
- if (resolved.status === "error")
126
- throw new Error(`Context for view "${resolved.view}" is unavailable.`);
127
- publish("loading");
128
- return;
129
- }
130
- client = createClient({ getSession });
131
- backend.current = client;
132
- const instances = await client.action(resolveInstances, {
133
- slot,
134
- path: resolved.view,
135
- context: resolved.context
136
- });
137
- publish("ready", instances);
138
- } catch (error) {
139
- publish("error", void 0, error instanceof Error ? error : new Error(String(error)));
140
- } finally {
141
- client?.close();
142
- if (backend.current === client) backend.current = null;
143
- }
144
- }, [request, store, slot, getSession]);
145
- useEffect(() => {
146
- void refetch();
147
- return () => {
148
- generation.current += 1;
149
- backend.current?.close();
150
- backend.current = null;
151
- };
152
- }, [refetch]);
153
- const status = snapshot.request === request ? snapshot.status : activeView ? "loading" : "idle";
124
+ const snapshot = useSyncExternalStore2(query.subscribe, query.getSnapshot, query.getSnapshot);
125
+ useEffect2(() => {
126
+ void query.fetch();
127
+ }, [query]);
128
+ const refetch = useCallback2(() => query.fetch({ force: true }), [query]);
154
129
  return {
155
- data: snapshot.request === request ? snapshot.data : void 0,
156
- error: snapshot.request === request ? snapshot.error : null,
157
- status,
158
- isPending: status === "idle" || status === "loading",
159
- isLoading: status === "loading",
160
- isSuccess: status === "ready",
161
- isError: status === "error",
130
+ ...snapshot,
131
+ isError: snapshot.status === "error",
132
+ isPending: snapshot.status === "idle" || snapshot.status === "loading",
133
+ isLoading: snapshot.status === "loading",
134
+ isSuccess: snapshot.status === "ready",
162
135
  refetch
163
136
  };
164
137
  }
165
138
 
139
+ // src/slot-view.ts
140
+ import { resolveSlotView, selectSlotView } from "@tailorkit/client-core";
141
+
166
142
  // src/primitives.tsx
167
- import { createContext as createContext2, useContext as useContext2, useId, useMemo as useMemo2 } from "react";
143
+ import { createContext as createContext2, useContext as useContext2, useId, useMemo as useMemo3 } from "react";
168
144
  import { resolveTheme } from "@tailorkit/core/primitives/theme";
169
145
  import { jsx, jsxs } from "react/jsx-runtime";
170
146
  var PrimitiveThemeContext = createContext2(null);
@@ -331,7 +307,7 @@ var buildThemeCss = (viewId, theme) => {
331
307
  };
332
308
  var usePrimitiveNodeId = () => {
333
309
  const id = useId();
334
- return useMemo2(() => `tailorkit-${id.replaceAll(":", "")}`, [id]);
310
+ return useMemo3(() => `tailorkit-${id.replaceAll(":", "")}`, [id]);
335
311
  };
336
312
  var Primitive = ({
337
313
  children,
@@ -367,122 +343,16 @@ import {
367
343
  Fragment,
368
344
  createElement,
369
345
  memo,
370
- useCallback as useCallback2,
346
+ useCallback as useCallback3,
371
347
  useContext as useContext3,
372
- useEffect as useEffect2,
373
- useMemo as useMemo3,
374
- useRef as useRef3,
375
- useState as useState2,
376
- useSyncExternalStore as useSyncExternalStore2
348
+ useEffect as useEffect3,
349
+ useMemo as useMemo4,
350
+ useRef as useRef2,
351
+ useState,
352
+ useSyncExternalStore as useSyncExternalStore3
377
353
  } from "react";
378
354
  import { createIframeUiHost } from "@tailorkit/sandbox/host";
379
-
380
- // src/node-store.ts
381
- var childIds = (children) => children.map((c) => c.id).join(",");
382
- var nodeSignature = (node) => {
383
- if (node.kind === "text") {
384
- return `text:${node.text}`;
385
- }
386
- if (node.kind === "fragment") {
387
- return `frag:${childIds(node.children)}`;
388
- }
389
- const propsStr = JSON.stringify(node.props);
390
- const callbacksStr = (node.callbacks ?? []).map((binding) => `${binding.callback}:${binding.event}`).join(",");
391
- return `elem:${node.type}|${childIds(node.children)}|${propsStr}|${callbacksStr}`;
392
- };
393
- var flattenTree = (node, out) => {
394
- out.set(node.id, node);
395
- if (node.kind === "text") {
396
- return;
397
- }
398
- for (const child of node.children) {
399
- flattenTree(child, out);
400
- }
401
- };
402
- var NodeStore = class {
403
- nodes = /* @__PURE__ */ new Map();
404
- signatures = /* @__PURE__ */ new Map();
405
- nodeListeners = /* @__PURE__ */ new Map();
406
- rootListeners = /* @__PURE__ */ new Set();
407
- rootId = null;
408
- clear() {
409
- const nodeIds = [...this.nodeListeners.keys()];
410
- const hadRoot = this.rootId !== null;
411
- this.nodes = /* @__PURE__ */ new Map();
412
- this.signatures = /* @__PURE__ */ new Map();
413
- this.rootId = null;
414
- for (const id of nodeIds) {
415
- const subs = this.nodeListeners.get(id);
416
- if (subs) {
417
- for (const l of subs) {
418
- l();
419
- }
420
- }
421
- }
422
- if (hadRoot) {
423
- for (const l of this.rootListeners) {
424
- l();
425
- }
426
- }
427
- }
428
- setSnapshot(tree) {
429
- const next = /* @__PURE__ */ new Map();
430
- flattenTree(tree, next);
431
- const changed = /* @__PURE__ */ new Set();
432
- for (const [id, node] of next) {
433
- const sig = nodeSignature(node);
434
- if (this.signatures.get(id) !== sig) {
435
- changed.add(id);
436
- this.signatures.set(id, sig);
437
- }
438
- }
439
- for (const id of this.signatures.keys()) {
440
- if (!next.has(id)) {
441
- this.signatures.delete(id);
442
- }
443
- }
444
- this.nodes = next;
445
- const prevRootId = this.rootId;
446
- this.rootId = tree.id;
447
- for (const id of changed) {
448
- const subs = this.nodeListeners.get(id);
449
- if (subs) {
450
- for (const l of subs) {
451
- l();
452
- }
453
- }
454
- }
455
- if (prevRootId !== this.rootId) {
456
- for (const l of this.rootListeners) {
457
- l();
458
- }
459
- }
460
- }
461
- getRootId() {
462
- return this.rootId;
463
- }
464
- getNode(id) {
465
- return this.nodes.get(id) ?? null;
466
- }
467
- subscribe(id, listener) {
468
- let subs = this.nodeListeners.get(id);
469
- if (!subs) {
470
- subs = /* @__PURE__ */ new Set();
471
- this.nodeListeners.set(id, subs);
472
- }
473
- subs.add(listener);
474
- return () => {
475
- subs.delete(listener);
476
- if (subs.size === 0) {
477
- this.nodeListeners.delete(id);
478
- }
479
- };
480
- }
481
- subscribeRoot(listener) {
482
- this.rootListeners.add(listener);
483
- return () => this.rootListeners.delete(listener);
484
- }
485
- };
355
+ import { NodeStore } from "@tailorkit/client-core";
486
356
 
487
357
  // src/remote-context.ts
488
358
  import { createContext as createContext3 } from "react";
@@ -515,34 +385,34 @@ function RemoteViewHost({
515
385
  props
516
386
  }) {
517
387
  const appKey = appUrl.toString();
518
- const storeRef = useRef3(null);
388
+ const storeRef = useRef2(null);
519
389
  if (storeRef.current === null) {
520
390
  storeRef.current = new NodeStore();
521
391
  }
522
392
  const store = storeRef.current;
523
- const appKeyRef = useRef3(appKey);
393
+ const appKeyRef = useRef2(appKey);
524
394
  if (appKeyRef.current !== appKey) {
525
395
  appKeyRef.current = appKey;
526
396
  store.clear();
527
397
  }
528
- const hostRef = useRef3(null);
529
- const propsRef = useRef3(props);
530
- useEffect2(() => {
398
+ const hostRef = useRef2(null);
399
+ const propsRef = useRef2(props);
400
+ useEffect3(() => {
531
401
  propsRef.current = props;
532
402
  hostRef.current?.setProps(props);
533
403
  }, [props]);
534
- const dispatchRef = useRef3(null);
535
- const [error, setError] = useState2(null);
536
- const [status, setStatus] = useState2("starting");
537
- const ctxRef = useRef3({
404
+ const dispatchRef = useRef2(null);
405
+ const [error, setError] = useState(null);
406
+ const [status, setStatus] = useState("starting");
407
+ const ctxRef = useRef2({
538
408
  components: components2,
539
409
  dispatch: (payload) => dispatchRef.current?.(payload),
540
410
  store
541
411
  });
542
- useEffect2(() => {
412
+ useEffect3(() => {
543
413
  ctxRef.current.components = components2;
544
414
  }, [components2]);
545
- const ui = useMemo3(() => {
415
+ const ui = useMemo4(() => {
546
416
  const ctx = ctxRef.current;
547
417
  return createElement(
548
418
  RemoteUIContext.Provider,
@@ -554,7 +424,7 @@ function RemoteViewHost({
554
424
  )
555
425
  );
556
426
  }, [appKey]);
557
- useEffect2(() => {
427
+ useEffect3(() => {
558
428
  const host = createIframeUiHost(appUrl, {
559
429
  createIframe,
560
430
  sourceText,
@@ -591,9 +461,9 @@ function RemoteViewHost({
591
461
  return ui;
592
462
  }
593
463
  function RemoteRoot({ store }) {
594
- const subscribe = useCallback2((listener) => store.subscribeRoot(listener), [store]);
595
- const getSnapshot = useCallback2(() => store.getRootId(), [store]);
596
- const rootId = useSyncExternalStore2(subscribe, getSnapshot, getSnapshot);
464
+ const subscribe = useCallback3((listener) => store.subscribeRoot(listener), [store]);
465
+ const getSnapshot = useCallback3(() => store.getRootId(), [store]);
466
+ const rootId = useSyncExternalStore3(subscribe, getSnapshot, getSnapshot);
597
467
  if (rootId === null) {
598
468
  return null;
599
469
  }
@@ -627,12 +497,12 @@ function RemoteViewComponent({ nodeId }) {
627
497
  return null;
628
498
  }
629
499
  const { store } = ctx;
630
- const subscribe = useCallback2(
500
+ const subscribe = useCallback3(
631
501
  (listener) => store.subscribe(nodeId, listener),
632
502
  [store, nodeId]
633
503
  );
634
- const getSnapshot = useCallback2(() => store.getNode(nodeId), [store, nodeId]);
635
- const node = useSyncExternalStore2(subscribe, getSnapshot, getSnapshot);
504
+ const getSnapshot = useCallback3(() => store.getNode(nodeId), [store, nodeId]);
505
+ const node = useSyncExternalStore3(subscribe, getSnapshot, getSnapshot);
636
506
  if (node === null) {
637
507
  return null;
638
508
  }
@@ -647,461 +517,12 @@ function RemoteViewComponent({ nodeId }) {
647
517
  }
648
518
  var RemoteView = memo(RemoteViewComponent);
649
519
 
650
- // src/view-registry.ts
651
- import { getViewDepth, isViewAncestor } from "@tailorkit/core/views";
652
- function createViewRegistry() {
653
- const entries = /* @__PURE__ */ new Map();
654
- const listeners = /* @__PURE__ */ new Set();
655
- let snapshot = null;
656
- let nextOrder = 0;
657
- let scheduled = false;
658
- const publish = () => {
659
- if (scheduled) {
660
- return;
661
- }
662
- scheduled = true;
663
- queueMicrotask(() => {
664
- scheduled = false;
665
- const ordered = [...entries.values()].toSorted(
666
- (a, b) => getViewDepth(b.view) - getViewDepth(a.view) || b.order - a.order
667
- );
668
- const selected = ordered[0];
669
- if (selected) {
670
- const peers = ordered.filter(
671
- (entry) => getViewDepth(entry.view) === getViewDepth(selected.view)
672
- );
673
- if (peers.length > 1) {
674
- console.warn(
675
- `TailorKit found multiple active views at the same hierarchy depth: ${peers.toReversed().map((entry) => `"${entry.view}"`).join(
676
- ", "
677
- )}. TailorKit selected "${selected.view}" by mount order. Only one route at a hierarchy depth should call useRegisterView.`
678
- );
679
- }
680
- snapshot = {
681
- view: selected.view,
682
- layers: ordered.filter((entry) => isViewAncestor(entry.view, selected.view)).toReversed().map((entry) => ({ path: entry.view, context: entry.context, status: entry.status }))
683
- };
684
- } else {
685
- snapshot = null;
686
- }
687
- for (const listener of listeners) {
688
- listener();
689
- }
690
- });
691
- };
692
- return {
693
- getSnapshot: () => snapshot,
694
- subscribe(listener) {
695
- listeners.add(listener);
696
- return () => {
697
- listeners.delete(listener);
698
- };
699
- },
700
- register(entry) {
701
- entries.set(entry.id, { ...entry, order: entries.get(entry.id)?.order ?? nextOrder++ });
702
- publish();
703
- },
704
- unregister(id) {
705
- entries.delete(id);
706
- publish();
707
- }
708
- };
709
- }
710
-
711
- // src/preview-manager.ts
712
- import {
713
- createPreviewWebSocketClient,
714
- previewMetadataSchema
715
- } from "@tailorkit/client-platform/preview";
716
- var empty = { revision: 0, source: null };
717
- function viewerTokenExpiresAt(metadata) {
718
- const expiresAt = Number(metadata.token.split(".", 1)[0]);
719
- return Number.isSafeInteger(expiresAt) ? expiresAt : 0;
720
- }
721
- function bytesFromBase64(value) {
722
- const raw = atob(value);
723
- const bytes = new Uint8Array(raw.length);
724
- for (let index = 0; index < raw.length; index++) {
725
- bytes[index] = raw.codePointAt(index) ?? 0;
726
- }
727
- return bytes;
728
- }
729
- async function verify(candidate) {
730
- let source = null;
731
- for (const [fileIndex, file] of candidate.manifest.files.entries()) {
732
- const parts = candidate.files[fileIndex];
733
- if (!parts || parts.length !== file.chunks || parts.some((part) => part === null)) {
734
- return null;
735
- }
736
- const bytes = new Uint8Array(file.size);
737
- let offset = 0;
738
- for (const part of parts) {
739
- if (!part) {
740
- return null;
741
- }
742
- bytes.set(part, offset);
743
- offset += part.length;
744
- }
745
- if (offset !== file.size) {
746
- return null;
747
- }
748
- const digest = [...new Uint8Array(await crypto.subtle.digest("SHA-256", bytes))].map((byte) => byte.toString(16).padStart(2, "0")).join("");
749
- if (digest !== file.sha256) {
750
- return null;
751
- }
752
- if (file.path === "client.js") {
753
- source = new TextDecoder().decode(bytes);
754
- }
755
- }
756
- return source;
757
- }
758
- function createPreviewManager(baseUrl, onEnded, onViews) {
759
- const entries = /* @__PURE__ */ new Map();
760
- const notify = (entry) => {
761
- for (const listener of entry.listeners) {
762
- listener();
763
- }
764
- };
765
- const close = (entry) => {
766
- if (entry.reconnect) {
767
- clearTimeout(entry.reconnect);
768
- }
769
- entry.reconnect = null;
770
- const socket = entry.socket;
771
- entry.socket = null;
772
- socket?.close();
773
- entry.connecting = false;
774
- entry.candidate = null;
775
- };
776
- const end = (entry) => {
777
- close(entry);
778
- entry.metadata = null;
779
- entry.snapshot = empty;
780
- notify(entry);
781
- onEnded();
782
- };
783
- const handleEvent = async (entry, event) => {
784
- if (event.type === "ended") {
785
- end(entry);
786
- return;
787
- }
788
- if (event.revision <= entry.snapshot.revision) {
789
- return;
790
- }
791
- if (event.type === "begin") {
792
- entry.candidate = {
793
- revision: event.revision,
794
- manifest: event.manifest,
795
- files: event.manifest.files.map((file) => Array.from({ length: file.chunks }, () => null))
796
- };
797
- return;
798
- }
799
- const candidate = entry.candidate;
800
- if (!candidate || candidate.revision !== event.revision) {
801
- return;
802
- }
803
- if (event.type === "chunk") {
804
- const file = candidate.manifest.files[event.fileIndex];
805
- const slots = candidate.files[event.fileIndex];
806
- if (!file || !slots || event.chunkIndex < 0 || event.chunkIndex >= slots.length || slots[event.chunkIndex]) {
807
- entry.candidate = null;
808
- return;
809
- }
810
- const bytes = bytesFromBase64(event.base64);
811
- if (bytes.length !== Math.min(256 * 1024, file.size - event.chunkIndex * 256 * 1024)) {
812
- entry.candidate = null;
813
- return;
814
- }
815
- slots[event.chunkIndex] = bytes;
816
- return;
817
- }
818
- if (event.type === "complete") {
819
- const source = await verify(candidate);
820
- if (source !== null && entry.candidate === candidate && candidate.revision > entry.snapshot.revision) {
821
- entry.snapshot = { revision: candidate.revision, source };
822
- if (candidate.manifest.views !== void 0)
823
- onViews?.(entry.app.id, candidate.manifest.views);
824
- notify(entry);
825
- }
826
- if (entry.candidate === candidate) {
827
- entry.candidate = null;
828
- }
829
- }
830
- };
831
- const connect = async (entry) => {
832
- if (!entry.listeners.size || !entry.app.preview || entry.socket || entry.connecting) {
833
- return;
834
- }
835
- entry.connecting = true;
836
- const appMetadata = entry.app.preview;
837
- let metadata = entry.metadata && viewerTokenExpiresAt(entry.metadata) > Math.max(Date.now(), viewerTokenExpiresAt(appMetadata)) ? entry.metadata : appMetadata;
838
- try {
839
- const refresh = new URL("preview/metadata", baseUrl);
840
- refresh.searchParams.set("sessionId", metadata.sessionId);
841
- const response = await fetch(refresh, { credentials: "same-origin" });
842
- if (response.ok) {
843
- metadata = previewMetadataSchema.parse(await response.json());
844
- entry.metadata = metadata;
845
- } else if (response.status === 404) {
846
- end(entry);
847
- return;
848
- }
849
- } catch {
850
- }
851
- if (!entry.listeners.size) {
852
- entry.connecting = false;
853
- return;
854
- }
855
- const socket = new WebSocket(metadata.websocketUrl, metadata.token);
856
- entry.socket = socket;
857
- entry.connecting = false;
858
- socket.addEventListener("open", () => {
859
- entry.delay = 1e3;
860
- const client = createPreviewWebSocketClient(socket);
861
- void (async () => {
862
- try {
863
- const stream = await client.subscribe();
864
- for await (const event of stream) {
865
- await handleEvent(entry, event);
866
- }
867
- } catch {
868
- } finally {
869
- socket.close();
870
- }
871
- })();
872
- });
873
- socket.addEventListener("error", () => socket.close());
874
- socket.addEventListener("close", () => {
875
- if (entry.socket !== socket) {
876
- return;
877
- }
878
- entry.socket = null;
879
- entry.candidate = null;
880
- if (entry.listeners.size) {
881
- entry.reconnect = setTimeout(() => {
882
- entry.reconnect = null;
883
- void connect(entry);
884
- }, entry.delay);
885
- entry.delay = Math.min(entry.delay * 2, 3e4);
886
- }
887
- });
888
- };
889
- return {
890
- getSnapshot: (sessionId) => entries.get(sessionId)?.snapshot ?? empty,
891
- updateApp: (app) => {
892
- const sessionId = app.preview?.sessionId;
893
- const entry = sessionId && entries.get(sessionId);
894
- if (entry) {
895
- entry.app = app;
896
- }
897
- },
898
- subscribe: (app, listener) => {
899
- const sessionId = app.preview?.sessionId;
900
- if (!sessionId) {
901
- return () => {
902
- };
903
- }
904
- let entry = entries.get(sessionId);
905
- if (!entry) {
906
- entry = {
907
- app,
908
- metadata: null,
909
- candidate: null,
910
- listeners: /* @__PURE__ */ new Set(),
911
- snapshot: empty,
912
- socket: null,
913
- connecting: false,
914
- reconnect: null,
915
- delay: 1e3
916
- };
917
- entries.set(sessionId, entry);
918
- }
919
- entry.app = app;
920
- entry.listeners.add(listener);
921
- void connect(entry);
922
- return () => {
923
- entry?.listeners.delete(listener);
924
- if (entry && !entry.listeners.size) {
925
- close(entry);
926
- entries.delete(sessionId);
927
- }
928
- };
929
- },
930
- dispose: () => {
931
- for (const entry of entries.values()) {
932
- close(entry);
933
- }
934
- entries.clear();
935
- }
936
- };
937
- }
938
-
939
- // src/store.ts
940
- function createTailorKitStore(baseUrlInput, initialApps) {
941
- const baseUrl = toBaseUrl(baseUrlInput);
942
- const listeners = /* @__PURE__ */ new Set();
943
- let providedApps = initialApps;
944
- let appsSnapshot = {
945
- apps: initialApps ?? [],
946
- views: listViews(initialApps ?? []),
947
- error: null,
948
- status: initialApps === void 0 ? "idle" : "ready"
949
- };
950
- let appsPromise = null;
951
- let appsRequestId = 0;
952
- let appsRequested = false;
953
- let appsSubscribers = 0;
954
- let metaSnapshot = {
955
- assetsBaseUrl: null,
956
- error: null,
957
- schema: null,
958
- status: "idle"
959
- };
960
- let fetchMetaPromise = null;
961
- let metaRequestId = 0;
962
- const emit = () => {
963
- for (const listener of listeners) {
964
- listener();
965
- }
966
- };
967
- const store = {
968
- baseUrl,
969
- views: createViewRegistry(),
970
- setProvidedApps(apps) {
971
- if (providedApps === apps) {
972
- return;
973
- }
974
- providedApps = apps;
975
- appsRequestId += 1;
976
- appsPromise = null;
977
- appsSnapshot = {
978
- apps: apps ?? [],
979
- views: listViews(apps ?? []),
980
- error: null,
981
- status: apps === void 0 ? "idle" : "ready"
982
- };
983
- emit();
984
- if (apps === void 0 && appsRequested && appsSubscribers > 0) {
985
- void store.fetchApps();
986
- }
987
- },
988
- fetchApps: (options = {}) => {
989
- appsRequested = true;
990
- if (providedApps !== void 0) {
991
- return Promise.resolve();
992
- }
993
- if (appsPromise && !options.force) {
994
- return appsPromise;
995
- }
996
- appsSnapshot = { ...appsSnapshot, error: null, status: "loading" };
997
- emit();
998
- const requestId = ++appsRequestId;
999
- appsPromise = fetch(new URL("apps", baseUrl)).then(async (response) => {
1000
- if (!response.ok) {
1001
- throw new Error(`Unable to fetch TailorKit apps from ${baseUrl.toString()}.`);
1002
- }
1003
- const apps = await response.json();
1004
- if (requestId !== appsRequestId) return;
1005
- appsSnapshot = { apps, views: listViews(apps), error: null, status: "ready" };
1006
- emit();
1007
- }).catch((error) => {
1008
- if (requestId !== appsRequestId) return;
1009
- appsSnapshot = {
1010
- ...appsSnapshot,
1011
- error: error instanceof Error ? error : new Error(String(error)),
1012
- status: "error"
1013
- };
1014
- emit();
1015
- });
1016
- return appsPromise;
1017
- },
1018
- fetchMeta: (options = {}) => {
1019
- if (fetchMetaPromise && !options.force) {
1020
- return fetchMetaPromise;
1021
- }
1022
- metaSnapshot = { ...metaSnapshot, error: null, status: "loading" };
1023
- emit();
1024
- const requestId = ++metaRequestId;
1025
- fetchMetaPromise = fetch(new URL("meta", baseUrl)).then(async (response) => {
1026
- if (!response.ok) {
1027
- throw new Error(`Unable to fetch TailorKit metadata from ${baseUrl.toString()}.`);
1028
- }
1029
- const meta = await response.json();
1030
- if (requestId !== metaRequestId) return;
1031
- metaSnapshot = {
1032
- assetsBaseUrl: meta.assetsBaseUrl ?? null,
1033
- error: null,
1034
- schema: meta.schema,
1035
- status: "ready"
1036
- };
1037
- emit();
1038
- }).catch((error) => {
1039
- if (requestId !== metaRequestId) return;
1040
- metaSnapshot = {
1041
- ...metaSnapshot,
1042
- error: error instanceof Error ? error : new Error(String(error)),
1043
- status: "error"
1044
- };
1045
- emit();
1046
- });
1047
- return fetchMetaPromise;
1048
- },
1049
- getAppsSnapshot: () => appsSnapshot,
1050
- getMetaSnapshot: () => metaSnapshot,
1051
- subscribe: (listener) => {
1052
- listeners.add(listener);
1053
- return () => {
1054
- listeners.delete(listener);
1055
- };
1056
- },
1057
- subscribeApps: (listener) => {
1058
- appsSubscribers += 1;
1059
- listeners.add(listener);
1060
- return () => {
1061
- listeners.delete(listener);
1062
- appsSubscribers -= 1;
1063
- };
1064
- }
1065
- };
1066
- return {
1067
- ...store,
1068
- previews: createPreviewManager(
1069
- baseUrl,
1070
- () => {
1071
- if (appsRequested) void store.fetchApps({ force: true });
1072
- },
1073
- (appId, views) => {
1074
- const apps = appsSnapshot.apps.map((app) => app.id === appId ? { ...app, views } : app);
1075
- appsSnapshot = { ...appsSnapshot, apps, views: listViews(apps) };
1076
- emit();
1077
- }
1078
- )
1079
- };
1080
- }
1081
- function toBaseUrl(value) {
1082
- const url = value instanceof URL ? new URL(value) : new URL(value);
1083
- if (!url.pathname.endsWith("/")) {
1084
- url.pathname = `${url.pathname}/`;
1085
- }
1086
- return url;
1087
- }
1088
- function listViews(apps) {
1089
- return apps.flatMap(
1090
- (app) => (app.views ?? []).filter((view) => !view.disabled).map(({ slot, path, instances }) => ({
1091
- id: JSON.stringify([app.id, slot, path]),
1092
- ...instances ? { instances } : {},
1093
- app,
1094
- slot,
1095
- path
1096
- }))
1097
- );
1098
- }
1099
-
1100
520
  // src/components/slot.tsx
521
+ import { toBaseUrl } from "@tailorkit/client-core";
1101
522
  import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
1102
523
  function ManagedSlot({ app, name, instanceKey }) {
1103
524
  const { store } = useTailorRootContext("Slot");
1104
- const state = useSyncExternalStore3(
525
+ const state = useSyncExternalStore4(
1105
526
  store.views.subscribe,
1106
527
  store.views.getSnapshot,
1107
528
  store.views.getSnapshot
@@ -1116,14 +537,16 @@ function InstanceSlot({
1116
537
  state
1117
538
  }) {
1118
539
  const { store } = useTailorRootContext("Slot");
1119
- const instances = useSlotInstances({ app, slot: name });
1120
- const meta = useSyncExternalStore3(store.subscribe, store.getMetaSnapshot, store.getMetaSnapshot);
540
+ const instances = useAppSlotInstances(app, name);
541
+ const meta = useSyncExternalStore4(store.subscribe, store.getMetaSnapshot, store.getMetaSnapshot);
1121
542
  const instance = instances.data?.find((instance2) => instance2.key === instanceKey);
1122
543
  const resolved = instances.isSuccess && instance && meta.schema ? resolveSlotView(app.views ?? [], name, state, meta.schema) : null;
1123
544
  const view = resolved?.status === "ready" ? resolved.view : void 0;
1124
545
  const stableContext = useStableContext(resolved?.context);
1125
- const stableInstance = useStableContext(instance);
1126
- const readyState = useMemo4(
546
+ const stableInstance = useStableContext(
547
+ instance ? { key: instance.key, metadata: instance.metadata, data: instance.data } : void 0
548
+ );
549
+ const readyState = useMemo5(
1127
550
  () => view === void 0 || !stableInstance ? null : {
1128
551
  controlled: true,
1129
552
  view,
@@ -1135,6 +558,12 @@ function InstanceSlot({
1135
558
  );
1136
559
  if (instances.isPending) return /* @__PURE__ */ jsx3("div", { role: "status", children: "Loading view\u2026" });
1137
560
  if (instances.error) return /* @__PURE__ */ jsx3("div", { role: "alert", children: instances.error.message });
561
+ if (meta.schema && meta.schema.slots[name]?.multiple !== true)
562
+ return /* @__PURE__ */ jsxs2("div", { role: "alert", children: [
563
+ 'Slot "',
564
+ name,
565
+ '" does not support instances.'
566
+ ] });
1138
567
  if (!instance) return /* @__PURE__ */ jsxs2("div", { role: "alert", children: [
1139
568
  'View instance "',
1140
569
  instanceKey,
@@ -1154,7 +583,7 @@ function ControlledSlot({
1154
583
  useTailorRootContext("Slot.Controlled");
1155
584
  const stableContext = useStableContext(context);
1156
585
  const stableInstance = useStableContext(instance);
1157
- const state = useMemo4(
586
+ const state = useMemo5(
1158
587
  () => ({
1159
588
  controlled: true,
1160
589
  view,
@@ -1175,15 +604,15 @@ var Slot = Object.assign(ManagedSlot, { Controlled: ControlledSlot });
1175
604
  function SlotRenderer({ app, name, state }) {
1176
605
  const { store, client } = useTailorRootContext("Slot");
1177
606
  const reactId = useId2();
1178
- const getBackendSession = useMemo4(
1179
- () => createSessionProvider2({ baseUrl: store.baseUrl, appId: app.id }),
1180
- [store.baseUrl, app.id, app.currentDeployment?.id]
607
+ const getBackendSession = useMemo5(
608
+ () => store.client.endpoints.getSessionProvider(app),
609
+ [store.client, app.id, app.currentDeployment?.id, app.preview?.sessionId]
1181
610
  );
1182
- const meta = useSyncExternalStore3(store.subscribe, store.getMetaSnapshot, store.getMetaSnapshot);
611
+ const meta = useSyncExternalStore4(store.subscribe, store.getMetaSnapshot, store.getMetaSnapshot);
1183
612
  const previewSessionId = app.preview?.sessionId ?? "";
1184
- const appRef = useRef4(app);
613
+ const appRef = useRef3(app);
1185
614
  appRef.current = app;
1186
- const subscribePreview = useCallback3(
615
+ const subscribePreview = useCallback4(
1187
616
  (listener) => {
1188
617
  const currentApp = appRef.current;
1189
618
  return currentApp.preview?.sessionId === previewSessionId ? store.previews.subscribe(currentApp, listener) : () => {
@@ -1191,12 +620,12 @@ function SlotRenderer({ app, name, state }) {
1191
620
  },
1192
621
  [store, previewSessionId]
1193
622
  );
1194
- const getPreviewSnapshot = useCallback3(
623
+ const getPreviewSnapshot = useCallback4(
1195
624
  () => store.previews.getSnapshot(previewSessionId),
1196
625
  [store, previewSessionId]
1197
626
  );
1198
- const preview = useSyncExternalStore3(subscribePreview, getPreviewSnapshot, getPreviewSnapshot);
1199
- const props = useMemo4(
627
+ const preview = useSyncExternalStore4(subscribePreview, getPreviewSnapshot, getPreviewSnapshot);
628
+ const props = useMemo5(
1200
629
  () => ({
1201
630
  ...state,
1202
631
  slot: name,
@@ -1205,7 +634,7 @@ function SlotRenderer({ app, name, state }) {
1205
634
  }),
1206
635
  [state, name, meta.schema]
1207
636
  );
1208
- const appUrl = useMemo4(() => {
637
+ const appUrl = useMemo5(() => {
1209
638
  if (app.clientPath) return new URL(app.clientPath, store.baseUrl);
1210
639
  if (!meta.assetsBaseUrl || !app.projectId || !app.currentDeployment?.id) return null;
1211
640
  return new URL(
@@ -1213,16 +642,33 @@ function SlotRenderer({ app, name, state }) {
1213
642
  toBaseUrl(meta.assetsBaseUrl)
1214
643
  );
1215
644
  }, [app, store.baseUrl, meta.assetsBaseUrl]);
1216
- useEffect3(() => {
645
+ useEffect4(() => {
1217
646
  void store.fetchMeta();
1218
647
  }, [store]);
1219
- useEffect3(() => {
648
+ useEffect4(() => {
1220
649
  store.previews.updateApp(app);
1221
650
  }, [store, app]);
1222
651
  if (meta.schema === null || appUrl === null && preview.source === null) return null;
652
+ const multiple = meta.schema.slots[name]?.multiple === true;
653
+ if ("controlled" in state && state.status === "ready") {
654
+ if (multiple && !state.instance)
655
+ return /* @__PURE__ */ jsx3("div", { role: "alert", children: "A view instance is required. Pass instance to Slot.Controlled." });
656
+ if (!multiple && state.instance)
657
+ return /* @__PURE__ */ jsxs2("div", { role: "alert", children: [
658
+ 'Slot "',
659
+ name,
660
+ '" does not support instances.'
661
+ ] });
662
+ }
1223
663
  if (!("controlled" in state)) {
1224
664
  const selected = selectSlotView(app.views ?? [], name, state.view, meta.schema);
1225
- if (selected?.instances)
665
+ if (selected?.instances && !multiple)
666
+ return /* @__PURE__ */ jsxs2("div", { role: "alert", children: [
667
+ 'Slot "',
668
+ name,
669
+ '" does not support instances.'
670
+ ] });
671
+ if (multiple)
1226
672
  return /* @__PURE__ */ jsx3("div", { role: "alert", children: "A view instance key is required. Pass instanceKey to Slot." });
1227
673
  }
1228
674
  const viewId = `tailorkit-view-${reactId.replaceAll(":", "")}`;
@@ -1243,23 +689,24 @@ function SlotRenderer({ app, name, state }) {
1243
689
  }
1244
690
 
1245
691
  // src/tailorkit.tsx
692
+ import { createTailorKitFetchClient } from "@tailorkit/client-core";
1246
693
  import { createElement as createElement2 } from "react";
1247
694
 
1248
695
  // src/hooks/use-register-view.ts
1249
- import { useEffect as useEffect4, useMemo as useMemo5 } from "react";
696
+ import { useEffect as useEffect5, useMemo as useMemo6 } from "react";
1250
697
  function useRegisterView(view, options) {
1251
698
  const { store } = useTailorRootContext("useRegisterView");
1252
- const id = useMemo5(() => /* @__PURE__ */ Symbol("tailorkit-current-view"), []);
699
+ const id = useMemo6(() => /* @__PURE__ */ Symbol("tailorkit-current-view"), []);
1253
700
  const status = options.status ?? "ready";
1254
701
  const context = "context" in options ? options.context : void 0;
1255
702
  const contextSnapshot = useStableContext(context);
1256
- useEffect4(
703
+ useEffect5(
1257
704
  () => () => {
1258
705
  store.views.unregister(id);
1259
706
  },
1260
707
  [id, store]
1261
708
  );
1262
- useEffect4(() => {
709
+ useEffect5(() => {
1263
710
  store.views.register({
1264
711
  context: contextSnapshot,
1265
712
  id,
@@ -1269,70 +716,16 @@ function useRegisterView(view, options) {
1269
716
  }, [contextSnapshot, id, view, status, store]);
1270
717
  }
1271
718
 
1272
- // src/hooks/use-apps.ts
1273
- import { useCallback as useCallback4, useEffect as useEffect5, useMemo as useMemo6, useSyncExternalStore as useSyncExternalStore4 } from "react";
1274
-
1275
- // src/scope-query.ts
1276
- function normalizeScopeSelection(scopes) {
1277
- if (scopes === void 0) {
1278
- return { key: "*" };
1279
- }
1280
- const names = [...new Set(scopes)].sort();
1281
- return { key: JSON.stringify(names), scopes: names };
1282
- }
1283
-
1284
- // src/hooks/use-apps.ts
1285
- function useApps(options = {}) {
1286
- return useAppsStore(useTailorRootContext("useApps").store, options);
1287
- }
1288
- function useAppsStore(store, options = {}) {
1289
- const snapshot = useAppsSnapshot(store);
1290
- const scopes = normalizeScopeSelection(options.scopes);
1291
- const appIds = normalizeScopeSelection(options.appIds);
1292
- const data = useMemo6(
1293
- () => snapshot.status === "ready" ? snapshot.apps.filter((app) => matchesApp(app, scopes.scopes, appIds.scopes)) : void 0,
1294
- [snapshot, scopes.key, appIds.key]
1295
- );
1296
- const refetch = useCallback4(() => store.fetchApps({ force: true }), [store]);
1297
- return { ...toUseAppsResult(snapshot, refetch), data };
1298
- }
1299
- function matchesApp(app, scopes, appIds) {
1300
- return (scopes === void 0 || app.scope !== void 0 && scopes.includes(app.scope.name)) && (appIds === void 0 || appIds.includes(app.id));
1301
- }
1302
- function useAppsSnapshot(store) {
1303
- const snapshot = useSyncExternalStore4(
1304
- store.subscribeApps,
1305
- store.getAppsSnapshot,
1306
- store.getAppsSnapshot
1307
- );
1308
- useEffect5(() => {
1309
- void store.fetchApps();
1310
- }, [store]);
1311
- return snapshot;
1312
- }
1313
- function toUseAppsResult(snapshot, refetch) {
1314
- return {
1315
- data: snapshot.status === "ready" ? snapshot.apps : void 0,
1316
- error: snapshot.error,
1317
- isError: snapshot.status === "error",
1318
- isLoading: snapshot.status === "loading",
1319
- isPending: snapshot.status === "idle" || snapshot.status === "loading",
1320
- isSuccess: snapshot.status === "ready",
1321
- refetch,
1322
- status: snapshot.status
1323
- };
1324
- }
1325
-
1326
719
  // src/hooks/use-views.ts
1327
720
  import { useCallback as useCallback5, useMemo as useMemo7 } from "react";
1328
721
  function useViews(options = {}) {
1329
722
  const { store } = useTailorRootContext("useViews");
1330
- const snapshot = useAppsSnapshot(store);
723
+ const snapshot = useAppsSnapshot(store, options);
1331
724
  const scopes = normalizeScopeSelection(options.scopes);
1332
725
  const appIds = normalizeScopeSelection(options.appIds);
1333
726
  const data = useMemo7(
1334
727
  () => snapshot.status === "ready" ? snapshot.views.filter(
1335
- (view) => (options.slot === void 0 || view.slot === options.slot) && matchesApp(view.app, scopes.scopes, appIds.scopes)
728
+ (view) => (options.slot === void 0 || view.slot === options.slot) && matchesApp2(view.app, scopes.scopes, appIds.scopes)
1336
729
  ) : void 0,
1337
730
  [snapshot, scopes.key, appIds.key, options.slot]
1338
731
  );
@@ -1369,6 +762,11 @@ function createReactTailorKitClient(options) {
1369
762
  }
1370
763
  const clientConfig = {
1371
764
  baseUrl: options.baseUrl,
765
+ fetchClient: createTailorKitFetchClient({
766
+ baseUrl: options.baseUrl,
767
+ cache: options.cache,
768
+ fetch: options.fetch
769
+ }),
1372
770
  components: wrappedComponents,
1373
771
  theme
1374
772
  };
@@ -1377,14 +775,12 @@ function createReactTailorKitClient(options) {
1377
775
  Slot: Object.assign(
1378
776
  function ClientSlot(props) {
1379
777
  useTailorRootContext("Slot", client);
1380
- const TypedReactSlot = Slot;
1381
- return createElement2(TypedReactSlot, props);
778
+ return createElement2(Slot, props);
1382
779
  },
1383
780
  {
1384
781
  Controlled: function ClientControlledSlot(props) {
1385
782
  useTailorRootContext("Slot.Controlled", client);
1386
- const TypedControlledSlot = Slot.Controlled;
1387
- return createElement2(TypedControlledSlot, props);
783
+ return createElement2(Slot.Controlled, props);
1388
784
  }
1389
785
  }
1390
786
  ),
@@ -1409,7 +805,8 @@ function createReactTailorKitClient(options) {
1409
805
  }
1410
806
 
1411
807
  // src/components/root.tsx
1412
- import { useEffect as useEffect6, useMemo as useMemo9, useState as useState3 } from "react";
808
+ import { useEffect as useEffect6, useMemo as useMemo9, useState as useState2 } from "react";
809
+ import { createTailorKitStore, toBaseUrl as toBaseUrl2 } from "@tailorkit/client-core";
1413
810
 
1414
811
  // src/components/render.tsx
1415
812
  import { cloneElement, createElement as createElement3, isValidElement, useMemo as useMemo8 } from "react";
@@ -1487,12 +884,16 @@ function isEventHandler(key) {
1487
884
  // src/components/root.tsx
1488
885
  import { jsx as jsx4 } from "react/jsx-runtime";
1489
886
  function Root({ apps: appsProp, children, render, client, ...props }) {
1490
- const baseUrl = toBaseUrl(client.baseUrl).toString();
1491
- const [previousStore, setStore] = useState3(() => createTailorKitStore(baseUrl, appsProp));
887
+ const baseUrl = toBaseUrl2(client.baseUrl).toString();
888
+ const [previous, setStore] = useState2(() => ({
889
+ store: createTailorKitStore(baseUrl, appsProp, client.fetchClient),
890
+ fetchClient: client.fetchClient
891
+ }));
892
+ const previousStore = previous.store;
1492
893
  let store = previousStore;
1493
- if (previousStore.baseUrl.toString() !== baseUrl) {
1494
- store = createTailorKitStore(baseUrl, appsProp);
1495
- setStore(store);
894
+ if (previousStore.baseUrl.toString() !== baseUrl || previous.fetchClient !== client.fetchClient) {
895
+ store = createTailorKitStore(baseUrl, appsProp, client.fetchClient);
896
+ setStore({ store, fetchClient: client.fetchClient });
1496
897
  }
1497
898
  useEffect6(() => {
1498
899
  store.setProvidedApps(appsProp);