@proveanything/smartlinks 2.0.36 → 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/api/ai.d.ts +2 -2
- package/dist/api/ai.js +2 -2
- package/dist/context.js +15 -0
- package/dist/docs/API_SUMMARY.md +76 -3
- package/dist/docs/headless-providers.md +104 -0
- package/dist/docs/server-functions.md +1 -1
- 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 +184 -5
- 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/ai.d.ts +12 -15
- package/dist/types/appManifest.d.ts +11 -0
- package/dist/types/appManifest.js +0 -1
- package/dist/types/collection.d.ts +1 -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 +76 -3
- package/docs/headless-providers.md +104 -0
- package/docs/server-functions.md +1 -1
- package/docs/site-seo.md +67 -0
- package/openapi.yaml +184 -5
- package/package.json +4 -2
- package/scripts/headless-check.mjs +87 -0
package/dist/openapi.yaml
CHANGED
|
@@ -1347,7 +1347,7 @@ paths:
|
|
|
1347
1347
|
get:
|
|
1348
1348
|
tags:
|
|
1349
1349
|
- sessions
|
|
1350
|
-
summary: AI usage
|
|
1350
|
+
summary: AI usage for a collection (daily totals), grouped by any of model/appId/feature/mode/surface/provider/day over an optional date window (YYYY-MM-DD).
|
|
1351
1351
|
operationId: sessions_usage
|
|
1352
1352
|
security:
|
|
1353
1353
|
- bearerAuth: []
|
|
@@ -16327,17 +16327,37 @@ components:
|
|
|
16327
16327
|
to:
|
|
16328
16328
|
type: string
|
|
16329
16329
|
totals:
|
|
16330
|
-
|
|
16331
|
-
additionalProperties: true
|
|
16330
|
+
$ref: "#/components/schemas/AiUsageTotals"
|
|
16332
16331
|
groups:
|
|
16333
16332
|
type: array
|
|
16334
16333
|
items:
|
|
16335
|
-
|
|
16336
|
-
additionalProperties: true
|
|
16334
|
+
$ref: "#/components/schemas/AiUsageTotals"
|
|
16337
16335
|
required:
|
|
16338
16336
|
- groupBy
|
|
16339
16337
|
- totals
|
|
16340
16338
|
- groups
|
|
16339
|
+
AiUsageTotals:
|
|
16340
|
+
type: object
|
|
16341
|
+
properties:
|
|
16342
|
+
promptTokens:
|
|
16343
|
+
type: number
|
|
16344
|
+
outputTokens:
|
|
16345
|
+
type: number
|
|
16346
|
+
cachedTokens:
|
|
16347
|
+
type: number
|
|
16348
|
+
totalTokens:
|
|
16349
|
+
type: number
|
|
16350
|
+
requests:
|
|
16351
|
+
type: number
|
|
16352
|
+
images:
|
|
16353
|
+
type: number
|
|
16354
|
+
required:
|
|
16355
|
+
- promptTokens
|
|
16356
|
+
- outputTokens
|
|
16357
|
+
- cachedTokens
|
|
16358
|
+
- totalTokens
|
|
16359
|
+
- requests
|
|
16360
|
+
- images
|
|
16341
16361
|
VoiceSessionRequest:
|
|
16342
16362
|
type: object
|
|
16343
16363
|
properties:
|
|
@@ -19439,6 +19459,10 @@ components:
|
|
|
19439
19459
|
$ref: "#/components/schemas/AppManifestExecutor"
|
|
19440
19460
|
functions:
|
|
19441
19461
|
$ref: "#/components/schemas/AppManifestFunctions"
|
|
19462
|
+
data:
|
|
19463
|
+
$ref: "#/components/schemas/AppDataDeclaration"
|
|
19464
|
+
headless:
|
|
19465
|
+
$ref: "#/components/schemas/AppHeadlessDeclaration"
|
|
19442
19466
|
required:
|
|
19443
19467
|
- name
|
|
19444
19468
|
- version
|
|
@@ -25135,6 +25159,161 @@ components:
|
|
|
25135
25159
|
type: boolean
|
|
25136
25160
|
FacetValueDefinition:
|
|
25137
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
|
|
25138
25317
|
CachedData:
|
|
25139
25318
|
type: object
|
|
25140
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
|
+
}
|
package/dist/types/ai.d.ts
CHANGED
|
@@ -412,25 +412,22 @@ export interface AiSessionCreate {
|
|
|
412
412
|
/** Seed items (e.g. a system/instruction turn). */
|
|
413
413
|
items?: ResponseInputItem[];
|
|
414
414
|
}
|
|
415
|
-
/** Per-collection AI usage
|
|
415
|
+
/** Per-collection AI usage report (daily totals). Usage only — requests, tokens, images; no cost figures. */
|
|
416
416
|
export interface AiUsageReport {
|
|
417
417
|
groupBy: string[];
|
|
418
418
|
from?: string | null;
|
|
419
419
|
to?: string | null;
|
|
420
|
-
totals:
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
requests: number;
|
|
432
|
-
costUnits: number;
|
|
433
|
-
}>;
|
|
420
|
+
totals: AiUsageTotals;
|
|
421
|
+
groups: Array<Record<string, any> & AiUsageTotals>;
|
|
422
|
+
}
|
|
423
|
+
/** Summed usage for a report group (or the whole window). */
|
|
424
|
+
export interface AiUsageTotals {
|
|
425
|
+
promptTokens: number;
|
|
426
|
+
outputTokens: number;
|
|
427
|
+
cachedTokens: number;
|
|
428
|
+
totalTokens: number;
|
|
429
|
+
requests: number;
|
|
430
|
+
images: number;
|
|
434
431
|
}
|
|
435
432
|
/** Voice session request */
|
|
436
433
|
export interface VoiceSessionRequest {
|
|
@@ -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
|
/**
|
|
@@ -48,7 +48,7 @@ export interface Collection {
|
|
|
48
48
|
hubCustomDomain?: string;
|
|
49
49
|
/**
|
|
50
50
|
* The collection's SITE HOST on the tenant domain — computed by the server, never stored:
|
|
51
|
-
* `<hubName>.smartlinks.host` when a name is claimed, else the default `c-<
|
|
51
|
+
* `<hubName>.smartlinks.host` when a name is claimed, else the default `c-<collectionId>.smartlinks.host`.
|
|
52
52
|
* Hub serves it, and app functions are reachable under it at `https://<siteHost>/_fn/…`
|
|
53
53
|
* (see SL.functions.siteUrl).
|
|
54
54
|
*/
|
|
@@ -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
|
+
}
|