@proveanything/smartlinks 2.0.37 → 2.0.39

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.
@@ -62,7 +62,7 @@ export declare namespace functions {
62
62
  }): string;
63
63
  /**
64
64
  * Call a PUBLIC app server function inline (surface `'public'`).
65
- * App-scoped: `POST /public/collection/:c/app/:appId/functions/:name`.
65
+ * App-scoped: `POST /fn/public/collection/:c/app/:appId/functions/:name`.
66
66
  *
67
67
  * @example
68
68
  * // App calling its own function (appId from initializeApi({ appId })):
@@ -73,7 +73,7 @@ export declare namespace functions {
73
73
  function call<T = FunctionCallResult>(collectionId: string, name: string, body?: Record<string, any>, opts?: FunctionCallOptions): Promise<T>;
74
74
  /**
75
75
  * Call an ADMIN app server function (surface `'admin'`; requires an admin session).
76
- * App-scoped: `POST /admin/collection/:c/app/:appId/functions/:name`.
76
+ * App-scoped: `POST /fn/admin/collection/:c/app/:appId/functions/:name`.
77
77
  */
78
78
  function callAdmin<T = FunctionCallResult>(collectionId: string, name: string, body?: Record<string, any>, opts?: FunctionCallOptions): Promise<T>;
79
79
  /**
@@ -14,6 +14,10 @@
14
14
  // which the server resolves by bare name and REJECTS with 409 AMBIGUOUS_FUNCTION when more than one
15
15
  // installed app defines that name. Always prefer an appId.
16
16
  //
17
+ // PREFIX. App-scoped calls go to /fn/{public|admin}/collection/…/functions/… (under the API base,
18
+ // so /api/v1/fn/…): one prefix for all function traffic, which the platform can route to its own
19
+ // service. The same call without /fn still works (older SDKs). The flat alias has no /fn form.
20
+ //
17
21
  // RELEASE CHANNEL. The channel is part of the URL — /collection/:c/app/:appId/<channel>/functions/:name
18
22
  // — never a query param (the function owns its query string, and a configured URL such as a webhook
19
23
  // can only ever hit the channel it names). With NO channel the server runs the release the collection
@@ -55,7 +59,7 @@ function appBase(surface, collectionId, opts) {
55
59
  if (!app)
56
60
  return `/${surface}/collection/${c}`; // deprecated flat alias — resolves installed apps only
57
61
  const ch = resolveFunctionChannel(opts, app);
58
- return `/${surface}/collection/${c}/app/${encodeURIComponent(app)}${ch ? `/${ch}` : ''}`;
62
+ return `/fn/${surface}/collection/${c}/app/${encodeURIComponent(app)}${ch ? `/${ch}` : ''}`;
59
63
  }
60
64
  /** The API path a function call goes to (exported for hosts/tests that need the exact URL). */
