@uniweb/core 0.6.1 → 0.6.2
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/package.json +1 -1
- package/src/datastore.js +102 -101
- package/src/entity-store.js +181 -193
- package/src/fetcher-dispatcher.js +333 -0
- package/src/index.js +19 -5
- package/src/observable-state.js +103 -0
- package/src/page.js +18 -0
- package/src/substitute-placeholders.js +63 -0
- package/src/uniweb.js +70 -79
- package/src/website.js +155 -46
package/src/uniweb.js
CHANGED
|
@@ -1,43 +1,92 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Uniweb Core Runtime
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Singleton that holds the Website, routing components, icon resolver, and
|
|
5
|
+
* foundation declaration references. Kit hooks, the icon resolver, and the
|
|
6
|
+
* prepare-props pipeline read from here via `globalThis.uniweb`.
|
|
7
|
+
*
|
|
8
|
+
* The foundation and extensions are passed at construction time — the Website
|
|
9
|
+
* constructs its FetcherDispatcher from them, and the Uniweb singleton caches
|
|
10
|
+
* the same references so the kit can still do `globalThis.uniweb.getComponent(name)`
|
|
11
|
+
* and similar lookups without touching the Website.
|
|
6
12
|
*/
|
|
7
13
|
|
|
8
14
|
import Website from './website.js'
|
|
9
15
|
import Analytics from './analytics.js'
|
|
10
16
|
|
|
11
17
|
export default class Uniweb {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
/**
|
|
19
|
+
* @param {Object} options
|
|
20
|
+
* @param {Object} options.content - Site content payload (pages, theme, config, layouts, ...).
|
|
21
|
+
* @param {Object|null} [options.foundation] - Loaded primary foundation module.
|
|
22
|
+
* @param {Array<Object>} [options.extensions] - Loaded extension modules.
|
|
23
|
+
* @param {{ resolve: Function }} [options.defaultFetcher] - Framework default fetcher
|
|
24
|
+
* used by the dispatcher's fallback when no foundation route matches.
|
|
25
|
+
* @param {{ resolve: Function, cacheKey?: Function }} [options.transport] -
|
|
26
|
+
* Runtime-level transport override. When set, every Layer-1 request is
|
|
27
|
+
* routed through this transport, bypassing foundation routes and the
|
|
28
|
+
* framework default. Used only by the editor's preview iframe; normal
|
|
29
|
+
* sites never pass this option.
|
|
30
|
+
*/
|
|
31
|
+
constructor({ content = {}, foundation = null, extensions = [], defaultFetcher = null, transport = null, dev = false } = {}) {
|
|
32
|
+
this.activeWebsite = new Website({ content, foundation, extensions, defaultFetcher, transport, dev })
|
|
33
|
+
|
|
34
|
+
this.foundation = foundation
|
|
35
|
+
this.foundationConfig = {}
|
|
36
|
+
this.meta = foundation?.default?.meta || {}
|
|
37
|
+
this.extensions = []
|
|
38
|
+
|
|
39
|
+
if (foundation?.default?.capabilities) {
|
|
40
|
+
this.foundationConfig = { ...foundation.default.capabilities }
|
|
41
|
+
}
|
|
42
|
+
if (foundation?.default?.layoutMeta) {
|
|
43
|
+
this.foundationConfig.layoutMeta = foundation.default.layoutMeta
|
|
44
|
+
}
|
|
45
|
+
if (foundation?.default?.handlers) {
|
|
46
|
+
this.foundationConfig.handlers = foundation.default.handlers
|
|
47
|
+
}
|
|
48
|
+
if (foundation?.default?.viewTransitions !== undefined) {
|
|
49
|
+
this.foundationConfig.viewTransitions = foundation.default.viewTransitions
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
for (const ext of extensions) {
|
|
53
|
+
this._wireExtension(ext)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
this.childBlockRenderer = null
|
|
57
|
+
this.routingComponents = {}
|
|
20
58
|
this.language = 'en'
|
|
21
59
|
|
|
22
60
|
// Icon resolver: (library, name) => Promise<string|null>
|
|
23
|
-
// Set by runtime
|
|
61
|
+
// Set by the runtime from site config.
|
|
24
62
|
this.iconResolver = null
|
|
25
63
|
|
|
26
64
|
// Pre-populated icon cache for SSR: Map<"family:name", svgString>
|
|
27
|
-
// Populated by prerender before rendering, read synchronously by Icon
|
|
65
|
+
// Populated by prerender before rendering, read synchronously by Icon.
|
|
28
66
|
this.iconCache = new Map()
|
|
29
67
|
|
|
30
|
-
|
|
31
|
-
this.analytics = new Analytics(configData.analytics || {})
|
|
68
|
+
this.analytics = new Analytics(content?.analytics || content?.config?.analytics || {})
|
|
32
69
|
|
|
33
70
|
Object.seal(this)
|
|
34
71
|
}
|
|
35
72
|
|
|
36
73
|
/**
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
74
|
+
* Wire an extension into the singleton. Kit's getComponent falls through
|
|
75
|
+
* from the primary foundation to each extension in declared order; meta
|
|
76
|
+
* lookups do the same.
|
|
77
|
+
*
|
|
78
|
+
* @private
|
|
79
|
+
*/
|
|
80
|
+
_wireExtension(foundation) {
|
|
81
|
+
const meta = foundation?.default?.meta || {}
|
|
82
|
+
this.extensions.push({ foundation, meta })
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Resolve an icon by library and name.
|
|
87
|
+
* @param {string} library
|
|
88
|
+
* @param {string} name
|
|
89
|
+
* @returns {Promise<string|null>}
|
|
41
90
|
*/
|
|
42
91
|
async resolveIcon(library, name) {
|
|
43
92
|
if (!this.iconResolver) {
|
|
@@ -48,114 +97,56 @@ export default class Uniweb {
|
|
|
48
97
|
}
|
|
49
98
|
|
|
50
99
|
/**
|
|
51
|
-
*
|
|
52
|
-
* @param {string} library - Icon family code
|
|
53
|
-
* @param {string} name - Icon name
|
|
54
|
-
* @returns {string|null} SVG string or null if not cached
|
|
100
|
+
* Synchronous icon lookup for SSR.
|
|
55
101
|
*/
|
|
56
102
|
getIconSync(library, name) {
|
|
57
103
|
return this.iconCache.get(`${library}:${name}`) || null
|
|
58
104
|
}
|
|
59
105
|
|
|
60
106
|
/**
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*/
|
|
64
|
-
setFoundation(foundation) {
|
|
65
|
-
this.foundation = foundation
|
|
66
|
-
|
|
67
|
-
// Store per-component metadata if present (lives under default export)
|
|
68
|
-
if (foundation.default?.meta) {
|
|
69
|
-
this.meta = foundation.default.meta
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Register an extension (secondary foundation)
|
|
75
|
-
* @param {Object} foundation - The loaded ESM extension module
|
|
76
|
-
*/
|
|
77
|
-
registerExtension(foundation) {
|
|
78
|
-
const meta = foundation.default?.meta || {}
|
|
79
|
-
this.extensions.push({ foundation, meta })
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Get runtime metadata for a component
|
|
84
|
-
* @param {string} componentName
|
|
85
|
-
* @returns {Object|null} Meta with defaults, context, initialState, background, data
|
|
107
|
+
* Get per-component runtime metadata — primary first, then extensions in
|
|
108
|
+
* declared order.
|
|
86
109
|
*/
|
|
87
110
|
getComponentMeta(componentName) {
|
|
88
111
|
const primary = this.meta[componentName]
|
|
89
112
|
if (primary) return primary
|
|
90
|
-
|
|
91
113
|
for (const ext of this.extensions) {
|
|
92
114
|
const meta = ext.meta[componentName]
|
|
93
115
|
if (meta) return meta
|
|
94
116
|
}
|
|
95
|
-
|
|
96
117
|
return null
|
|
97
118
|
}
|
|
98
119
|
|
|
99
|
-
/**
|
|
100
|
-
* Get default param values for a component
|
|
101
|
-
* @param {string} componentName
|
|
102
|
-
* @returns {Object} Default values (empty object if none)
|
|
103
|
-
*/
|
|
104
120
|
getComponentDefaults(componentName) {
|
|
105
121
|
return this.getComponentMeta(componentName)?.defaults || {}
|
|
106
122
|
}
|
|
107
123
|
|
|
108
|
-
/**
|
|
109
|
-
* Get a component from the foundation by name
|
|
110
|
-
* @param {string} name - Component name
|
|
111
|
-
* @returns {React.ComponentType|undefined}
|
|
112
|
-
*/
|
|
113
124
|
getComponent(name) {
|
|
114
125
|
if (!this.foundation) {
|
|
115
126
|
console.warn('[Runtime] No foundation loaded')
|
|
116
127
|
return undefined
|
|
117
128
|
}
|
|
118
|
-
|
|
119
|
-
// Primary foundation first (components are named exports)
|
|
120
129
|
const primary = this.foundation[name]
|
|
121
130
|
if (primary) return primary
|
|
122
|
-
|
|
123
|
-
// Fall through to extensions (declared order)
|
|
124
131
|
for (const ext of this.extensions) {
|
|
125
132
|
const component = ext.foundation[name]
|
|
126
133
|
if (component) return component
|
|
127
134
|
}
|
|
128
|
-
|
|
129
135
|
return undefined
|
|
130
136
|
}
|
|
131
137
|
|
|
132
|
-
/**
|
|
133
|
-
* List available components from the foundation
|
|
134
|
-
* @returns {string[]}
|
|
135
|
-
*/
|
|
136
138
|
listComponents() {
|
|
137
139
|
const names = new Set()
|
|
138
|
-
|
|
139
140
|
if (this.foundation) {
|
|
140
141
|
for (const name of Object.keys(this.foundation)) {
|
|
141
142
|
if (name !== 'default') names.add(name)
|
|
142
143
|
}
|
|
143
144
|
}
|
|
144
|
-
|
|
145
145
|
for (const ext of this.extensions) {
|
|
146
146
|
for (const name of Object.keys(ext.foundation)) {
|
|
147
147
|
if (name !== 'default') names.add(name)
|
|
148
148
|
}
|
|
149
149
|
}
|
|
150
|
-
|
|
151
150
|
return [...names]
|
|
152
151
|
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Set foundation configuration
|
|
156
|
-
* @param {Object} config
|
|
157
|
-
*/
|
|
158
|
-
setFoundationConfig(config) {
|
|
159
|
-
this.foundationConfig = config
|
|
160
|
-
}
|
|
161
152
|
}
|
package/src/website.js
CHANGED
|
@@ -7,18 +7,111 @@
|
|
|
7
7
|
import Page from './page.js'
|
|
8
8
|
import DataStore from './datastore.js'
|
|
9
9
|
import EntityStore from './entity-store.js'
|
|
10
|
+
import FetcherDispatcher from './fetcher-dispatcher.js'
|
|
11
|
+
import ObservableState from './observable-state.js'
|
|
10
12
|
import singularize from './singularize.js'
|
|
11
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Website — orchestration root for a single site instance.
|
|
16
|
+
*
|
|
17
|
+
* Accepts the site content payload plus the primary foundation and any
|
|
18
|
+
* extensions. Owns the DataStore (pure cache), EntityStore (cascade resolver),
|
|
19
|
+
* FetcherDispatcher (route walker + cache+in-flight wiring), and `state`
|
|
20
|
+
* (site-wide observable slots). Pages are constructed from the content payload;
|
|
21
|
+
* each page owns its own ObservableState.
|
|
22
|
+
*
|
|
23
|
+
* Content-only rebuilds keep the dispatcher and state in place. Foundation
|
|
24
|
+
* swaps reassemble the dispatcher but preserve the DataStore and state so the
|
|
25
|
+
* editor's live-edit path doesn't wipe either between keystrokes.
|
|
26
|
+
*
|
|
27
|
+
* new Website({ content, foundation?, extensions?, defaultFetcher?, dev? })
|
|
28
|
+
*/
|
|
12
29
|
export default class Website {
|
|
13
|
-
constructor(
|
|
14
|
-
|
|
30
|
+
constructor({
|
|
31
|
+
content = {},
|
|
32
|
+
foundation = null,
|
|
33
|
+
extensions = [],
|
|
34
|
+
defaultFetcher = null,
|
|
35
|
+
transport = null,
|
|
36
|
+
dev = false,
|
|
37
|
+
} = {}) {
|
|
38
|
+
|
|
39
|
+
// ─── Foundation / dispatcher state (not re-derived on rebuild) ───
|
|
40
|
+
this._foundation = foundation
|
|
41
|
+
this._extensions = extensions
|
|
42
|
+
this._defaultFetcher = defaultFetcher
|
|
43
|
+
// Runtime-level transport override (editor preview bridge). Stored so
|
|
44
|
+
// rebuild() reassembles the dispatcher with the same override in place.
|
|
45
|
+
this._transport = transport
|
|
46
|
+
this._dev = dev
|
|
47
|
+
|
|
48
|
+
this.dataStore = new DataStore()
|
|
49
|
+
this.fetcher = new FetcherDispatcher({
|
|
50
|
+
foundation,
|
|
51
|
+
extensions,
|
|
52
|
+
dataStore: this.dataStore,
|
|
53
|
+
defaultFetcher,
|
|
54
|
+
transport,
|
|
55
|
+
dev,
|
|
56
|
+
})
|
|
57
|
+
this.entityStore = new EntityStore({ website: this })
|
|
58
|
+
|
|
59
|
+
// Observable site-wide state — allocated on first access via the `state`
|
|
60
|
+
// getter, survives content rebuilds. Read-only prop (no `website.state = X`
|
|
61
|
+
// reassignment) so callers can only mutate slots via website.state.set(...).
|
|
62
|
+
this._state = null
|
|
63
|
+
|
|
64
|
+
// ─── Fields populated by _applyContent (declared up front so Object.seal works) ───
|
|
65
|
+
this.name = ''
|
|
66
|
+
this.description = ''
|
|
67
|
+
this.url = ''
|
|
68
|
+
this._layoutSets = {}
|
|
69
|
+
this.notFoundPage = null
|
|
70
|
+
this._dynamicPageData = new Map()
|
|
71
|
+
this._dynamicPageCache = new Map()
|
|
72
|
+
this.pages = []
|
|
73
|
+
this.activePage = null
|
|
74
|
+
this.pageRoutes = []
|
|
75
|
+
this.themeData = {}
|
|
76
|
+
this.config = {}
|
|
77
|
+
this.siteDefaultLocale = 'en'
|
|
78
|
+
this.defaultLocale = 'en'
|
|
79
|
+
this.activeLocale = 'en'
|
|
80
|
+
this.locales = []
|
|
81
|
+
this.activeLang = 'en'
|
|
82
|
+
this.langs = []
|
|
83
|
+
this._routeTranslations = {}
|
|
84
|
+
this.basePath = ''
|
|
85
|
+
this.versionedScopes = {}
|
|
86
|
+
this._pageIdMap = new Map()
|
|
87
|
+
|
|
88
|
+
this._applyContent(content)
|
|
89
|
+
|
|
90
|
+
Object.seal(this)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Populate content-derived fields from a site-content payload. Called once
|
|
95
|
+
* from the constructor and again from `rebuild({ content })`. All state that
|
|
96
|
+
* belongs on the Website but derives from the content payload lives here.
|
|
97
|
+
*
|
|
98
|
+
* @private
|
|
99
|
+
*/
|
|
100
|
+
_applyContent(content) {
|
|
101
|
+
const {
|
|
102
|
+
pages = [],
|
|
103
|
+
theme = {},
|
|
104
|
+
config = {},
|
|
105
|
+
layouts,
|
|
106
|
+
notFound,
|
|
107
|
+
versionedScopes = {},
|
|
108
|
+
} = content || {}
|
|
15
109
|
|
|
16
|
-
// Site metadata
|
|
17
110
|
this.name = config.name || ''
|
|
18
111
|
this.description = config.description || ''
|
|
19
112
|
this.url = config.url || ''
|
|
20
113
|
|
|
21
|
-
//
|
|
114
|
+
// Layout areas (header/footer/left/right pages scoped per named layout).
|
|
22
115
|
this._layoutSets = {}
|
|
23
116
|
if (layouts && typeof layouts === 'object') {
|
|
24
117
|
for (const [name, areaData] of Object.entries(layouts)) {
|
|
@@ -31,77 +124,90 @@ export default class Website {
|
|
|
31
124
|
}
|
|
32
125
|
}
|
|
33
126
|
|
|
34
|
-
//
|
|
35
|
-
// Convention: pages/404/ directory
|
|
127
|
+
// 404 / not-found page (content payload or /404 route).
|
|
36
128
|
const notFoundData = notFound || pages.find((p) => p.route === '/404') || null
|
|
37
129
|
this.notFoundPage = notFoundData ? new Page(notFoundData, 'notFound', this) : null
|
|
38
130
|
|
|
39
|
-
// Filter out 404 from regular pages array
|
|
40
131
|
const regularPages = pages.filter((page) => page.route !== '/404')
|
|
41
132
|
|
|
42
|
-
//
|
|
133
|
+
// Dynamic route templates — retained in original form so the Website can
|
|
134
|
+
// materialize concrete pages on demand (/blog/:slug → /blog/my-post).
|
|
43
135
|
this._dynamicPageData = new Map()
|
|
44
136
|
for (const pageData of regularPages) {
|
|
45
137
|
if (pageData.isDynamic || pageData.route?.includes(':')) {
|
|
46
138
|
this._dynamicPageData.set(pageData.route, pageData)
|
|
47
139
|
}
|
|
48
140
|
}
|
|
49
|
-
|
|
50
|
-
// Cache for dynamically created page instances
|
|
51
141
|
this._dynamicPageCache = new Map()
|
|
52
142
|
|
|
53
|
-
|
|
54
|
-
this.pages = regularPages.map(
|
|
55
|
-
(page, index) => new Page(page, index, this)
|
|
56
|
-
)
|
|
57
|
-
|
|
58
|
-
// Build parent-child relationships based on route structure
|
|
143
|
+
this.pages = regularPages.map((page, index) => new Page(page, index, this))
|
|
59
144
|
this.buildPageHierarchy()
|
|
60
145
|
|
|
61
|
-
// Find the homepage (root-level index page)
|
|
62
146
|
this.activePage =
|
|
63
147
|
this.pages.find((page) => page.isIndex && page.getNavRoute() === '/') ||
|
|
64
|
-
this.pages[0]
|
|
148
|
+
this.pages[0] ||
|
|
149
|
+
null
|
|
65
150
|
|
|
66
151
|
this.pageRoutes = this.pages.map((page) => page.route)
|
|
67
152
|
this.themeData = theme
|
|
68
153
|
this.config = config
|
|
69
154
|
|
|
70
|
-
// Locale configuration
|
|
71
|
-
// siteDefaultLocale: the site's true default language (for route translations)
|
|
72
|
-
// defaultLocale: effective default for URL prefix logic — domainLocale overrides
|
|
73
|
-
// to prevent unnecessary /{locale}/ prefixes on domain-locale pages
|
|
74
155
|
this.siteDefaultLocale = config.defaultLanguage || 'en'
|
|
75
156
|
this.defaultLocale = config.domainLocale || this.siteDefaultLocale
|
|
76
157
|
this.activeLocale = config.activeLocale || this.defaultLocale
|
|
77
158
|
|
|
78
|
-
// Build locales list from i18n config
|
|
79
159
|
this.locales = this.buildLocalesList(config)
|
|
80
|
-
|
|
81
|
-
// Legacy language support (for editor multilingual)
|
|
82
160
|
this.activeLang = this.activeLocale
|
|
83
|
-
this.langs = this.locales.map(l => ({
|
|
84
|
-
label: l.label || l.code,
|
|
85
|
-
value: l.code
|
|
86
|
-
}))
|
|
161
|
+
this.langs = this.locales.map((l) => ({ label: l.label || l.code, value: l.code }))
|
|
87
162
|
|
|
88
|
-
// Route translations: locale → { forward, reverse } maps
|
|
89
163
|
this._routeTranslations = this._buildRouteTranslations(config)
|
|
164
|
+
this.versionedScopes = versionedScopes
|
|
165
|
+
}
|
|
90
166
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
167
|
+
/**
|
|
168
|
+
* Rebuild in place. Content-only rebuilds preserve the dispatcher and all
|
|
169
|
+
* state (site and per-page). Passing `foundation` or `extensions` reassembles
|
|
170
|
+
* the dispatcher; the DataStore cache survives so warm entries aren't lost.
|
|
171
|
+
*
|
|
172
|
+
* The returned value is `this` for chaining.
|
|
173
|
+
*
|
|
174
|
+
* @param {Object} options
|
|
175
|
+
* @param {Object} [options.content] - New site-content payload.
|
|
176
|
+
* @param {Object} [options.foundation] - New primary foundation module.
|
|
177
|
+
* @param {Array} [options.extensions] - New extensions array.
|
|
178
|
+
* @returns {Website}
|
|
179
|
+
*/
|
|
180
|
+
rebuild({ content, foundation, extensions } = {}) {
|
|
181
|
+
const foundationChanged = foundation !== undefined
|
|
182
|
+
const extensionsChanged = extensions !== undefined
|
|
183
|
+
if (foundationChanged) this._foundation = foundation
|
|
184
|
+
if (extensionsChanged) this._extensions = extensions
|
|
185
|
+
|
|
186
|
+
if (foundationChanged || extensionsChanged) {
|
|
187
|
+
this.fetcher = new FetcherDispatcher({
|
|
188
|
+
foundation: this._foundation,
|
|
189
|
+
extensions: this._extensions,
|
|
190
|
+
dataStore: this.dataStore,
|
|
191
|
+
defaultFetcher: this._defaultFetcher,
|
|
192
|
+
transport: this._transport,
|
|
193
|
+
dev: this._dev,
|
|
194
|
+
})
|
|
195
|
+
}
|
|
99
196
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
197
|
+
if (content !== undefined) this._applyContent(content)
|
|
198
|
+
return this
|
|
199
|
+
}
|
|
103
200
|
|
|
104
|
-
|
|
201
|
+
/**
|
|
202
|
+
* Observable site-wide state. Foundations write cross-page values here
|
|
203
|
+
* (authenticated user, appearance preference, a filter set on /search
|
|
204
|
+
* that other pages honor); fetchers read it via ctx.website.state when
|
|
205
|
+
* handling site-level fetch configs. Lazily allocated on first read —
|
|
206
|
+
* sites that never touch state never build one.
|
|
207
|
+
*/
|
|
208
|
+
get state() {
|
|
209
|
+
if (!this._state) this._state = new ObservableState()
|
|
210
|
+
return this._state
|
|
105
211
|
}
|
|
106
212
|
|
|
107
213
|
/**
|
|
@@ -452,16 +558,19 @@ export default class Website {
|
|
|
452
558
|
const parentPage = this.pages.find(p => p.route === parentRoute || p.getNavRoute() === parentRoute)
|
|
453
559
|
|
|
454
560
|
if (parentPage && pluralSchema) {
|
|
455
|
-
// Find collection data from parent's fetch config via
|
|
561
|
+
// Find collection data from parent's fetch config via the dispatcher's
|
|
562
|
+
// peek (sync cache probe). Used to populate the page title / notFound
|
|
563
|
+
// flag on dynamic pages before the page instance is constructed.
|
|
456
564
|
const parentFetch = parentPage.fetch
|
|
457
565
|
let items = []
|
|
458
566
|
|
|
459
|
-
if (parentFetch) {
|
|
567
|
+
if (parentFetch && this.fetcher) {
|
|
460
568
|
const fetchConfig = Array.isArray(parentFetch)
|
|
461
569
|
? parentFetch.find(f => f.schema === pluralSchema)
|
|
462
570
|
: (parentFetch.schema === pluralSchema ? parentFetch : null)
|
|
463
571
|
if (fetchConfig) {
|
|
464
|
-
|
|
572
|
+
const cached = this.fetcher.peek(fetchConfig, { website: this })
|
|
573
|
+
items = Array.isArray(cached?.data) ? cached.data : []
|
|
465
574
|
}
|
|
466
575
|
}
|
|
467
576
|
|
|
@@ -664,7 +773,7 @@ export default class Website {
|
|
|
664
773
|
|
|
665
774
|
if (!page) {
|
|
666
775
|
// Page not found - return original href (or could warn in dev)
|
|
667
|
-
if (typeof console !== 'undefined' && process?.env?.NODE_ENV !== 'production') {
|
|
776
|
+
if (typeof console !== 'undefined' && typeof process !== 'undefined' && process?.env?.NODE_ENV !== 'production') {
|
|
668
777
|
console.warn(`[makeHref] Page not found: ${pageId}`)
|
|
669
778
|
}
|
|
670
779
|
return href
|