@proveanything/smartlinks 1.8.6 → 1.8.9
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/appConfiguration.d.ts +19 -5
- package/dist/api/appConfiguration.js +10 -3
- package/dist/api/collection.d.ts +8 -1
- package/dist/api/collection.js +8 -1
- package/dist/docs/API_SUMMARY.md +24 -7
- package/dist/docs/ai-guide-template.md +21 -0
- package/dist/docs/app-data-storage.md +44 -1
- package/dist/docs/building-react-components.md +319 -0
- package/dist/docs/containers.md +184 -0
- package/dist/docs/overview.md +30 -0
- package/dist/docs/widgets.md +98 -0
- package/dist/openapi.yaml +3 -3
- package/docs/API_SUMMARY.md +24 -7
- package/docs/ai-guide-template.md +21 -0
- package/docs/app-data-storage.md +44 -1
- package/docs/building-react-components.md +319 -0
- package/docs/containers.md +184 -0
- package/docs/overview.md +30 -0
- package/docs/widgets.md +98 -0
- package/openapi.yaml +3 -3
- package/package.json +1 -1
package/dist/docs/containers.md
CHANGED
|
@@ -26,6 +26,190 @@ Imagine a homepage displaying 10 app widgets. If containers were bundled with wi
|
|
|
26
26
|
|
|
27
27
|
---
|
|
28
28
|
|
|
29
|
+
## Dual-Mode Rendering
|
|
30
|
+
|
|
31
|
+
Containers can run in **two modes**, and the same container code must work in both:
|
|
32
|
+
|
|
33
|
+
| Mode | How It Works | Who Provides the Router? |
|
|
34
|
+
|------|-------------|--------------------------|
|
|
35
|
+
| **Direct Component** | Container runs directly in the parent's React context | **The framework** — wraps your component in `MemoryRouter` |
|
|
36
|
+
| **Iframe** | Container runs inside an iframe with its own URL | **Your app** — you manage your own `HashRouter` |
|
|
37
|
+
|
|
38
|
+
### The Critical Rule: No Router Wrappers in Your Export
|
|
39
|
+
|
|
40
|
+
> **❌ Your exported `PublicContainer` must NOT be wrapped in a `<Router>`, `<MemoryRouter>`, `<HashRouter>`, or `<BrowserRouter>`.**
|
|
41
|
+
|
|
42
|
+
**Why?** In direct-component mode, the framework already wraps your container in a `MemoryRouter`. If your component includes its own Router, React Router will throw: _"You cannot render a `<Router>` inside another `<Router>`"_.
|
|
43
|
+
|
|
44
|
+
**The routing architecture:**
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
Direct Component Mode:
|
|
48
|
+
Portal Shell (HashRouter)
|
|
49
|
+
└─ ContentOrchestrator
|
|
50
|
+
└─ MemoryRouter ← framework provides this
|
|
51
|
+
└─ <YourContainer /> ← only contains <Routes>, no <Router>
|
|
52
|
+
└─ <Route path="/" element={<Home />} />
|
|
53
|
+
└─ <Route path="/detail/:id" element={<Detail />} />
|
|
54
|
+
|
|
55
|
+
Iframe Mode:
|
|
56
|
+
<iframe src="your-app-url">
|
|
57
|
+
└─ <HashRouter> ← your App.tsx provides this
|
|
58
|
+
└─ <Routes>
|
|
59
|
+
└─ <Route path="/" element={<Home />} />
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**Where routing goes:**
|
|
63
|
+
- ✅ Your **iframe entry point** (`App.tsx` / `main.tsx`) should use `HashRouter`
|
|
64
|
+
- ✅ Your **exported container** (`PublicContainer.tsx`) should include `<Routes>` and `<Route>` elements
|
|
65
|
+
- ❌ Your **exported container** should NOT include any `<Router>` wrapper
|
|
66
|
+
|
|
67
|
+
### The Router Contract
|
|
68
|
+
|
|
69
|
+
In direct-component mode, the framework's `MemoryRouter` gives you full React Router capabilities:
|
|
70
|
+
|
|
71
|
+
- ✅ `useNavigate()` works — navigates within your container's routes
|
|
72
|
+
- ✅ `useLocation()` works — returns current location in the MemoryRouter
|
|
73
|
+
- ✅ `useParams()` works — reads params from your route definitions
|
|
74
|
+
- ✅ `<Routes>` / `<Route>` work — define your internal navigation
|
|
75
|
+
- ✅ `useSearchParams()` works — manages search params within the MemoryRouter
|
|
76
|
+
- ⚠️ `window.location` does **NOT** reflect your container's route — it reflects the portal's URL
|
|
77
|
+
|
|
78
|
+
**Example: Correct Container Structure**
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
// ❌ WRONG — has Router wrapper
|
|
82
|
+
export const PublicContainer = (props: Record<string, any>) => {
|
|
83
|
+
return (
|
|
84
|
+
<MemoryRouter> {/* ❌ Don't do this! */}
|
|
85
|
+
<Routes>
|
|
86
|
+
<Route path="/" element={<Home />} />
|
|
87
|
+
</Routes>
|
|
88
|
+
</MemoryRouter>
|
|
89
|
+
);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// ✅ CORRECT — no Router wrapper
|
|
93
|
+
export const PublicContainer = (props: Record<string, any>) => {
|
|
94
|
+
return (
|
|
95
|
+
<AppContext.Provider value={props}>
|
|
96
|
+
<Routes> {/* ✅ Routes are fine, just no Router */}
|
|
97
|
+
<Route path="/" element={<Home />} />
|
|
98
|
+
<Route path="/detail/:id" element={<Detail />} />
|
|
99
|
+
</Routes>
|
|
100
|
+
</AppContext.Provider>
|
|
101
|
+
);
|
|
102
|
+
};
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### The `useAppContext()` Pattern
|
|
106
|
+
|
|
107
|
+
To write containers that work identically in both modes, use this abstraction pattern:
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
// src/hooks/useAppContext.ts
|
|
111
|
+
import { useContext, createContext, useMemo } from 'react';
|
|
112
|
+
import { useSearchParams } from 'react-router-dom';
|
|
113
|
+
|
|
114
|
+
export interface AppContextValue {
|
|
115
|
+
collectionId: string;
|
|
116
|
+
appId: string;
|
|
117
|
+
productId?: string;
|
|
118
|
+
proofId?: string;
|
|
119
|
+
pageId?: string;
|
|
120
|
+
initialPath?: string;
|
|
121
|
+
lang?: string;
|
|
122
|
+
user?: { id: string; email: string; name?: string };
|
|
123
|
+
SL: typeof import('@proveanything/smartlinks');
|
|
124
|
+
onNavigate?: (request: any) => void;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export const AppContext = createContext<AppContextValue | null>(null);
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Returns app context regardless of rendering mode.
|
|
131
|
+
* - Direct component mode: reads from AppContext (props)
|
|
132
|
+
* - Iframe mode: reads from URL search params
|
|
133
|
+
*/
|
|
134
|
+
export function useAppContext(): AppContextValue {
|
|
135
|
+
const ctx = useContext(AppContext);
|
|
136
|
+
|
|
137
|
+
// If context exists, we're in direct-component mode
|
|
138
|
+
if (ctx) return ctx;
|
|
139
|
+
|
|
140
|
+
// Otherwise, we're in iframe mode — read from URL params
|
|
141
|
+
const [searchParams] = useSearchParams();
|
|
142
|
+
const SL = (window as any).SL ?? require('@proveanything/smartlinks');
|
|
143
|
+
|
|
144
|
+
return useMemo(() => ({
|
|
145
|
+
collectionId: searchParams.get('collectionId') ?? '',
|
|
146
|
+
appId: searchParams.get('appId') ?? '',
|
|
147
|
+
productId: searchParams.get('productId') ?? undefined,
|
|
148
|
+
proofId: searchParams.get('proofId') ?? undefined,
|
|
149
|
+
pageId: searchParams.get('pageId') ?? undefined,
|
|
150
|
+
lang: searchParams.get('lang') ?? undefined,
|
|
151
|
+
SL,
|
|
152
|
+
}), [searchParams, SL]);
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
**Usage in your container:**
|
|
157
|
+
|
|
158
|
+
```tsx
|
|
159
|
+
// src/exports/PublicContainer.tsx
|
|
160
|
+
import { Routes, Route } from 'react-router-dom';
|
|
161
|
+
import { AppContext } from '@/hooks/useAppContext';
|
|
162
|
+
import { Home } from '@/pages/Home';
|
|
163
|
+
import { Detail } from '@/pages/Detail';
|
|
164
|
+
|
|
165
|
+
export const PublicContainer = (props: Record<string, any>) => {
|
|
166
|
+
return (
|
|
167
|
+
<AppContext.Provider value={props}>
|
|
168
|
+
<Routes>
|
|
169
|
+
<Route path="/" element={<Home />} />
|
|
170
|
+
<Route path="/detail/:id" element={<Detail />} />
|
|
171
|
+
</Routes>
|
|
172
|
+
</AppContext.Provider>
|
|
173
|
+
);
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
// src/pages/Home.tsx
|
|
177
|
+
import { useAppContext } from '@/hooks/useAppContext';
|
|
178
|
+
import { useNavigate } from 'react-router-dom';
|
|
179
|
+
|
|
180
|
+
export function Home() {
|
|
181
|
+
const { collectionId, productId, SL } = useAppContext();
|
|
182
|
+
const navigate = useNavigate();
|
|
183
|
+
|
|
184
|
+
return (
|
|
185
|
+
<div>
|
|
186
|
+
<h1>Collection: {collectionId}</h1>
|
|
187
|
+
<button onClick={() => navigate('/detail/123')}>
|
|
188
|
+
View Detail
|
|
189
|
+
</button>
|
|
190
|
+
</div>
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Deep Linking
|
|
196
|
+
|
|
197
|
+
The framework sets the `MemoryRouter`'s initial route based on the `pageId` or `initialPath` prop. For example, if the portal navigates to your container with `pageId: 'settings'`, your router will start at `/settings`.
|
|
198
|
+
|
|
199
|
+
To advertise deep-linkable pages, declare them in your `app.manifest.json`:
|
|
200
|
+
|
|
201
|
+
```json
|
|
202
|
+
{
|
|
203
|
+
"linkable": [
|
|
204
|
+
{ "title": "Home", "path": "/" },
|
|
205
|
+
{ "title": "Settings", "path": "/settings" },
|
|
206
|
+
{ "title": "Item Detail", "path": "/item/:itemId" }
|
|
207
|
+
]
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
29
213
|
## Container Props
|
|
30
214
|
|
|
31
215
|
Container props extend the standard `SmartLinksWidgetProps` with an additional `className` prop:
|
package/dist/docs/overview.md
CHANGED
|
@@ -45,6 +45,7 @@ The SmartLinks SDK (`@proveanything/smartlinks`) includes comprehensive document
|
|
|
45
45
|
| Topic | File | When to Use |
|
|
46
46
|
|-------|------|-------------|
|
|
47
47
|
| **API Reference** | `docs/API_SUMMARY.md` | Complete SDK function reference, types, error handling |
|
|
48
|
+
| **Building React Components** | `docs/building-react-components.md` | **READ THIS FIRST** — Dual-mode rendering, router rules, useAppContext pattern |
|
|
48
49
|
| **Multi-Page Architecture** | `docs/mpa.md` | Build pipeline, entry points, multi-page setup, content hashing |
|
|
49
50
|
| **AI & Chat** | `docs/ai.md` | Chat completions, RAG, streaming, tool calling, voice, podcasts, TTS |
|
|
50
51
|
| **Analytics** | `docs/analytics.md` | Fire-and-forget page/click/tag analytics plus admin dashboard queries |
|
|
@@ -140,6 +141,33 @@ await SL.appConfiguration.setConfig({ collectionId, appId, config: myConfig, adm
|
|
|
140
141
|
|
|
141
142
|
This applies to all write operations: `setConfig`, `setDataItem`, `updateDataItem`, etc.
|
|
142
143
|
|
|
144
|
+
### Endpoint Auth vs Data Visibility
|
|
145
|
+
|
|
146
|
+
`admin: true` is an endpoint selector, not a privacy marker.
|
|
147
|
+
|
|
148
|
+
- It tells the SDK to call the admin endpoint.
|
|
149
|
+
- It allows writes and admin reads.
|
|
150
|
+
- It does **not** mean every root-level field you save becomes admin-only.
|
|
151
|
+
|
|
152
|
+
For `appConfiguration` config blobs and `collection.getSettings()` groups, root-level fields are typically the public-facing settings. If you need private values such as tokens or secrets, store them inside a top-level `admin` object:
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
await SL.appConfiguration.setConfig({
|
|
156
|
+
collectionId,
|
|
157
|
+
appId,
|
|
158
|
+
admin: true,
|
|
159
|
+
config: {
|
|
160
|
+
publicLabel: 'Warranty Portal',
|
|
161
|
+
color: '#B68C2A',
|
|
162
|
+
admin: {
|
|
163
|
+
accessToken: 'secret-token'
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
})
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Public reads omit the `admin` block. Admin reads include it.
|
|
170
|
+
|
|
143
171
|
### Config vs Data
|
|
144
172
|
|
|
145
173
|
| Storage Type | Function | Use Case |
|
|
@@ -150,6 +178,8 @@ This applies to all write operations: `setConfig`, `setDataItem`, `updateDataIte
|
|
|
150
178
|
|
|
151
179
|
Both can be scoped to **collection level** or **product level** by including `productId`.
|
|
152
180
|
|
|
181
|
+
For config/settings visibility, remember: root fields are the normal shared payload, while a reserved top-level `admin` object is the place for admin-only values.
|
|
182
|
+
|
|
153
183
|
Prefer `app.records` over `setDataItem` when the data is becoming a real entity that needs lifecycle, ownership, visibility, relationships, or filtering. Keep `setDataItem` for simple keyed scoped documents and config-adjacent content.
|
|
154
184
|
|
|
155
185
|
### Attestations (Proof-level data)
|
package/dist/docs/widgets.md
CHANGED
|
@@ -30,6 +30,104 @@ Widgets are self-contained React components that:
|
|
|
30
30
|
|
|
31
31
|
---
|
|
32
32
|
|
|
33
|
+
## Dual-Mode Rendering
|
|
34
|
+
|
|
35
|
+
Widgets can run in **two modes**, and the same widget code must work in both:
|
|
36
|
+
|
|
37
|
+
| Mode | How It Works | When Used |
|
|
38
|
+
|------|-------------|-----------|
|
|
39
|
+
| **Direct Component** | Widget runs directly in the parent's React context | Default - when loaded as ESM/UMD in the parent app |
|
|
40
|
+
| **Iframe** | Widget runs inside an iframe with its own URL | Fallback or when full isolation is needed |
|
|
41
|
+
|
|
42
|
+
### The Critical Rule: No Router Wrappers
|
|
43
|
+
|
|
44
|
+
> **❌ Your widget component must NOT be wrapped in a `<Router>`, `<MemoryRouter>`, `<HashRouter>`, or `<BrowserRouter>`.**
|
|
45
|
+
|
|
46
|
+
**Why?** In direct component mode, the parent application already provides routing context. If your widget includes its own Router, React Router will throw: _"You cannot render a `<Router>` inside another `<Router>`"_.
|
|
47
|
+
|
|
48
|
+
**Where routing goes:**
|
|
49
|
+
- ✅ Your **iframe entry point** (`App.tsx` / `main.tsx`) should use `HashRouter`
|
|
50
|
+
- ❌ Your **exported widget** (`PublicComponent.tsx`) should NOT include any Router
|
|
51
|
+
|
|
52
|
+
Widgets are typically single-view components and don't need internal routing. If you need multi-page navigation, use a container instead.
|
|
53
|
+
|
|
54
|
+
### The `useAppContext()` Pattern
|
|
55
|
+
|
|
56
|
+
To write widgets that work identically in both modes, use this abstraction pattern:
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
// src/hooks/useAppContext.ts
|
|
60
|
+
import { useContext, createContext, useMemo } from 'react';
|
|
61
|
+
import { useSearchParams } from 'react-router-dom';
|
|
62
|
+
|
|
63
|
+
export interface AppContextValue {
|
|
64
|
+
collectionId: string;
|
|
65
|
+
appId: string;
|
|
66
|
+
productId?: string;
|
|
67
|
+
proofId?: string;
|
|
68
|
+
pageId?: string;
|
|
69
|
+
lang?: string;
|
|
70
|
+
user?: { id: string; email: string; name?: string };
|
|
71
|
+
SL: typeof import('@proveanything/smartlinks');
|
|
72
|
+
onNavigate?: (request: any) => void;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export const AppContext = createContext<AppContextValue | null>(null);
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Returns app context regardless of rendering mode.
|
|
79
|
+
* - Direct component mode: reads from AppContext (props)
|
|
80
|
+
* - Iframe mode: reads from URL search params
|
|
81
|
+
*/
|
|
82
|
+
export function useAppContext(): AppContextValue {
|
|
83
|
+
const ctx = useContext(AppContext);
|
|
84
|
+
|
|
85
|
+
// If context exists, we're in direct-component mode
|
|
86
|
+
if (ctx) return ctx;
|
|
87
|
+
|
|
88
|
+
// Otherwise, we're in iframe mode — read from URL params
|
|
89
|
+
const [searchParams] = useSearchParams();
|
|
90
|
+
const SL = (window as any).SL ?? require('@proveanything/smartlinks');
|
|
91
|
+
|
|
92
|
+
return useMemo(() => ({
|
|
93
|
+
collectionId: searchParams.get('collectionId') ?? '',
|
|
94
|
+
appId: searchParams.get('appId') ?? '',
|
|
95
|
+
productId: searchParams.get('productId') ?? undefined,
|
|
96
|
+
proofId: searchParams.get('proofId') ?? undefined,
|
|
97
|
+
pageId: searchParams.get('pageId') ?? undefined,
|
|
98
|
+
lang: searchParams.get('lang') ?? undefined,
|
|
99
|
+
SL,
|
|
100
|
+
}), [searchParams, SL]);
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**Usage in your widget:**
|
|
105
|
+
|
|
106
|
+
```tsx
|
|
107
|
+
// src/exports/PublicComponent.tsx
|
|
108
|
+
import { AppContext } from '@/hooks/useAppContext';
|
|
109
|
+
import { WidgetContent } from '@/components/WidgetContent';
|
|
110
|
+
|
|
111
|
+
export const PublicComponent = (props: Record<string, any>) => {
|
|
112
|
+
return (
|
|
113
|
+
<AppContext.Provider value={props}>
|
|
114
|
+
<WidgetContent />
|
|
115
|
+
</AppContext.Provider>
|
|
116
|
+
);
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// src/components/WidgetContent.tsx
|
|
120
|
+
import { useAppContext } from '@/hooks/useAppContext';
|
|
121
|
+
|
|
122
|
+
export function WidgetContent() {
|
|
123
|
+
const { collectionId, productId, SL } = useAppContext();
|
|
124
|
+
// Works in both direct-component and iframe modes!
|
|
125
|
+
return <div>Collection: {collectionId}</div>;
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
33
131
|
## Widget Props Interface
|
|
34
132
|
|
|
35
133
|
All widgets receive the `SmartLinksWidgetProps` interface:
|
package/dist/openapi.yaml
CHANGED
|
@@ -79,7 +79,7 @@ paths:
|
|
|
79
79
|
post:
|
|
80
80
|
tags:
|
|
81
81
|
- collection
|
|
82
|
-
summary:
|
|
82
|
+
summary: Create a new collection (admin only).
|
|
83
83
|
operationId: collection_create
|
|
84
84
|
security:
|
|
85
85
|
- bearerAuth: []
|
|
@@ -5446,7 +5446,7 @@ paths:
|
|
|
5446
5446
|
post:
|
|
5447
5447
|
tags:
|
|
5448
5448
|
- collection
|
|
5449
|
-
summary:
|
|
5449
|
+
summary: Update a specific settings group for a collection (admin endpoint).
|
|
5450
5450
|
operationId: collection_updateSettings
|
|
5451
5451
|
security:
|
|
5452
5452
|
- bearerAuth: []
|
|
@@ -7080,7 +7080,7 @@ paths:
|
|
|
7080
7080
|
get:
|
|
7081
7081
|
tags:
|
|
7082
7082
|
- collection
|
|
7083
|
-
summary: Retrieve
|
|
7083
|
+
summary: Retrieve all configured app module definitions for a collection (public endpoint).
|
|
7084
7084
|
operationId: collection_getAppsConfig
|
|
7085
7085
|
security: []
|
|
7086
7086
|
parameters:
|
package/docs/API_SUMMARY.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Smartlinks API Summary
|
|
2
2
|
|
|
3
|
-
Version: 1.8.
|
|
3
|
+
Version: 1.8.9 | Generated: 2026-03-19T15:02:36.521Z
|
|
4
4
|
|
|
5
5
|
This is a concise summary of all available API functions and types.
|
|
6
6
|
|
|
@@ -42,6 +42,16 @@ When you need flexible app-specific data, choose the storage model based on shap
|
|
|
42
42
|
|
|
43
43
|
Rule of thumb: if you are modelling a real domain object that users will browse, filter, secure, or evolve over time, start with app objects. If you just need a simple keyed payload hanging off a collection or product, scoped data items are still a good fit.
|
|
44
44
|
|
|
45
|
+
## Settings Visibility
|
|
46
|
+
|
|
47
|
+
For `appConfiguration` config blobs and `collection` settings groups, keep endpoint choice separate from visibility semantics.
|
|
48
|
+
|
|
49
|
+
- **`admin: true` means "use the admin endpoint"** for reads or writes.
|
|
50
|
+
- **It does not make every root field private.** Writing through an admin endpoint still saves the normal shared payload.
|
|
51
|
+
- **Root-level fields are the public/shared settings view.** Put public labels, colors, toggles, and general config there.
|
|
52
|
+
- **Use a top-level `admin` object for confidential values.** Public reads omit that block; admin reads include it.
|
|
53
|
+
- **Applies to both** `appConfiguration.getConfig` / `setConfig` **and** `collection.getSettings` / `updateSettings`.
|
|
54
|
+
|
|
45
55
|
## API Namespaces
|
|
46
56
|
|
|
47
57
|
The Smartlinks SDK is organized into the following namespaces:
|
|
@@ -5834,10 +5844,17 @@ type AppConfigOptions = {
|
|
|
5834
5844
|
/** Item ID - required for getDataItem/deleteDataItem */
|
|
5835
5845
|
itemId?: string
|
|
5836
5846
|
|
|
5837
|
-
/**
|
|
5847
|
+
/**
|
|
5848
|
+
* Use admin endpoints instead of public.
|
|
5849
|
+
* This selects which endpoint is called; it does not by itself make root-level config fields private.
|
|
5850
|
+
*/
|
|
5838
5851
|
admin?: boolean
|
|
5839
5852
|
|
|
5840
|
-
/**
|
|
5853
|
+
/**
|
|
5854
|
+
* Configuration object for setConfig.
|
|
5855
|
+
* For admin-only values in app config, store them under a top-level `admin` object.
|
|
5856
|
+
* Public reads return the root config but omit `config.admin`.
|
|
5857
|
+
*/
|
|
5841
5858
|
config?: any
|
|
5842
5859
|
/** Data object for setDataItem. Best for small keyed scoped documents rather than richer app domain objects. */
|
|
5843
5860
|
data?: any
|
|
@@ -6130,7 +6147,7 @@ Get aggregate statistics for threads POST /threads/aggregate
|
|
|
6130
6147
|
Scoped config and keyed data items for collections, products, variants, or batches. Best for settings and small standalone documents, not as the default answer for every app-owned entity.
|
|
6131
6148
|
|
|
6132
6149
|
**getConfig**(opts: AppConfigOptions) → `Promise<any>`
|
|
6133
|
-
Get app configuration for a collection/product scope. ```typescript const config = await appConfiguration.getConfig({ appId: 'warranty-portal', collectionId: 'my-collection' }); ```
|
|
6150
|
+
Get app configuration for a collection/product scope. Public reads return the public view of the config. If the stored config contains a top-level `admin` object, that block is omitted from public responses and included when `opts.admin === true`. ```typescript const config = await appConfiguration.getConfig({ appId: 'warranty-portal', collectionId: 'my-collection' }); ```
|
|
6134
6151
|
|
|
6135
6152
|
**getWidgetInstance**(opts: GetWidgetInstanceOptions) → `Promise<WidgetInstance<TWidget>>`
|
|
6136
6153
|
Resolve a configured widget instance by ID from an app's stored config. This is a thin convenience wrapper over `getConfig()` that reads `config.widgets[widgetId]`. ```typescript const widget = await appConfiguration.getWidgetInstance({ collectionId: 'my-collection', appId: 'widget-toolkit', widgetId: 'launch-countdown' }) ```
|
|
@@ -6139,7 +6156,7 @@ Resolve a configured widget instance by ID from an app's stored config. This is
|
|
|
6139
6156
|
List configured widget instances for an app. Useful for picker UIs, setup schemas, and widget-to-widget references. ```typescript const widgets = await appConfiguration.listWidgetInstances({ collectionId: 'my-collection', appId: 'widget-toolkit' }) ```
|
|
6140
6157
|
|
|
6141
6158
|
**setConfig**(opts: AppConfigOptions) → `Promise<any>`
|
|
6142
|
-
Set app configuration for a collection/product scope. Requires admin authentication. ```typescript await appConfiguration.setConfig({ appId: 'warranty-portal', collectionId: 'my-collection', admin: true, config: { warrantyPeriod: 24, supportEmail: 'support@example.com' } }); ```
|
|
6159
|
+
Set app configuration for a collection/product scope. Requires admin authentication. Writing through the admin endpoint does not make every root-level field private. Use `config.admin` for confidential values that should only be returned on admin reads. ```typescript await appConfiguration.setConfig({ appId: 'warranty-portal', collectionId: 'my-collection', admin: true, config: { warrantyPeriod: 24, supportEmail: 'support@example.com' } }); ```
|
|
6143
6160
|
|
|
6144
6161
|
**deleteConfig**(opts: AppConfigOptions) → `Promise<void>`
|
|
6145
6162
|
Delete app configuration for a collection/product scope. Requires admin authentication. ```typescript await appConfiguration.deleteConfig({ appId: 'warranty-portal', collectionId: 'my-collection', admin: true }); ```
|
|
@@ -6614,13 +6631,13 @@ Retrieves all Collections.
|
|
|
6614
6631
|
Retrieve a collection by its shortId (public endpoint).
|
|
6615
6632
|
|
|
6616
6633
|
**getSettings**(collectionId: string, settingGroup: string, admin?: boolean) → `Promise<any>`
|
|
6617
|
-
Retrieve a specific settings group for a collection
|
|
6634
|
+
Retrieve a specific settings group for a collection. Public reads return the public view of the settings group. If the stored payload contains a top-level `admin` object, that block is omitted from public responses and included when `admin === true`.
|
|
6618
6635
|
|
|
6619
6636
|
**getAppsConfig**(collectionId: string) → `Promise<AppsConfigResponse>`
|
|
6620
6637
|
Retrieve all configured app module definitions for a collection (public endpoint).
|
|
6621
6638
|
|
|
6622
6639
|
**updateSettings**(collectionId: string, settingGroup: string, settings: any) → `Promise<any>`
|
|
6623
|
-
Update a specific settings group for a collection (admin endpoint).
|
|
6640
|
+
Update a specific settings group for a collection (admin endpoint). This writes through the admin endpoint, but root-level fields are still part of the public settings payload. Put confidential values under `settings.admin` if they should only be returned on admin reads.
|
|
6624
6641
|
|
|
6625
6642
|
**create**(data: CollectionCreateRequest) → `Promise<CollectionResponse>`
|
|
6626
6643
|
Create a new collection (admin only).
|
|
@@ -199,6 +199,26 @@ await SL.appConfiguration.setConfig({
|
|
|
199
199
|
});
|
|
200
200
|
```
|
|
201
201
|
|
|
202
|
+
Important visibility rule for app settings:
|
|
203
|
+
|
|
204
|
+
- `admin: true` means the SDK uses the admin endpoint.
|
|
205
|
+
- It does not make every root field private.
|
|
206
|
+
- If a value should only be visible to admins, put it under `config.admin`.
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
await SL.appConfiguration.setConfig({
|
|
210
|
+
collectionId,
|
|
211
|
+
appId,
|
|
212
|
+
admin: true,
|
|
213
|
+
config: {
|
|
214
|
+
enableNotifications: true,
|
|
215
|
+
admin: {
|
|
216
|
+
apiToken: 'secret-token'
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
})
|
|
220
|
+
```
|
|
221
|
+
|
|
202
222
|
---
|
|
203
223
|
|
|
204
224
|
## Metrics & Analytics
|
|
@@ -228,6 +248,7 @@ await SL.appConfiguration.setConfig({
|
|
|
228
248
|
| Issue | Cause | Fix |
|
|
229
249
|
| --------------------- | -------------------------- | ----------------------------------------------------- |
|
|
230
250
|
| Config save fails | Missing `admin: true` flag | Always include `admin: true` for admin operations |
|
|
251
|
+
| Secret visible publicly | Saved secret at root level | Put confidential values under the top-level `admin` object |
|
|
231
252
|
| Widget doesn't render | Missing required props | Ensure `collectionId`, `appId`, and `SL` are provided |
|
|
232
253
|
| Import skips rows | Invalid `productId` | Verify product IDs exist in the collection |
|
|
233
254
|
| Theme not applied | Missing `?theme=` param | Check URL parameters or postMessage setup |
|
package/docs/app-data-storage.md
CHANGED
|
@@ -104,6 +104,49 @@ This data is scoped to specific collections, products, variants, or batches. It'
|
|
|
104
104
|
|
|
105
105
|
If you need richer app-owned entities with querying, lifecycle, access zones, parent-child relationships, or workflow semantics, use [app-objects.md](app-objects.md) instead of forcing everything through `setDataItem`.
|
|
106
106
|
|
|
107
|
+
### Critical: `admin: true` Controls the Endpoint, Not Field Privacy
|
|
108
|
+
|
|
109
|
+
This is the part that often gets misunderstood by apps and AI tools:
|
|
110
|
+
|
|
111
|
+
- `admin: true` means "call the admin endpoint".
|
|
112
|
+
- It does **not** mean "everything I wrote is now admin-only".
|
|
113
|
+
- For app config and collection settings, root-level fields are usually part of the public payload.
|
|
114
|
+
- If you need confidential values, store them under a top-level `admin` object.
|
|
115
|
+
- Public reads omit that `admin` block; admin reads include it.
|
|
116
|
+
|
|
117
|
+
Example for app config:
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
await appConfiguration.setConfig({
|
|
121
|
+
appId: 'warranty-portal',
|
|
122
|
+
collectionId: 'my-collection',
|
|
123
|
+
admin: true,
|
|
124
|
+
config: {
|
|
125
|
+
theme: 'gold',
|
|
126
|
+
supportEmail: 'support@example.com',
|
|
127
|
+
admin: {
|
|
128
|
+
accessToken: 'secret-token',
|
|
129
|
+
webhookSecret: 'top-secret'
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
const publicView = await appConfiguration.getConfig({
|
|
135
|
+
appId: 'warranty-portal',
|
|
136
|
+
collectionId: 'my-collection'
|
|
137
|
+
})
|
|
138
|
+
// { theme: 'gold', supportEmail: 'support@example.com' }
|
|
139
|
+
|
|
140
|
+
const adminView = await appConfiguration.getConfig({
|
|
141
|
+
appId: 'warranty-portal',
|
|
142
|
+
collectionId: 'my-collection',
|
|
143
|
+
admin: true
|
|
144
|
+
})
|
|
145
|
+
// { theme: 'gold', supportEmail: 'support@example.com', admin: { ... } }
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
The same visibility pattern applies to collection settings via `collection.getSettings()` / `collection.updateSettings()`.
|
|
149
|
+
|
|
107
150
|
### Use Cases
|
|
108
151
|
- App-specific settings for a collection
|
|
109
152
|
- Product-level configuration
|
|
@@ -178,7 +221,7 @@ await appConfiguration.setDataItem({
|
|
|
178
221
|
| **Shared across collections?** | ✅ Yes | ❌ No | ❌ No |
|
|
179
222
|
| **Requires auth?** | ✅ Yes (user token) | ✅ Yes (admin token for write) | Depends on public/admin endpoint and visibility |
|
|
180
223
|
| **Querying / filtering** | Minimal | Minimal | Strong |
|
|
181
|
-
| **Access control zones** | User-scoped only |
|
|
224
|
+
| **Access control zones** | User-scoped only | Root fields + reserved `admin` block for settings/config | `data` / `owner` / `admin` zones |
|
|
182
225
|
| **Admin write required?** | ❌ No | ✅ Yes (for write operations) | Only for admin endpoints / admin fields |
|
|
183
226
|
|
|
184
227
|
---
|