@weaverse/next 0.1.0-alpha.2 → 0.1.0-alpha.20

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.
Files changed (107) hide show
  1. package/README.md +586 -123
  2. package/dist/chunk-LH6AAEIK.mjs +326 -0
  3. package/dist/chunk-LH6AAEIK.mjs.map +1 -0
  4. package/dist/index.js +784 -60
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +588 -58
  7. package/dist/index.mjs.map +1 -1
  8. package/dist/server.js +537 -76
  9. package/dist/server.js.map +1 -1
  10. package/dist/server.mjs +363 -27
  11. package/dist/server.mjs.map +1 -1
  12. package/dist/types/client.d.ts +16 -0
  13. package/dist/types/client.d.ts.map +1 -0
  14. package/dist/types/hooks.d.ts +20 -0
  15. package/dist/types/hooks.d.ts.map +1 -0
  16. package/dist/types/index.d.ts +34 -0
  17. package/dist/types/index.d.ts.map +1 -0
  18. package/dist/types/item.d.ts +60 -0
  19. package/dist/types/item.d.ts.map +1 -0
  20. package/dist/types/loader.d.ts +25 -0
  21. package/dist/types/loader.d.ts.map +1 -0
  22. package/dist/types/main.d.ts +11 -0
  23. package/dist/types/main.d.ts.map +1 -0
  24. package/dist/types/provider.d.ts +82 -0
  25. package/dist/types/provider.d.ts.map +1 -0
  26. package/dist/types/registry.d.ts +15 -0
  27. package/dist/types/registry.d.ts.map +1 -0
  28. package/dist/types/renderer.d.ts +21 -0
  29. package/dist/types/renderer.d.ts.map +1 -0
  30. package/dist/types/request-info.d.ts +19 -0
  31. package/dist/types/request-info.d.ts.map +1 -0
  32. package/dist/types/revalidate-item.d.ts +82 -0
  33. package/dist/types/revalidate-item.d.ts.map +1 -0
  34. package/dist/types/root-provider.d.ts +62 -0
  35. package/dist/types/root-provider.d.ts.map +1 -0
  36. package/dist/types/runtime.d.ts +113 -0
  37. package/dist/types/runtime.d.ts.map +1 -0
  38. package/dist/types/seo.d.ts +55 -0
  39. package/dist/types/seo.d.ts.map +1 -0
  40. package/dist/types/server/configs.d.ts +27 -0
  41. package/dist/types/server/configs.d.ts.map +1 -0
  42. package/dist/types/server/normalize-page-url.d.ts +19 -0
  43. package/dist/types/server/normalize-page-url.d.ts.map +1 -0
  44. package/dist/types/server/revalidate-handler.d.ts +55 -0
  45. package/dist/types/server/revalidate-handler.d.ts.map +1 -0
  46. package/dist/types/server/server-client.d.ts +21 -0
  47. package/dist/types/server/server-client.d.ts.map +1 -0
  48. package/dist/types/server.d.ts +19 -0
  49. package/dist/types/server.d.ts.map +1 -0
  50. package/dist/types/studio-bridge.d.ts +16 -0
  51. package/dist/types/studio-bridge.d.ts.map +1 -0
  52. package/dist/types/studio-connect.d.ts +27 -0
  53. package/dist/types/studio-connect.d.ts.map +1 -0
  54. package/dist/types/studio-router.d.ts +50 -0
  55. package/dist/types/studio-router.d.ts.map +1 -0
  56. package/dist/types/studio-script-src.d.ts +12 -0
  57. package/dist/types/studio-script-src.d.ts.map +1 -0
  58. package/dist/types/theme-settings-store.d.ts +18 -0
  59. package/dist/types/theme-settings-store.d.ts.map +1 -0
  60. package/dist/types/translation-context.d.ts +156 -0
  61. package/dist/types/translation-context.d.ts.map +1 -0
  62. package/dist/types/translation-store.d.ts +44 -0
  63. package/dist/types/translation-store.d.ts.map +1 -0
  64. package/dist/types/types.d.ts +665 -0
  65. package/dist/types/types.d.ts.map +1 -0
  66. package/dist/types/use-weaverse-next-studio.d.ts +34 -0
  67. package/dist/types/use-weaverse-next-studio.d.ts.map +1 -0
  68. package/dist/types/utils.d.ts +12 -0
  69. package/dist/types/utils.d.ts.map +1 -0
  70. package/package.json +12 -8
  71. package/src/client.ts +102 -0
  72. package/src/hooks.ts +50 -0
  73. package/src/index.ts +153 -0
  74. package/src/item.ts +167 -0
  75. package/src/loader.ts +135 -0
  76. package/src/main.tsx +45 -0
  77. package/src/provider.tsx +225 -0
  78. package/src/registry.ts +63 -0
  79. package/src/renderer.tsx +100 -0
  80. package/src/request-info.ts +124 -0
  81. package/src/revalidate-item.ts +211 -0
  82. package/src/root-provider.tsx +182 -0
  83. package/src/runtime.ts +555 -0
  84. package/src/seo.ts +157 -0
  85. package/src/server/configs.ts +178 -0
  86. package/src/server/normalize-page-url.ts +73 -0
  87. package/src/server/revalidate-handler.ts +357 -0
  88. package/src/server/server-client.ts +728 -0
  89. package/src/server.ts +67 -0
  90. package/src/studio-bridge.tsx +82 -0
  91. package/src/studio-connect.tsx +88 -0
  92. package/src/studio-router.ts +85 -0
  93. package/src/studio-script-src.ts +128 -0
  94. package/src/theme-settings-store.ts +47 -0
  95. package/src/translation-context.tsx +299 -0
  96. package/src/translation-store.ts +69 -0
  97. package/src/types.ts +753 -0
  98. package/src/use-weaverse-next-studio.tsx +106 -0
  99. package/src/utils.ts +37 -0
  100. package/dist/chunk-SS6QUKYY.mjs +0 -149
  101. package/dist/chunk-SS6QUKYY.mjs.map +0 -1
  102. package/dist/index.d.mts +0 -250
  103. package/dist/index.d.ts +0 -250
  104. package/dist/server.d.mts +0 -60
  105. package/dist/server.d.ts +0 -60
  106. package/dist/types-DvEeLX6L.d.mts +0 -321
  107. package/dist/types-DvEeLX6L.d.ts +0 -321
