@uniweb/core 0.24.3 → 0.24.4
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/services.js +62 -14
- package/src/website.js +20 -14
package/package.json
CHANGED
package/src/services.js
CHANGED
|
@@ -12,10 +12,14 @@ import { applyBasePath } from './base-path.js'
|
|
|
12
12
|
* address comes from configuration, and there are exactly two places it can
|
|
13
13
|
* come from:
|
|
14
14
|
*
|
|
15
|
-
* 1. **The
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
15
|
+
* 1. **The host**, served — `config.services.<name>` in the payload. What the
|
|
16
|
+
* deployment offers, which the site never had to know about. When a host
|
|
17
|
+
* offers a service, that is the answer: it is the authority on what a
|
|
18
|
+
* hosted site is given.
|
|
19
|
+
* 2. **The site**, authored — `search:`, `submit:`, `assistant:`, `tracking:`
|
|
20
|
+
* in site.yml. The operator's own declaration — used for anything the host
|
|
21
|
+
* does not provide, and on a static site, where no host speaks, the whole
|
|
22
|
+
* answer.
|
|
19
23
|
*
|
|
20
24
|
* Absent from both means the site has no such service, and the caller acts on
|
|
21
25
|
* that rather than guessing an address. That is the same rule for every service,
|
|
@@ -117,7 +121,37 @@ const ABSOLUTE_URL_RE = /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i
|
|
|
117
121
|
* @param {*} declaration
|
|
118
122
|
* @returns {string} the endpoint, or '' when there is none
|
|
119
123
|
*/
|
|
124
|
+
/**
|
|
125
|
+
* Has this declaration switched the service OFF?
|
|
126
|
+
*
|
|
127
|
+
* ⭐ `false`, or an object with `enabled: false` — on either tier, for every
|
|
128
|
+
* name. It is the spelling `search` has always honoured
|
|
129
|
+
* (`Website.isSearchEnabled`), now read for every service.
|
|
130
|
+
*
|
|
131
|
+
* ⛔ **Until 2026-09-10 this module read it for NO service** — so the same
|
|
132
|
+
* spelling behaved two ways: `search: false` turned search off, while
|
|
133
|
+
* `submit: false` or `submit: { endpoint, enabled: false }` drew the form
|
|
134
|
+
* anyway. On a static site the authored key is the only place a service can be
|
|
135
|
+
* declared, so there the switch worked for search alone.
|
|
136
|
+
*
|
|
137
|
+
* ⚖️ **A site's refusal does not override a host's offer** — step 3 of
|
|
138
|
+
* `resolveService` says why.
|
|
139
|
+
*
|
|
140
|
+
* Module-private on purpose: it is a rule of the two readers below, not a
|
|
141
|
+
* question a caller should ask. `resolveService` is the question.
|
|
142
|
+
*/
|
|
143
|
+
function isServiceRefused(declaration) {
|
|
144
|
+
return (
|
|
145
|
+
declaration === false ||
|
|
146
|
+
(declaration !== null && typeof declaration === 'object' && declaration.enabled === false)
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
|
|
120
150
|
export function readEndpoint(declaration) {
|
|
151
|
+
// A refused service offers no address, whatever else its block carries — so
|
|
152
|
+
// a host row with `enabled: false` reads as a decline, and the records lane
|
|
153
|
+
// (`records-service.js`) withholds its address the same way.
|
|
154
|
+
if (isServiceRefused(declaration)) return ''
|
|
121
155
|
if (typeof declaration === 'string') return declaration.trim()
|
|
122
156
|
if (typeof declaration?.endpoint === 'string') return declaration.endpoint.trim()
|
|
123
157
|
return ''
|
|
@@ -168,28 +202,42 @@ export function resolveServiceUrl(endpoint, basePath = '') {
|
|
|
168
202
|
* @param {string} name - service name, e.g. 'submit' · 'search' · 'tracking'
|
|
169
203
|
* @returns {{ url: string|null, source: 'site'|'host'|null }}
|
|
170
204
|
* `url` is the whole answer for acting. `source` says which declaration
|
|
171
|
-
* answered — a diagnostic, and the thing to check when a
|
|
172
|
-
* not to be taking effect. `'host'` with a null `url` means the host answered
|
|
205
|
+
* answered — a diagnostic, and the thing to check when a value you set
|
|
206
|
+
* appears not to be taking effect. `'host'` with a null `url` means the host answered
|
|
173
207
|
* and offered no address; `null` means nothing declared the service at all.
|
|
174
208
|
*/
|
|
175
209
|
export function resolveService(website, name) {
|
|
176
210
|
const config = website?.config
|
|
177
211
|
const basePath = website?.basePath
|
|
178
212
|
|
|
179
|
-
// 1 — the
|
|
180
|
-
//
|
|
181
|
-
|
|
182
|
-
if (authored) {
|
|
183
|
-
return { url: resolveServiceUrl(authored, basePath), source: 'site' }
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// 2 — what the host says it offers.
|
|
213
|
+
// 1 — what the host OFFERS. A host that returns an address is the authority
|
|
214
|
+
// on what a hosted site is given: nothing the site declares overrides it —
|
|
215
|
+
// not its own address (step 2), not its off switch (step 3).
|
|
187
216
|
const hostDeclaration = config?.services?.[name]
|
|
188
217
|
const hostEndpoint = readEndpoint(hostDeclaration)
|
|
189
218
|
if (hostEndpoint) {
|
|
190
219
|
return { url: resolveServiceUrl(hostEndpoint, basePath), source: 'host' }
|
|
191
220
|
}
|
|
192
221
|
|
|
222
|
+
// 2 — the site's own ADDRESS, for a service the host does not provide: an
|
|
223
|
+
// operator bringing their own provider — a form service, self-hosted search
|
|
224
|
+
// on a host that does not sell it — keeps working. On a static site no host
|
|
225
|
+
// speaks, so this is the whole answer. (Until 2026-09-10 it came first and
|
|
226
|
+
// outranked a host's offer too.)
|
|
227
|
+
const authored = readEndpoint(config?.[name])
|
|
228
|
+
if (authored) {
|
|
229
|
+
return { url: resolveServiceUrl(authored, basePath), source: 'site' }
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// 3 — ⛔ A SITE THAT SWITCHED THE SERVICE OFF — but only where no host offers
|
|
233
|
+
// it. A host that returns an address is the authority on what a hosted site
|
|
234
|
+
// is given: the site asked for the service where the host manages it, and a
|
|
235
|
+
// hosted service is turned off there — not with a second switch here that
|
|
236
|
+
// would hide a service the host is still providing. On a static site no host
|
|
237
|
+
// speaks, so this is the only switch there is, and it decides. (It briefly
|
|
238
|
+
// ran first, on 2026-09-10, and beat a host's offer — the wrong way round.)
|
|
239
|
+
if (isServiceRefused(config?.[name])) return { url: null, source: 'site' }
|
|
240
|
+
|
|
193
241
|
// A host may declare the name while offering no address — a decline. It is
|
|
194
242
|
// still the host answering, which is all a caller can use: any *wording* for
|
|
195
243
|
// that state would be ours to invent, in one language, for a visitor who has
|
package/src/website.js
CHANGED
|
@@ -1140,13 +1140,15 @@ export default class Website {
|
|
|
1140
1140
|
* @returns {boolean}
|
|
1141
1141
|
*/
|
|
1142
1142
|
isSearchEnabled() {
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1143
|
+
// ⭐ THE ORDER IS resolveService's, the same for every service: the host's
|
|
1144
|
+
// offer, then the site's own endpoint, then the site's `false` /
|
|
1145
|
+
// `{ enabled: false }` — which decides only where no host offers search. So
|
|
1146
|
+
// an author's `search: false` turns the local index off on a static site
|
|
1147
|
+
// and does not hide search a host provides; a hosted service is turned off
|
|
1148
|
+
// where the host provides it. Until 2026-09-10 this method read the
|
|
1149
|
+
// author's switch FIRST, so `search: false` beat a host's offer — the one
|
|
1150
|
+
// service where it did, and the opposite of every other.
|
|
1151
|
+
//
|
|
1150
1152
|
// ⭐ THE HOST GETS A SAY, and this is the half that reaches an already-
|
|
1151
1153
|
// published foundation. A foundation bundles its own frozen copy of
|
|
1152
1154
|
// `@uniweb/kit`, so a fix made in kit never reaches one built before it.
|
|
@@ -1164,14 +1166,18 @@ export default class Website {
|
|
|
1164
1166
|
// error, and any text a visitor reads is site content — authored and
|
|
1165
1167
|
// localized — never a string a service layer invents.
|
|
1166
1168
|
//
|
|
1167
|
-
// A site's
|
|
1168
|
-
//
|
|
1169
|
-
//
|
|
1169
|
+
// A site's own `search.endpoint` still works where the host does not offer
|
|
1170
|
+
// search, so self-hosted search on a host that does not sell it is
|
|
1171
|
+
// untouched. Where the host offers search, its offer wins.
|
|
1170
1172
|
const { url, source } = resolveService(this, 'search')
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
//
|
|
1174
|
-
|
|
1173
|
+
// An address from anyone — the site's own endpoint, or a host's offer.
|
|
1174
|
+
if (url) return true
|
|
1175
|
+
// 'site': switched off, with no host offering it. 'host': the host declines.
|
|
1176
|
+
if (source) return false
|
|
1177
|
+
|
|
1178
|
+
// Nobody spoke: enabled by default — absent means enabled, and a host that
|
|
1179
|
+
// publishes no services block at all has declined nothing (the static-host
|
|
1180
|
+
// path, where the build emits the local index).
|
|
1175
1181
|
return true
|
|
1176
1182
|
}
|
|
1177
1183
|
|