@proveanything/smartlinks 2.0.20 → 2.0.22
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/functions.d.ts +25 -8
- package/dist/api/functions.js +42 -14
- package/dist/docs/API_SUMMARY.md +22 -7
- package/dist/docs/server-functions.md +83 -6
- package/dist/http.d.ts +10 -0
- package/dist/http.js +18 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/openapi.yaml +6 -28
- package/docs/API_SUMMARY.md +22 -7
- package/docs/server-functions.md +83 -6
- package/openapi.yaml +6 -28
- package/package.json +1 -1
package/dist/api/functions.d.ts
CHANGED
|
@@ -8,20 +8,37 @@ export interface FunctionListEntry {
|
|
|
8
8
|
export interface FunctionListResponse {
|
|
9
9
|
functions: FunctionListEntry[];
|
|
10
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* Options for a function call.
|
|
13
|
+
* - `appId` scopes resolution to one app (recommended; falls back to the SDK app context).
|
|
14
|
+
* - `channel` selects which release to run when addressing an app that is NOT enabled on the
|
|
15
|
+
* collection (enablement isn't required — the app is resolved directly by id). Defaults to
|
|
16
|
+
* `stable` server-side, so pass `channel: 'dev'` to test a dev build before installing it.
|
|
17
|
+
*/
|
|
18
|
+
export interface FunctionCallOptions {
|
|
19
|
+
appId?: string;
|
|
20
|
+
channel?: string;
|
|
21
|
+
}
|
|
11
22
|
export declare namespace functions {
|
|
12
23
|
/**
|
|
13
|
-
* Call a PUBLIC app server function inline.
|
|
14
|
-
* `POST /public/collection/:
|
|
24
|
+
* Call a PUBLIC app server function inline (surface `'public'`).
|
|
25
|
+
* App-scoped: `POST /public/collection/:c/app/:appId/functions/:name`.
|
|
15
26
|
*
|
|
16
27
|
* @example
|
|
17
|
-
*
|
|
28
|
+
* // App calling its own function (appId from initializeApi({ appId })):
|
|
29
|
+
* const { value } = await SL.functions.call<{ value: number }>(collectionId, 'pressCounter')
|
|
30
|
+
* // Or address another app explicitly:
|
|
31
|
+
* await SL.functions.call(collectionId, 'pressCounter', {}, { appId: 'my-counter-app' })
|
|
18
32
|
*/
|
|
19
|
-
function call<T = FunctionCallResult>(collectionId: string, name: string, body?: Record<string, any
|
|
33
|
+
function call<T = FunctionCallResult>(collectionId: string, name: string, body?: Record<string, any>, opts?: FunctionCallOptions): Promise<T>;
|
|
20
34
|
/**
|
|
21
35
|
* Call an ADMIN app server function (surface `'admin'`; requires an admin session).
|
|
22
|
-
* `POST /admin/collection/:
|
|
36
|
+
* App-scoped: `POST /admin/collection/:c/app/:appId/functions/:name`.
|
|
37
|
+
*/
|
|
38
|
+
function callAdmin<T = FunctionCallResult>(collectionId: string, name: string, body?: Record<string, any>, opts?: FunctionCallOptions): Promise<T>;
|
|
39
|
+
/**
|
|
40
|
+
* List the public functions available for a collection (discovery). Scoped to one app when an
|
|
41
|
+
* appId is given (or set as the SDK app context): `GET /public/collection/:c[/app/:appId]/functions`.
|
|
23
42
|
*/
|
|
24
|
-
function
|
|
25
|
-
/** List the public functions available for a collection (discovery). `GET /public/collection/:c/functions`. */
|
|
26
|
-
function list(collectionId: string): Promise<FunctionListResponse>;
|
|
43
|
+
function list(collectionId: string, opts?: FunctionCallOptions): Promise<FunctionListResponse>;
|
|
27
44
|
}
|
package/dist/api/functions.js
CHANGED
|
@@ -5,33 +5,61 @@
|
|
|
5
5
|
// client half. The PUBLIC route runs a function inline on the caller's own authority (owner if
|
|
6
6
|
// signed in via authKit, else public); the ADMIN route runs on the admin surface (needs an admin
|
|
7
7
|
// session). The returned value is the function's own result (`event.body` is what you pass here).
|
|
8
|
-
|
|
8
|
+
//
|
|
9
|
+
// ADDRESSING. A function always belongs to an app, so the canonical route is app-scoped:
|
|
10
|
+
// /collection/:c/app/:appId/functions/:name
|
|
11
|
+
// The appId comes from `opts.appId`, else the SDK's app context (initializeApi({ appId })) — so an
|
|
12
|
+
// app calling its OWN function just writes `SL.functions.call(collectionId, name)`. With no appId
|
|
13
|
+
// available, the call falls back to the DEPRECATED flat path `/collection/:c/functions/:name`,
|
|
14
|
+
// which the server resolves by bare name and REJECTS with 409 AMBIGUOUS_FUNCTION when more than one
|
|
15
|
+
// installed app defines that name. Always prefer an appId.
|
|
16
|
+
import { post, request, getAppContext } from "../http.js";
|
|
17
|
+
function fnPath(surface, collectionId, name, opts = {}) {
|
|
18
|
+
var _a;
|
|
19
|
+
const c = encodeURIComponent(collectionId);
|
|
20
|
+
const n = encodeURIComponent(name);
|
|
21
|
+
const app = (_a = opts.appId) !== null && _a !== void 0 ? _a : getAppContext();
|
|
22
|
+
const q = opts.channel ? `?channel=${encodeURIComponent(opts.channel)}` : '';
|
|
23
|
+
return app
|
|
24
|
+
? `/${surface}/collection/${c}/app/${encodeURIComponent(app)}/functions/${n}${q}`
|
|
25
|
+
: `/${surface}/collection/${c}/functions/${n}${q}`; // deprecated flat alias
|
|
26
|
+
}
|
|
9
27
|
export var functions;
|
|
10
28
|
(function (functions) {
|
|
11
29
|
/**
|
|
12
|
-
* Call a PUBLIC app server function inline.
|
|
13
|
-
* `POST /public/collection/:
|
|
30
|
+
* Call a PUBLIC app server function inline (surface `'public'`).
|
|
31
|
+
* App-scoped: `POST /public/collection/:c/app/:appId/functions/:name`.
|
|
14
32
|
*
|
|
15
33
|
* @example
|
|
16
|
-
*
|
|
34
|
+
* // App calling its own function (appId from initializeApi({ appId })):
|
|
35
|
+
* const { value } = await SL.functions.call<{ value: number }>(collectionId, 'pressCounter')
|
|
36
|
+
* // Or address another app explicitly:
|
|
37
|
+
* await SL.functions.call(collectionId, 'pressCounter', {}, { appId: 'my-counter-app' })
|
|
17
38
|
*/
|
|
18
|
-
async function call(collectionId, name, body = {}) {
|
|
19
|
-
|
|
20
|
-
return post(path, body);
|
|
39
|
+
async function call(collectionId, name, body = {}, opts = {}) {
|
|
40
|
+
return post(fnPath('public', collectionId, name, opts), body);
|
|
21
41
|
}
|
|
22
42
|
functions.call = call;
|
|
23
43
|
/**
|
|
24
44
|
* Call an ADMIN app server function (surface `'admin'`; requires an admin session).
|
|
25
|
-
* `POST /admin/collection/:
|
|
45
|
+
* App-scoped: `POST /admin/collection/:c/app/:appId/functions/:name`.
|
|
26
46
|
*/
|
|
27
|
-
async function callAdmin(collectionId, name, body = {}) {
|
|
28
|
-
|
|
29
|
-
return post(path, body);
|
|
47
|
+
async function callAdmin(collectionId, name, body = {}, opts = {}) {
|
|
48
|
+
return post(fnPath('admin', collectionId, name, opts), body);
|
|
30
49
|
}
|
|
31
50
|
functions.callAdmin = callAdmin;
|
|
32
|
-
/**
|
|
33
|
-
|
|
34
|
-
|
|
51
|
+
/**
|
|
52
|
+
* List the public functions available for a collection (discovery). Scoped to one app when an
|
|
53
|
+
* appId is given (or set as the SDK app context): `GET /public/collection/:c[/app/:appId]/functions`.
|
|
54
|
+
*/
|
|
55
|
+
async function list(collectionId, opts = {}) {
|
|
56
|
+
var _a;
|
|
57
|
+
const c = encodeURIComponent(collectionId);
|
|
58
|
+
const app = (_a = opts.appId) !== null && _a !== void 0 ? _a : getAppContext();
|
|
59
|
+
const q = opts.channel ? `?channel=${encodeURIComponent(opts.channel)}` : '';
|
|
60
|
+
const path = app
|
|
61
|
+
? `/public/collection/${c}/app/${encodeURIComponent(app)}/functions${q}`
|
|
62
|
+
: `/public/collection/${c}/functions${q}`;
|
|
35
63
|
return request(path);
|
|
36
64
|
}
|
|
37
65
|
functions.list = list;
|
package/dist/docs/API_SUMMARY.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Smartlinks API Summary
|
|
2
2
|
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.22 | Generated: 2026-09-25T18:07:22.478Z
|
|
4
4
|
|
|
5
5
|
This is a concise summary of all available API functions and types.
|
|
6
6
|
|
|
@@ -166,6 +166,12 @@ Reset the diagnostics counters (does not touch the cache itself).
|
|
|
166
166
|
**isProxyEnabled**() → `boolean`
|
|
167
167
|
Return whether proxy mode is currently enabled.
|
|
168
168
|
|
|
169
|
+
**getAppContext**() → `string | undefined`
|
|
170
|
+
The current app context (appId), if the SDK was initialized with one.
|
|
171
|
+
|
|
172
|
+
**setAppContext**(id: string | undefined) → `void`
|
|
173
|
+
Set (or clear) the current app context — the appId used to scope SL.functions calls.
|
|
174
|
+
|
|
169
175
|
**initializeApi**(options: {
|
|
170
176
|
baseURL: string
|
|
171
177
|
apiKey?: string
|
|
@@ -8960,6 +8966,13 @@ interface FunctionListResponse {
|
|
|
8960
8966
|
}
|
|
8961
8967
|
```
|
|
8962
8968
|
|
|
8969
|
+
**FunctionCallOptions** (interface)
|
|
8970
|
+
```typescript
|
|
8971
|
+
interface FunctionCallOptions {
|
|
8972
|
+
appId?: string; channel?: string
|
|
8973
|
+
}
|
|
8974
|
+
```
|
|
8975
|
+
|
|
8963
8976
|
**FunctionCallResult** = `any`
|
|
8964
8977
|
|
|
8965
8978
|
### sequence (api)
|
|
@@ -10458,16 +10471,18 @@ Delete a form for a collection (admin only).
|
|
|
10458
10471
|
|
|
10459
10472
|
**call**(collectionId: string,
|
|
10460
10473
|
name: string,
|
|
10461
|
-
body: Record<string, any> = {}
|
|
10462
|
-
|
|
10474
|
+
body: Record<string, any> = {},
|
|
10475
|
+
opts: FunctionCallOptions = {}) → `Promise<T>`
|
|
10476
|
+
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' })
|
|
10463
10477
|
|
|
10464
10478
|
**callAdmin**(collectionId: string,
|
|
10465
10479
|
name: string,
|
|
10466
|
-
body: Record<string, any> = {}
|
|
10467
|
-
|
|
10480
|
+
body: Record<string, any> = {},
|
|
10481
|
+
opts: FunctionCallOptions = {}) → `Promise<T>`
|
|
10482
|
+
Call an ADMIN app server function (surface `'admin'`; requires an admin session). App-scoped: `POST /admin/collection/:c/app/:appId/functions/:name`.
|
|
10468
10483
|
|
|
10469
|
-
**list**(collectionId: string) → `Promise<FunctionListResponse>`
|
|
10470
|
-
List the public functions available for a collection (discovery). `GET /public/collection/:c/functions`.
|
|
10484
|
+
**list**(collectionId: string, opts: FunctionCallOptions = {}) → `Promise<FunctionListResponse>`
|
|
10485
|
+
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`.
|
|
10471
10486
|
|
|
10472
10487
|
### http
|
|
10473
10488
|
|
|
@@ -181,11 +181,14 @@ interface ServerFunctionContext {
|
|
|
181
181
|
collectionId: string
|
|
182
182
|
appId: string
|
|
183
183
|
|
|
184
|
-
/** SmartLinks SDK, pre-scoped to your declared `authority`. Capabilities cap what it may do.
|
|
184
|
+
/** SmartLinks SDK, pre-scoped to your declared `authority`. Capabilities cap what it may do.
|
|
185
|
+
* Key resources: `sl.appRecords` (per-app records), `sl.appData` (general/app-wide config +
|
|
186
|
+
* data — URLs, flags, counters, arrays), `sl.products`, `sl.attestations`. */
|
|
185
187
|
sl: SmartLinks
|
|
186
188
|
|
|
187
|
-
/** Capability-gated secrets
|
|
188
|
-
|
|
189
|
+
/** Capability-gated secrets (need `secrets:<ref>`). `get(ref)` resolves the collection's OWN
|
|
190
|
+
* secret first, then the app-level one; `app(ref)` reads the app-level secret only. */
|
|
191
|
+
secrets: { get(ref: string): Promise<string | null>; app(ref: string): Promise<string | null> }
|
|
189
192
|
|
|
190
193
|
/** Who invoked this function. */
|
|
191
194
|
caller: {
|
|
@@ -211,6 +214,61 @@ already authenticated as your declared authority and scoped to the collection. D
|
|
|
211
214
|
to construct your own SDK client or carry your own key; that's what `ctx.sl` is for, and it's
|
|
212
215
|
the only way authority stays correct.
|
|
213
216
|
|
|
217
|
+
### Calling a function from your app (client side)
|
|
218
|
+
|
|
219
|
+
From a page/UI, invoke an `http`-trigger function with the SDK — no manual fetch, no URL building:
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
// public function (runs on the caller's authority — signed-in owner, else public):
|
|
223
|
+
const res = await SL.functions.call(collectionId, 'increment', { /* becomes event.body */ })
|
|
224
|
+
// admin surface (needs an admin session):
|
|
225
|
+
const res = await SL.functions.callAdmin(collectionId, 'recomputeTotals', { … })
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
`res` is whatever your handler returns. See the `edge-function-test` example for a full page +
|
|
229
|
+
function + manifest.
|
|
230
|
+
|
|
231
|
+
#### Functions are ALWAYS app-scoped
|
|
232
|
+
|
|
233
|
+
A function belongs to an app, so the canonical route carries the appId:
|
|
234
|
+
`POST /collection/:collectionId/app/:appId/functions/:name`. Two different installed apps can each
|
|
235
|
+
ship a function of the same name without clashing.
|
|
236
|
+
|
|
237
|
+
The SDK fills the appId in for you. Initialize with your app's id once, then call by bare name —
|
|
238
|
+
the SDK routes it app-scoped:
|
|
239
|
+
|
|
240
|
+
```ts
|
|
241
|
+
SL.initializeApi({ baseURL, appId: 'my-counter-app' }) // usually done by the host/bootstrap
|
|
242
|
+
await SL.functions.call(collectionId, 'pressCounter') // → /collection/:c/app/my-counter-app/functions/pressCounter
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
To call a *different* app's function, pass the appId explicitly:
|
|
246
|
+
|
|
247
|
+
```ts
|
|
248
|
+
await SL.functions.call(collectionId, 'pressCounter', {}, { appId: 'some-other-app' })
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
**The app does NOT have to be enabled on the collection.** Because the appId is explicit, the
|
|
252
|
+
function is resolved directly from the app's release — so you can test an app on any collection
|
|
253
|
+
before installing it (and without it showing up in that collection's menus). Enabling an app is
|
|
254
|
+
currently just a UX courtesy (dropdowns/menus), not a gate on running its functions. For a build
|
|
255
|
+
that isn't the default `stable` channel — e.g. a **dev** app you haven't installed — pass the
|
|
256
|
+
channel:
|
|
257
|
+
|
|
258
|
+
```ts
|
|
259
|
+
await SL.functions.call(collectionId, 'pressCounter', {}, { appId: 'my-counter-app', channel: 'dev' })
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
If no appId is available (not set on init, none passed), the call falls back to the **deprecated
|
|
263
|
+
flat path** `/collection/:c/functions/:name`, which searches only the collection's **enabled** apps,
|
|
264
|
+
resolves by bare name, and **rejects with `409 AMBIGUOUS_FUNCTION`** when more than one defines that
|
|
265
|
+
name (a first-party builtin still wins). Always prefer an appId.
|
|
266
|
+
|
|
267
|
+
> There is no such thing as an app-less function. First-party builtins (e.g. the `functions.ping`
|
|
268
|
+
> diagnostic) belong to the reserved `smartlinks` app. A "global" function is just an app enabled on
|
|
269
|
+
> the `global` collection and called at `/collection/global/app/:appId/functions/:name` — same two
|
|
270
|
+
> facets (collection + app), with `global` as the sentinel collection.
|
|
271
|
+
|
|
214
272
|
---
|
|
215
273
|
|
|
216
274
|
## Runtime — what your function can use
|
|
@@ -262,6 +320,8 @@ Prefer the platform primitives above over a dependency; when you do need one, pi
|
|
|
262
320
|
| Hash / HMAC-sign / verify / encrypt | `crypto.subtle` (Web Crypto) | — |
|
|
263
321
|
| Random id / bytes | `crypto.randomUUID()` / `crypto.getRandomValues()` | — |
|
|
264
322
|
| Read or write SmartLinks data | `ctx.sl.*` (records, products, attestations, …) | the matching `sl:<res>:<read\|write>` |
|
|
323
|
+
| Read/write general or app-wide data (config, URLs, counters, arrays) | `ctx.sl.appData.get()` / `ctx.sl.appData.set({…})` — pass `{ scope: 'global' }` for app-wide (shared by every install; global writes need collection authority) | `sl:data:read` / `sl:data:write` |
|
|
324
|
+
| Guarantee a UNIQUE claim (pool of numbers, one-per-user, idempotency key) | `ctx.sl.appRecords.create({ ref: 'ball:57' })` — the DB unique index rejects a duplicate `ref` (catch = "already taken") | `sl:records:write` |
|
|
265
325
|
|
|
266
326
|
Notes:
|
|
267
327
|
- **Talking to the SmartLinks core is `ctx.sl`, not HTTP.** The common pattern — *validate /
|
|
@@ -318,14 +378,31 @@ other secrets, or any other collection.
|
|
|
318
378
|
|
|
319
379
|
## Invoking an http function
|
|
320
380
|
|
|
321
|
-
An `http` function is called by POSTing to the
|
|
381
|
+
An `http` function is called by POSTing to the app-scoped functions endpoint on the
|
|
322
382
|
surface that matches its `visibility`:
|
|
323
383
|
|
|
324
384
|
```
|
|
325
|
-
POST /admin/collection/:collectionId/functions/:name # visibility: admin (collection-admin auth)
|
|
326
|
-
POST /public/collection/:collectionId/functions/:name
|
|
385
|
+
POST /admin/collection/:collectionId/app/:appId/functions/:name # visibility: admin (collection-admin auth)
|
|
386
|
+
POST /public/collection/:collectionId/app/:appId/functions/:name # visibility: public (auth optional)
|
|
387
|
+
GET /{admin|public}/collection/:collectionId/app/:appId/functions # list this app's functions on the surface
|
|
327
388
|
```
|
|
328
389
|
|
|
390
|
+
The app-scoped route resolves the app **directly by id**, so the app need not be enabled on the
|
|
391
|
+
collection — handy for testing an un-installed (dev) app. Add `?channel=dev` (default `stable`) to
|
|
392
|
+
pick the release. Enablement (`appConfig.apps[]`) currently only controls menus/discovery, not
|
|
393
|
+
whether a function can run.
|
|
394
|
+
|
|
395
|
+
The bare-name form (`/collection/:c/functions/:name`, no `/app/:appId`) is a **deprecated alias**:
|
|
396
|
+
it searches only the collection's **enabled** apps, resolves by name, lets a first-party builtin
|
|
397
|
+
win, and returns `409 AMBIGUOUS_FUNCTION` when two enabled apps define the same name. Prefer the
|
|
398
|
+
app-scoped route (the SDK emits it automatically once an appId is set — see "Calling a function
|
|
399
|
+
from your app").
|
|
400
|
+
|
|
401
|
+
> **Security note (first-party model, today):** because enablement is not an auth gate, any app's
|
|
402
|
+
> function can be invoked on any collection by id. That's fine while all apps are first-party and
|
|
403
|
+
> trusted. When untrusted third-party apps arrive, gate *elevated* (`public` + `collection`)
|
|
404
|
+
> invocation of a **non-enabled** app behind install/consent — the app-scoped resolver is the seam.
|
|
405
|
+
|
|
329
406
|
The request body is delivered to the handler as `event.body` (query string as
|
|
330
407
|
`event.query`). A function only runs on its own surface — calling an `admin` function on
|
|
331
408
|
the public endpoint is a `403`. The response is `{ ok: true, result }` on success, or
|
package/dist/http.d.ts
CHANGED
|
@@ -27,6 +27,10 @@ export declare function getHttpCacheDiagnostics(): {
|
|
|
27
27
|
export declare function resetHttpCacheDiagnostics(): void;
|
|
28
28
|
/** Return whether proxy mode is currently enabled. */
|
|
29
29
|
export declare function isProxyEnabled(): boolean;
|
|
30
|
+
/** The current app context (appId), if the SDK was initialized with one. */
|
|
31
|
+
export declare function getAppContext(): string | undefined;
|
|
32
|
+
/** Set (or clear) the current app context — the appId used to scope SL.functions calls. */
|
|
33
|
+
export declare function setAppContext(id: string | undefined): void;
|
|
30
34
|
export declare function initializeApi(options: {
|
|
31
35
|
baseURL: string;
|
|
32
36
|
apiKey?: string;
|
|
@@ -42,6 +46,12 @@ export declare function initializeApi(options: {
|
|
|
42
46
|
* across re-initialization when not supplied.
|
|
43
47
|
*/
|
|
44
48
|
platform?: 'native' | 'web';
|
|
49
|
+
/**
|
|
50
|
+
* The appId of the micro-app initializing the SDK. When set, `SL.functions.call(...)` and
|
|
51
|
+
* `callAdmin(...)` resolve app-scoped by default (no need to pass appId on every call).
|
|
52
|
+
* Preserved across re-initialization when not supplied.
|
|
53
|
+
*/
|
|
54
|
+
appId?: string;
|
|
45
55
|
iframeAutoResize?: boolean;
|
|
46
56
|
logger?: Logger;
|
|
47
57
|
/**
|
package/dist/http.js
CHANGED
|
@@ -45,6 +45,13 @@ let clientPlatform = undefined;
|
|
|
45
45
|
* data call that touches the granted proof (attestations, threads, app data).
|
|
46
46
|
*/
|
|
47
47
|
let grantToken = undefined;
|
|
48
|
+
/**
|
|
49
|
+
* The current app context — the appId of the micro-app this SDK instance belongs to.
|
|
50
|
+
* Set via initializeApi({ appId }) or setAppContext(). Lets an app call its OWN server
|
|
51
|
+
* functions without repeating its id: SL.functions.call(collectionId, name) resolves
|
|
52
|
+
* app-scoped when this is set. Explicit `appId` on a call always overrides it.
|
|
53
|
+
*/
|
|
54
|
+
let appContextId = undefined;
|
|
48
55
|
/** Whether initializeApi has been successfully called at least once. */
|
|
49
56
|
let initialized = false;
|
|
50
57
|
/** Safely returns the current browser hostname, or an empty string in non-browser / Node environments. */
|
|
@@ -293,6 +300,14 @@ function logDebug(...args) {
|
|
|
293
300
|
export function isProxyEnabled() {
|
|
294
301
|
return proxyMode;
|
|
295
302
|
}
|
|
303
|
+
/** The current app context (appId), if the SDK was initialized with one. */
|
|
304
|
+
export function getAppContext() {
|
|
305
|
+
return appContextId;
|
|
306
|
+
}
|
|
307
|
+
/** Set (or clear) the current app context — the appId used to scope SL.functions calls. */
|
|
308
|
+
export function setAppContext(id) {
|
|
309
|
+
appContextId = id;
|
|
310
|
+
}
|
|
296
311
|
function maskSensitive(value) {
|
|
297
312
|
if (!value)
|
|
298
313
|
return value;
|
|
@@ -443,6 +458,9 @@ export function initializeApi(options) {
|
|
|
443
458
|
}
|
|
444
459
|
baseURL = normalizedBaseURL;
|
|
445
460
|
apiKey = options.apiKey;
|
|
461
|
+
// Preserve the app context across re-inits that omit it (mirrors platform/token handling).
|
|
462
|
+
if (options.appId !== undefined)
|
|
463
|
+
appContextId = options.appId;
|
|
446
464
|
// Enable token persistence before restoring the token.
|
|
447
465
|
if (options.persistToken !== undefined)
|
|
448
466
|
tokenPersistenceEnabled = options.persistToken;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { initializeApi, isInitialized, hasAuthCredentials, configureSdkCache, invalidateCache, getHttpCacheDiagnostics, resetHttpCacheDiagnostics, request, post, put, patch, del, sendCustomProxyMessage, getApiHeaders, getBaseURL, isProxyEnabled, setBearerToken, getBearerToken, setGrantToken, getGrantToken } from "./http.js";
|
|
1
|
+
export { initializeApi, isInitialized, hasAuthCredentials, configureSdkCache, invalidateCache, getHttpCacheDiagnostics, resetHttpCacheDiagnostics, request, post, put, patch, del, sendCustomProxyMessage, getApiHeaders, getBaseURL, isProxyEnabled, setBearerToken, getBearerToken, setGrantToken, getGrantToken, getAppContext, setAppContext } from "./http.js";
|
|
2
2
|
export * from "./api/index.js";
|
|
3
3
|
export * from "./types/index.js";
|
|
4
4
|
export { iframe } from "./iframe.js";
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
// Top-level entrypoint of the npm package. Re-export initializeApi + all namespaces.
|
|
3
|
-
export { initializeApi, isInitialized, hasAuthCredentials, configureSdkCache, invalidateCache, getHttpCacheDiagnostics, resetHttpCacheDiagnostics, request, post, put, patch, del, sendCustomProxyMessage, getApiHeaders, getBaseURL, isProxyEnabled, setBearerToken, getBearerToken, setGrantToken, getGrantToken } from "./http.js";
|
|
3
|
+
export { initializeApi, isInitialized, hasAuthCredentials, configureSdkCache, invalidateCache, getHttpCacheDiagnostics, resetHttpCacheDiagnostics, request, post, put, patch, del, sendCustomProxyMessage, getApiHeaders, getBaseURL, isProxyEnabled, setBearerToken, getBearerToken, setGrantToken, getGrantToken, getAppContext, setAppContext } from "./http.js";
|
|
4
4
|
export * from "./api/index.js";
|
|
5
5
|
export * from "./types/index.js";
|
|
6
6
|
// Iframe namespace
|
package/dist/openapi.yaml
CHANGED
|
@@ -72,8 +72,6 @@ tags:
|
|
|
72
72
|
description: Product facet querying and aggregation.
|
|
73
73
|
- name: form
|
|
74
74
|
description: Form definitions and submissions.
|
|
75
|
-
- name: functions
|
|
76
|
-
description: functions API
|
|
77
75
|
- name: integrations
|
|
78
76
|
description: Third-party integration flows and imports.
|
|
79
77
|
- name: interactions
|
|
@@ -12152,32 +12150,6 @@ paths:
|
|
|
12152
12150
|
description: Unauthorized
|
|
12153
12151
|
404:
|
|
12154
12152
|
description: Not found
|
|
12155
|
-
/public/collection/{collectionId}/functions:
|
|
12156
|
-
get:
|
|
12157
|
-
tags:
|
|
12158
|
-
- functions
|
|
12159
|
-
summary: List the public functions available for a collection (discovery).
|
|
12160
|
-
operationId: functions_list
|
|
12161
|
-
security: []
|
|
12162
|
-
parameters:
|
|
12163
|
-
- name: collectionId
|
|
12164
|
-
in: path
|
|
12165
|
-
required: true
|
|
12166
|
-
schema:
|
|
12167
|
-
type: string
|
|
12168
|
-
responses:
|
|
12169
|
-
200:
|
|
12170
|
-
description: Success
|
|
12171
|
-
content:
|
|
12172
|
-
application/json:
|
|
12173
|
-
schema:
|
|
12174
|
-
$ref: "#/components/schemas/FunctionListResponse"
|
|
12175
|
-
400:
|
|
12176
|
-
description: Bad request
|
|
12177
|
-
401:
|
|
12178
|
-
description: Unauthorized
|
|
12179
|
-
404:
|
|
12180
|
-
description: Not found
|
|
12181
12153
|
/public/collection/{collectionId}/interactions:
|
|
12182
12154
|
get:
|
|
12183
12155
|
tags:
|
|
@@ -29314,6 +29286,12 @@ components:
|
|
|
29314
29286
|
$ref: "#/components/schemas/FunctionListEntry"
|
|
29315
29287
|
required:
|
|
29316
29288
|
- functions
|
|
29289
|
+
FunctionCallOptions:
|
|
29290
|
+
type: object
|
|
29291
|
+
properties:
|
|
29292
|
+
appId:
|
|
29293
|
+
type: object
|
|
29294
|
+
additionalProperties: true
|
|
29317
29295
|
AllocateSequenceInput:
|
|
29318
29296
|
type: object
|
|
29319
29297
|
properties:
|
package/docs/API_SUMMARY.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Smartlinks API Summary
|
|
2
2
|
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.22 | Generated: 2026-09-25T18:07:22.478Z
|
|
4
4
|
|
|
5
5
|
This is a concise summary of all available API functions and types.
|
|
6
6
|
|
|
@@ -166,6 +166,12 @@ Reset the diagnostics counters (does not touch the cache itself).
|
|
|
166
166
|
**isProxyEnabled**() → `boolean`
|
|
167
167
|
Return whether proxy mode is currently enabled.
|
|
168
168
|
|
|
169
|
+
**getAppContext**() → `string | undefined`
|
|
170
|
+
The current app context (appId), if the SDK was initialized with one.
|
|
171
|
+
|
|
172
|
+
**setAppContext**(id: string | undefined) → `void`
|
|
173
|
+
Set (or clear) the current app context — the appId used to scope SL.functions calls.
|
|
174
|
+
|
|
169
175
|
**initializeApi**(options: {
|
|
170
176
|
baseURL: string
|
|
171
177
|
apiKey?: string
|
|
@@ -8960,6 +8966,13 @@ interface FunctionListResponse {
|
|
|
8960
8966
|
}
|
|
8961
8967
|
```
|
|
8962
8968
|
|
|
8969
|
+
**FunctionCallOptions** (interface)
|
|
8970
|
+
```typescript
|
|
8971
|
+
interface FunctionCallOptions {
|
|
8972
|
+
appId?: string; channel?: string
|
|
8973
|
+
}
|
|
8974
|
+
```
|
|
8975
|
+
|
|
8963
8976
|
**FunctionCallResult** = `any`
|
|
8964
8977
|
|
|
8965
8978
|
### sequence (api)
|
|
@@ -10458,16 +10471,18 @@ Delete a form for a collection (admin only).
|
|
|
10458
10471
|
|
|
10459
10472
|
**call**(collectionId: string,
|
|
10460
10473
|
name: string,
|
|
10461
|
-
body: Record<string, any> = {}
|
|
10462
|
-
|
|
10474
|
+
body: Record<string, any> = {},
|
|
10475
|
+
opts: FunctionCallOptions = {}) → `Promise<T>`
|
|
10476
|
+
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' })
|
|
10463
10477
|
|
|
10464
10478
|
**callAdmin**(collectionId: string,
|
|
10465
10479
|
name: string,
|
|
10466
|
-
body: Record<string, any> = {}
|
|
10467
|
-
|
|
10480
|
+
body: Record<string, any> = {},
|
|
10481
|
+
opts: FunctionCallOptions = {}) → `Promise<T>`
|
|
10482
|
+
Call an ADMIN app server function (surface `'admin'`; requires an admin session). App-scoped: `POST /admin/collection/:c/app/:appId/functions/:name`.
|
|
10468
10483
|
|
|
10469
|
-
**list**(collectionId: string) → `Promise<FunctionListResponse>`
|
|
10470
|
-
List the public functions available for a collection (discovery). `GET /public/collection/:c/functions`.
|
|
10484
|
+
**list**(collectionId: string, opts: FunctionCallOptions = {}) → `Promise<FunctionListResponse>`
|
|
10485
|
+
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`.
|
|
10471
10486
|
|
|
10472
10487
|
### http
|
|
10473
10488
|
|
package/docs/server-functions.md
CHANGED
|
@@ -181,11 +181,14 @@ interface ServerFunctionContext {
|
|
|
181
181
|
collectionId: string
|
|
182
182
|
appId: string
|
|
183
183
|
|
|
184
|
-
/** SmartLinks SDK, pre-scoped to your declared `authority`. Capabilities cap what it may do.
|
|
184
|
+
/** SmartLinks SDK, pre-scoped to your declared `authority`. Capabilities cap what it may do.
|
|
185
|
+
* Key resources: `sl.appRecords` (per-app records), `sl.appData` (general/app-wide config +
|
|
186
|
+
* data — URLs, flags, counters, arrays), `sl.products`, `sl.attestations`. */
|
|
185
187
|
sl: SmartLinks
|
|
186
188
|
|
|
187
|
-
/** Capability-gated secrets
|
|
188
|
-
|
|
189
|
+
/** Capability-gated secrets (need `secrets:<ref>`). `get(ref)` resolves the collection's OWN
|
|
190
|
+
* secret first, then the app-level one; `app(ref)` reads the app-level secret only. */
|
|
191
|
+
secrets: { get(ref: string): Promise<string | null>; app(ref: string): Promise<string | null> }
|
|
189
192
|
|
|
190
193
|
/** Who invoked this function. */
|
|
191
194
|
caller: {
|
|
@@ -211,6 +214,61 @@ already authenticated as your declared authority and scoped to the collection. D
|
|
|
211
214
|
to construct your own SDK client or carry your own key; that's what `ctx.sl` is for, and it's
|
|
212
215
|
the only way authority stays correct.
|
|
213
216
|
|
|
217
|
+
### Calling a function from your app (client side)
|
|
218
|
+
|
|
219
|
+
From a page/UI, invoke an `http`-trigger function with the SDK — no manual fetch, no URL building:
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
// public function (runs on the caller's authority — signed-in owner, else public):
|
|
223
|
+
const res = await SL.functions.call(collectionId, 'increment', { /* becomes event.body */ })
|
|
224
|
+
// admin surface (needs an admin session):
|
|
225
|
+
const res = await SL.functions.callAdmin(collectionId, 'recomputeTotals', { … })
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
`res` is whatever your handler returns. See the `edge-function-test` example for a full page +
|
|
229
|
+
function + manifest.
|
|
230
|
+
|
|
231
|
+
#### Functions are ALWAYS app-scoped
|
|
232
|
+
|
|
233
|
+
A function belongs to an app, so the canonical route carries the appId:
|
|
234
|
+
`POST /collection/:collectionId/app/:appId/functions/:name`. Two different installed apps can each
|
|
235
|
+
ship a function of the same name without clashing.
|
|
236
|
+
|
|
237
|
+
The SDK fills the appId in for you. Initialize with your app's id once, then call by bare name —
|
|
238
|
+
the SDK routes it app-scoped:
|
|
239
|
+
|
|
240
|
+
```ts
|
|
241
|
+
SL.initializeApi({ baseURL, appId: 'my-counter-app' }) // usually done by the host/bootstrap
|
|
242
|
+
await SL.functions.call(collectionId, 'pressCounter') // → /collection/:c/app/my-counter-app/functions/pressCounter
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
To call a *different* app's function, pass the appId explicitly:
|
|
246
|
+
|
|
247
|
+
```ts
|
|
248
|
+
await SL.functions.call(collectionId, 'pressCounter', {}, { appId: 'some-other-app' })
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
**The app does NOT have to be enabled on the collection.** Because the appId is explicit, the
|
|
252
|
+
function is resolved directly from the app's release — so you can test an app on any collection
|
|
253
|
+
before installing it (and without it showing up in that collection's menus). Enabling an app is
|
|
254
|
+
currently just a UX courtesy (dropdowns/menus), not a gate on running its functions. For a build
|
|
255
|
+
that isn't the default `stable` channel — e.g. a **dev** app you haven't installed — pass the
|
|
256
|
+
channel:
|
|
257
|
+
|
|
258
|
+
```ts
|
|
259
|
+
await SL.functions.call(collectionId, 'pressCounter', {}, { appId: 'my-counter-app', channel: 'dev' })
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
If no appId is available (not set on init, none passed), the call falls back to the **deprecated
|
|
263
|
+
flat path** `/collection/:c/functions/:name`, which searches only the collection's **enabled** apps,
|
|
264
|
+
resolves by bare name, and **rejects with `409 AMBIGUOUS_FUNCTION`** when more than one defines that
|
|
265
|
+
name (a first-party builtin still wins). Always prefer an appId.
|
|
266
|
+
|
|
267
|
+
> There is no such thing as an app-less function. First-party builtins (e.g. the `functions.ping`
|
|
268
|
+
> diagnostic) belong to the reserved `smartlinks` app. A "global" function is just an app enabled on
|
|
269
|
+
> the `global` collection and called at `/collection/global/app/:appId/functions/:name` — same two
|
|
270
|
+
> facets (collection + app), with `global` as the sentinel collection.
|
|
271
|
+
|
|
214
272
|
---
|
|
215
273
|
|
|
216
274
|
## Runtime — what your function can use
|
|
@@ -262,6 +320,8 @@ Prefer the platform primitives above over a dependency; when you do need one, pi
|
|
|
262
320
|
| Hash / HMAC-sign / verify / encrypt | `crypto.subtle` (Web Crypto) | — |
|
|
263
321
|
| Random id / bytes | `crypto.randomUUID()` / `crypto.getRandomValues()` | — |
|
|
264
322
|
| Read or write SmartLinks data | `ctx.sl.*` (records, products, attestations, …) | the matching `sl:<res>:<read\|write>` |
|
|
323
|
+
| Read/write general or app-wide data (config, URLs, counters, arrays) | `ctx.sl.appData.get()` / `ctx.sl.appData.set({…})` — pass `{ scope: 'global' }` for app-wide (shared by every install; global writes need collection authority) | `sl:data:read` / `sl:data:write` |
|
|
324
|
+
| Guarantee a UNIQUE claim (pool of numbers, one-per-user, idempotency key) | `ctx.sl.appRecords.create({ ref: 'ball:57' })` — the DB unique index rejects a duplicate `ref` (catch = "already taken") | `sl:records:write` |
|
|
265
325
|
|
|
266
326
|
Notes:
|
|
267
327
|
- **Talking to the SmartLinks core is `ctx.sl`, not HTTP.** The common pattern — *validate /
|
|
@@ -318,14 +378,31 @@ other secrets, or any other collection.
|
|
|
318
378
|
|
|
319
379
|
## Invoking an http function
|
|
320
380
|
|
|
321
|
-
An `http` function is called by POSTing to the
|
|
381
|
+
An `http` function is called by POSTing to the app-scoped functions endpoint on the
|
|
322
382
|
surface that matches its `visibility`:
|
|
323
383
|
|
|
324
384
|
```
|
|
325
|
-
POST /admin/collection/:collectionId/functions/:name # visibility: admin (collection-admin auth)
|
|
326
|
-
POST /public/collection/:collectionId/functions/:name
|
|
385
|
+
POST /admin/collection/:collectionId/app/:appId/functions/:name # visibility: admin (collection-admin auth)
|
|
386
|
+
POST /public/collection/:collectionId/app/:appId/functions/:name # visibility: public (auth optional)
|
|
387
|
+
GET /{admin|public}/collection/:collectionId/app/:appId/functions # list this app's functions on the surface
|
|
327
388
|
```
|
|
328
389
|
|
|
390
|
+
The app-scoped route resolves the app **directly by id**, so the app need not be enabled on the
|
|
391
|
+
collection — handy for testing an un-installed (dev) app. Add `?channel=dev` (default `stable`) to
|
|
392
|
+
pick the release. Enablement (`appConfig.apps[]`) currently only controls menus/discovery, not
|
|
393
|
+
whether a function can run.
|
|
394
|
+
|
|
395
|
+
The bare-name form (`/collection/:c/functions/:name`, no `/app/:appId`) is a **deprecated alias**:
|
|
396
|
+
it searches only the collection's **enabled** apps, resolves by name, lets a first-party builtin
|
|
397
|
+
win, and returns `409 AMBIGUOUS_FUNCTION` when two enabled apps define the same name. Prefer the
|
|
398
|
+
app-scoped route (the SDK emits it automatically once an appId is set — see "Calling a function
|
|
399
|
+
from your app").
|
|
400
|
+
|
|
401
|
+
> **Security note (first-party model, today):** because enablement is not an auth gate, any app's
|
|
402
|
+
> function can be invoked on any collection by id. That's fine while all apps are first-party and
|
|
403
|
+
> trusted. When untrusted third-party apps arrive, gate *elevated* (`public` + `collection`)
|
|
404
|
+
> invocation of a **non-enabled** app behind install/consent — the app-scoped resolver is the seam.
|
|
405
|
+
|
|
329
406
|
The request body is delivered to the handler as `event.body` (query string as
|
|
330
407
|
`event.query`). A function only runs on its own surface — calling an `admin` function on
|
|
331
408
|
the public endpoint is a `403`. The response is `{ ok: true, result }` on success, or
|
package/openapi.yaml
CHANGED
|
@@ -72,8 +72,6 @@ tags:
|
|
|
72
72
|
description: Product facet querying and aggregation.
|
|
73
73
|
- name: form
|
|
74
74
|
description: Form definitions and submissions.
|
|
75
|
-
- name: functions
|
|
76
|
-
description: functions API
|
|
77
75
|
- name: integrations
|
|
78
76
|
description: Third-party integration flows and imports.
|
|
79
77
|
- name: interactions
|
|
@@ -12152,32 +12150,6 @@ paths:
|
|
|
12152
12150
|
description: Unauthorized
|
|
12153
12151
|
404:
|
|
12154
12152
|
description: Not found
|
|
12155
|
-
/public/collection/{collectionId}/functions:
|
|
12156
|
-
get:
|
|
12157
|
-
tags:
|
|
12158
|
-
- functions
|
|
12159
|
-
summary: List the public functions available for a collection (discovery).
|
|
12160
|
-
operationId: functions_list
|
|
12161
|
-
security: []
|
|
12162
|
-
parameters:
|
|
12163
|
-
- name: collectionId
|
|
12164
|
-
in: path
|
|
12165
|
-
required: true
|
|
12166
|
-
schema:
|
|
12167
|
-
type: string
|
|
12168
|
-
responses:
|
|
12169
|
-
200:
|
|
12170
|
-
description: Success
|
|
12171
|
-
content:
|
|
12172
|
-
application/json:
|
|
12173
|
-
schema:
|
|
12174
|
-
$ref: "#/components/schemas/FunctionListResponse"
|
|
12175
|
-
400:
|
|
12176
|
-
description: Bad request
|
|
12177
|
-
401:
|
|
12178
|
-
description: Unauthorized
|
|
12179
|
-
404:
|
|
12180
|
-
description: Not found
|
|
12181
12153
|
/public/collection/{collectionId}/interactions:
|
|
12182
12154
|
get:
|
|
12183
12155
|
tags:
|
|
@@ -29314,6 +29286,12 @@ components:
|
|
|
29314
29286
|
$ref: "#/components/schemas/FunctionListEntry"
|
|
29315
29287
|
required:
|
|
29316
29288
|
- functions
|
|
29289
|
+
FunctionCallOptions:
|
|
29290
|
+
type: object
|
|
29291
|
+
properties:
|
|
29292
|
+
appId:
|
|
29293
|
+
type: object
|
|
29294
|
+
additionalProperties: true
|
|
29317
29295
|
AllocateSequenceInput:
|
|
29318
29296
|
type: object
|
|
29319
29297
|
properties:
|