@proveanything/smartlinks 2.0.37 → 2.0.38
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/dist/docs/API_SUMMARY.md +62 -1
- package/dist/docs/headless-providers.md +104 -0
- package/dist/docs/site-seo.md +67 -0
- package/dist/headless.d.ts +31 -0
- package/dist/headless.js +225 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +6 -0
- package/dist/openapi.yaml +159 -0
- package/dist/seo.d.ts +85 -0
- package/dist/seo.js +172 -0
- package/dist/site.d.ts +10 -0
- package/dist/site.js +18 -0
- package/dist/types/appManifest.d.ts +11 -0
- package/dist/types/appManifest.js +0 -1
- package/dist/types/headless.d.ts +96 -0
- package/dist/types/headless.js +16 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/docs/API_SUMMARY.md +62 -1
- package/docs/headless-providers.md +104 -0
- package/docs/site-seo.md +67 -0
- package/openapi.yaml +159 -0
- package/package.json +4 -2
- package/scripts/headless-check.mjs +87 -0
package/dist/openapi.yaml
CHANGED
|
@@ -19459,6 +19459,10 @@ components:
|
|
|
19459
19459
|
$ref: "#/components/schemas/AppManifestExecutor"
|
|
19460
19460
|
functions:
|
|
19461
19461
|
$ref: "#/components/schemas/AppManifestFunctions"
|
|
19462
|
+
data:
|
|
19463
|
+
$ref: "#/components/schemas/AppDataDeclaration"
|
|
19464
|
+
headless:
|
|
19465
|
+
$ref: "#/components/schemas/AppHeadlessDeclaration"
|
|
19462
19466
|
required:
|
|
19463
19467
|
- name
|
|
19464
19468
|
- version
|
|
@@ -25155,6 +25159,161 @@ components:
|
|
|
25155
25159
|
type: boolean
|
|
25156
25160
|
FacetValueDefinition:
|
|
25157
25161
|
$ref: "#/components/schemas/FacetValue"
|
|
25162
|
+
AppDataField:
|
|
25163
|
+
type: object
|
|
25164
|
+
properties:
|
|
25165
|
+
type:
|
|
25166
|
+
$ref: "#/components/schemas/AppDataFieldType"
|
|
25167
|
+
label:
|
|
25168
|
+
type: string
|
|
25169
|
+
description:
|
|
25170
|
+
type: string
|
|
25171
|
+
required:
|
|
25172
|
+
type: boolean
|
|
25173
|
+
localized:
|
|
25174
|
+
type: boolean
|
|
25175
|
+
zone:
|
|
25176
|
+
type: string
|
|
25177
|
+
enum:
|
|
25178
|
+
- data
|
|
25179
|
+
- owner
|
|
25180
|
+
- admin
|
|
25181
|
+
public:
|
|
25182
|
+
type: boolean
|
|
25183
|
+
to:
|
|
25184
|
+
type: string
|
|
25185
|
+
options:
|
|
25186
|
+
type: array
|
|
25187
|
+
items:
|
|
25188
|
+
type: string
|
|
25189
|
+
required:
|
|
25190
|
+
- type
|
|
25191
|
+
AppDataType:
|
|
25192
|
+
type: object
|
|
25193
|
+
properties:
|
|
25194
|
+
description:
|
|
25195
|
+
type: string
|
|
25196
|
+
storage:
|
|
25197
|
+
$ref: "#/components/schemas/AppDataStorage"
|
|
25198
|
+
visibility:
|
|
25199
|
+
type: string
|
|
25200
|
+
enum:
|
|
25201
|
+
- public
|
|
25202
|
+
- owner
|
|
25203
|
+
- admin
|
|
25204
|
+
anchors:
|
|
25205
|
+
type: array
|
|
25206
|
+
items:
|
|
25207
|
+
type: string
|
|
25208
|
+
enum:
|
|
25209
|
+
- product
|
|
25210
|
+
- variant
|
|
25211
|
+
- batch
|
|
25212
|
+
- proof
|
|
25213
|
+
- contact
|
|
25214
|
+
fields:
|
|
25215
|
+
type: object
|
|
25216
|
+
additionalProperties:
|
|
25217
|
+
$ref: "#/components/schemas/AppDataField"
|
|
25218
|
+
listing:
|
|
25219
|
+
type: object
|
|
25220
|
+
additionalProperties: true
|
|
25221
|
+
examples:
|
|
25222
|
+
type: array
|
|
25223
|
+
items:
|
|
25224
|
+
type: object
|
|
25225
|
+
additionalProperties: true
|
|
25226
|
+
read:
|
|
25227
|
+
type: object
|
|
25228
|
+
additionalProperties: true
|
|
25229
|
+
since:
|
|
25230
|
+
type: string
|
|
25231
|
+
required:
|
|
25232
|
+
- description
|
|
25233
|
+
- storage
|
|
25234
|
+
- fields
|
|
25235
|
+
AppDataDeclaration:
|
|
25236
|
+
type: object
|
|
25237
|
+
properties:
|
|
25238
|
+
schemaVersion:
|
|
25239
|
+
type: string
|
|
25240
|
+
types:
|
|
25241
|
+
type: object
|
|
25242
|
+
additionalProperties:
|
|
25243
|
+
$ref: "#/components/schemas/AppDataType"
|
|
25244
|
+
required:
|
|
25245
|
+
- schemaVersion
|
|
25246
|
+
- types
|
|
25247
|
+
AppHeadlessDeclaration:
|
|
25248
|
+
type: object
|
|
25249
|
+
properties:
|
|
25250
|
+
purpose:
|
|
25251
|
+
type: string
|
|
25252
|
+
categories:
|
|
25253
|
+
type: array
|
|
25254
|
+
items:
|
|
25255
|
+
$ref: "#/components/schemas/HeadlessCategory"
|
|
25256
|
+
primaryTypes:
|
|
25257
|
+
type: array
|
|
25258
|
+
items:
|
|
25259
|
+
type: string
|
|
25260
|
+
editedIn:
|
|
25261
|
+
type: object
|
|
25262
|
+
additionalProperties: true
|
|
25263
|
+
render:
|
|
25264
|
+
type: object
|
|
25265
|
+
additionalProperties: true
|
|
25266
|
+
required:
|
|
25267
|
+
- purpose
|
|
25268
|
+
- categories
|
|
25269
|
+
- primaryTypes
|
|
25270
|
+
- editedIn
|
|
25271
|
+
AppDataFieldType:
|
|
25272
|
+
type: string
|
|
25273
|
+
enum:
|
|
25274
|
+
- string
|
|
25275
|
+
- text
|
|
25276
|
+
- richtext
|
|
25277
|
+
- markdown
|
|
25278
|
+
- number
|
|
25279
|
+
- boolean
|
|
25280
|
+
- date
|
|
25281
|
+
- datetime
|
|
25282
|
+
- enum
|
|
25283
|
+
- url
|
|
25284
|
+
- image
|
|
25285
|
+
- file
|
|
25286
|
+
- ref
|
|
25287
|
+
- "string[]"
|
|
25288
|
+
- "ref[]"
|
|
25289
|
+
- json
|
|
25290
|
+
AppDataStorage:
|
|
25291
|
+
type: object
|
|
25292
|
+
additionalProperties: true
|
|
25293
|
+
HeadlessCategory:
|
|
25294
|
+
type: string
|
|
25295
|
+
enum:
|
|
25296
|
+
- faq
|
|
25297
|
+
- media
|
|
25298
|
+
- pages
|
|
25299
|
+
- articles
|
|
25300
|
+
- catalog
|
|
25301
|
+
- events
|
|
25302
|
+
- locations
|
|
25303
|
+
- people
|
|
25304
|
+
- reviews
|
|
25305
|
+
- documents
|
|
25306
|
+
- forms
|
|
25307
|
+
- other
|
|
25308
|
+
HeadlessSeoHelper:
|
|
25309
|
+
type: string
|
|
25310
|
+
enum:
|
|
25311
|
+
- faqPage
|
|
25312
|
+
- product
|
|
25313
|
+
- article
|
|
25314
|
+
- breadcrumbs
|
|
25315
|
+
- organization
|
|
25316
|
+
- localBusiness
|
|
25158
25317
|
CachedData:
|
|
25159
25318
|
type: object
|
|
25160
25319
|
properties:
|
package/dist/seo.d.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
export interface SeoHead {
|
|
2
|
+
title?: string;
|
|
3
|
+
description?: string;
|
|
4
|
+
/** Absolute image URL for link previews (og:image / twitter:image). */
|
|
5
|
+
image?: string;
|
|
6
|
+
/** Absolute canonical URL. Usually leave unset: the platform sends the site's canonical address. */
|
|
7
|
+
canonical?: string;
|
|
8
|
+
/** Keep this page out of search engines. */
|
|
9
|
+
noindex?: boolean;
|
|
10
|
+
/** og:type — 'website' (default), 'article' or 'product'. */
|
|
11
|
+
type?: 'website' | 'article' | 'product';
|
|
12
|
+
/** The site's name (og:site_name). */
|
|
13
|
+
siteName?: string;
|
|
14
|
+
}
|
|
15
|
+
/** Set this route's head tags. Call on every route change; unset fields are removed (if we added them). */
|
|
16
|
+
export declare function head(h: SeoHead): void;
|
|
17
|
+
/**
|
|
18
|
+
* Place a JSON-LD structured-data block in <head>. `id` names the block, so calling again replaces it
|
|
19
|
+
* (e.g. per route); pass null to remove it. `<` is escaped so data can't close the script tag.
|
|
20
|
+
*/
|
|
21
|
+
export declare function jsonLd(id: string, data: object | object[] | null): void;
|
|
22
|
+
type Obj = Record<string, any>;
|
|
23
|
+
export interface ProductSchemaOptions {
|
|
24
|
+
/** The page's absolute URL. */
|
|
25
|
+
url?: string;
|
|
26
|
+
/** Brand name (defaults to none). */
|
|
27
|
+
brand?: string;
|
|
28
|
+
/** An offer, when the product is sold: price as a number or string, ISO currency, availability. */
|
|
29
|
+
offer?: {
|
|
30
|
+
price: number | string;
|
|
31
|
+
currency: string;
|
|
32
|
+
availability?: 'InStock' | 'OutOfStock' | 'PreOrder';
|
|
33
|
+
url?: string;
|
|
34
|
+
};
|
|
35
|
+
/** Aggregate rating, when the site shows one. */
|
|
36
|
+
rating?: {
|
|
37
|
+
value: number;
|
|
38
|
+
count: number;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export declare const schema: {
|
|
42
|
+
/** schema.org Product from a SmartLinks product. */
|
|
43
|
+
product(product: Obj, opts?: ProductSchemaOptions): Obj;
|
|
44
|
+
/** schema.org FAQPage — answer-shaped content AI search can cite, and FAQ rich results. */
|
|
45
|
+
faqPage(items: Array<{
|
|
46
|
+
question: string;
|
|
47
|
+
answer: string;
|
|
48
|
+
}>): Obj;
|
|
49
|
+
/** schema.org Organization from a collection (the brand behind the site). */
|
|
50
|
+
organization(collection: Obj, opts?: {
|
|
51
|
+
url?: string;
|
|
52
|
+
sameAs?: string[];
|
|
53
|
+
}): Obj;
|
|
54
|
+
/** schema.org LocalBusiness — for a business with a place (shop, restaurant, studio). */
|
|
55
|
+
localBusiness(collection: Obj, opts?: {
|
|
56
|
+
url?: string;
|
|
57
|
+
telephone?: string;
|
|
58
|
+
address?: {
|
|
59
|
+
street?: string;
|
|
60
|
+
locality?: string;
|
|
61
|
+
region?: string;
|
|
62
|
+
postalCode?: string;
|
|
63
|
+
country?: string;
|
|
64
|
+
};
|
|
65
|
+
openingHours?: string[];
|
|
66
|
+
type?: string;
|
|
67
|
+
}): Obj;
|
|
68
|
+
/** schema.org BreadcrumbList from a trail of { name, url } (home first). */
|
|
69
|
+
breadcrumbs(trail: Array<{
|
|
70
|
+
name: string;
|
|
71
|
+
url: string;
|
|
72
|
+
}>): Obj;
|
|
73
|
+
/** schema.org Article (a blog post, news item, guide). Dates as ISO strings. */
|
|
74
|
+
article(a: {
|
|
75
|
+
headline: string;
|
|
76
|
+
description?: string;
|
|
77
|
+
image?: string;
|
|
78
|
+
url?: string;
|
|
79
|
+
datePublished?: string;
|
|
80
|
+
dateModified?: string;
|
|
81
|
+
author?: string;
|
|
82
|
+
publisher?: string;
|
|
83
|
+
}): Obj;
|
|
84
|
+
};
|
|
85
|
+
export {};
|
package/dist/seo.js
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// SEO + GEO helpers for apps served as websites (framework-agnostic).
|
|
3
|
+
// =============================================================================
|
|
4
|
+
// seo.head() per-route <title>, description, Open Graph / Twitter tags, canonical, robots
|
|
5
|
+
// seo.jsonLd() place a structured-data block (replaces the previous one with the same id)
|
|
6
|
+
// seo.schema.* schema.org builders from platform data (Product, FAQPage, Organization, …)
|
|
7
|
+
//
|
|
8
|
+
// They write plain DOM, so whatever pre-renders the page (the platform's renderer, a crawler that runs
|
|
9
|
+
// JavaScript) captures them. In SSR / no-document environments they're no-ops; the builders are pure.
|
|
10
|
+
// =============================================================================
|
|
11
|
+
const MARK = 'data-sl-seo';
|
|
12
|
+
function doc() {
|
|
13
|
+
return typeof document !== 'undefined' && document && document.head ? document : null;
|
|
14
|
+
}
|
|
15
|
+
function setMeta(d, attr, key, content) {
|
|
16
|
+
let el = d.head.querySelector(`meta[${attr}="${key}"]`);
|
|
17
|
+
if (content === undefined || content === null || content === '') {
|
|
18
|
+
if (el && el.getAttribute(MARK) !== null)
|
|
19
|
+
el.remove();
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (!el) {
|
|
23
|
+
el = d.createElement('meta');
|
|
24
|
+
el.setAttribute(attr, key);
|
|
25
|
+
el.setAttribute(MARK, '');
|
|
26
|
+
d.head.appendChild(el);
|
|
27
|
+
}
|
|
28
|
+
el.setAttribute('content', content);
|
|
29
|
+
}
|
|
30
|
+
/** Set this route's head tags. Call on every route change; unset fields are removed (if we added them). */
|
|
31
|
+
export function head(h) {
|
|
32
|
+
const d = doc();
|
|
33
|
+
if (!d)
|
|
34
|
+
return;
|
|
35
|
+
if (h.title)
|
|
36
|
+
d.title = h.title;
|
|
37
|
+
setMeta(d, 'name', 'description', h.description);
|
|
38
|
+
setMeta(d, 'property', 'og:title', h.title);
|
|
39
|
+
setMeta(d, 'property', 'og:description', h.description);
|
|
40
|
+
setMeta(d, 'property', 'og:image', h.image);
|
|
41
|
+
setMeta(d, 'property', 'og:type', h.type || 'website');
|
|
42
|
+
setMeta(d, 'property', 'og:site_name', h.siteName);
|
|
43
|
+
setMeta(d, 'name', 'twitter:card', h.image ? 'summary_large_image' : 'summary');
|
|
44
|
+
setMeta(d, 'name', 'twitter:title', h.title);
|
|
45
|
+
setMeta(d, 'name', 'twitter:description', h.description);
|
|
46
|
+
setMeta(d, 'name', 'twitter:image', h.image);
|
|
47
|
+
setMeta(d, 'name', 'robots', h.noindex ? 'noindex, nofollow' : undefined);
|
|
48
|
+
let link = d.head.querySelector('link[rel="canonical"]');
|
|
49
|
+
if (h.canonical) {
|
|
50
|
+
if (!link) {
|
|
51
|
+
link = d.createElement('link');
|
|
52
|
+
link.setAttribute('rel', 'canonical');
|
|
53
|
+
link.setAttribute(MARK, '');
|
|
54
|
+
d.head.appendChild(link);
|
|
55
|
+
}
|
|
56
|
+
link.setAttribute('href', h.canonical);
|
|
57
|
+
}
|
|
58
|
+
else if (link && link.getAttribute(MARK) !== null) {
|
|
59
|
+
link.remove();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Place a JSON-LD structured-data block in <head>. `id` names the block, so calling again replaces it
|
|
64
|
+
* (e.g. per route); pass null to remove it. `<` is escaped so data can't close the script tag.
|
|
65
|
+
*/
|
|
66
|
+
export function jsonLd(id, data) {
|
|
67
|
+
const d = doc();
|
|
68
|
+
if (!d)
|
|
69
|
+
return;
|
|
70
|
+
const sel = `script[type="application/ld+json"][${MARK}="${id}"]`;
|
|
71
|
+
let el = d.head.querySelector(sel);
|
|
72
|
+
if (data === null) {
|
|
73
|
+
if (el)
|
|
74
|
+
el.remove();
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (!el) {
|
|
78
|
+
el = d.createElement('script');
|
|
79
|
+
el.setAttribute('type', 'application/ld+json');
|
|
80
|
+
el.setAttribute(MARK, id);
|
|
81
|
+
d.head.appendChild(el);
|
|
82
|
+
}
|
|
83
|
+
el.textContent = JSON.stringify(data).replace(/</g, '\\u003c');
|
|
84
|
+
}
|
|
85
|
+
const CTX = 'https://schema.org';
|
|
86
|
+
/** Drop undefined / null / empty values so the output stays valid and small. */
|
|
87
|
+
function clean(o) {
|
|
88
|
+
for (const k of Object.keys(o)) {
|
|
89
|
+
const v = o[k];
|
|
90
|
+
if (v === undefined || v === null || v === '' || (Array.isArray(v) && v.length === 0))
|
|
91
|
+
delete o[k];
|
|
92
|
+
}
|
|
93
|
+
return o;
|
|
94
|
+
}
|
|
95
|
+
const imageOf = (x) => { var _a, _b, _c; return ((_a = x === null || x === void 0 ? void 0 : x.heroImage) === null || _a === void 0 ? void 0 : _a.url) || ((_b = x === null || x === void 0 ? void 0 : x.logoImage) === null || _b === void 0 ? void 0 : _b.url) || ((_c = x === null || x === void 0 ? void 0 : x.image) === null || _c === void 0 ? void 0 : _c.url) || (typeof (x === null || x === void 0 ? void 0 : x.image) === 'string' ? x.image : undefined); };
|
|
96
|
+
export const schema = {
|
|
97
|
+
/** schema.org Product from a SmartLinks product. */
|
|
98
|
+
product(product, opts = {}) {
|
|
99
|
+
const images = [imageOf(product), ...(product.additionalImages || []).map((i) => i === null || i === void 0 ? void 0 : i.url)].filter(Boolean);
|
|
100
|
+
return clean({
|
|
101
|
+
'@context': CTX,
|
|
102
|
+
'@type': 'Product',
|
|
103
|
+
name: product.name,
|
|
104
|
+
description: product.description || undefined,
|
|
105
|
+
image: images.length > 1 ? images : images[0],
|
|
106
|
+
sku: product.sku || undefined,
|
|
107
|
+
gtin: product.gtin || product.ownGtin || undefined,
|
|
108
|
+
url: opts.url,
|
|
109
|
+
brand: opts.brand ? { '@type': 'Brand', name: opts.brand } : undefined,
|
|
110
|
+
offers: opts.offer ? clean({
|
|
111
|
+
'@type': 'Offer',
|
|
112
|
+
price: String(opts.offer.price),
|
|
113
|
+
priceCurrency: opts.offer.currency,
|
|
114
|
+
availability: opts.offer.availability ? `${CTX}/${opts.offer.availability}` : undefined,
|
|
115
|
+
url: opts.offer.url || opts.url,
|
|
116
|
+
}) : undefined,
|
|
117
|
+
aggregateRating: opts.rating ? { '@type': 'AggregateRating', ratingValue: opts.rating.value, reviewCount: opts.rating.count } : undefined,
|
|
118
|
+
});
|
|
119
|
+
},
|
|
120
|
+
/** schema.org FAQPage — answer-shaped content AI search can cite, and FAQ rich results. */
|
|
121
|
+
faqPage(items) {
|
|
122
|
+
return {
|
|
123
|
+
'@context': CTX,
|
|
124
|
+
'@type': 'FAQPage',
|
|
125
|
+
mainEntity: items.filter((i) => i && i.question && i.answer).map((i) => ({
|
|
126
|
+
'@type': 'Question',
|
|
127
|
+
name: i.question,
|
|
128
|
+
acceptedAnswer: { '@type': 'Answer', text: i.answer },
|
|
129
|
+
})),
|
|
130
|
+
};
|
|
131
|
+
},
|
|
132
|
+
/** schema.org Organization from a collection (the brand behind the site). */
|
|
133
|
+
organization(collection, opts = {}) {
|
|
134
|
+
return clean({
|
|
135
|
+
'@context': CTX,
|
|
136
|
+
'@type': 'Organization',
|
|
137
|
+
name: collection.title || collection.name,
|
|
138
|
+
description: collection.description || undefined,
|
|
139
|
+
logo: imageOf(collection),
|
|
140
|
+
url: opts.url || (collection.hubCustomDomain ? `https://${collection.hubCustomDomain}` : collection.siteHost ? `https://${collection.siteHost}` : undefined),
|
|
141
|
+
sameAs: opts.sameAs,
|
|
142
|
+
});
|
|
143
|
+
},
|
|
144
|
+
/** schema.org LocalBusiness — for a business with a place (shop, restaurant, studio). */
|
|
145
|
+
localBusiness(collection, opts = {}) {
|
|
146
|
+
const a = opts.address;
|
|
147
|
+
return clean(Object.assign(Object.assign({}, schema.organization(collection, { url: opts.url })), { '@type': opts.type || 'LocalBusiness', telephone: opts.telephone, address: a ? clean({ '@type': 'PostalAddress', streetAddress: a.street, addressLocality: a.locality, addressRegion: a.region, postalCode: a.postalCode, addressCountry: a.country }) : undefined, openingHours: opts.openingHours }));
|
|
148
|
+
},
|
|
149
|
+
/** schema.org BreadcrumbList from a trail of { name, url } (home first). */
|
|
150
|
+
breadcrumbs(trail) {
|
|
151
|
+
return {
|
|
152
|
+
'@context': CTX,
|
|
153
|
+
'@type': 'BreadcrumbList',
|
|
154
|
+
itemListElement: trail.map((t, i) => ({ '@type': 'ListItem', position: i + 1, name: t.name, item: t.url })),
|
|
155
|
+
};
|
|
156
|
+
},
|
|
157
|
+
/** schema.org Article (a blog post, news item, guide). Dates as ISO strings. */
|
|
158
|
+
article(a) {
|
|
159
|
+
return clean({
|
|
160
|
+
'@context': CTX,
|
|
161
|
+
'@type': 'Article',
|
|
162
|
+
headline: a.headline,
|
|
163
|
+
description: a.description,
|
|
164
|
+
image: a.image,
|
|
165
|
+
url: a.url,
|
|
166
|
+
datePublished: a.datePublished,
|
|
167
|
+
dateModified: a.dateModified || a.datePublished,
|
|
168
|
+
author: a.author ? { '@type': 'Person', name: a.author } : undefined,
|
|
169
|
+
publisher: a.publisher ? { '@type': 'Organization', name: a.publisher } : undefined,
|
|
170
|
+
});
|
|
171
|
+
},
|
|
172
|
+
};
|
package/dist/site.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
declare global {
|
|
2
|
+
var __SL_READY__: boolean | undefined;
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Tell the platform this page has finished rendering its content (data loaded, head tags set). The
|
|
6
|
+
* platform's page renderer takes its snapshot for search engines and AI crawlers at this point;
|
|
7
|
+
* without it, it waits for the network to go idle (capped). Call once per page, after your data
|
|
8
|
+
* renders. Harmless anywhere else.
|
|
9
|
+
*/
|
|
10
|
+
export declare function ready(): void;
|
package/dist/site.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// site — for apps served as websites.
|
|
3
|
+
// =============================================================================
|
|
4
|
+
/**
|
|
5
|
+
* Tell the platform this page has finished rendering its content (data loaded, head tags set). The
|
|
6
|
+
* platform's page renderer takes its snapshot for search engines and AI crawlers at this point;
|
|
7
|
+
* without it, it waits for the network to go idle (capped). Call once per page, after your data
|
|
8
|
+
* renders. Harmless anywhere else.
|
|
9
|
+
*/
|
|
10
|
+
export function ready() {
|
|
11
|
+
if (typeof globalThis === 'undefined')
|
|
12
|
+
return;
|
|
13
|
+
globalThis.__SL_READY__ = true;
|
|
14
|
+
const w = typeof window !== 'undefined' ? window : null;
|
|
15
|
+
if (w && typeof w.dispatchEvent === 'function' && typeof w.CustomEvent === 'function') {
|
|
16
|
+
w.dispatchEvent(new w.CustomEvent('smartlinks:ready'));
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
/// <reference types="node" />
|
|
3
|
+
import type { AppDataDeclaration, AppHeadlessDeclaration } from './headless.js';
|
|
3
4
|
/**
|
|
4
5
|
* A bundle (widget or container) as returned by the collection widgets endpoint.
|
|
5
6
|
*
|
|
@@ -662,6 +663,16 @@ export interface AppManifest {
|
|
|
662
663
|
* @see AppManifestFunctions
|
|
663
664
|
*/
|
|
664
665
|
functions?: AppManifestFunctions;
|
|
666
|
+
/**
|
|
667
|
+
* The app's public data model: its data types, how each is stored, fields, examples and read
|
|
668
|
+
* recipes. Required for `headless`. @see AppDataDeclaration · docs/headless-providers.md
|
|
669
|
+
*/
|
|
670
|
+
data?: AppDataDeclaration;
|
|
671
|
+
/**
|
|
672
|
+
* Opt-in: other sites and apps may install this app and use its content (a headless content
|
|
673
|
+
* provider). Validated by `smartlinks-headless`. @see AppHeadlessDeclaration
|
|
674
|
+
*/
|
|
675
|
+
headless?: AppHeadlessDeclaration;
|
|
665
676
|
[key: string]: any;
|
|
666
677
|
}
|
|
667
678
|
/**
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/** Field value types. Keep this set small: generated editors, typed reads and sites rely on it. */
|
|
2
|
+
export type AppDataFieldType = 'string' | 'text' | 'richtext' | 'markdown' | 'number' | 'boolean' | 'date' | 'datetime' | 'enum' | 'url' | 'image' | 'file' | 'ref' | 'string[]' | 'ref[]' | 'json';
|
|
3
|
+
export interface AppDataField {
|
|
4
|
+
type: AppDataFieldType;
|
|
5
|
+
label?: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
required?: boolean;
|
|
8
|
+
/** Translated per language (string / text / richtext / markdown only). */
|
|
9
|
+
localized?: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* The JSONB zone it lives in (app objects): `data` (default), `owner`, or `admin`. Only `data`
|
|
12
|
+
* can be public; `admin` is never readable on public endpoints.
|
|
13
|
+
*/
|
|
14
|
+
zone?: 'data' | 'owner' | 'admin';
|
|
15
|
+
/** Readable by anonymous visitors (when the item's visibility is public). */
|
|
16
|
+
public?: boolean;
|
|
17
|
+
/** For `ref` / `ref[]`: the declared type id, or 'product' | 'contact' | 'proof'. */
|
|
18
|
+
to?: string;
|
|
19
|
+
/** For `enum`: the allowed values. */
|
|
20
|
+
options?: string[];
|
|
21
|
+
}
|
|
22
|
+
/** How a type's items are stored. */
|
|
23
|
+
export type AppDataStorage =
|
|
24
|
+
/** App records (`SL.app.records`), filtered by recordType. The usual home for content. */
|
|
25
|
+
{
|
|
26
|
+
kind: 'record';
|
|
27
|
+
recordType: string;
|
|
28
|
+
}
|
|
29
|
+
/** App cases (`SL.app.cases`) — requests that get resolved. */
|
|
30
|
+
| {
|
|
31
|
+
kind: 'case';
|
|
32
|
+
category?: string;
|
|
33
|
+
}
|
|
34
|
+
/** App threads (`SL.app.threads`) — discussions, Q&A, reviews. */
|
|
35
|
+
| {
|
|
36
|
+
kind: 'thread';
|
|
37
|
+
slug?: string;
|
|
38
|
+
}
|
|
39
|
+
/** App configuration (`SL.appConfiguration.getConfig` / data items) — settings, small lists. */
|
|
40
|
+
| {
|
|
41
|
+
kind: 'config';
|
|
42
|
+
key?: string;
|
|
43
|
+
};
|
|
44
|
+
export interface AppDataType {
|
|
45
|
+
/** What one item is, in a sentence. */
|
|
46
|
+
description: string;
|
|
47
|
+
storage: AppDataStorage;
|
|
48
|
+
/** The visibility items normally have (app objects): public, owner or admin. Default public. */
|
|
49
|
+
visibility?: 'public' | 'owner' | 'admin';
|
|
50
|
+
/** What an item can be attached to (app objects' anchor columns). */
|
|
51
|
+
anchors?: Array<'product' | 'variant' | 'batch' | 'proof' | 'contact'>;
|
|
52
|
+
fields: Record<string, AppDataField>;
|
|
53
|
+
/** How a site normally lists them. */
|
|
54
|
+
listing?: {
|
|
55
|
+
sort?: string[];
|
|
56
|
+
filters?: string[];
|
|
57
|
+
};
|
|
58
|
+
/** Real-shaped sample items (the `data` zone's JSON). At least one for a headless type. */
|
|
59
|
+
examples?: Array<Record<string, unknown>>;
|
|
60
|
+
/** SDK recipes a site uses. If omitted, the checker prints the standard ones for the storage. */
|
|
61
|
+
read?: {
|
|
62
|
+
list?: string;
|
|
63
|
+
get?: string;
|
|
64
|
+
notes?: string;
|
|
65
|
+
};
|
|
66
|
+
/** The app version that introduced this type. */
|
|
67
|
+
since?: string;
|
|
68
|
+
}
|
|
69
|
+
export interface AppDataDeclaration {
|
|
70
|
+
/** Semver of the data contract itself (not the app's version). Major = breaking. */
|
|
71
|
+
schemaVersion: string;
|
|
72
|
+
types: Record<string, AppDataType>;
|
|
73
|
+
}
|
|
74
|
+
/** What kind of content a provider supplies — how site builders find it. */
|
|
75
|
+
export type HeadlessCategory = 'faq' | 'media' | 'pages' | 'articles' | 'catalog' | 'events' | 'locations' | 'people' | 'reviews' | 'documents' | 'forms' | 'other';
|
|
76
|
+
export declare const HEADLESS_CATEGORIES: readonly HeadlessCategory[];
|
|
77
|
+
/** SEO structured-data helpers a site should use with this content (`SL.seo.schema.*`). */
|
|
78
|
+
export type HeadlessSeoHelper = 'faqPage' | 'product' | 'article' | 'breadcrumbs' | 'organization' | 'localBusiness';
|
|
79
|
+
export interface AppHeadlessDeclaration {
|
|
80
|
+
/** What content this app holds, and when a site should use it rather than build its own. */
|
|
81
|
+
purpose: string;
|
|
82
|
+
categories: HeadlessCategory[];
|
|
83
|
+
/** The `data` types a site renders (the rest are supporting types, e.g. categories). */
|
|
84
|
+
primaryTypes: string[];
|
|
85
|
+
/** Where the business edits the content — the app's admin inside SmartLinks. Sites never build an editor for it. */
|
|
86
|
+
editedIn: {
|
|
87
|
+
label: string;
|
|
88
|
+
adminPath?: string;
|
|
89
|
+
notes?: string;
|
|
90
|
+
};
|
|
91
|
+
/** How a site should present it. */
|
|
92
|
+
render?: {
|
|
93
|
+
guidance?: string;
|
|
94
|
+
seo?: HeadlessSeoHelper[];
|
|
95
|
+
};
|
|
96
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Headless providers — an app's declared public data model, and its promise that other sites and
|
|
3
|
+
// apps can install it and use its content (FAQ, media, content pages, articles…).
|
|
4
|
+
//
|
|
5
|
+
// Two manifest blocks:
|
|
6
|
+
// data the app's data types: how each is stored, its fields (JSON types, zone, public or not),
|
|
7
|
+
// examples, and how to read it. Useful on its own (typed reads, generated editors).
|
|
8
|
+
// headless the opt-in: purpose, categories, which types a site renders, where the owner edits the
|
|
9
|
+
// content, rendering + SEO guidance. Requires a complete `data` block.
|
|
10
|
+
//
|
|
11
|
+
// Validate with `smartlinks-headless` (scripts/headless-check.mjs) or validateHeadless() (SDK).
|
|
12
|
+
// Docs: docs/headless-providers.md.
|
|
13
|
+
// =============================================================================
|
|
14
|
+
export const HEADLESS_CATEGORIES = [
|
|
15
|
+
'faq', 'media', 'pages', 'articles', 'catalog', 'events', 'locations', 'people', 'reviews', 'documents', 'forms', 'other',
|
|
16
|
+
];
|
package/dist/types/index.d.ts
CHANGED
package/dist/types/index.js
CHANGED