@escape-game-over/atlas 0.1.115 → 0.1.116

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.
@@ -78,13 +78,7 @@ export type MergedCatalog<L extends string> = Readonly<
78
78
  Record<string, Readonly<Record<L, string>>>
79
79
  >;
80
80
 
81
- /** Untyped translation lookup, wrapped below in a key-aware signature. */
82
- export type RawTranslate = (
83
- key: string,
84
- params?: Readonly<Record<string, string>>
85
- ) => string;
86
-
87
- export interface RawTranslateOptions<L extends string> {
81
+ export interface TranslateOptions<L extends string> {
88
82
  readonly catalog: MergedCatalog<L>;
89
83
  readonly locale: L;
90
84
  /**
@@ -120,7 +114,11 @@ export function lookupTemplate<L extends string>(
120
114
  }
121
115
 
122
116
  /**
123
- * Builds the runtime half of `t()`.
117
+ * Builds `t()`.
118
+ *
119
+ * Untyped: `site.translate` declares the key-aware `TranslateFor` as what it
120
+ * returns, and this function fits that type with no cast. The key and params
121
+ * checks live in the type; what fails at runtime fails here.
124
122
  *
125
123
  * Every failure here is a build-time failure by design: a static site should not
126
124
  * ship a page with a literal `{name}` in the markup, so a missing message or an
@@ -132,9 +130,9 @@ export function lookupTemplate<L extends string>(
132
130
  * failure wearing different brackets. `rich()` reads those, and `plain()` gives
133
131
  * back the sentence without them.
134
132
  */
135
- export function createRawTranslate<L extends string>(
136
- options: RawTranslateOptions<L>
137
- ): RawTranslate {
133
+ export function createTranslate<L extends string>(
134
+ options: TranslateOptions<L>
135
+ ): (key: string, params?: Readonly<Record<string, string>>) => string {
138
136
  const { catalog, locale, values } = options;
139
137
 
140
138
  return (key, params) => {
@@ -211,40 +209,3 @@ export type TranslateFor<Catalog, Bound extends string = never> = <
211
209
  >(
212
210
  ...args: TranslateArgsOf<Catalog, K, Bound>
213
211
  ) => string;
214
-
215
- /**
216
- * Builds the typed `t()` factory for one catalog type.
217
- *
218
- * Curried for the usual reason — `Catalog` must be supplied explicitly while the
219
- * locale stays inferred, and TypeScript has no partial type-argument inference.
220
- * `Catalog` is the *base* catalog's type (it carries the literal strings the
221
- * placeholders are read from); the value passed in is the merged runtime
222
- * catalog, which only needs to be indexable by key and locale.
223
- */
224
- export function createTranslateFactory<Catalog, L extends string>() {
225
- return function createTranslate<Bound extends string = never>(
226
- catalog: MergedCatalog<L>,
227
- locale: L,
228
- values?: Readonly<Record<Bound, string>>
229
- ): TranslateFor<Catalog, Bound> {
230
- const raw = createRawTranslate({
231
- catalog,
232
- locale,
233
- ...(values === undefined ? {} : { values }),
234
- });
235
-
236
- function t<K extends StringKeys<Catalog>>(
237
- ...args: TranslateArgsOf<Catalog, K, Bound>
238
- ): string {
239
- // The conditional tuple cannot be destructured directly; the runtime
240
- // shape is always `[key]` or `[key, params]`.
241
- const [key, params] = args as [
242
- K,
243
- Readonly<Record<string, string>>?,
244
- ];
245
- return raw(key, params);
246
- }
247
-
248
- return t;
249
- };
250
- }
package/src/index.ts CHANGED
@@ -18,19 +18,14 @@ export {
18
18
  CONSENT_CATEGORIES,
19
19
  CONSENT_MONTHS,
20
20
  type ConsentCategory,
21
- type ConsentChoice,
22
- type ConsentChoices,
23
21
  } from "./analytics/consent-storage.ts";
24
22
  export {
25
23
  type AnalyticsSettings,
26
- type AnalyticsTags,
27
24
  type AxonPixelSettings,
28
25
  type ClaritySettings,
29
- CONSENT_UPDATE_GLOBAL,
30
26
  type ConsentDefaults,
31
27
  type ConsentState,
32
28
  consentRequired,
33
- consentVendors,
34
29
  type DripSettings,
35
30
  type GoogleSettings,
36
31
  type GoogleTagId,
@@ -45,74 +40,36 @@ export {
45
40
  type Uuid,
46
41
  } from "./analytics/index.ts";
47
42
  export {
48
- type AtlasStoredId,
49
- compareStoredItems,
50
- type Lifetime,
51
43
  STORAGE_VENDOR_NAMES,
52
- type StorageVendor,
53
44
  type StoredId,
54
45
  type StoredItem,
55
46
  type StoredItemsOptions,
56
47
  storedItems,
57
- withPurposes,
58
48
  } from "./analytics/storage.ts";
59
- export {
60
- checkEscapeNavigator,
61
- ESCAPE_NAVIGATOR_LOADER,
62
- type EscapeNavigatorVars,
63
- type EscapeNavigatorWidget,
64
- escapeNavigatorLanguage,
65
- } from "./booking/escape-navigator.ts";
66
- export {
67
- checkEscapeAll,
68
- ESCAPEALL_LANGUAGES,
69
- type EscapeAllLanguage,
70
- type EscapeAllVars,
71
- type EscapeAllWidget,
72
- escapeAllAssets,
73
- escapeAllLanguage,
74
- } from "./booking/escapeall.ts";
75
- export {
76
- checkFourEscape,
77
- FOUR_ESCAPE_LANGUAGES,
78
- FOUR_ESCAPE_SCRIPT,
79
- FOUR_ESCAPE_STYLESHEET,
80
- type FourEscapeLanguage,
81
- type FourEscapeVars,
82
- type FourEscapeWidget,
83
- fourEscapeLanguage,
84
- fourEscapeSettings,
85
- fourEscapeStyle,
86
- } from "./booking/four-escape.ts";
49
+ export type { EscapeNavigatorWidget } from "./booking/escape-navigator.ts";
87
50
  export type {
88
- BookingVendor,
89
- BookingVendorVars,
90
- BookingWidget,
91
- BookingWidgetVars,
92
- } from "./booking/widget.ts";
51
+ EscapeAllLanguage,
52
+ EscapeAllWidget,
53
+ } from "./booking/escapeall.ts";
93
54
  export type {
94
- LanguageTag,
95
- LocaleMeta,
96
- LocalesOf,
97
- ResolvedLocaleMeta,
98
- RoutingConfig,
99
- SiteConfigShape,
100
- } from "./config.ts";
55
+ FourEscapeLanguage,
56
+ FourEscapeVars,
57
+ FourEscapeWidget,
58
+ } from "./booking/four-escape.ts";
59
+ export type { BookingVendor, BookingWidget } from "./booking/widget.ts";
60
+ export type { LanguageTag, LocaleMeta, LocalesOf } from "./config.ts";
101
61
  export {
102
62
  type AddressFormatOptions,
103
63
  type AddressLineOptions,
104
64
  addressLine,
105
65
  addressLines,
106
- assertCoordinates,
107
66
  type CallingCode,
108
67
  type Coordinates,
109
68
  type CountryCode,
110
69
  type E164,
111
70
  type EmailAddress,
112
71
  e164,
113
- e164Of,
114
72
  formatPhone,
115
- isEmailAddress,
116
73
  type MailtoUrl,
117
74
  mailtoHref,
118
75
  type PhoneNumber,
@@ -132,44 +89,26 @@ export {
132
89
  // them has a second `rich()` that is not bound to the site's route table — so
133
90
  // a slot filled with a route id resolves against nothing there. `site.rich(locale)` is
134
91
  // the only way in, and `Span` is what a renderer switches on.
135
- export {
136
- html,
137
- type LinkTarget,
138
- type MessageBindings,
139
- type PlainTextFor,
140
- plain,
141
- type RichText,
142
- type RichTextFor,
143
- type Span,
144
- } from "./content/index.ts";
145
- export {
146
- COUNTRY_CODES,
147
- countriesFor,
148
- type NamedCountry,
149
- } from "./countries.ts";
92
+ export { html, plain, type RichText, type Span } from "./content/index.ts";
93
+ export { countriesFor, type NamedCountry } from "./countries.ts";
150
94
  // `quantitiesOf`, `quantityRange` and `isContiguous` are deliberately not here.
151
95
  // They are how lib decides between "2–6" and "2, 4, 6", and a consumer holding
152
96
  // them writes that decision a second time — which is how `llms.txt` came to
153
97
  // advertise a range for a room that skips sizes. `formatQuantities` answers it
154
98
  // for prose and `quantitiesFor` for structured data; both read the same tiers.
155
99
  export type { CurrencyCode } from "./currency.ts";
156
- export type { GeneratedFile } from "./file.ts";
157
100
  export type { PublicFilePath, PublicFileRegistry } from "./files.ts";
158
101
  export type { Font, Fonts } from "./fonts.ts";
159
102
  export {
160
- assertHours,
161
103
  type DayHours,
162
104
  type FormattedHours,
163
105
  formatHours,
164
- formatTime,
165
- groupHours,
166
106
  type HourCycle,
167
107
  type HoursFormat,
168
108
  type HoursGroup,
169
109
  type HoursSentenceFormat,
170
110
  hoursSentence,
171
111
  type OpenStatus,
172
- openStatus,
173
112
  type TimeFormat,
174
113
  type TimeOfDay,
175
114
  type TimeRange,
@@ -177,42 +116,18 @@ export {
177
116
  type Weekday,
178
117
  type WeeklyHours,
179
118
  } from "./hours.ts";
180
- export type {
181
- CatalogKey,
182
- MessagesOf,
183
- ProjectCatalog,
184
- PublishedOf,
185
- } from "./i18n/catalog.ts";
186
- export type {
187
- BaseCatalog,
188
- CatalogCovers,
189
- MessageOverrides,
190
- OverrideCatalog,
191
- } from "./i18n/define.ts";
192
- export type {
193
- EntryPlaceholders,
194
- Placeholders,
195
- } from "./i18n/placeholders.ts";
196
- export type {
197
- MessageParamsOf,
198
- TranslateArgsOf,
199
- TranslateFor,
200
- } from "./i18n/translate.ts";
119
+ export type { CatalogKey } from "./i18n/catalog.ts";
120
+ export type { CatalogCovers, MessageOverrides } from "./i18n/define.ts";
121
+ export type { MessageParamsOf } from "./i18n/translate.ts";
201
122
  export type { ImageAsset } from "./image.ts";
202
123
  export {
203
- type ArticleInput,
204
- article,
205
- type BreadcrumbStep,
206
124
  type BusinessId,
207
- breadcrumbList,
208
125
  businessId,
209
- type CatalogEntry,
210
126
  type CreativeWorkInput,
211
127
  type CreativeWorkProperty,
212
128
  creativeWork,
213
129
  type FaqEntry,
214
130
  faqPage,
215
- geoCoordinates,
216
131
  type JsonLdNode,
217
132
  type LocalBusinessInput,
218
133
  localBusiness,
@@ -220,45 +135,30 @@ export {
220
135
  type OrganizationId,
221
136
  type OrganizationInput,
222
137
  offerCatalog,
223
- openingHours,
224
138
  organization,
225
139
  organizationId,
226
140
  type ProductInput,
227
- postalAddress,
228
141
  product,
229
142
  quantitativeValue,
230
143
  quantitiesFor,
231
144
  type ServiceArea,
232
145
  type ServiceId,
233
146
  type ServiceInput,
234
- serializeJsonLd,
235
147
  service,
236
- serviceId,
237
148
  type VideoInput,
238
149
  type VideoSource,
239
150
  videoObject,
240
151
  type WebsiteId,
241
152
  type WebsiteInput,
242
153
  website,
243
- websiteId,
244
154
  } from "./jsonld/index.ts";
245
- export {
246
- buildLlms,
247
- type LlmsConfig,
248
- type LlmsItem,
249
- type LlmsSection,
250
- } from "./llms.ts";
155
+ export type { LlmsSection } from "./llms.ts";
251
156
  export { googleMapsUrl, type MapPlace, type MapsTarget } from "./map.ts";
252
157
  export {
253
158
  type ArticleContent,
254
- buildMeta,
255
- buildNotFoundMeta,
256
159
  fitDescription,
257
160
  fitTitle,
258
161
  type MetaContent,
259
- type MetaTag,
260
- type OpenGraphType,
261
- type PageKind,
262
162
  type RobotsPolicy,
263
163
  type ShareImage,
264
164
  type SiteVerification,
@@ -267,8 +167,6 @@ export {
267
167
  type AudiencePrice,
268
168
  type AudienceTier,
269
169
  amountAt,
270
- assertAudiences,
271
- assertPriceTiers,
272
170
  formatQuantities,
273
171
  type Price,
274
172
  type PriceTier,
@@ -284,70 +182,35 @@ export {
284
182
  type PriceFormat,
285
183
  } from "./price-format.ts";
286
184
  export type {
287
- DefinedProject,
288
- ProjectInput,
289
- ProjectRouteId,
290
- } from "./project.ts";
291
- export {
292
- buildCloudflareRedirects,
293
- buildRedirects,
294
- type CloudflareRedirectsOptions,
295
- type ExternalUrl,
296
- type ProjectRedirects,
297
- type RedirectKind,
298
- type RedirectRule,
299
- type RedirectStatus,
300
- type RedirectTarget,
301
- type ResolvedRedirect,
302
- type SitePath,
303
- type ValidateRedirectTargets,
185
+ ProjectRedirects,
186
+ RedirectKind,
187
+ RedirectRule,
188
+ RedirectTarget,
189
+ SitePath,
304
190
  } from "./redirects.ts";
305
- export type { RobotsConfig, RobotsGroup } from "./robots.ts";
306
- export type {
307
- ChangeFreq,
308
- EnabledRouteIdFor,
309
- RouteData,
310
- RouteOverride,
311
- RouteOverrides,
312
- RouteRegistry,
313
- RouteSitemap,
314
- } from "./routes/define.ts";
315
191
  export {
316
192
  enableRoutes,
317
- type FamilyMember,
318
- type FamilyMemberInput,
319
- type FamilyMemberSlugs,
320
- type FamilyOptions,
321
193
  familyGuard,
322
194
  familyMemberSlugs,
323
195
  type RouteIdsOf,
324
196
  } from "./routes/family.ts";
325
- export type { RouteEntry } from "./routes/resolve.ts";
326
197
  export { defineSite } from "./site/define.ts";
327
198
  export type {
328
- Alternate,
329
199
  Crumb,
330
200
  LinkOptions,
331
201
  LlmsOptions,
332
- LocaleLink,
333
202
  PageContent,
334
203
  PageMeta,
335
204
  PageProps,
336
205
  PerRoute,
337
- Site,
338
- StaticPath,
339
206
  } from "./site/index.ts";
340
- export type { Sitemap, SitemapConfig } from "./sitemap.ts";
341
- export { TIME_ZONES, type TimeZone } from "./time-zones.ts";
342
- export type { IsoDate, Letter, NonEmpty, StringKeys } from "./types.ts";
207
+ export type { TimeZone } from "./time-zones.ts";
208
+ export type { IsoDate, NonEmpty, StringKeys } from "./types.ts";
343
209
  export {
344
210
  absoluteUrl,
345
- type Hash,
346
211
  type HttpsUrl,
347
- isHash,
348
212
  isHttpsUrl,
349
213
  isUrlPath,
350
214
  joinUrl,
351
- normalizeOrigin,
352
215
  type UrlPath,
353
216
  } from "./url.ts";
@@ -2,7 +2,7 @@ import type { IsoDate } from "../types.ts";
2
2
  import type { HttpsUrl } from "../url.ts";
3
3
  import { warn } from "../warn.ts";
4
4
  import type { BusinessId, OrganizationId } from "./ids.ts";
5
- import { type JsonLdNode, oneOrMany } from "./node.ts";
5
+ import { type JsonLdNode, listed, oneOrMany, reference } from "./node.ts";
6
6
 
7
7
  export interface ArticleInput {
8
8
  /**
@@ -137,29 +137,19 @@ export function article(item: ArticleInput): JsonLdNode {
137
137
  return {
138
138
  "@type": item.kind ?? "Article",
139
139
  headline: item.headline,
140
- ...(item.description === undefined
141
- ? {}
142
- : { description: item.description }),
140
+ description: item.description,
143
141
  // Not `url`: an article is a thing, and this is the page it is *on*.
144
142
  mainEntityOfPage: item.url,
145
143
  datePublished: item.datePublished,
146
- ...(item.dateModified === undefined
147
- ? {}
148
- : { dateModified: item.dateModified }),
144
+ dateModified: item.dateModified,
149
145
  // Passed through in the shape it arrived in, bar a list of one — which
150
146
  // is `schemaType`'s rule applied to a second field: writing `[x]` is
151
147
  // fine, emitting it reads as a bug.
152
- ...(item.image === undefined ? {} : { image: oneOrMany(item.image) }),
153
- ...(item.author === undefined
154
- ? {}
155
- : { author: { "@id": item.author } }),
156
- ...(item.publisher === undefined
157
- ? {}
158
- : { publisher: { "@id": item.publisher } }),
148
+ image: item.image === undefined ? undefined : oneOrMany(item.image),
149
+ author: reference(item.author),
150
+ publisher: reference(item.publisher),
159
151
  // An empty list is not a set of no keywords, it is a field saying
160
152
  // nothing — so it is left out, as every optional here is.
161
- ...(item.tags === undefined || item.tags.length === 0
162
- ? {}
163
- : { keywords: item.tags }),
153
+ keywords: listed(item.tags),
164
154
  };
165
155
  }
@@ -10,7 +10,13 @@ import type { ImageAsset } from "../image.ts";
10
10
  import type { NonEmpty } from "../types.ts";
11
11
  import { absoluteUrl, type HttpsUrl } from "../url.ts";
12
12
  import { businessId, type OrganizationId } from "./ids.ts";
13
- import { alternateName, type JsonLdNode, schemaType } from "./node.ts";
13
+ import {
14
+ alternateName,
15
+ type JsonLdNode,
16
+ listed,
17
+ reference,
18
+ schemaType,
19
+ } from "./node.ts";
14
20
  import { geoCoordinates, openingHours, postalAddress } from "./place.ts";
15
21
 
16
22
  export interface LocalBusinessInput {
@@ -173,34 +179,27 @@ export function localBusiness(business: LocalBusinessInput): JsonLdNode {
173
179
  "@type": schemaType(business.type ?? "LocalBusiness"),
174
180
  "@id": businessId(business.url, business.location),
175
181
  name: business.name,
176
- ...alternateName(business.name, business.alternateName),
182
+ alternateName: alternateName(business.name, business.alternateName),
177
183
  url: business.url,
178
184
  address: postalAddress(business.address),
179
- ...(business.geo === undefined
180
- ? {}
181
- : { geo: geoCoordinates(business.geo) }),
182
- ...(business.priceRange === undefined
183
- ? {}
184
- : { priceRange: business.priceRange }),
185
- ...(business.image === undefined || business.image.length === 0
186
- ? {}
187
- : {
188
- image: business.image.map((photo) =>
189
- absoluteUrl(business.url, photo.src, "venue photo")
190
- ),
191
- }),
192
- ...(business.telephone === undefined
193
- ? {}
194
- : { telephone: e164(business.telephone) }),
195
- ...(business.email === undefined ? {} : { email: business.email }),
196
- ...(business.hours === undefined
197
- ? {}
198
- : { openingHoursSpecification: openingHours(business.hours) }),
199
- ...(business.sameAs === undefined ? {} : { sameAs: business.sameAs }),
200
- ...(business.parentOrganization === undefined
201
- ? {}
202
- : {
203
- parentOrganization: { "@id": business.parentOrganization },
204
- }),
185
+ geo:
186
+ business.geo === undefined
187
+ ? undefined
188
+ : geoCoordinates(business.geo),
189
+ priceRange: business.priceRange,
190
+ image: listed(business.image)?.map((photo) =>
191
+ absoluteUrl(business.url, photo.src, "venue photo")
192
+ ),
193
+ telephone:
194
+ business.telephone === undefined
195
+ ? undefined
196
+ : e164(business.telephone),
197
+ email: business.email,
198
+ openingHoursSpecification:
199
+ business.hours === undefined
200
+ ? undefined
201
+ : openingHours(business.hours),
202
+ sameAs: business.sameAs,
203
+ parentOrganization: reference(business.parentOrganization),
205
204
  };
206
205
  }
@@ -1,7 +1,7 @@
1
1
  import type { Quantity } from "../money.ts";
2
2
  import type { HttpsUrl } from "../url.ts";
3
3
  import type { BusinessId, OrganizationId } from "./ids.ts";
4
- import { type JsonLdNode, schemaType } from "./node.ts";
4
+ import { type JsonLdNode, listed, reference, schemaType } from "./node.ts";
5
5
  import { quantitativeValue } from "./quantity.ts";
6
6
 
7
7
  /**
@@ -92,19 +92,11 @@ export function creativeWork(work: CreativeWorkInput): JsonLdNode {
92
92
  "@type": schemaType(["CreativeWork", ...(work.alsoA ?? [])]),
93
93
  name: work.name,
94
94
  url: work.url,
95
- ...(work.description === undefined
96
- ? {}
97
- : { description: work.description }),
98
- ...(work.image === undefined ? {} : { image: work.image }),
99
- ...(work.genre === undefined || work.genre.length === 0
100
- ? {}
101
- : { genre: work.genre }),
102
- ...(work.creator === undefined
103
- ? {}
104
- : { creator: { "@id": work.creator } }),
105
- ...(work.properties === undefined || work.properties.length === 0
106
- ? {}
107
- : { additionalProperty: work.properties.map(propertyValue) }),
95
+ description: work.description,
96
+ image: work.image,
97
+ genre: listed(work.genre),
98
+ creator: reference(work.creator),
99
+ additionalProperty: listed(work.properties)?.map(propertyValue),
108
100
  };
109
101
  }
110
102
 
@@ -114,12 +106,8 @@ function propertyValue(property: CreativeWorkProperty): JsonLdNode {
114
106
  name: property.name,
115
107
  value: {
116
108
  ...quantitativeValue(property.value),
117
- ...(property.unitCode === undefined
118
- ? {}
119
- : { unitCode: property.unitCode }),
120
- ...(property.unitText === undefined
121
- ? {}
122
- : { unitText: property.unitText }),
109
+ unitCode: property.unitCode,
110
+ unitText: property.unitText,
123
111
  },
124
112
  };
125
113
  }
@@ -7,7 +7,13 @@
7
7
 
8
8
  import { literal } from "../analytics/tags.ts";
9
9
 
10
- /** A node in the graph. Deliberately loose: `@type` is what a consumer varies. */
10
+ /**
11
+ * A node in the graph. Deliberately loose: `@type` is what a consumer varies.
12
+ *
13
+ * An optional field it lacks is written as `undefined`, never branched around
14
+ * with a conditional spread: `serializeJsonLd` goes through JSON, which drops
15
+ * it, so the published node has no such key either way.
16
+ */
11
17
  export interface JsonLdNode {
12
18
  readonly "@type": string | readonly string[];
13
19
  readonly [key: string]: unknown;
@@ -47,8 +53,8 @@ export function schemaType(
47
53
  * engine reads it as *another* name and a repeat of the first one is not that:
48
54
  * it widens nothing and is one more string to reconcile. Decided here rather
49
55
  * than by the caller — three nodes take the field, the rule is the same on all
50
- * of them, and a project asked to write `name === alt ? {} : { alt }` at each
51
- * one is being handed lib's rule to enforce by hand.
56
+ * of them, and a project asked to write `alt === name ? undefined : alt` at
57
+ * each one is being handed lib's rule to enforce by hand.
52
58
  *
53
59
  * Which makes `alternateName: brand.name` safe to pass unconditionally: a venue
54
60
  * whose name already *is* the brand's simply emits nothing.
@@ -56,10 +62,34 @@ export function schemaType(
56
62
  export function alternateName(
57
63
  name: string,
58
64
  alternate: string | undefined
59
- ): { readonly alternateName?: string } {
60
- return alternate === undefined || alternate === name
61
- ? {}
62
- : { alternateName: alternate };
65
+ ): string | undefined {
66
+ return alternate === name ? undefined : alternate;
67
+ }
68
+
69
+ /**
70
+ * A pointer to a node published elsewhere in the graph, or nothing.
71
+ *
72
+ * Every field built from it is optional, and an `undefined` field is dropped
73
+ * when the graph is serialised: so a node can write `author:
74
+ * reference(item.author)` without a branch.
75
+ */
76
+ export function reference<Id extends string>(
77
+ id: Id | undefined
78
+ ): { readonly "@id": Id } | undefined {
79
+ return id === undefined ? undefined : { "@id": id };
80
+ }
81
+
82
+ /**
83
+ * A list, or nothing when it is empty.
84
+ *
85
+ * An empty list is not a set of nothing, it is a field saying nothing — a
86
+ * claim to have been asked and had no answer — so it is left out like any
87
+ * other absent field.
88
+ */
89
+ export function listed<T>(
90
+ list: readonly T[] | undefined
91
+ ): readonly T[] | undefined {
92
+ return list === undefined || list.length === 0 ? undefined : list;
63
93
  }
64
94
 
65
95
  /** The graph, serialised for a `<script type="application/ld+json">`. */