@uniweb/core 0.37.2 → 0.38.1
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/data-keys.js +40 -34
- package/src/index.js +2 -1
- package/src/page-data.js +39 -17
- package/src/resolve-route.js +44 -0
- package/src/services.js +5 -4
- package/src/website.js +1 -1
package/package.json
CHANGED
package/src/data-keys.js
CHANGED
|
@@ -24,49 +24,55 @@
|
|
|
24
24
|
|
|
25
25
|
import { fetchEntries } from './fetch-config.js'
|
|
26
26
|
|
|
27
|
-
/** The suffix that asks for whole records: `'@std/article/*'`. */
|
|
28
|
-
export const WHOLE_SUFFIX = '/*'
|
|
29
|
-
|
|
30
27
|
/**
|
|
31
|
-
* A declared key's schema ref, and
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* ⭐
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* `
|
|
45
|
-
*
|
|
28
|
+
* A declared key's schema ref, and its two flags — as `data:` spells them: a ref string
|
|
29
|
+
* (`'@std/article'`), the long form (`{ schema: '@std/article', single: true, whole: true }`), or
|
|
30
|
+
* neither for an inline shape — a field map or a form — which names no schema.
|
|
31
|
+
*
|
|
32
|
+
* ⭐ TWO FLAGS, INDEPENDENT (ruled 2026-10-07 [Diego]):
|
|
33
|
+
*
|
|
34
|
+
* - **`single`** — how many: one record, or `null`, where a key holds a list by default. The
|
|
35
|
+
* runtime asks for the first record the binding selects (`./page-data.js`) and delivers it.
|
|
36
|
+
* - **`whole`** — how much of each: the record as stored, one key per section, where a key holds
|
|
37
|
+
* briefs by default, the brief's fields at the top (ruled 2026-09-27). The runtime asks each
|
|
38
|
+
* key's question accordingly; a key declared whole that its fetch cannot fill whole is `null`.
|
|
39
|
+
*
|
|
40
|
+
* ⛔ `'@std/article/*'` asked for whole records until 2026-10-07, and is retired: the build and
|
|
41
|
+
* `register` refuse it, so no artifact carries it, and it is not read here.
|
|
42
|
+
*
|
|
43
|
+
* Who passes what. The runtime: a section type's LEAN `data` — a ref string, `{ schema, single?,
|
|
44
|
+
* whole? }` or null, from the runtime schema the build extracts (`@uniweb/build`'s
|
|
45
|
+
* `runtime-schema.js`) — and the foundation's `main.js` `data:` leaned the same way. A build-time
|
|
46
|
+
* reader: a `meta.js` value as written, or a registered foundation schema's in its normalized form
|
|
47
|
+
* (`schemaFormat: 3`, `@uniweb/schemas`'s `normalizeFoundationSchema`), which writes every ref as
|
|
48
|
+
* `{ kind: 'schema', schema, single, whole }` — the ref qualified and both flags said outright.
|
|
46
49
|
*
|
|
47
50
|
* @param {*} value - one `data:` entry's value, authored, leaned or normalized
|
|
48
|
-
* @returns {{ ref: string|null, whole: boolean }}
|
|
51
|
+
* @returns {{ ref: string|null, single: boolean, whole: boolean }}
|
|
49
52
|
*/
|
|
50
53
|
export function dataRefOf(value) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
return { ref:
|
|
54
|
+
if (typeof value === 'string') return { ref: value || null, single: false, whole: false }
|
|
55
|
+
// The long form's `schema` is a ref; an inline field map may have a field called `schema`.
|
|
56
|
+
if (value && typeof value === 'object' && typeof value.schema === 'string' && value.schema.startsWith('@')) {
|
|
57
|
+
return { ref: value.schema, single: value.single === true, whole: value.whole === true }
|
|
58
|
+
}
|
|
59
|
+
return { ref: null, single: false, whole: false }
|
|
57
60
|
}
|
|
58
61
|
|
|
59
62
|
/**
|
|
60
|
-
* The keys a section's component receives, in order, each with its schema ref and
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
+
* The keys a section's component receives, in order, each with its schema ref and its two flags
|
|
64
|
+
* (`dataRefOf`) — its component's `data:` first, then its foundation's. A key both declare is the
|
|
65
|
+
* component's.
|
|
66
|
+
*
|
|
67
|
+
* `data: false`, a missing `data:` or anything that is not a map declares nothing. Each value
|
|
68
|
+
* may be as authored, as the build leans it, or as a registered schema normalizes it.
|
|
63
69
|
*
|
|
64
|
-
* `
|
|
65
|
-
*
|
|
70
|
+
* ⚠️ `single` is the FOURTH member, appended 2026-10-07, so a caller destructuring the first three
|
|
71
|
+
* reads what it always did.
|
|
66
72
|
*
|
|
67
73
|
* @param {Object|false|null|undefined} componentData - the component's `data:`
|
|
68
74
|
* @param {Object|false|null|undefined} [foundationData] - its foundation's `main.js` `data:`
|
|
69
|
-
* @returns {Array<[string, string|null, boolean]>} `[key, ref, whole]`
|
|
75
|
+
* @returns {Array<[string, string|null, boolean, boolean]>} `[key, ref, whole, single]`
|
|
70
76
|
*/
|
|
71
77
|
export function declaredKeys(componentData, foundationData = null) {
|
|
72
78
|
const out = []
|
|
@@ -76,8 +82,8 @@ export function declaredKeys(componentData, foundationData = null) {
|
|
|
76
82
|
for (const [key, value] of Object.entries(data)) {
|
|
77
83
|
if (!key || seen.has(key)) continue
|
|
78
84
|
seen.add(key)
|
|
79
|
-
const { ref, whole } = dataRefOf(value)
|
|
80
|
-
out.push([key, ref, whole])
|
|
85
|
+
const { ref, single, whole } = dataRefOf(value)
|
|
86
|
+
out.push([key, ref, whole, single])
|
|
81
87
|
}
|
|
82
88
|
}
|
|
83
89
|
return out
|
|
@@ -137,7 +143,7 @@ function parseRef(ref) {
|
|
|
137
143
|
* The mapping says where an answer lands; it renames no fetch. A fetch keeps its `as`
|
|
138
144
|
* for its question, its cache key and its prefetch.
|
|
139
145
|
*
|
|
140
|
-
* @param {Array<[string, string|null, boolean]>} declared - `declaredKeys`
|
|
146
|
+
* @param {Array<[string, string|null, boolean, boolean]>} declared - `declaredKeys`
|
|
141
147
|
* @param {Array<*>} levels - each level's `fetch`, most specific first; falsy levels skipped
|
|
142
148
|
* @param {Object} [options]
|
|
143
149
|
* @param {Object|null} [options.queries] - the site's `config.queries`, for each fetch's
|
package/src/index.js
CHANGED
|
@@ -24,7 +24,7 @@ export { substitutePlaceholders } from './substitute-placeholders.js'
|
|
|
24
24
|
// reads them and a consumer that needs them imports `@uniweb/core/records-service`.
|
|
25
25
|
export { resolveFetchConfigs, routeQuery, pageRouteQuery, routeSelection, sectionFetches, siteReaches, withoutRouteVariables, CURRENT_MODES, currentFor } from './fetch-config.js'
|
|
26
26
|
export { buildDetailConfig, ROUTE_HANDLE_KEY } from './detail-url.js'
|
|
27
|
-
export { declaredKeys, sameSchema, fillDeclaredKeys, dataRefOf
|
|
27
|
+
export { declaredKeys, sameSchema, fillDeclaredKeys, dataRefOf } from './data-keys.js'
|
|
28
28
|
// ⭐ What a page needs — the one rule. `fetchLevels` is the part every lane has an answer for
|
|
29
29
|
// (which fetches reach a block); the drivers and the per-key program are `@uniweb/core/page-data`.
|
|
30
30
|
export { fetchLevels } from './page-data.js'
|
|
@@ -91,6 +91,7 @@ export {
|
|
|
91
91
|
localeOfPath,
|
|
92
92
|
localeUrl,
|
|
93
93
|
landingRoute,
|
|
94
|
+
carryQuery,
|
|
94
95
|
RESOLUTION_STATUS,
|
|
95
96
|
} from './resolve-route.js'
|
|
96
97
|
// ⛔ No `isRichSchema` (removed 2026-10-05). It told a form-shaped `data:` value from a field
|
package/src/page-data.js
CHANGED
|
@@ -80,13 +80,14 @@ export function fetchLevels({ own = null, page = null, parent = null, route = nu
|
|
|
80
80
|
* Each filling fetch is resolved on its own, so two fetches of one `as` can fill two keys.
|
|
81
81
|
*
|
|
82
82
|
* @param {Object} input
|
|
83
|
-
* @param {Array<[string, string|null, boolean]>} input.declared - `declaredKeys(meta)`
|
|
83
|
+
* @param {Array<[string, string|null, boolean, boolean]>} input.declared - `declaredKeys(meta)`
|
|
84
84
|
* @param {Array<*>} input.levels - `fetchLevels`
|
|
85
85
|
* @param {Object} [input.holds] - what the block already holds, by key
|
|
86
86
|
* @param {Object|null} [input.queries] - the site's `config.queries`
|
|
87
87
|
* @param {Object} [input.options] - what each fetch is resolved with (`resolveFetchConfigs`)
|
|
88
|
-
* @returns {Map<string, { cfg: Object, held: *, whole: boolean }>} declared key → its
|
|
89
|
-
* and whether its component expects whole records,
|
|
88
|
+
* @returns {Map<string, { cfg: Object, held: *, whole: boolean, single: boolean }>} declared key → its
|
|
89
|
+
* filling config, and its two flags — whether its component expects whole records, and one record —
|
|
90
|
+
* in the order `declared` lists the keys
|
|
90
91
|
*/
|
|
91
92
|
export function blockFills({ declared, levels, holds = {}, queries = null, options = {} }) {
|
|
92
93
|
const out = new Map()
|
|
@@ -102,12 +103,12 @@ export function blockFills({ declared, levels, holds = {}, queries = null, optio
|
|
|
102
103
|
if (!firstOfKey.has(fetch.as)) firstOfKey.set(fetch.as, `${at}:${index}`)
|
|
103
104
|
}))
|
|
104
105
|
|
|
105
|
-
const
|
|
106
|
+
const flags = new Map(declared.map(([key, , whole, single]) => [key, { whole: whole === true, single: single === true }]))
|
|
106
107
|
for (const [key, { fetch, level, index }] of fills) {
|
|
107
108
|
const cfg = resolveFetchConfigs([fetch], options).get(fetch.as)
|
|
108
109
|
if (!cfg) continue
|
|
109
110
|
const held = firstOfKey.get(fetch.as) === `${level}:${index}` ? holds[fetch.as] : undefined
|
|
110
|
-
out.set(key, { cfg, held,
|
|
111
|
+
out.set(key, { cfg, held, ...flags.get(key) })
|
|
111
112
|
}
|
|
112
113
|
return out
|
|
113
114
|
}
|
|
@@ -159,13 +160,19 @@ function heldWhole(peekRecord, match) {
|
|
|
159
160
|
* reading the cache never does, because the cache holds answers only.
|
|
160
161
|
*
|
|
161
162
|
* ⭐ **What is asked follows what the component expects** (`whole`, from its `data:` —
|
|
162
|
-
* `
|
|
163
|
-
* as it is, a compiled file's list, and on a parametric page the record FOUND in its query's
|
|
164
|
-
* Whole records when declared: the question carries `whole: true`, and a list off the service is
|
|
163
|
+
* `{ schema, whole: true }`; ruled 2026-09-27 [Diego]). Briefs by default: the records service's
|
|
164
|
+
* question as it is, a compiled file's list, and on a parametric page the record FOUND in its query's
|
|
165
|
+
* set. Whole records when declared: the question carries `whole: true`, and a list off the service is
|
|
165
166
|
* followed by each record's own request. A key declared whole that cannot be filled whole — its
|
|
166
167
|
* source has no request for one record — is `null`: the contract cannot be met. An external query's
|
|
167
168
|
* records have no data schema and so no brief: what its source answers is each record whole.
|
|
168
169
|
*
|
|
170
|
+
* ⭐ **And how many** (`single`, ruled 2026-10-07 [Diego]): a key that holds one record asks for one —
|
|
171
|
+
* the binding's question cut to its first record (`narrow.limit: 1`), so a whole one is fetched whole
|
|
172
|
+
* alone, never each record of the set. The answer is still a list, of one or none, which the runtime
|
|
173
|
+
* makes the record or `null` (`runtime/src/prepare-props.js`). A parametric page's record is found in
|
|
174
|
+
* the query's whole set as before: `current: only` asks for one already.
|
|
175
|
+
*
|
|
169
176
|
* ⛔ **A failed request delivers NOTHING under its key, and says so.** The key is absent from the
|
|
170
177
|
* returned value and the message rides on `error`, because `[]` is a value: a request that failed
|
|
171
178
|
* must not be indistinguishable from a query with no records. ⛔ Until 2026-09-27 a parametric
|
|
@@ -188,9 +195,10 @@ function heldWhole(peekRecord, match) {
|
|
|
188
195
|
* and a record found there answers with the brief it holds. No value at all means *unknown*,
|
|
189
196
|
* which is not the same as `[]`, *not in the set*.
|
|
190
197
|
* @param {boolean} [where.whole] - the component expects whole records
|
|
198
|
+
* @param {boolean} [where.single] - the component expects one record
|
|
191
199
|
* @returns {Generator<Object|Object[], { value?: *, error?: string, errorConfig?: Object }>}
|
|
192
200
|
*/
|
|
193
|
-
export function* keyProgram(cfg, { dynamicContext = null, route = null, held = undefined, peekRecord = null, current: forced = null, opportunistic = false, whole = false } = {}) {
|
|
201
|
+
export function* keyProgram(cfg, { dynamicContext = null, route = null, held = undefined, peekRecord = null, current: forced = null, opportunistic = false, whole = false, single = false } = {}) {
|
|
194
202
|
/** A step this caller can do without: a miss answers `{ missing: true }` instead of stopping. */
|
|
195
203
|
const maybe = (request) => (opportunistic ? { request, optional: true } : request)
|
|
196
204
|
// The block holds this fetch's answer already — a static build prerendered it.
|
|
@@ -207,14 +215,19 @@ export function* keyProgram(cfg, { dynamicContext = null, route = null, held = u
|
|
|
207
215
|
// How this fetch uses the page's record: by the query it names (`currentFor`).
|
|
208
216
|
const current = forced ?? (dynamicContext ? currentFor(cfg, route) : null)
|
|
209
217
|
|
|
218
|
+
// What a list of the binding's records is asked as — its first only, for a key holding one — and
|
|
219
|
+
// the answer cut to it here too, since a source need not honour `narrow` (a foundation's transport).
|
|
220
|
+
const listed = single ? firstOnly(cfg) : cfg
|
|
221
|
+
const cut = (data) => (single && Array.isArray(data) ? data.slice(0, 1) : data)
|
|
222
|
+
|
|
210
223
|
if (current === 'exclude') {
|
|
211
224
|
// The query's records without this page's, its `limit` counting the others.
|
|
212
|
-
const view = othersView(
|
|
225
|
+
const view = othersView(listed)
|
|
213
226
|
const asked = whole && cfg.ask ? { ...view, whole: true } : view
|
|
214
227
|
const answer = yield asked
|
|
215
228
|
if (answer.error) return { error: answer.error, errorConfig: asked }
|
|
216
229
|
if (answer.data === undefined || answer.data === null) return {}
|
|
217
|
-
const others = othersOf(answer.data,
|
|
230
|
+
const others = othersOf(answer.data, listed, isPageRecord(dynamicContext, cfg.routeField))
|
|
218
231
|
if (!whole || cfg.ask) return { value: others }
|
|
219
232
|
return yield* wholeRecordsOf(cfg, others)
|
|
220
233
|
}
|
|
@@ -273,12 +286,21 @@ export function* keyProgram(cfg, { dynamicContext = null, route = null, held = u
|
|
|
273
286
|
|
|
274
287
|
// A fetch the page's record plays no part in, and `current: include`: the records as the fetch
|
|
275
288
|
// describes them, the page's among the rest.
|
|
276
|
-
const asked = whole && cfg.ask ? { ...
|
|
289
|
+
const asked = whole && cfg.ask ? { ...listed, whole: true } : listed
|
|
277
290
|
const answer = yield asked
|
|
278
291
|
if (answer.error) return { error: answer.error, errorConfig: asked }
|
|
279
292
|
if (answer.data === undefined || answer.data === null) return {}
|
|
280
|
-
if (!whole || cfg.ask) return { value: answer.data }
|
|
281
|
-
return yield* wholeRecordsOf(cfg, answer.data)
|
|
293
|
+
if (!whole || cfg.ask) return { value: cut(answer.data) }
|
|
294
|
+
return yield* wholeRecordsOf(cfg, cut(answer.data))
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* A `single` key's question: the binding's own, cut to its first record. The fetch's `narrow` is
|
|
299
|
+
* where a cut belongs — applied after the query's set on every lane — and a `limit` of its own is at
|
|
300
|
+
* least one, so the first record is the same either way.
|
|
301
|
+
*/
|
|
302
|
+
function firstOnly(cfg) {
|
|
303
|
+
return { ...cfg, narrow: { ...(cfg.narrow || {}), limit: 1 } }
|
|
282
304
|
}
|
|
283
305
|
|
|
284
306
|
/**
|
|
@@ -340,14 +362,14 @@ function asSteps(value) {
|
|
|
340
362
|
/**
|
|
341
363
|
* Every filling key's program, ready to run.
|
|
342
364
|
*
|
|
343
|
-
* @param {Map<string, { cfg: Object, held: *, whole?: boolean }>} fills - `blockFills`
|
|
365
|
+
* @param {Map<string, { cfg: Object, held: *, whole?: boolean, single?: boolean }>} fills - `blockFills`
|
|
344
366
|
* @param {Object} where - passed to each `keyProgram`
|
|
345
367
|
* @returns {Map<string, Generator>}
|
|
346
368
|
*/
|
|
347
369
|
export function planFor(fills, where = {}) {
|
|
348
370
|
const plan = new Map()
|
|
349
|
-
for (const [key, { cfg, held, whole = false }] of fills) {
|
|
350
|
-
plan.set(key, keyProgram(cfg, { ...where, held, whole }))
|
|
371
|
+
for (const [key, { cfg, held, whole = false, single = false }] of fills) {
|
|
372
|
+
plan.set(key, keyProgram(cfg, { ...where, held, whole, single }))
|
|
351
373
|
}
|
|
352
374
|
return plan
|
|
353
375
|
}
|
package/src/resolve-route.js
CHANGED
|
@@ -330,6 +330,10 @@ export function landingRoute(site, page, seen = new Set()) {
|
|
|
330
330
|
*
|
|
331
331
|
* `status` is what a host that sends one sends. A static host has its own ways to say each kind.
|
|
332
332
|
*
|
|
333
|
+
* ⭐ **A redirect's `location` names nothing of the request's query or fragment** — a path in, a
|
|
334
|
+
* location out. `carryQuery` adds them, as the runtime does before it navigates and as a host that
|
|
335
|
+
* answers with an HTTP redirect should, so a link's parameters reach the page the visitor lands on.
|
|
336
|
+
*
|
|
333
337
|
* @param {Object} site - one locale's site content (`{ pages, config, notFound? }`), or its `routeIndex`
|
|
334
338
|
* @param {string} path - the request path, with any deployment base removed
|
|
335
339
|
* @param {{ activeLocale?: string, defaultLocale?: string }} [context] - defaults to the content's
|
|
@@ -415,6 +419,46 @@ function redirect(location, status, reason) {
|
|
|
415
419
|
return { kind: 'redirect', status, location, reason }
|
|
416
420
|
}
|
|
417
421
|
|
|
422
|
+
/**
|
|
423
|
+
* ⭐ **WHAT THE VISITOR ASKED WITH RIDES ALONG.** A redirect's `location` with the query and the
|
|
424
|
+
* fragment of the URL the visitor arrived at, as an HTTP redirect keeps them: the request's query
|
|
425
|
+
* unless the location names its own, and its fragment unless the location names one. So a link's
|
|
426
|
+
* parameters reach the page the visitor lands on — a campaign's tags, or a token a site's backend
|
|
427
|
+
* mails to the site's root (`/?verify=…`).
|
|
428
|
+
*
|
|
429
|
+
* ⛔ **Never onto another origin.** A query can carry a token minted for this site, so a location
|
|
430
|
+
* on another origin — another site, a locale's own domain — goes as written. Without an `origin`,
|
|
431
|
+
* every location with a scheme, or protocol-relative, is taken to be elsewhere.
|
|
432
|
+
*
|
|
433
|
+
* ⛔ Until 2026-10-11 the runtime navigated to the location alone, so on a root that redirects — an
|
|
434
|
+
* authored `redirect:`, a page with no content of its own — a mailed link lost its token before any
|
|
435
|
+
* code could read it.
|
|
436
|
+
*
|
|
437
|
+
* @param {string} location - a redirect's `location`
|
|
438
|
+
* @param {{ search?: string, hash?: string }} [request] - the request's query (`?…`) and fragment
|
|
439
|
+
* (`#…`) — a `Location` will do
|
|
440
|
+
* @param {string|null} [origin] - the site's origin (`https://example.com`)
|
|
441
|
+
* @returns {string}
|
|
442
|
+
*/
|
|
443
|
+
export function carryQuery(location, { search = '', hash = '' } = {}, origin = null) {
|
|
444
|
+
const query = search && search !== '?' ? (search.startsWith('?') ? search : `?${search}`) : ''
|
|
445
|
+
const fragment = hash && hash !== '#' ? (hash.startsWith('#') ? hash : `#${hash}`) : ''
|
|
446
|
+
if (typeof location !== 'string' || !location || (!query && !fragment)) return location
|
|
447
|
+
if (location.startsWith('//') || HAS_SCHEME.test(location)) {
|
|
448
|
+
let target = null
|
|
449
|
+
try {
|
|
450
|
+
target = origin ? new URL(location, origin) : null
|
|
451
|
+
} catch {
|
|
452
|
+
target = null
|
|
453
|
+
}
|
|
454
|
+
if (!target || target.origin !== origin) return location
|
|
455
|
+
}
|
|
456
|
+
const cut = location.indexOf('#')
|
|
457
|
+
const path = cut < 0 ? location : location.slice(0, cut)
|
|
458
|
+
const own = cut < 0 ? '' : location.slice(cut)
|
|
459
|
+
return path + (path.includes('?') ? '' : query) + (own || fragment)
|
|
460
|
+
}
|
|
461
|
+
|
|
418
462
|
const HAS_SCHEME = /^[a-z][a-z0-9+.-]*:/i
|
|
419
463
|
|
|
420
464
|
/**
|
package/src/services.js
CHANGED
|
@@ -16,10 +16,11 @@ import { applyBasePath } from './base-path.js'
|
|
|
16
16
|
* deployment offers, which the site never had to know about. When a host
|
|
17
17
|
* offers a service, that is the answer: it is the authority on what a
|
|
18
18
|
* hosted site is given.
|
|
19
|
-
* 2. **The site**, authored —
|
|
20
|
-
*
|
|
21
|
-
* does not provide, and on a static site, where
|
|
22
|
-
* answer.
|
|
19
|
+
* 2. **The site**, authored — its entry under `services:` in site.yml, which a
|
|
20
|
+
* built site carries as `config.<name>`. The operator's own declaration —
|
|
21
|
+
* used for anything the host does not provide, and on a static site, where
|
|
22
|
+
* no host speaks, the whole answer. *(This named the top-level `search:`,
|
|
23
|
+
* `submit:`, `assistant:` and `tracking:` keys, retired 2026-10-06.)*
|
|
23
24
|
*
|
|
24
25
|
* ## ⛔ THE TWO TIERS ARE NOT TWO SOURCES OF ONE KIND OF THING
|
|
25
26
|
*
|
package/src/website.js
CHANGED
|
@@ -1158,7 +1158,7 @@ export default class Website {
|
|
|
1158
1158
|
* The registry is open: any name resolves, whether or not the framework ships
|
|
1159
1159
|
* a client for it.
|
|
1160
1160
|
*
|
|
1161
|
-
* @param {string} name - the service name — `search`, `submit`, `
|
|
1161
|
+
* @param {string} name - the service name — `search`, `submit`, `backend`, …
|
|
1162
1162
|
* @returns {boolean}
|
|
1163
1163
|
*/
|
|
1164
1164
|
isServiceEnabled(name) {
|