61
65
  export function functionPath(surface, collectionId, name, opts = {}) {
@@ -94,7 +98,7 @@ export var functions;
94
98
  functions.siteUrl = siteUrl;
95
99
  /**
96
100
  * Call a PUBLIC app server function inline (surface `'public'`).
97
- * App-scoped: `POST /public/collection/:c/app/:appId/functions/:name`.
101
+ * App-scoped: `POST /fn/public/collection/:c/app/:appId/functions/:name`.
98
102
  *
99
103
  * @example
100
104
  * // App calling its own function (appId from initializeApi({ appId })):
@@ -108,7 +112,7 @@ export var functions;
108
112
  functions.call = call;
109
113
  /**
110
114
  * Call an ADMIN app server function (surface `'admin'`; requires an admin session).
111
- * App-scoped: `POST /admin/collection/:c/app/:appId/functions/:name`.
115
+ * App-scoped: `POST /fn/admin/collection/:c/app/:appId/functions/:name`.
112
116
  */
113
117
  async function callAdmin(collectionId, name, body = {}, opts = {}) {
114
118
  return post(fnPath('admin', collectionId, name, opts), body);
@@ -1,6 +1,6 @@
1
1
  # Smartlinks API Summary
2
2
 
3
- Version: 2.0.37 | Generated: 2026-10-04T09:59:59.885Z
3
+ Version: 2.0.39 | Generated: 2026-10-05T14:14:46.558Z
4
4
 
5
5
  This is a concise summary of all available API functions and types.
6
6
 
@@ -26,6 +26,12 @@ For detailed guides on specific features:
26
26
  - **[iframe Responder](iframe-responder.md)** - iframe integration and cross-origin communication (incl. hand-rolled streaming protocol)
27
27
  - **[Utilities](utils.md)** - Helper functions for building portal paths, URLs, and common tasks
28
28
  - **[UI Utils](ui-utils.md)** - Reusable, themeable admin UI React component library for microapps
29
+ - **[Headless Providers](headless-providers.md)** - Declaring an app's content for other sites: the manifest `data` + `headless` blocks (types, storage, public fields, examples, read recipes, categories), `SL.headless.validate`, `smartlinks-headless`, and the procedure for adding a headless mode
30
+ - **[Websites: SEO + GEO](site-seo.md)** - For apps served as websites: platform-generated robots/sitemap/llms.txt, canonical addresses, `SL.seo.head` / `SL.seo.jsonLd` / `SL.seo.schema.*`, `SL.site.ready()`, routes in `sitemap-paths.txt`
31
+ - **[Agent Tools](agent-tools.md)** - Exposing app functions as AI agent tools
32
+ - **[Host Dependency Contract](host-dependency-contract.md)** - The shared dependencies (React, the SDK…) the host provides, and what an app must externalise
33
+ - **[Theme Tokens](theme-tokens.md)** - The `--sl-*` semantic token contract hosts set and apps bind to (`theme.css`)
34
+ - **[CSS Baseline](css-baseline.md)** - The frozen `sl-*` structural helper classes hosts guarantee
29
35
  - **[Caching](caching.md)** - Multi-tier caching strategy (in-memory, SessionStorage, IndexedDB) used by the SDK
30
36
  - **[Native Facade](native-facade.md)** - Contract layer for accessing device capabilities (share, NFC, haptics) across host shells
31
37
  - **[i18n](i18n.md)** - Internationalization and localization
@@ -2748,6 +2754,8 @@ interface AppManifest {
2748
2754
  publicViews?: PublicView[];
2749
2755
  executor?: AppManifestExecutor;
2750
2756
  functions?: AppManifestFunctions;
2757
+ data?: AppDataDeclaration;
2758
+ headless?: AppHeadlessDeclaration;
2751
2759
  [key: string]: any;
2752
2760
  }
2753
2761
  ```
@@ -6459,6 +6467,65 @@ interface FacetValueGetParams {
6459
6467
 
6460
6468
  **FacetValueDefinition** = `FacetValue`
6461
6469
 
6470
+ ### headless
6471
+
6472
+ **AppDataField** (interface)
6473
+ ```typescript
6474
+ interface AppDataField {
6475
+ type: AppDataFieldType;
6476
+ label?: string;
6477
+ description?: string;
6478
+ required?: boolean;
6479
+ localized?: boolean;
6480
+ zone?: 'data' | 'owner' | 'admin';
6481
+ public?: boolean;
6482
+ to?: string;
6483
+ options?: string[];
6484
+ }
6485
+ ```
6486
+
6487
+ **AppDataType** (interface)
6488
+ ```typescript
6489
+ interface AppDataType {
6490
+ description: string;
6491
+ storage: AppDataStorage;
6492
+ visibility?: 'public' | 'owner' | 'admin';
6493
+ anchors?: Array<'product' | 'variant' | 'batch' | 'proof' | 'contact'>;
6494
+ fields: Record<string, AppDataField>;
6495
+ listing?: { sort?: string[]; filters?: string[] };
6496
+ examples?: Array<Record<string, unknown>>;
6497
+ read?: { list?: string; get?: string; notes?: string };
6498
+ since?: string;
6499
+ }
6500
+ ```
6501
+
6502
+ **AppDataDeclaration** (interface)
6503
+ ```typescript
6504
+ interface AppDataDeclaration {
6505
+ schemaVersion: string;
6506
+ types: Record<string, AppDataType>;
6507
+ }
6508
+ ```
6509
+
6510
+ **AppHeadlessDeclaration** (interface)
6511
+ ```typescript
6512
+ interface AppHeadlessDeclaration {
6513
+ purpose: string;
6514
+ categories: HeadlessCategory[];
6515
+ primaryTypes: string[];
6516
+ editedIn: { label: string; adminPath?: string; notes?: string };
6517
+ render?: { guidance?: string; seo?: HeadlessSeoHelper[] };
6518
+ }
6519
+ ```
6520
+
6521
+ **AppDataFieldType** = ``
6522
+
6523
+ **AppDataStorage** = ``
6524
+
6525
+ **HeadlessCategory** = ``
6526
+
6527
+ **HeadlessSeoHelper** = `'faqPage' | 'product' | 'article' | 'breadcrumbs' | 'organization' | 'localBusiness'`
6528
+
6462
6529
  ### iframeResponder
6463
6530
 
6464
6531
  **CachedData** (interface)
@@ -11131,22 +11198,22 @@ The release channel a call targets, or undefined for "the collection's installed
11131
11198
  **functionPath**(surface: 'public' | 'admin', collectionId: string, name: string, opts: FunctionCallOptions = {}) → `string`
11132
11199
  The API path a function call goes to (exported for hosts/tests that need the exact URL).
11133
11200
 
11134
- **siteUrl**(collection: { siteHost?: string | null } | string,
11135
- name: string,
11201
+ **siteUrl**(collection: { siteHost?: string | null } | string,
11202
+ name: string,
11136
11203
  opts: FunctionSiteUrlOptions & { appId?: string } = {}) → `string`
11137
11204
  The PUBLIC address of an app function on the collection's own site — what you give a third party as a webhook URL, or call from the collection's public pages: `https://<siteHost>/_fn/<appId>[/<channel>]/<name>[/<path>]`. Every HTTP method the function declares works there, with the raw body for signature checks. Pass the collection (or its siteHost). This address is for public/integration calls; signed-in calls from your app keep using {@link call} / {@link callAdmin}, so the user's SmartLinks session never goes to a tenant hostname. const col = await SL.collection.get(collectionId) const hookUrl = SL.functions.siteUrl(col, 'stripeWebhook', { appId: 'my-shop' }) // → https://acme.smartlinks.host/_fn/my-shop/stripeWebhook
11138
11205
 
11139
- **call**(collectionId: string,
11140
- name: string,
11141
- body: Record<string, any> = {},
11206
+ **call**(collectionId: string,
11207
+ name: string,
11208
+ body: Record<string, any> = {},
11142
11209
  opts: FunctionCallOptions = {}) → `Promise<T>`
11143
- Call a PUBLIC app server function inline (surface `'public'`). App-scoped: `POST /public/collection/:c/app/:appId/functions/:name`. // App calling its own function (appId from initializeApi({ appId })): const { value } = await SL.functions.call<{ value: number }>(collectionId, 'pressCounter') // Or address another app explicitly: await SL.functions.call(collectionId, 'pressCounter', {}, { appId: 'my-counter-app' })
11210
+ Call a PUBLIC app server function inline (surface `'public'`). App-scoped: `POST /fn/public/collection/:c/app/:appId/functions/:name`. // App calling its own function (appId from initializeApi({ appId })): const { value } = await SL.functions.call<{ value: number }>(collectionId, 'pressCounter') // Or address another app explicitly: await SL.functions.call(collectionId, 'pressCounter', {}, { appId: 'my-counter-app' })
11144
11211
 
11145
- **callAdmin**(collectionId: string,
11146
- name: string,
11147
- body: Record<string, any> = {},
11212
+ **callAdmin**(collectionId: string,
11213
+ name: string,
11214
+ body: Record<string, any> = {},
11148
11215
  opts: FunctionCallOptions = {}) → `Promise<T>`
11149
- Call an ADMIN app server function (surface `'admin'`; requires an admin session). App-scoped: `POST /admin/collection/:c/app/:appId/functions/:name`.
11216
+ Call an ADMIN app server function (surface `'admin'`; requires an admin session). App-scoped: `POST /fn/admin/collection/:c/app/:appId/functions/:name`.
11150
11217
 
11151
11218
  **list**(collectionId: string, opts: FunctionCallOptions = {}) → `Promise<FunctionListResponse>`
11152
11219
  List the public functions available for a collection (discovery). Scoped to one app when an appId is given (or set as the SDK app context): `GET /public/collection/:c[/app/:appId]/functions`.
@@ -0,0 +1,112 @@
1
+ # Headless providers: declaring an app's content for other sites
2
+
3
+ A **headless provider** is an app whose content other sites and apps may use: an FAQ app, a media gallery, content pages, a blog. The site renders the content its own way. The business keeps editing it in the provider's own admin, inside SmartLinks. A site never builds an editor for provider content.
4
+
5
+ To become a provider, an app declares two blocks in `app.manifest.json`:
6
+ - **`data`**: its data types, meaning how each is stored, its fields, examples and read recipes;
7
+ - **`headless`**: the opt-in, meaning its purpose, categories, which types a site renders, where the owner edits it, and rendering guidance.
8
+
9
+ Site builders (the Forge agent included) read these declarations to decide "install the FAQ app and use its content" instead of inventing their own.
10
+
11
+ ## The `data` block
12
+
13
+ ```jsonc
14
+ "data": {
15
+ "schemaVersion": "1.0.0", // semver of THIS contract; a major = a breaking change
16
+ "types": {
17
+ "<app>.<thing>": { // lowercase, dot-separated, e.g. "faq.item"
18
+ "description": "What one item is, in a sentence.",
19
+ "storage": { "kind": "record", "recordType": "<the recordType the app writes>" },
20
+ "visibility": "public", // the visibility items normally have: public | owner | admin
21
+ "anchors": ["product"], // optional: what items can be attached to
22
+ "fields": {
23
+ "<key>": {
24
+ "type": "string", // see field types
25
+ "label": "Question",
26
+ "required": true,
27
+ "localized": true, // translated per language (text types only)
28
+ "public": true, // readable by anonymous visitors
29
+ "zone": "data" // data (default) | owner | admin
30
+ }
31
+ },
32
+ "listing": { "sort": ["order"], "filters": ["category"] },
33
+ "examples": [ { "<key>": "realistic value" } ],
34
+ "read": { // optional: defaults to the standard calls for the storage
35
+ "list": "SL.app.records.list(collectionId, appId, { recordType: '...', limit: 50 })",
36
+ "get": "SL.app.records.get(collectionId, appId, recordId)",
37
+ "notes": "anything a site must know, e.g. 'sort by order, then title'"
38
+ }
39
+ }
40
+ }
41
+ }
42
+ ```
43
+
44
+ **Storage kinds:**
45
+
46
+ | `storage.kind` | The app keeps items in | Read with |
47
+ |---|---|---|
48
+ | `record` (+ `recordType`) | App records: the usual home for content | `SL.app.records.list / get`, filtered by `recordType` |
49
+ | `case` | App cases: requests that get resolved | `SL.app.cases.list / get` |
50
+ | `thread` | App threads: discussions, Q&A, reviews | `SL.app.threads.list / get` |
51
+ | `config` (+ optional `key`) | App configuration: settings, small fixed lists | `SL.appConfiguration.getConfig({ collectionId, appId })` |
52
+
53
+ **What the read calls return.** This is fixed by the storage kind, so a provider never needs to describe it, and site builders get it from the checker and `forge-cms describe`:
54
+
55
+ - **`record`, `case`, `thread`:** `list` returns `{ data: Item[], pagination: { total, limit, offset, hasMore } }`.
56
+ - The items are in **`response.data`**, never `response.items` or `response.records`.
57
+ - Each item's declared fields are in **`item.data`** (e.g. `record.data.question`). `id`, `status`, `productId` and the dates are top-level.
58
+ - Page with `offset` / `limit` while `pagination.hasMore`. `get` returns one item.
59
+ - **`config`:** the configuration object. Items are in `config.<key>` when the type names a `key`.
60
+
61
+ Products, contacts and proofs are platform data. Reference them with `ref` fields (`"to": "product"`); never redeclare them.
62
+
63
+ **Field types:** `string`, `text`, `richtext` (HTML), `markdown`, `number`, `boolean`, `date` (YYYY-MM-DD), `datetime`, `enum` (+ `options`), `url`, `image` and `file` (a URL or `{ url, ... }`), `ref` and `ref[]` (+ `to`: a declared type id, or `product` / `contact` / `proof`), `string[]`, `json` (avoid where a typed field fits).
64
+
65
+ **Zones and what's public.** App records, cases and threads have three JSONB zones: `data`, `owner` and `admin`. Only `data` can be public, and only when the item's visibility is `public`. Mark each field a visitor may read with `"public": true`. Internal fields (notes, moderation flags) are `zone: "admin"` and never public.
66
+
67
+ **Examples** are the item's `data` JSON exactly as the app writes it, with realistic content, not lorem ipsum.
68
+
69
+ ## The `headless` block
70
+
71
+ ```jsonc
72
+ "headless": {
73
+ "purpose": "What content this holds and when a site should use it instead of building its own.",
74
+ "categories": ["faq"], // faq | media | pages | articles | catalog | events | locations
75
+ // | people | reviews | documents | forms | other
76
+ "primaryTypes": ["faq.item"], // the types a site renders; others are supporting (e.g. categories)
77
+ "editedIn": { "label": "FAQ → Questions", "adminPath": "#/questions" },
78
+ "render": {
79
+ "guidance": "Group by category; questions expand to show answers.",
80
+ "seo": ["faqPage"] // SL.seo.schema helpers a site should use
81
+ }
82
+ }
83
+ ```
84
+
85
+ A primary type must be readable by visitors: public visibility, at least one public field, and at least one example.
86
+
87
+ ## Checking a declaration
88
+
89
+ - **In Forge:** `forge-headless check` validates it against the app's real public data on its test collection. `forge-headless register` validates it and records it in Forge, which makes the app a provider that site builds can find.
90
+ - **Anywhere else:**
91
+ - `npx smartlinks-headless [appDir] [--collection <id> --app <appId>]`;
92
+ - or `SL.headless.validate(manifest, { samples })` in code.
93
+
94
+ Errors block. Warnings are worth fixing, and the most useful one is *real data has "x" but it isn't declared*.
95
+
96
+ ## Adding a headless mode to an existing app (procedure)
97
+
98
+ 1. **Find what the app stores.** Search the source for its writes and reads:
99
+ - `SL.app.records.create / upsert / bulkUpsert` (note each `recordType`);
100
+ - `SL.app.cases`, `SL.app.threads`;
101
+ - `appConfiguration.setConfig` / data items.
102
+
103
+ Each distinct `recordType` (or config key) the business edits is a candidate type.
104
+ 2. **Find the fields.** Read the admin forms and the TypeScript types for what each item holds. Note which zone each field is written to.
105
+ 3. **Decide what's public.** Content a visitor sees is public. Notes, flags and anything personal are not, and move to `zone: "admin"` if the app keeps them in `data`.
106
+ 4. **Write the `data` block:** a type per content thing, its storage, fields, listing, and one or two realistic examples taken from the app's real data.
107
+ 5. **Write the `headless` block:** purpose, categories, primary types, where it's edited (the admin screen's name and route), and render and SEO guidance.
108
+ 6. **Check against real data** (`forge-headless check`). Declare any real fields you missed, or confirm they're internal.
109
+ 7. **Register** (`forge-headless register`) once it's valid.
110
+ 8. **Keep the data contract stable.** Adding fields or types is a minor bump of `schemaVersion`; renaming or removing them is a major one.
111
+
112
+ Don't change how the app stores data just to declare it. The declaration describes what already exists. If something must change (say, an internal field sitting in the public `data` zone), make that a separate, deliberate change.
@@ -0,0 +1,68 @@
1
+ # Websites: search engines and AI crawlers (SEO + GEO)
2
+
3
+ For apps served as websites (an app site at `<name>.smartlinks.host` or a custom domain). Hub sites
4
+ get all of this from Hub.
5
+
6
+ ## What the platform does for you
7
+
8
+ - **`/robots.txt`, `/sitemap.xml`, `/llms.txt`** are generated on the site's own address. Don't ship
9
+ `robots.txt` or `sitemap.xml`: one build serves many sites, and sitemap URLs must be absolute on
10
+ the site's host, which the build can't know.
11
+ - **List every page** in `public/sitemap-paths.txt`, one `<path> [Title]` per line, in menu order
12
+ (`/ Home`, `/menu Menu`, `/book Book a table`). They go into the sitemap and `llms.txt` (titled)
13
+ on the right host, and Forge's preview uses the same list as its page menu. Keep it in step with
14
+ the router. Pre-rendered pages (`about/index.html`) are found automatically.
15
+ - **Canonical address.** A site can answer on its automatic address, a chosen name and a custom
16
+ domain. Every page gets `Link: <https://{canonical}{path}>; rel="canonical"`, so search engines
17
+ consolidate on one: the custom domain, else the chosen name, else the automatic address. Don't set
18
+ your own canonical unless a page has a different canonical page.
19
+ - **Previews stay out of search.** A sandbox collection's sites, and any test build (dev, alpha,
20
+ beta), are served with `X-Robots-Tag: noindex` and a `robots.txt` that disallows everything,
21
+ whatever the build ships.
22
+
23
+ ## What you do: `SL.seo` and `SL.site`
24
+
25
+ ```ts
26
+ import * as SL from '@proveanything/smartlinks'
27
+
28
+ // Per route, from its data — on every route change:
29
+ SL.seo.head({
30
+ title: `${product.name} — ${brand}`,
31
+ description: product.description, // ~150 characters, says what the page is
32
+ image: product.heroImage?.url, // absolute; used for link previews
33
+ type: 'product', // 'website' | 'article' | 'product'
34
+ })
35
+
36
+ // Structured data (schema.org JSON-LD). The id names the block, so calling again replaces it:
37
+ SL.seo.jsonLd('page', SL.seo.schema.product(product, { url: location.href, brand }))
38
+ SL.seo.jsonLd('faq', SL.seo.schema.faqPage(faqs.map((f) => ({ question: f.q, answer: f.a }))))
39
+ SL.seo.jsonLd('org', SL.seo.schema.organization(collection))
40
+
41
+ // When the page's data has rendered:
42
+ SL.site.ready()
43
+ ```
44
+
45
+ | Builder | For |
46
+ |---|---|
47
+ | `schema.product(product, { url, brand, offer, rating })` | product pages (`offer` only when it's really sold) |
48
+ | `schema.faqPage([{ question, answer }])` | FAQs: rich results, and the answer-shaped content AI search cites |
49
+ | `schema.organization(collection)` | the brand behind the site (home page) |
50
+ | `schema.localBusiness(collection, { type, address, telephone, openingHours })` | a business with a place (`type: 'Florist'`, `'Restaurant'`…) |
51
+ | `schema.breadcrumbs([{ name, url }])` | nested pages |
52
+ | `schema.article({ headline, datePublished, … })` | posts, guides, news |
53
+
54
+ `seo.head` removes tags a previous route set and the next one doesn't. Both are no-ops without a
55
+ `document` (SSR, tests).
56
+
57
+ `site.ready()` tells the platform's page renderer the page is complete, so it can snapshot the
58
+ content for crawlers that don't run JavaScript. Without it, the renderer waits for the network to go
59
+ quiet.
60
+
61
+ ## Content that gets found
62
+
63
+ - **Content in the page, not behind interaction.** FAQ answers in `<details>` are fine; answers
64
+ fetched only when a question is clicked aren't seen.
65
+ - **One `<h1>` per page**, a logical heading order, `alt` text on content images.
66
+ - **Answer-shaped writing.** A short summary near the top, question-style headings where they fit.
67
+ This is what AI assistants quote.
68
+ - **Real links** (`<a href>`) between pages, never hash routes.
@@ -0,0 +1,45 @@
1
+ import type { AppDataDeclaration, AppDataType, AppHeadlessDeclaration } from './types/headless.js';
2
+ export interface HeadlessIssue {
3
+ /** Where: e.g. `headless.purpose`, `data.types.faq.item.fields.answer`. */
4
+ path: string;
5
+ message: string;
6
+ }
7
+ export interface HeadlessValidation {
8
+ ok: boolean;
9
+ errors: HeadlessIssue[];
10
+ warnings: HeadlessIssue[];
11
+ /**
12
+ * The read recipe for each type: the declared one, else the standard one for its storage — plus what
13
+ * the call returns and where an item's fields are. Those two always come from the storage kind (the
14
+ * platform's response shape), never from the app, so a custom recipe can't leave them out.
15
+ */
16
+ recipes: Record<string, {
17
+ list: string;
18
+ get?: string;
19
+ returns: string;
20
+ item: string;
21
+ }>;
22
+ }
23
+ /**
24
+ * What a type's read calls return, and where one item's declared fields live — fixed by the storage
25
+ * kind (see app-objects.md "Paginated List Responses"). Sites must read items from `response.data`.
26
+ */
27
+ export declare function responseShape(type: AppDataType): {
28
+ returns: string;
29
+ item: string;
30
+ };
31
+ /** The standard SDK read calls for a type, from how it's stored. `appId` / `collectionId` are the caller's variables. */
32
+ export declare function standardRecipe(type: AppDataType): {
33
+ list: string;
34
+ get?: string;
35
+ };
36
+ export interface ValidateOptions {
37
+ /** Real items per type id (each item's `data` zone JSON), e.g. sampled from a test collection. */
38
+ samples?: Record<string, Array<Record<string, unknown>>>;
39
+ }
40
+ /** Validate a manifest's `data` + `headless` blocks. */
41
+ export declare function validate(manifest: {
42
+ data?: AppDataDeclaration;
43
+ headless?: AppHeadlessDeclaration;
44
+ [k: string]: any;
45
+ }, opts?: ValidateOptions): HeadlessValidation;