package/src/server.ts ADDED
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Server-only Weaverse APIs for Next.js App Router server components and route
3
+ * handlers.
4
+ *
5
+ * When migrating from Hydrogen's `context.weaverse`, create a request-scoped
6
+ * client with {@link createWeaverseNextServerClient}, then call `loadPage()` or
7
+ * `loadThemeSettings()`. Keep rendering providers and components imported from
8
+ * the root `@weaverse/next` entrypoint.
9
+ *
10
+ * @packageDocumentation
11
+ */
12
+
13
+ export type { WeaverseNextRevalidateRouteContext } from './revalidate-item'
14
+ export {
15
+ formatWeaverseNextSeoMetadata,
16
+ getWeaverseNextSeoMetadata,
17
+ type NextOpenGraph,
18
+ type NextTwitter,
19
+ type WeaverseNextSeoInput,
20
+ type WeaverseNextSeoMetadata,
21
+ type WeaverseNextSeoPageContainer,
22
+ } from './seo'
23
+ export {
24
+ getWeaverseNextConfigs,
25
+ type ResolveConfigsOptions,
26
+ } from './server/configs'
27
+ export {
28
+ normalizeNextPageUrl,
29
+ resolveRequestUrl,
30
+ } from './server/normalize-page-url'
31
+ export {
32
+ createWeaverseNextRevalidateHandler,
33
+ type WeaverseNextRevalidateHandler,
34
+ type WeaverseNextRevalidateHandlerConfig,
35
+ type WeaverseNextRevalidateRequestBody,
36
+ } from './server/revalidate-handler'
37
+ export { createWeaverseNextServerClient } from './server/server-client'
38
+ export type {
39
+ WeaverseNextBaseConfigs,
40
+ WeaverseNextCacheConfig,
41
+ WeaverseNextClient,
42
+ WeaverseNextCommerceContext,
43
+ WeaverseNextComponent,
44
+ WeaverseNextComponentData,
45
+ WeaverseNextComponentLoaderArgs,
46
+ WeaverseNextComponentProps,
47
+ WeaverseNextConfigs,
48
+ WeaverseNextCustomPageEntry,
49
+ WeaverseNextFetchCustomPagesOptions,
50
+ WeaverseNextFetchOptions,
51
+ WeaverseNextI18n,
52
+ WeaverseNextLoaderData,
53
+ WeaverseNextLoadPageInput,
54
+ WeaverseNextPageAssignment,
55
+ WeaverseNextPageData,
56
+ WeaverseNextProjectId,
57
+ WeaverseNextRequestContext,
58
+ WeaverseNextServerClient,
59
+ WeaverseNextServerClientConfig,
60
+ WeaverseNextServerLoadPageInput,
61
+ WeaverseNextStorefront,
62
+ WeaverseNextThemeSchema,
63
+ WeaverseNextThemeSchemaGroup,
64
+ WeaverseNextThemeSchemaInput,
65
+ WeaverseNextThemeSettingsOptions,
66
+ WeaverseNextThemeSettingsResponse,
67
+ } from './types'
@@ -0,0 +1,82 @@
1
+ 'use client'
2
+
3
+ import { useEffect, useRef } from 'react'
4
+ import {
5
+ bindWeaverseNextStudioRuntime,
6
+ type WeaverseNextRuntime,
7
+ } from './runtime'
8
+ import type { WeaverseNextRuntimeInternal } from './types'
9
+
10
+ const STUDIO_BIND_RETRY_INTERVAL_MS = 50
11
+ const STUDIO_BIND_MAX_ATTEMPTS = 100
12
+
13
+ /** Runtime and host callbacks attached to the Builder Studio bridge. */
14
+ export interface WeaverseNextStudioBridgeProps {
15
+ /** Preview navigation callback. */
16
+ navigate?: WeaverseNextRuntimeInternal['navigate']
17
+ /** Whole-route refresh callback used as a legacy fallback. */
18
+ revalidate?: WeaverseNextRuntimeInternal['revalidate']
19
+ /** Preferred per-item loader revalidation callback. */
20
+ revalidateItem?: WeaverseNextRuntimeInternal['revalidateItem']
21
+ /** Browser runtime to initialize or refresh in Studio. */
22
+ runtime: WeaverseNextRuntime
23
+ }
24
+
25
+ /** Page-level Studio runtime binder. */
26
+ export function WeaverseNextStudioBridge(props: WeaverseNextStudioBridgeProps) {
27
+ let { runtime, navigate, revalidate, revalidateItem } = props
28
+ let lastRuntimeRef = useRef(runtime)
29
+ let lastRuntimeDataRef = useRef(runtime.data)
30
+ let lastRequestInfoRef = useRef(runtime.requestInfo)
31
+
32
+ useEffect(() => {
33
+ if (navigate) {
34
+ runtime.internal.navigate = navigate
35
+ }
36
+ if (revalidate) {
37
+ runtime.internal.revalidate = revalidate
38
+ }
39
+ if (revalidateItem) {
40
+ runtime.internal.revalidateItem = revalidateItem
41
+ }
42
+
43
+ if (bindWeaverseNextStudioRuntime(runtime) || !runtime.isDesignMode) {
44
+ return
45
+ }
46
+
47
+ let attempts = 0
48
+ let interval = window.setInterval(() => {
49
+ attempts += 1
50
+ if (
51
+ bindWeaverseNextStudioRuntime(runtime) ||
52
+ attempts >= STUDIO_BIND_MAX_ATTEMPTS
53
+ ) {
54
+ window.clearInterval(interval)
55
+ }
56
+ }, STUDIO_BIND_RETRY_INTERVAL_MS)
57
+
58
+ return () => window.clearInterval(interval)
59
+ }, [runtime, navigate, revalidate, revalidateItem])
60
+
61
+ useEffect(() => {
62
+ if (lastRuntimeRef.current !== runtime) {
63
+ lastRuntimeRef.current = runtime
64
+ lastRuntimeDataRef.current = runtime.data
65
+ lastRequestInfoRef.current = runtime.requestInfo
66
+ return
67
+ }
68
+
69
+ if (
70
+ lastRuntimeDataRef.current === runtime.data &&
71
+ lastRequestInfoRef.current === runtime.requestInfo
72
+ ) {
73
+ return
74
+ }
75
+
76
+ lastRuntimeDataRef.current = runtime.data
77
+ lastRequestInfoRef.current = runtime.requestInfo
78
+ bindWeaverseNextStudioRuntime(runtime)
79
+ }, [runtime, runtime.data, runtime.requestInfo])
80
+
81
+ return null
82
+ }
@@ -0,0 +1,88 @@
1
+ 'use client'
2
+
3
+ import { useEffect } from 'react'
4
+ import { resolveWeaverseNextStudioScriptSrc } from './studio-script-src'
5
+ import type { WeaverseNextRequestContext } from './types'
6
+
7
+ const loadedScriptSrcs = new Set<string>()
8
+
9
+ function loadScript(src: string) {
10
+ if (typeof document === 'undefined') {
11
+ return
12
+ }
13
+
14
+ if (loadedScriptSrcs.has(src)) {
15
+ return
16
+ }
17
+
18
+ let existing = document.querySelector(`script[src="${src}"]`)
19
+ if (existing) {
20
+ loadedScriptSrcs.add(src)
21
+ return
22
+ }
23
+
24
+ let script = document.createElement('script')
25
+ script.async = true
26
+ script.src = src
27
+ document.head.appendChild(script)
28
+ loadedScriptSrcs.add(src)
29
+ }
30
+
31
+ /** Trust context used while loading the Studio bridge script. */
32
+ export interface LoadWeaverseNextStudioScriptOptions {
33
+ /** Storefront hostname; required to permit loopback Studio origins. */
34
+ storefrontHostname?: string
35
+ }
36
+
37
+ /**
38
+ * Resolve and append the Next Studio bridge script for the given request
39
+ * context, deduping by src. SSR-safe (no-op without `document`) so it can run
40
+ * unconditionally from a root layout/error boundary before any page tree or
41
+ * runtime exists — e.g. App Router `not-found.tsx` / `error.tsx` routes.
42
+ */
43
+ export function loadWeaverseNextStudioScript(
44
+ context?: WeaverseNextRequestContext,
45
+ options: LoadWeaverseNextStudioScriptOptions = {}
46
+ ) {
47
+ let resolvedContext = context
48
+ if (!resolvedContext && typeof window !== 'undefined') {
49
+ resolvedContext = {
50
+ searchParams: new URLSearchParams(window.location.search),
51
+ url: window.location.href,
52
+ }
53
+ }
54
+
55
+ let src = resolveWeaverseNextStudioScriptSrc(resolvedContext, {
56
+ storefrontHostname:
57
+ options.storefrontHostname ??
58
+ (typeof window === 'undefined' ? undefined : window.location.hostname),
59
+ })
60
+ if (src) {
61
+ loadScript(src)
62
+ }
63
+ }
64
+
65
+ /** Props for the root-level Studio script connector. */
66
+ export interface WeaverseNextStudioConnectProps {
67
+ /** Explicit request context used to resolve Studio mode and host. */
68
+ context?: WeaverseNextRequestContext
69
+ /** Storefront hostname; required to permit loopback Studio origins. */
70
+ storefrontHostname?: string
71
+ }
72
+
73
+ /**
74
+ * Root-level Studio script connector for Next client boundaries. Mount once
75
+ * near the app root (e.g. root layout) so it also covers routes that never
76
+ * render `WeaverseNextRenderer`, such as `not-found.tsx` / `error.tsx`.
77
+ */
78
+ export function WeaverseNextStudioConnect(
79
+ props: WeaverseNextStudioConnectProps
80
+ ) {
81
+ useEffect(() => {
82
+ loadWeaverseNextStudioScript(props.context, {
83
+ storefrontHostname: props.storefrontHostname,
84
+ })
85
+ }, [props.context, props.storefrontHostname])
86
+
87
+ return null
88
+ }
@@ -0,0 +1,85 @@
1
+ import type { WeaverseNextRuntimeInternal } from './types'
2
+
3
+ /**
4
+ * Minimal structural shape of the Next App Router returned by `useRouter()`
5
+ * (`next/navigation`) that the Studio bridge needs. Declared locally — rather
6
+ * than importing `AppRouterInstance` from `next` — so the adapter stays a pure
7
+ * function that unit tests can drive with a plain mock and never pulls
8
+ * `next/navigation` into the test graph.
9
+ */
10
+ export interface WeaverseNextRouterLike {
11
+ /** Add a route to browser history. */
12
+ push: (
13
+ href: string,
14
+ options?: {
15
+ /** Whether to scroll to the top after navigation. */
16
+ scroll?: boolean
17
+ }
18
+ ) => void
19
+ /** Refresh the current App Router route and server data. */
20
+ refresh: () => void
21
+ /** Replace the current browser-history entry. */
22
+ replace: (
23
+ href: string,
24
+ options?: {
25
+ /** Whether to scroll to the top after navigation. */
26
+ scroll?: boolean
27
+ }
28
+ ) => void
29
+ }
30
+
31
+ /** Resolved Studio navigation/revalidation callbacks for a Next runtime. */
32
+ export interface WeaverseNextStudioInternals {
33
+ /** Navigate Studio's preview with React Router-compatible options. */
34
+ navigate: NonNullable<WeaverseNextRuntimeInternal['navigate']>
35
+ /** Refresh the current App Router route. */
36
+ revalidate: NonNullable<WeaverseNextRuntimeInternal['revalidate']>
37
+ }
38
+
39
+ /** Controls how Studio navigation maps to the Next App Router. */
40
+ export interface CreateWeaverseNextStudioInternalsOptions {
41
+ /** Use `router.replace` instead of `router.push` for navigation. */
42
+ replace?: boolean
43
+ }
44
+
45
+ function shouldResetScroll(
46
+ options?: { preventScrollReset?: boolean } | Record<string, unknown>
47
+ ): boolean {
48
+ if (!options || typeof options !== 'object') {
49
+ return true
50
+ }
51
+ return !(options as { preventScrollReset?: boolean }).preventScrollReset
52
+ }
53
+
54
+ /**
55
+ * Build Studio `navigate` / `revalidate` callbacks from a Next App Router.
56
+ *
57
+ * Maps Hydrogen's React Router semantics onto Next App Router:
58
+ * - `navigate(to, { preventScrollReset })` → `router.push(to, { scroll })`,
59
+ * where `scroll` is the inverse of `preventScrollReset`. Studio passes
60
+ * `preventScrollReset` for in-place edits so the preview viewport is kept,
61
+ * matching React Router's `navigate({ preventScrollReset })`.
62
+ * - `revalidate()` → `router.refresh()`, the App Router's loader-backed refresh
63
+ * and the closest equivalent to React Router's `useRevalidator`.
64
+ *
65
+ * Pure (no React, no `next/navigation`) so it is unit-testable with a mock
66
+ * router; the {@link WeaverseNextRouterLike} contract is the only coupling.
67
+ */
68
+ export function createWeaverseNextStudioInternals(
69
+ router: WeaverseNextRouterLike,
70
+ options: CreateWeaverseNextStudioInternalsOptions = {}
71
+ ): WeaverseNextStudioInternals {
72
+ return {
73
+ navigate: (to, navigateOptions) => {
74
+ let scroll = shouldResetScroll(navigateOptions)
75
+ if (options.replace) {
76
+ router.replace(to, { scroll })
77
+ } else {
78
+ router.push(to, { scroll })
79
+ }
80
+ },
81
+ revalidate: () => {
82
+ router.refresh()
83
+ },
84
+ }
85
+ }
@@ -0,0 +1,128 @@
1
+ import type { WeaverseNextRequestContext } from './types'
2
+
3
+ /**
4
+ * Weaverse-controlled apex domains the Studio bridge may be served from
5
+ * (matched on the host itself and any subdomain), mirroring Hydrogen's
6
+ * `TRUSTED_STUDIO_DOMAINS`.
7
+ */
8
+ const TRUSTED_STUDIO_DOMAINS = ['weaverse.io', 'weaverse.dev']
9
+
10
+ function isLoopbackHostname(hostname: string): boolean {
11
+ return (
12
+ hostname === 'localhost' ||
13
+ hostname === '127.0.0.1' ||
14
+ hostname.endsWith('.localhost')
15
+ )
16
+ }
17
+
18
+ function normalizeOrigin(value: string | null): string | null {
19
+ if (!value) {
20
+ return null
21
+ }
22
+
23
+ try {
24
+ return new URL(value).origin
25
+ } catch {
26
+ return null
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Whether `origin` is one we trust to serve the Studio bridge script. Matches
32
+ * Hydrogen's `isTrustedStudioHost`: trusts `weaverse.io` / `weaverse.dev` and
33
+ * their subdomains over https, plus loopback (`localhost` / `*.localhost` /
34
+ * `127.0.0.1`) — but only when the storefront itself is loopback (genuine
35
+ * local-builder dev). Loopback is the only place http is allowed; every other
36
+ * trusted host must be https. Arbitrary origins are rejected.
37
+ */
38
+ function isTrustedStudioOrigin(
39
+ origin: string,
40
+ storefrontHostname: string
41
+ ): boolean {
42
+ let parsed: URL
43
+ try {
44
+ parsed = new URL(origin)
45
+ } catch {
46
+ return false
47
+ }
48
+
49
+ let { hostname, protocol } = parsed
50
+ if (isLoopbackHostname(storefrontHostname) && isLoopbackHostname(hostname)) {
51
+ return protocol === 'http:' || protocol === 'https:'
52
+ }
53
+
54
+ if (protocol !== 'https:') {
55
+ return false
56
+ }
57
+
58
+ return TRUSTED_STUDIO_DOMAINS.some(
59
+ (domain) => hostname === domain || hostname.endsWith(`.${domain}`)
60
+ )
61
+ }
62
+
63
+ function toSearchParams(context?: WeaverseNextRequestContext): URLSearchParams {
64
+ if (context?.searchParams) {
65
+ return new URLSearchParams(context.searchParams)
66
+ }
67
+
68
+ if (context?.url) {
69
+ let url =
70
+ typeof context.url === 'string'
71
+ ? new URL(context.url, 'http://localhost')
72
+ : context.url
73
+ return new URLSearchParams(url.searchParams)
74
+ }
75
+
76
+ return new URLSearchParams()
77
+ }
78
+
79
+ function last(searchParams: URLSearchParams, key: string): string | null {
80
+ let values = searchParams.getAll(key)
81
+ return values.length ? (values.at(-1) ?? null) : null
82
+ }
83
+
84
+ /** Trust context used when resolving a Studio bridge script URL. */
85
+ export interface ResolveWeaverseNextStudioScriptSrcOptions {
86
+ /** Storefront hostname; required to permit loopback Studio origins. */
87
+ storefrontHostname?: string
88
+ }
89
+
90
+ /**
91
+ * Resolve the Studio script URL from request/search context. `@weaverse/next`
92
+ * uses the dedicated Next bridge path served by Builder.
93
+ */
94
+ export function resolveWeaverseNextStudioScriptSrc(
95
+ context?: WeaverseNextRequestContext,
96
+ options: ResolveWeaverseNextStudioScriptSrcOptions = {}
97
+ ): string | null {
98
+ let searchParams = toSearchParams(context)
99
+
100
+ let isDesignMode =
101
+ context?.isDesignMode ?? last(searchParams, 'isDesignMode') === 'true'
102
+ let isPreviewMode =
103
+ context?.isPreviewMode ?? last(searchParams, 'isPreviewMode') === 'true'
104
+ let isRevisionPreview =
105
+ context?.isRevisionPreview ?? Boolean(last(searchParams, '__revisionId'))
106
+
107
+ if (!(isDesignMode || isPreviewMode || isRevisionPreview)) {
108
+ return null
109
+ }
110
+
111
+ let origin = normalizeOrigin(last(searchParams, 'weaverseHost'))
112
+ if (!origin) {
113
+ return null
114
+ }
115
+
116
+ let storefrontHostname = options.storefrontHostname ?? ''
117
+ if (!isTrustedStudioOrigin(origin, storefrontHostname)) {
118
+ return null
119
+ }
120
+
121
+ // Builder section-preview URLs carry both isDesignMode and isPreviewMode;
122
+ // the preview iframe waits for `preview-size`, which only preview.js emits,
123
+ // so preview/revision mode takes precedence (matching Hydrogen).
124
+ let bundle = isPreviewMode || isRevisionPreview ? 'preview.js' : 'index.js'
125
+ let version = last(searchParams, 'weaverseVersion')
126
+ let url = `${origin}/static/studio/next/${bundle}`
127
+ return version ? `${url}?v=${encodeURIComponent(version)}` : url
128
+ }
@@ -0,0 +1,47 @@
1
+ import type { SchemaType } from '@weaverse/schema'
2
+ import type {
3
+ WeaverseNextThemeSchema,
4
+ WeaverseNextThemeSettingsStore,
5
+ } from './types'
6
+
7
+ /** Initial values for a root-scoped theme-settings store. */
8
+ export interface CreateWeaverseNextThemeSettingsStoreOptions {
9
+ /** Public environment values exposed alongside the store. */
10
+ publicEnv?: Record<string, string | undefined>
11
+ /** Theme schema associated with the settings. */
12
+ schema?: SchemaType | WeaverseNextThemeSchema
13
+ /** Initial setting values. */
14
+ settings?: Record<string, unknown>
15
+ }
16
+
17
+ /**
18
+ * Small external store shape compatible with the Studio bridge and React's
19
+ * `useSyncExternalStore` contract. The bridge calls `updateThemeSettings` during
20
+ * live theme edits, so keep that method name stable.
21
+ */
22
+ export function createWeaverseNextThemeSettingsStore(
23
+ options: CreateWeaverseNextThemeSettingsStoreOptions = {}
24
+ ): WeaverseNextThemeSettingsStore {
25
+ let listeners = new Set<() => void>()
26
+ let store: WeaverseNextThemeSettingsStore = {
27
+ publicEnv: options.publicEnv,
28
+ schema: options.schema,
29
+ settings: { ...(options.settings ?? {}) },
30
+ getServerSnapshot: () => store.settings,
31
+ getSnapshot: () => store.settings,
32
+ subscribe: (listener: () => void) => {
33
+ listeners.add(listener)
34
+ return () => {
35
+ listeners.delete(listener)
36
+ }
37
+ },
38
+ updateThemeSettings: (next: Record<string, unknown>) => {
39
+ store.settings = { ...store.settings, ...next }
40
+ for (let listener of listeners) {
41
+ listener()
42
+ }
43
+ },
44
+ }
45
+
46
+ return store
47
+ }