@tailorkit/react 0.1.0-beta.13 → 0.1.0-beta.16

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.
@@ -1,3 +1,3 @@
1
1
  import type { ReactNode } from "react";
2
- import type { AppViewProps } from "../tailor-kit";
2
+ import type { AppViewProps } from "../tailorkit";
3
3
  export declare const AppView: ({ app, slot, createIframe, fallback, ...viewProps }: AppViewProps) => ReactNode;
@@ -1,8 +1,8 @@
1
1
  import type { TailorKitStore } from "../store";
2
- import type { TailorKitInstance } from "../tailor-kit";
2
+ import type { TailorKitClientConfig } from "../tailorkit";
3
3
  export interface TailorRootContextValue {
4
4
  store: TailorKitStore;
5
- client: TailorKitInstance;
5
+ client: TailorKitClientConfig;
6
6
  }
7
7
  export declare const TailorRootContext: import("react").Context<TailorRootContextValue | null>;
8
- export declare function useTailorRootContext(component: string): TailorRootContextValue;
8
+ export declare function useTailorRootContext(component: string, expectedClient?: TailorKitClientConfig): TailorRootContextValue;
@@ -1,9 +1,9 @@
1
1
  import type { ReactNode } from "react";
2
- import type { TailorKitApp, TailorKitInstance } from "../tailor-kit";
2
+ import type { TailorKitApp, TailorKitClientConfig } from "../tailorkit";
3
3
  import type { ComponentProps } from "./render";
4
4
  export interface RootProps extends ComponentProps<"div"> {
5
5
  apps?: TailorKitApp[];
6
6
  children?: ReactNode;
7
- client: TailorKitInstance;
7
+ client: TailorKitClientConfig;
8
8
  }
9
9
  export declare function Root({ apps: appsProp, children, render, client, ...props }: RootProps): ReactNode;
@@ -1,5 +1,8 @@
1
- import type { TailorKitApp } from "../tailor-kit";
1
+ import type { TailorKitApp } from "../tailorkit";
2
2
  import type { TailorKitStore } from "../store";
3
+ export interface UseAppsOptions<TScopeNames extends string = string> {
4
+ scopes?: readonly TScopeNames[];
5
+ }
3
6
  export interface UseAppsResult {
4
7
  data: TailorKitApp[] | undefined;
5
8
  error: Error | null;
@@ -10,5 +13,5 @@ export interface UseAppsResult {
10
13
  refetch: () => Promise<void>;
11
14
  status: "error" | "idle" | "loading" | "ready";
12
15
  }
13
- export declare function useApps(): UseAppsResult;
14
- export declare function useAppsStore(store: TailorKitStore): UseAppsResult;
16
+ export declare function useApps(options?: UseAppsOptions): UseAppsResult;
17
+ export declare function useAppsStore(store: TailorKitStore, options?: UseAppsOptions): UseAppsResult;
@@ -1,8 +1,8 @@
1
1
  import type { StandardJSONSchemaV1 } from "@standard-schema/spec";
2
2
  import type { ViewDefinition } from "@tailorkit/core/schema";
3
- import type { RegisteredViews } from "../tailor-kit";
3
+ type DefaultViews = Record<`/${string}`, ViewDefinition>;
4
4
  export type ViewName<TViews extends Record<string, ViewDefinition>> = keyof TViews & string;
5
- export type ViewContext<TView> = TView extends ViewDefinition<infer TContext> ? TContext extends StandardJSONSchemaV1 ? StandardJSONSchemaV1.InferOutput<TContext> : Record<string, never> : never;
5
+ export type ViewContext<TView> = TView extends StandardJSONSchemaV1 ? StandardJSONSchemaV1.InferOutput<TView> : Record<string, never>;
6
6
  interface ReadyViewOptions<TViews extends Record<string, ViewDefinition>, TView extends ViewName<TViews>> {
7
7
  context: ViewContext<TViews[TView]>;
8
8
  view: TView;
@@ -18,7 +18,8 @@ interface ErrorViewOptions<TView extends string> {
18
18
  view: TView;
19
19
  status: "error";
20
20
  }
21
- export type ViewOptions<TViews extends Record<string, ViewDefinition> = RegisteredViews, TView extends ViewName<TViews> = ViewName<TViews>> = TView extends ViewName<TViews> ? ReadyViewOptions<TViews, TView> | LoadingViewOptions<TView> | ErrorViewOptions<TView> : never;
22
- export type ViewState<TViews extends Record<string, ViewDefinition> = RegisteredViews, TView extends ViewName<TViews> = ViewName<TViews>> = Omit<ReadyViewOptions<TViews, TView>, "view"> | Omit<LoadingViewOptions<TView>, "view"> | Omit<ErrorViewOptions<TView>, "view">;
23
- export declare function useView<TView extends ViewName<RegisteredViews>>(view: TView, options: ViewState<RegisteredViews, NoInfer<TView>>): void;
21
+ export type ViewOptions<TViews extends Record<string, ViewDefinition> = DefaultViews, TView extends ViewName<TViews> = ViewName<TViews>> = TView extends ViewName<TViews> ? ReadyViewOptions<TViews, TView> | LoadingViewOptions<TView> | ErrorViewOptions<TView> : never;
22
+ export type ViewState<TViews extends Record<string, ViewDefinition> = DefaultViews, TView extends ViewName<TViews> = ViewName<TViews>> = Omit<ReadyViewOptions<TViews, TView>, "view"> | Omit<LoadingViewOptions<TView>, "view"> | Omit<ErrorViewOptions<TView>, "view">;
23
+ export type UseView<TViews extends Record<string, ViewDefinition>> = <TView extends ViewName<TViews>>(view: TView, options: ViewState<TViews, NoInfer<TView>>) => void;
24
+ export declare function useView<TViews extends Record<string, ViewDefinition> = DefaultViews, TView extends ViewName<TViews> = ViewName<TViews>>(view: TView, options: ViewState<TViews, NoInfer<TView>>): void;
24
25
  export {};
package/dist/index.d.ts CHANGED
@@ -1,8 +1,7 @@
1
1
  export { AppView } from "./components/app-view";
2
- export { components, createTailorKitClient } from "./tailor-kit";
3
- export type { AppViewProps, TailorKitApp, TailorKitInstance, Register } from "./tailor-kit";
2
+ export { components, createTailorKitClient } from "./tailorkit";
3
+ export type { AppViewProps, TailorKitApp, TailorKitClientConfig, TailorKitInstance, } from "./tailorkit";
4
4
  export { Root } from "./components/root";
5
- export { useApps } from "./hooks/use-apps";
6
- export { useView } from "./hooks/use-view";
7
- export type { ViewOptions, ViewState } from "./hooks/use-view";
5
+ export type { UseAppsOptions, UseAppsResult } from "./hooks/use-apps";
6
+ export type { UseView, ViewOptions, ViewState } from "./hooks/use-view";
8
7
  export { primitives } from "./primitives";
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  // src/components/app-view.tsx
2
- import { useEffect as useEffect2, useId as useId2, useMemo as useMemo3, useSyncExternalStore as useSyncExternalStore2 } from "react";
2
+ import { createSessionProvider } from "@tailorkit/app/client";
3
+ import { useCallback as useCallback2, useEffect as useEffect2, useId as useId2, useMemo as useMemo3, useRef as useRef3, useSyncExternalStore as useSyncExternalStore2 } from "react";
3
4
  import { isViewAncestor as isViewAncestor2 } from "@tailorkit/core/views";
4
5
 
5
6
  // src/view-registry.ts
@@ -63,25 +64,287 @@ function createViewRegistry() {
63
64
  };
64
65
  }
65
66
 
67
+ // src/preview-manager.ts
68
+ import {
69
+ createPreviewWebSocketClient,
70
+ previewMetadataSchema
71
+ } from "@tailorkit/client-platform/preview";
72
+ var empty = { revision: 0, source: null };
73
+ function viewerTokenExpiresAt(metadata) {
74
+ const expiresAt = Number(metadata.token.split(".", 1)[0]);
75
+ return Number.isSafeInteger(expiresAt) ? expiresAt : 0;
76
+ }
77
+ function bytesFromBase64(value) {
78
+ const raw = atob(value);
79
+ const bytes = new Uint8Array(raw.length);
80
+ for (let index = 0; index < raw.length; index++) {
81
+ bytes[index] = raw.codePointAt(index) ?? 0;
82
+ }
83
+ return bytes;
84
+ }
85
+ async function verify(candidate) {
86
+ let source = null;
87
+ for (const [fileIndex, file] of candidate.manifest.files.entries()) {
88
+ const parts = candidate.files[fileIndex];
89
+ if (!parts || parts.length !== file.chunks || parts.some((part) => part === null)) {
90
+ return null;
91
+ }
92
+ const bytes = new Uint8Array(file.size);
93
+ let offset = 0;
94
+ for (const part of parts) {
95
+ if (!part) {
96
+ return null;
97
+ }
98
+ bytes.set(part, offset);
99
+ offset += part.length;
100
+ }
101
+ if (offset !== file.size) {
102
+ return null;
103
+ }
104
+ const digest = [...new Uint8Array(await crypto.subtle.digest("SHA-256", bytes))].map((byte) => byte.toString(16).padStart(2, "0")).join("");
105
+ if (digest !== file.sha256) {
106
+ return null;
107
+ }
108
+ if (file.path === "client.js") {
109
+ source = new TextDecoder().decode(bytes);
110
+ }
111
+ }
112
+ return source;
113
+ }
114
+ function createPreviewManager(baseUrl, onEnded) {
115
+ const entries = /* @__PURE__ */ new Map();
116
+ const notify = (entry) => {
117
+ for (const listener of entry.listeners) {
118
+ listener();
119
+ }
120
+ };
121
+ const close = (entry) => {
122
+ if (entry.reconnect) {
123
+ clearTimeout(entry.reconnect);
124
+ }
125
+ entry.reconnect = null;
126
+ const socket = entry.socket;
127
+ entry.socket = null;
128
+ socket?.close();
129
+ entry.connecting = false;
130
+ entry.candidate = null;
131
+ };
132
+ const end = (entry) => {
133
+ close(entry);
134
+ entry.metadata = null;
135
+ entry.snapshot = empty;
136
+ notify(entry);
137
+ onEnded();
138
+ };
139
+ const handleEvent = async (entry, event) => {
140
+ if (event.type === "ended") {
141
+ end(entry);
142
+ return;
143
+ }
144
+ if (event.revision <= entry.snapshot.revision) {
145
+ return;
146
+ }
147
+ if (event.type === "begin") {
148
+ entry.candidate = {
149
+ revision: event.revision,
150
+ manifest: event.manifest,
151
+ files: event.manifest.files.map((file) => Array.from({ length: file.chunks }, () => null))
152
+ };
153
+ return;
154
+ }
155
+ const candidate = entry.candidate;
156
+ if (!candidate || candidate.revision !== event.revision) {
157
+ return;
158
+ }
159
+ if (event.type === "chunk") {
160
+ const file = candidate.manifest.files[event.fileIndex];
161
+ const slots = candidate.files[event.fileIndex];
162
+ if (!file || !slots || event.chunkIndex < 0 || event.chunkIndex >= slots.length || slots[event.chunkIndex]) {
163
+ entry.candidate = null;
164
+ return;
165
+ }
166
+ const bytes = bytesFromBase64(event.base64);
167
+ if (bytes.length !== Math.min(256 * 1024, file.size - event.chunkIndex * 256 * 1024)) {
168
+ entry.candidate = null;
169
+ return;
170
+ }
171
+ slots[event.chunkIndex] = bytes;
172
+ return;
173
+ }
174
+ if (event.type === "complete") {
175
+ const source = await verify(candidate);
176
+ if (source !== null && entry.candidate === candidate && candidate.revision > entry.snapshot.revision) {
177
+ entry.snapshot = { revision: candidate.revision, source };
178
+ notify(entry);
179
+ }
180
+ if (entry.candidate === candidate) {
181
+ entry.candidate = null;
182
+ }
183
+ }
184
+ };
185
+ const connect = async (entry) => {
186
+ if (!entry.listeners.size || !entry.app.preview || entry.socket || entry.connecting) {
187
+ return;
188
+ }
189
+ entry.connecting = true;
190
+ const appMetadata = entry.app.preview;
191
+ let metadata = entry.metadata && viewerTokenExpiresAt(entry.metadata) > Math.max(Date.now(), viewerTokenExpiresAt(appMetadata)) ? entry.metadata : appMetadata;
192
+ try {
193
+ const refresh = new URL("preview/metadata", baseUrl);
194
+ refresh.searchParams.set("sessionId", metadata.sessionId);
195
+ const response = await fetch(refresh, { credentials: "same-origin" });
196
+ if (response.ok) {
197
+ metadata = previewMetadataSchema.parse(await response.json());
198
+ entry.metadata = metadata;
199
+ } else if (response.status === 404) {
200
+ end(entry);
201
+ return;
202
+ }
203
+ } catch {
204
+ }
205
+ if (!entry.listeners.size) {
206
+ entry.connecting = false;
207
+ return;
208
+ }
209
+ const socket = new WebSocket(metadata.websocketUrl, metadata.token);
210
+ entry.socket = socket;
211
+ entry.connecting = false;
212
+ socket.addEventListener("open", () => {
213
+ entry.delay = 1e3;
214
+ const client = createPreviewWebSocketClient(socket);
215
+ void (async () => {
216
+ try {
217
+ const stream = await client.subscribe();
218
+ for await (const event of stream) {
219
+ await handleEvent(entry, event);
220
+ }
221
+ } catch {
222
+ } finally {
223
+ socket.close();
224
+ }
225
+ })();
226
+ });
227
+ socket.addEventListener("error", () => socket.close());
228
+ socket.addEventListener("close", () => {
229
+ if (entry.socket !== socket) {
230
+ return;
231
+ }
232
+ entry.socket = null;
233
+ entry.candidate = null;
234
+ if (entry.listeners.size) {
235
+ entry.reconnect = setTimeout(() => {
236
+ entry.reconnect = null;
237
+ void connect(entry);
238
+ }, entry.delay);
239
+ entry.delay = Math.min(entry.delay * 2, 3e4);
240
+ }
241
+ });
242
+ };
243
+ return {
244
+ getSnapshot: (sessionId) => entries.get(sessionId)?.snapshot ?? empty,
245
+ updateApp: (app) => {
246
+ const sessionId = app.preview?.sessionId;
247
+ const entry = sessionId && entries.get(sessionId);
248
+ if (entry) {
249
+ entry.app = app;
250
+ }
251
+ },
252
+ subscribe: (app, listener) => {
253
+ const sessionId = app.preview?.sessionId;
254
+ if (!sessionId) {
255
+ return () => {
256
+ };
257
+ }
258
+ let entry = entries.get(sessionId);
259
+ if (!entry) {
260
+ entry = {
261
+ app,
262
+ metadata: null,
263
+ candidate: null,
264
+ listeners: /* @__PURE__ */ new Set(),
265
+ snapshot: empty,
266
+ socket: null,
267
+ connecting: false,
268
+ reconnect: null,
269
+ delay: 1e3
270
+ };
271
+ entries.set(sessionId, entry);
272
+ }
273
+ entry.app = app;
274
+ entry.listeners.add(listener);
275
+ void connect(entry);
276
+ return () => {
277
+ entry?.listeners.delete(listener);
278
+ if (entry && !entry.listeners.size) {
279
+ close(entry);
280
+ entries.delete(sessionId);
281
+ }
282
+ };
283
+ },
284
+ dispose: () => {
285
+ for (const entry of entries.values()) {
286
+ close(entry);
287
+ }
288
+ entries.clear();
289
+ }
290
+ };
291
+ }
292
+
293
+ // src/scope-query.ts
294
+ function appendScopeSelection(url, scopes) {
295
+ if (scopes === void 0) {
296
+ return;
297
+ }
298
+ if (scopes.length === 0) {
299
+ url.searchParams.append("scopes", "");
300
+ return;
301
+ }
302
+ for (const scope of scopes) {
303
+ url.searchParams.append("scopes", scope);
304
+ }
305
+ }
306
+ function normalizeScopeSelection(scopes) {
307
+ if (scopes === void 0) {
308
+ return { key: "*" };
309
+ }
310
+ const names = [...new Set(scopes)].sort();
311
+ return { key: JSON.stringify(names), scopes: names };
312
+ }
313
+
66
314
  // src/store.ts
67
- function createTailorKitStore(baseUrlInput, initialApps, previewSessionId) {
315
+ function createTailorKitStore(baseUrlInput, initialApps) {
68
316
  const baseUrl = toBaseUrl(baseUrlInput);
69
317
  const listeners = /* @__PURE__ */ new Set();
70
318
  let providedApps = initialApps;
71
- let appsSnapshot = {
319
+ let unrequestedSnapshot = {
72
320
  apps: initialApps ?? [],
73
321
  error: null,
74
322
  status: initialApps === void 0 ? "idle" : "ready"
75
323
  };
324
+ const appsEntries = /* @__PURE__ */ new Map();
76
325
  let metaSnapshot = {
77
326
  assetsBaseUrl: null,
78
327
  error: null,
79
328
  schema: null,
80
329
  status: "idle"
81
330
  };
82
- let fetchAppsPromise = null;
83
331
  let fetchMetaPromise = null;
84
- let fetchAppsRequestId = 0;
332
+ const getAppsEntry = (scopes) => {
333
+ const selection = normalizeScopeSelection(scopes);
334
+ let entry = appsEntries.get(selection.key);
335
+ if (!entry) {
336
+ entry = {
337
+ scopes: selection.scopes,
338
+ snapshot: unrequestedSnapshot,
339
+ promise: null,
340
+ requestId: 0,
341
+ requested: false,
342
+ subscribers: 0
343
+ };
344
+ appsEntries.set(selection.key, entry);
345
+ }
346
+ return entry;
347
+ };
85
348
  const emit = () => {
86
349
  for (const listener of listeners) {
87
350
  listener();
@@ -95,58 +358,65 @@ function createTailorKitStore(baseUrlInput, initialApps, previewSessionId) {
95
358
  return;
96
359
  }
97
360
  providedApps = apps;
98
- fetchAppsRequestId += 1;
99
- fetchAppsPromise = null;
100
- appsSnapshot = {
361
+ unrequestedSnapshot = {
101
362
  apps: apps ?? [],
102
363
  error: null,
103
364
  status: apps === void 0 ? "idle" : "ready"
104
365
  };
366
+ for (const entry of appsEntries.values()) {
367
+ entry.requestId += 1;
368
+ entry.promise = null;
369
+ entry.snapshot = unrequestedSnapshot;
370
+ }
105
371
  emit();
106
372
  if (apps === void 0) {
107
- void store.fetchApps();
373
+ for (const entry of appsEntries.values()) {
374
+ if (entry.requested && entry.subscribers > 0) {
375
+ void store.fetchApps({ scopes: entry.scopes });
376
+ }
377
+ }
108
378
  }
109
379
  },
110
380
  fetchApps: (options = {}) => {
381
+ const entry = getAppsEntry(options.scopes);
382
+ entry.requested = true;
111
383
  if (providedApps !== void 0) {
112
384
  return Promise.resolve();
113
385
  }
114
- if (fetchAppsPromise && !options.force) {
115
- return fetchAppsPromise;
386
+ if (entry.promise && !options.force) {
387
+ return entry.promise;
116
388
  }
117
- appsSnapshot = { ...appsSnapshot, status: "loading" };
389
+ entry.snapshot = { ...entry.snapshot, status: "loading" };
118
390
  emit();
119
- const requestId = ++fetchAppsRequestId;
391
+ const requestId = ++entry.requestId;
120
392
  const appsUrl = new URL("apps", baseUrl);
121
- if (previewSessionId) {
122
- appsUrl.searchParams.set("previewSessionId", previewSessionId);
123
- }
124
- fetchAppsPromise = fetch(appsUrl).then(async (response) => {
393
+ appendScopeSelection(appsUrl, entry.scopes);
394
+ entry.promise = fetch(appsUrl).then(async (response) => {
125
395
  if (!response.ok) {
126
396
  throw new Error(`Unable to fetch TailorKit apps from ${baseUrl.toString()}.`);
127
397
  }
128
398
  const apps = await response.json();
129
- if (requestId !== fetchAppsRequestId) {
399
+ if (requestId !== entry.requestId) {
130
400
  return;
131
401
  }
132
- appsSnapshot = {
402
+ entry.snapshot = {
133
403
  apps,
134
404
  error: null,
135
405
  status: "ready"
136
406
  };
137
407
  emit();
138
408
  }).catch((error) => {
139
- if (requestId !== fetchAppsRequestId) {
409
+ if (requestId !== entry.requestId) {
140
410
  return;
141
411
  }
142
- appsSnapshot = {
143
- ...appsSnapshot,
412
+ entry.snapshot = {
413
+ ...entry.snapshot,
144
414
  error: error instanceof Error ? error : new Error(String(error)),
145
415
  status: "error"
146
416
  };
147
417
  emit();
148
418
  });
149
- return fetchAppsPromise;
419
+ return entry.promise;
150
420
  },
151
421
  fetchMeta: () => {
152
422
  if (fetchMetaPromise) {
@@ -176,16 +446,39 @@ function createTailorKitStore(baseUrlInput, initialApps, previewSessionId) {
176
446
  });
177
447
  return fetchMetaPromise;
178
448
  },
179
- getAppsSnapshot: () => appsSnapshot,
449
+ getAppsSnapshot: (scopes) => appsEntries.get(normalizeScopeSelection(scopes).key)?.snapshot ?? unrequestedSnapshot,
180
450
  getMetaSnapshot: () => metaSnapshot,
181
451
  subscribe: (listener) => {
182
452
  listeners.add(listener);
183
453
  return () => {
184
454
  listeners.delete(listener);
185
455
  };
456
+ },
457
+ subscribeApps: (scopes, listener) => {
458
+ const selection = normalizeScopeSelection(scopes);
459
+ const entry = getAppsEntry(selection.scopes);
460
+ entry.subscribers += 1;
461
+ listeners.add(listener);
462
+ return () => {
463
+ listeners.delete(listener);
464
+ entry.subscribers -= 1;
465
+ if (entry.subscribers === 0) {
466
+ entry.requestId += 1;
467
+ appsEntries.delete(selection.key);
468
+ }
469
+ };
186
470
  }
187
471
  };
188
- return store;
472
+ return {
473
+ ...store,
474
+ previews: createPreviewManager(baseUrl, () => {
475
+ for (const entry of appsEntries.values()) {
476
+ if (entry.requested && entry.subscribers > 0) {
477
+ void store.fetchApps({ force: true, scopes: entry.scopes });
478
+ }
479
+ }
480
+ })
481
+ };
189
482
  }
190
483
  function toBaseUrl(value) {
191
484
  const url = value instanceof URL ? new URL(value) : new URL(value);
@@ -209,11 +502,16 @@ function useStableContext(value) {
209
502
  // src/components/context.ts
210
503
  import { createContext, useContext } from "react";
211
504
  var TailorRootContext = createContext(null);
212
- function useTailorRootContext(component) {
505
+ function useTailorRootContext(component, expectedClient) {
213
506
  const context = useContext(TailorRootContext);
214
507
  if (!context) {
215
508
  throw new Error(`${component} must be rendered inside Root.`);
216
509
  }
510
+ if (expectedClient && context.client !== expectedClient) {
511
+ throw new Error(
512
+ `${component} was created for a different TailorKit client than the one passed to Root.`
513
+ );
514
+ }
217
515
  return context;
218
516
  }
219
517
 
@@ -562,6 +860,8 @@ var RemoteErrorBoundary = class extends Component {
562
860
  };
563
861
  function RemoteViewHost({
564
862
  appUrl,
863
+ sourceText,
864
+ getBackendSession,
565
865
  components: components2,
566
866
  createIframe,
567
867
  props
@@ -609,6 +909,8 @@ function RemoteViewHost({
609
909
  useEffect(() => {
610
910
  const host = createIframeUiHost(appUrl, {
611
911
  createIframe,
912
+ sourceText,
913
+ getBackendSession,
612
914
  onError: (error2) => {
613
915
  console.error("TailorKit remote app failed", error2);
614
916
  setError(error2);
@@ -634,7 +936,7 @@ function RemoteViewHost({
634
936
  hostRef.current = null;
635
937
  host.destroy();
636
938
  };
637
- }, [appUrl, createIframe, store]);
939
+ }, [appUrl, createIframe, sourceText, getBackendSession, store]);
638
940
  if (status === "error" && error) {
639
941
  return createElement("div", null, formatError(error));
640
942
  }
@@ -709,6 +1011,10 @@ var AppView = ({
709
1011
  const { store, client } = useTailorRootContext("AppView");
710
1012
  const { theme, components: wrappedComponents } = client;
711
1013
  const reactId = useId2();
1014
+ const getBackendSession = useMemo3(
1015
+ () => createSessionProvider({ baseUrl: store.baseUrl, appId: app.id }),
1016
+ [store.baseUrl, app.id]
1017
+ );
712
1018
  const currentView = useSyncExternalStore2(
713
1019
  store.views.subscribe,
714
1020
  store.views.getSnapshot,
@@ -734,6 +1040,29 @@ var AppView = ({
734
1040
  return currentView === null ? void 0 : { slot, ...currentView };
735
1041
  }, [context, currentView, view, status, slot]);
736
1042
  const meta = useSyncExternalStore2(store.subscribe, store.getMetaSnapshot, store.getMetaSnapshot);
1043
+ const previewSessionId = app.preview?.sessionId ?? "";
1044
+ const appRef = useRef3(app);
1045
+ appRef.current = app;
1046
+ const subscribePreview = useCallback2(
1047
+ (listener) => {
1048
+ const currentApp = appRef.current;
1049
+ return currentApp.preview?.sessionId === previewSessionId ? store.previews.subscribe(currentApp, listener) : () => {
1050
+ };
1051
+ },
1052
+ [store, previewSessionId]
1053
+ );
1054
+ const getPreviewSnapshot = useCallback2(
1055
+ () => store.previews.getSnapshot(previewSessionId),
1056
+ [store, previewSessionId]
1057
+ );
1058
+ const previewSnapshot = useSyncExternalStore2(
1059
+ subscribePreview,
1060
+ getPreviewSnapshot,
1061
+ getPreviewSnapshot
1062
+ );
1063
+ useEffect2(() => {
1064
+ store.previews.updateApp(app);
1065
+ }, [store, app]);
737
1066
  const runtimeProps = useMemo3(
738
1067
  () => props === void 0 ? void 0 : {
739
1068
  ...props,
@@ -750,7 +1079,7 @@ var AppView = ({
750
1079
  useEffect2(() => {
751
1080
  void store.fetchMeta();
752
1081
  }, [store]);
753
- if (props === void 0 || appUrl === null || meta.schema === null) {
1082
+ if (props === void 0 || appUrl === null && previewSnapshot.source === null || meta.schema === null) {
754
1083
  return fallback;
755
1084
  }
756
1085
  const viewId = `tailorkit-view-${reactId.replaceAll(":", "")}`;
@@ -759,11 +1088,14 @@ var AppView = ({
759
1088
  /* @__PURE__ */ jsx3(
760
1089
  RemoteViewHost,
761
1090
  {
762
- appUrl: appUrl.toString(),
1091
+ appUrl: (appUrl ?? new URL(`preview/${previewSessionId}/client.js`, store.baseUrl)).toString(),
1092
+ sourceText: previewSnapshot.source ?? void 0,
1093
+ getBackendSession,
763
1094
  components: wrappedComponents,
764
1095
  createIframe,
765
1096
  props: runtimeProps
766
- }
1097
+ },
1098
+ previewSnapshot.revision || appUrl?.toString()
767
1099
  )
768
1100
  ] }) });
769
1101
  };
@@ -775,12 +1107,77 @@ function resolveAppUrl(app, baseUrl, assetsBaseUrl) {
775
1107
  return null;
776
1108
  }
777
1109
  return new URL(
778
- `projects/${app.projectId}/apps/${app.id}/deployments/${app.currentDeployment.id}/files/client.js`,
1110
+ `projects/${app.projectId}/apps/${app.id}/deployments/${app.currentDeployment.id}/client/client.js`,
779
1111
  toBaseUrl(assetsBaseUrl)
780
1112
  );
781
1113
  }
782
1114
 
783
- // src/tailor-kit.tsx
1115
+ // src/tailorkit.tsx
1116
+ import { createElement as createElement2 } from "react";
1117
+
1118
+ // src/hooks/use-view.ts
1119
+ import { useEffect as useEffect3, useMemo as useMemo4 } from "react";
1120
+ function useView(view, options) {
1121
+ const { store } = useTailorRootContext("useView");
1122
+ const id = useMemo4(() => /* @__PURE__ */ Symbol("tailorkit-current-view"), []);
1123
+ const status = options.status ?? "ready";
1124
+ const context = "context" in options ? options.context : void 0;
1125
+ const contextSnapshot = useStableContext(context);
1126
+ useEffect3(
1127
+ () => () => {
1128
+ store.views.unregister(id);
1129
+ },
1130
+ [id, store]
1131
+ );
1132
+ useEffect3(() => {
1133
+ store.views.register({
1134
+ context: contextSnapshot,
1135
+ id,
1136
+ view,
1137
+ status
1138
+ });
1139
+ }, [contextSnapshot, id, view, status, store]);
1140
+ }
1141
+
1142
+ // src/hooks/use-apps.ts
1143
+ import { useCallback as useCallback3, useEffect as useEffect4, useSyncExternalStore as useSyncExternalStore3 } from "react";
1144
+ function useApps(options = {}) {
1145
+ return useAppsStore(useTailorRootContext("useApps").store, options);
1146
+ }
1147
+ function useAppsStore(store, options = {}) {
1148
+ const selection = normalizeScopeSelection(options.scopes);
1149
+ const subscribe = useCallback3(
1150
+ (listener) => store.subscribeApps(selection.scopes, listener),
1151
+ [store, selection.key]
1152
+ );
1153
+ const snapshot = useSyncExternalStore3(
1154
+ subscribe,
1155
+ () => store.getAppsSnapshot(selection.scopes),
1156
+ () => store.getAppsSnapshot(selection.scopes)
1157
+ );
1158
+ useEffect4(() => {
1159
+ void store.fetchApps({ scopes: selection.scopes });
1160
+ }, [store, selection.key]);
1161
+ const refetch = useCallback3(
1162
+ () => store.fetchApps({ force: true, scopes: selection.scopes }),
1163
+ [store, selection.key]
1164
+ );
1165
+ return toUseAppsResult(snapshot, refetch);
1166
+ }
1167
+ function toUseAppsResult(snapshot, refetch) {
1168
+ return {
1169
+ data: snapshot.status === "ready" ? snapshot.apps : void 0,
1170
+ error: snapshot.error,
1171
+ isError: snapshot.status === "error",
1172
+ isLoading: snapshot.status === "loading",
1173
+ isPending: snapshot.status === "idle" || snapshot.status === "loading",
1174
+ isSuccess: snapshot.status === "ready",
1175
+ refetch,
1176
+ status: snapshot.status
1177
+ };
1178
+ }
1179
+
1180
+ // src/tailorkit.tsx
784
1181
  var componentTagPrefix = "tailorkit-";
785
1182
  var toComponentTagName = (name) => `${componentTagPrefix}${name.replaceAll(/([a-z0-9])([A-Z])/gu, "$1-$2").replaceAll(/[\s_]+/gu, "-").toLowerCase()}`;
786
1183
  function components(_schema, customComponents) {
@@ -807,32 +1204,51 @@ function createReactTailorKitClient(options) {
807
1204
  wrappedComponents[toComponentTagName(name)] = TailorKitComponent;
808
1205
  }
809
1206
  }
810
- return {
1207
+ const clientConfig = {
811
1208
  baseUrl: options.baseUrl,
812
1209
  components: wrappedComponents,
813
- previewSessionId: options.previewSessionId,
814
1210
  theme
815
1211
  };
1212
+ const client = {
1213
+ ...clientConfig,
1214
+ AppView: function ClientAppView(props) {
1215
+ useTailorRootContext("AppView", client);
1216
+ const TypedReactAppView = AppView;
1217
+ return createElement2(
1218
+ TypedReactAppView,
1219
+ props
1220
+ );
1221
+ },
1222
+ useApps: function useClientApps(options2) {
1223
+ useTailorRootContext("useApps", client);
1224
+ return useApps(options2);
1225
+ },
1226
+ useView: function useClientView(view, options2) {
1227
+ useTailorRootContext("useView", client);
1228
+ useView(view, options2);
1229
+ }
1230
+ };
1231
+ return client;
816
1232
  }
817
1233
 
818
1234
  // src/components/root.tsx
819
- import { useEffect as useEffect3, useMemo as useMemo5, useState as useState2 } from "react";
1235
+ import { useEffect as useEffect5, useMemo as useMemo6, useState as useState2 } from "react";
820
1236
 
821
1237
  // src/components/render.tsx
822
- import { cloneElement, createElement as createElement2, isValidElement, useMemo as useMemo4 } from "react";
1238
+ import { cloneElement, createElement as createElement3, isValidElement, useMemo as useMemo5 } from "react";
823
1239
  function useRender({
824
1240
  defaultTagName,
825
1241
  props,
826
1242
  render
827
1243
  }) {
828
- return useMemo4(() => {
1244
+ return useMemo5(() => {
829
1245
  if (render) {
830
1246
  if (!isValidElement(render)) {
831
1247
  throw new Error("The render prop must be a valid React element.");
832
1248
  }
833
1249
  return cloneElement(render, mergeProps(render.props, props));
834
1250
  }
835
- return createElement2(defaultTagName, props);
1251
+ return createElement3(defaultTagName, props);
836
1252
  }, [defaultTagName, props, render]);
837
1253
  }
838
1254
  function mergeProps(...propSets) {
@@ -895,18 +1311,17 @@ function isEventHandler(key) {
895
1311
  import { jsx as jsx4 } from "react/jsx-runtime";
896
1312
  function Root({ apps: appsProp, children, render, client, ...props }) {
897
1313
  const baseUrl = toBaseUrl(client.baseUrl).toString();
898
- const [previousStore, setStore] = useState2(
899
- () => createTailorKitStore(baseUrl, appsProp, client.previewSessionId)
900
- );
1314
+ const [previousStore, setStore] = useState2(() => createTailorKitStore(baseUrl, appsProp));
901
1315
  let store = previousStore;
902
1316
  if (previousStore.baseUrl.toString() !== baseUrl) {
903
- store = createTailorKitStore(baseUrl, appsProp, client.previewSessionId);
1317
+ store = createTailorKitStore(baseUrl, appsProp);
904
1318
  setStore(store);
905
1319
  }
906
- useEffect3(() => {
1320
+ useEffect5(() => {
907
1321
  store.setProvidedApps(appsProp);
908
1322
  }, [store, appsProp]);
909
- const context = useMemo5(
1323
+ useEffect5(() => () => store.previews.dispose(), [store]);
1324
+ const context = useMemo6(
910
1325
  () => ({
911
1326
  store,
912
1327
  client
@@ -920,66 +1335,10 @@ function Root({ apps: appsProp, children, render, client, ...props }) {
920
1335
  }) : children;
921
1336
  return /* @__PURE__ */ jsx4(TailorRootContext.Provider, { value: context, children: element });
922
1337
  }
923
-
924
- // src/hooks/use-apps.ts
925
- import { useCallback as useCallback2, useEffect as useEffect4, useSyncExternalStore as useSyncExternalStore3 } from "react";
926
- function useApps() {
927
- return useAppsStore(useTailorRootContext("useApps").store);
928
- }
929
- function useAppsStore(store) {
930
- const snapshot = useSyncExternalStore3(
931
- store.subscribe,
932
- store.getAppsSnapshot,
933
- store.getAppsSnapshot
934
- );
935
- useEffect4(() => {
936
- void store.fetchApps();
937
- }, [store]);
938
- const refetch = useCallback2(() => store.fetchApps({ force: true }), [store]);
939
- return toUseAppsResult(snapshot, refetch);
940
- }
941
- function toUseAppsResult(snapshot, refetch) {
942
- return {
943
- data: snapshot.status === "ready" ? snapshot.apps : void 0,
944
- error: snapshot.error,
945
- isError: snapshot.status === "error",
946
- isLoading: snapshot.status === "loading",
947
- isPending: snapshot.status === "idle" || snapshot.status === "loading",
948
- isSuccess: snapshot.status === "ready",
949
- refetch,
950
- status: snapshot.status
951
- };
952
- }
953
-
954
- // src/hooks/use-view.ts
955
- import { useEffect as useEffect5, useMemo as useMemo6 } from "react";
956
- function useView(view, options) {
957
- const { store } = useTailorRootContext("useView");
958
- const id = useMemo6(() => /* @__PURE__ */ Symbol("tailorkit-current-view"), []);
959
- const status = options.status ?? "ready";
960
- const context = "context" in options ? options.context : void 0;
961
- const contextSnapshot = useStableContext(context);
962
- useEffect5(
963
- () => () => {
964
- store.views.unregister(id);
965
- },
966
- [id, store]
967
- );
968
- useEffect5(() => {
969
- store.views.register({
970
- context: contextSnapshot,
971
- id,
972
- view,
973
- status
974
- });
975
- }, [contextSnapshot, id, view, status, store]);
976
- }
977
1338
  export {
978
1339
  AppView,
979
1340
  Root,
980
1341
  components,
981
1342
  createTailorKitClient,
982
- primitives,
983
- useApps,
984
- useView
1343
+ primitives
985
1344
  };
@@ -0,0 +1,11 @@
1
+ import type { TailorKitApp } from "./tailorkit";
2
+ export interface PreviewSnapshot {
3
+ revision: number;
4
+ source: string | null;
5
+ }
6
+ export declare function createPreviewManager(baseUrl: URL, onEnded: () => void): {
7
+ getSnapshot: (sessionId: string) => PreviewSnapshot;
8
+ updateApp: (app: TailorKitApp) => void;
9
+ subscribe: (app: TailorKitApp, listener: () => void) => (() => void);
10
+ dispose: () => void;
11
+ };
@@ -1,11 +1,16 @@
1
+ import type { Session } from "@tailorkit/app/client";
1
2
  import type { ReactNode } from "react";
2
3
  interface RemoteViewHostProps {
3
4
  appUrl: string | URL;
5
+ sourceText?: string;
6
+ getBackendSession?: (options: {
7
+ refresh: boolean;
8
+ }) => Promise<Session>;
4
9
  components: Record<string, unknown>;
5
10
  createIframe?: () => HTMLIFrameElement;
6
11
  props?: Record<string, unknown>;
7
12
  }
8
- export declare function RemoteViewHost({ appUrl, components, createIframe, props, }: RemoteViewHostProps): ReactNode;
13
+ export declare function RemoteViewHost({ appUrl, sourceText, getBackendSession, components, createIframe, props, }: RemoteViewHostProps): ReactNode;
9
14
  interface RemoteViewProps {
10
15
  nodeId: string;
11
16
  }
@@ -0,0 +1,5 @@
1
+ export declare function appendScopeSelection(url: URL, scopes?: readonly string[]): void;
2
+ export declare function normalizeScopeSelection(scopes?: readonly string[]): {
3
+ key: string;
4
+ scopes?: readonly string[];
5
+ };
package/dist/store.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { TailorKitSchemaSpecType } from "@tailorkit/core/spec";
2
- import type { TailorKitApp } from "./tailor-kit";
2
+ import type { TailorKitApp } from "./tailorkit";
3
3
  export interface TailorKitAppsSnapshot {
4
4
  apps: TailorKitApp[];
5
5
  error: Error | null;
@@ -12,7 +12,7 @@ interface TailorKitMetaSnapshot {
12
12
  status: "error" | "idle" | "loading" | "ready";
13
13
  }
14
14
  export type TailorKitStore = ReturnType<typeof createTailorKitStore>;
15
- export declare function createTailorKitStore(baseUrlInput: string | URL, initialApps?: TailorKitApp[], previewSessionId?: string): {
15
+ export declare function createTailorKitStore(baseUrlInput: string | URL, initialApps?: TailorKitApp[]): {
16
16
  baseUrl: URL;
17
17
  views: {
18
18
  getSnapshot: () => import("@tailorkit/core/views").ActiveView | null;
@@ -23,11 +23,19 @@ export declare function createTailorKitStore(baseUrlInput: string | URL, initial
23
23
  setProvidedApps(apps: TailorKitApp[] | undefined): void;
24
24
  fetchApps: (options?: {
25
25
  force?: boolean;
26
+ scopes?: readonly string[];
26
27
  }) => Promise<void>;
27
28
  fetchMeta: () => Promise<void>;
28
- getAppsSnapshot: () => TailorKitAppsSnapshot;
29
+ getAppsSnapshot: (scopes?: readonly string[]) => TailorKitAppsSnapshot;
29
30
  getMetaSnapshot: () => TailorKitMetaSnapshot;
30
31
  subscribe: (listener: () => void) => (() => void);
32
+ subscribeApps: (scopes: readonly string[] | undefined, listener: () => void) => (() => void);
33
+ previews: {
34
+ getSnapshot: (sessionId: string) => import("./preview-manager").PreviewSnapshot;
35
+ updateApp: (app: TailorKitApp) => void;
36
+ subscribe: (app: TailorKitApp, listener: () => void) => (() => void);
37
+ dispose: () => void;
38
+ };
31
39
  };
32
40
  export declare function toBaseUrl(value: string | URL): URL;
33
41
  export {};
@@ -1,7 +1,8 @@
1
1
  import type { ReactNode } from "react";
2
2
  import type { TailorKitTheme, CallbackMap, ComponentDefinition, ComponentProps, Schema, ViewDefinition, SlotDefinitions, TailorKitSchema } from "@tailorkit/core/schema";
3
3
  import type { primitives } from "./primitives";
4
- import type { ViewOptions, ViewName } from "./hooks/use-view";
4
+ import type { UseView, ViewName, ViewOptions } from "./hooks/use-view";
5
+ import type { UseAppsOptions, UseAppsResult } from "./hooks/use-apps";
5
6
  type AnyComponentDefinition = ComponentDefinition<Schema | undefined, CallbackMap, boolean | undefined>;
6
7
  type ComponentRenderer<TComponent extends AnyComponentDefinition> = (args: {
7
8
  props: ComponentProps<TComponent>;
@@ -30,7 +31,9 @@ export interface TailorKitApp {
30
31
  name?: string;
31
32
  preview?: {
32
33
  sessionId: string;
33
- status: "connected" | "offline";
34
+ expiresAt: string;
35
+ websocketUrl: string;
36
+ token: string;
34
37
  };
35
38
  }
36
39
  interface AppViewBaseProps {
@@ -47,37 +50,26 @@ type AppViewViewProps<TViews extends Record<string, ViewDefinition>, TView exten
47
50
  view?: never;
48
51
  status?: never;
49
52
  } | ViewOptions<TViews, TView>;
50
- export type AppViewProps<TViews extends Record<string, ViewDefinition> = RegisteredViews, TView extends ViewName<TViews> = ViewName<TViews>> = {
51
- [V in RegisteredSlots]: AppViewBaseProps & {
53
+ export type AppViewProps<TViews extends Record<string, ViewDefinition> = Record<`/${string}`, ViewDefinition>, TView extends ViewName<TViews> = ViewName<TViews>, TSlots extends SlotDefinitions = SlotDefinitions> = {
54
+ [V in keyof TSlots & string]: AppViewBaseProps & {
52
55
  slot: V;
53
- } & AppViewViewProps<TViews, Extract<TView, SlotView<V>>>;
54
- }[RegisteredSlots];
55
- export interface Register {
56
- }
57
- export type RegisteredViews = Register extends {
58
- client: TailorKitInstance<infer S>;
59
- } ? S : Record<`/${string}`, ViewDefinition>;
60
- type RegisteredSlotMap = Register extends {
61
- client: {
62
- readonly $slots?: infer V;
63
- };
64
- } ? V : SlotDefinitions;
65
- export type RegisteredSlots = keyof RegisteredSlotMap & string;
66
- type SlotView<V extends RegisteredSlots> = RegisteredSlotMap[V] extends {
56
+ } & AppViewViewProps<TViews, Extract<TView, SlotView<TSlots, V>>>;
57
+ }[keyof TSlots & string];
58
+ type SlotView<TSlots extends SlotDefinitions, V extends keyof TSlots & string> = TSlots[V] extends {
67
59
  views: readonly (infer P)[];
68
60
  } ? Extract<P, string> : never;
69
- export interface TailorKitInstance<TViews extends Record<string, ViewDefinition> = Record<string, ViewDefinition>, TSlots extends SlotDefinitions = SlotDefinitions> {
70
- readonly $slots?: TSlots;
71
- readonly $views?: TViews;
61
+ export interface TailorKitClientConfig {
72
62
  readonly baseUrl: string | URL;
73
- /**
74
- * A host-selected, scope-authorized local-development preview session.
75
- * The host handler resolves this server-side before returning app URLs.
76
- */
77
- readonly previewSessionId?: string;
78
63
  readonly components: Record<string, unknown>;
79
64
  readonly theme: TailorKitTheme;
80
65
  }
66
+ export interface TailorKitInstance<TViews extends Record<string, ViewDefinition> = Record<string, ViewDefinition>, TSlots extends SlotDefinitions = SlotDefinitions, TScopeNames extends string = string> extends TailorKitClientConfig {
67
+ readonly $slots?: TSlots;
68
+ readonly $views?: TViews;
69
+ readonly AppView: (props: AppViewProps<TViews, ViewName<TViews>, TSlots>) => ReactNode;
70
+ readonly useApps: (options?: UseAppsOptions<TScopeNames>) => UseAppsResult;
71
+ readonly useView: UseView<TViews>;
72
+ }
81
73
  type PrimitiveRenderers = typeof primitives;
82
74
  type CustomComponentRenderers<TComponents extends Record<string, AnyComponentDefinition>> = {
83
75
  [TName in Exclude<keyof TComponents, keyof PrimitiveRenderers>]?: ComponentRenderer<TComponents[TName]>;
@@ -87,7 +79,7 @@ interface TailorKitServerShape {
87
79
  $internal: {
88
80
  schema: {
89
81
  components: Record<string, unknown>;
90
- views: Record<string, unknown>;
82
+ contexts: Record<string, unknown>;
91
83
  };
92
84
  };
93
85
  }
@@ -95,16 +87,24 @@ type ServerComponentMap<TTailor extends TailorKitServerShape> = TTailor["$intern
95
87
  type ServerComponents<TTailor extends TailorKitServerShape> = {
96
88
  [TName in keyof ServerComponentMap<TTailor>]: ServerComponentMap<TTailor>[TName] extends AnyComponentDefinition ? ServerComponentMap<TTailor>[TName] : never;
97
89
  };
98
- type ServerViewMap<TTailor extends TailorKitServerShape> = TTailor["$internal"]["schema"]["views"];
90
+ type ServerViewMap<TTailor extends TailorKitServerShape> = TTailor["$internal"]["schema"]["contexts"];
91
+ type ServerScopeNames<TTailor extends TailorKitServerShape> = TTailor extends {
92
+ handler: (request: Request, options: infer TOptions) => unknown;
93
+ } ? TOptions extends {
94
+ authenticate: infer TAuthenticate;
95
+ } ? TAuthenticate extends (...args: infer _TArgs) => infer TResult ? Extract<Awaited<TResult>, {
96
+ scopes: unknown;
97
+ }> extends {
98
+ scopes: infer TScopes;
99
+ } ? keyof TScopes & string : never : never : never : never;
99
100
  type ServerViews<TTailor extends TailorKitServerShape> = {
100
101
  [TName in keyof ServerViewMap<TTailor>]: ServerViewMap<TTailor>[TName] extends ViewDefinition ? ServerViewMap<TTailor>[TName] : never;
101
102
  };
102
103
  export declare function createTailorKitClient<TTailor extends TailorKitServerShape>(options: {
103
104
  baseUrl: string | URL;
104
- previewSessionId?: string;
105
105
  components?: CompleteComponentRenderers<ServerComponents<TTailor>>;
106
106
  theme?: TailorKitTheme;
107
107
  }): TailorKitInstance<ServerViews<TTailor>, TTailor extends {
108
108
  readonly $slots?: infer V extends SlotDefinitions;
109
- } ? V : SlotDefinitions>;
109
+ } ? V : SlotDefinitions, ServerScopeNames<TTailor>>;
110
110
  export type { ViewOptions } from "./hooks/use-view";
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@tailorkit/react",
3
- "version": "0.1.0-beta.13",
3
+ "version": "0.1.0-beta.16",
4
4
  "license": "Apache-2.0",
5
5
  "repository": {
6
6
  "type": "git",
7
- "url": "git+https://github.com/AlfieJones/tailorkit.git",
7
+ "url": "git+https://github.com/tailorkit-labs/TailorKit.git",
8
8
  "directory": "packages/react"
9
9
  },
10
10
  "files": [
@@ -23,20 +23,21 @@
23
23
  "dependencies": {
24
24
  "@floating-ui/react-dom": "^2.1.9",
25
25
  "@standard-schema/spec": "^1.1.0",
26
- "@tailorkit/core": "0.1.0-beta.13",
27
- "@tailorkit/sandbox": "0.1.0-beta.13"
26
+ "@tailorkit/app": "0.1.0-beta.16",
27
+ "@tailorkit/client-platform": "0.1.0-beta.16",
28
+ "@tailorkit/core": "0.1.0-beta.16",
29
+ "@tailorkit/sandbox": "0.1.0-beta.16"
28
30
  },
29
31
  "devDependencies": {
30
32
  "@tailorkit/config": "0.0.0",
31
33
  "@testing-library/react": "^16.3.3",
32
- "@types/node": "26.2.0",
34
+ "@types/node": "26.6.3",
33
35
  "@types/react": "^19.3.0",
34
36
  "happy-dom": "^20.14.5",
35
37
  "react": "^19.3.0",
36
38
  "react-dom": "^19.3.0",
37
39
  "tsup": "^8.5.1",
38
- "typescript": "^7.0.2",
39
- "vitest": "^5.0.0"
40
+ "typescript": "^7.0.2"
40
41
  },
41
42
  "peerDependencies": {
42
43
  "react": ">=19.2.6"
@@ -44,6 +45,6 @@
44
45
  "scripts": {
45
46
  "build": "tsup src/index.ts --format esm --target node20 --clean && tsc -p tsconfig.build.json",
46
47
  "check-types": "tsc --noEmit",
47
- "test": "vitest run"
48
+ "test": "vp test run"
48
49
  }
49
50
  }