@proveanything/smartlinks 2.0.19 → 2.0.21

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.
@@ -0,0 +1,37 @@
1
+ /** A server function's returned value — shape is app-defined, so untyped by default. */
2
+ export type FunctionCallResult = any;
3
+ export interface FunctionListEntry {
4
+ name: string;
5
+ visibility?: string;
6
+ trigger?: string;
7
+ }
8
+ export interface FunctionListResponse {
9
+ functions: FunctionListEntry[];
10
+ }
11
+ /** Options for a function call. `appId` scopes resolution to one app (recommended). */
12
+ export interface FunctionCallOptions {
13
+ appId?: string;
14
+ }
15
+ export declare namespace functions {
16
+ /**
17
+ * Call a PUBLIC app server function inline (surface `'public'`).
18
+ * App-scoped: `POST /public/collection/:c/app/:appId/functions/:name`.
19
+ *
20
+ * @example
21
+ * // App calling its own function (appId from initializeApi({ appId })):
22
+ * const { value } = await SL.functions.call<{ value: number }>(collectionId, 'pressCounter')
23
+ * // Or address another app explicitly:
24
+ * await SL.functions.call(collectionId, 'pressCounter', {}, { appId: 'my-counter-app' })
25
+ */
26
+ function call<T = FunctionCallResult>(collectionId: string, name: string, body?: Record<string, any>, opts?: FunctionCallOptions): Promise<T>;
27
+ /**
28
+ * Call an ADMIN app server function (surface `'admin'`; requires an admin session).
29
+ * App-scoped: `POST /admin/collection/:c/app/:appId/functions/:name`.
30
+ */
31
+ function callAdmin<T = FunctionCallResult>(collectionId: string, name: string, body?: Record<string, any>, opts?: FunctionCallOptions): Promise<T>;
32
+ /**
33
+ * List the public functions available for a collection (discovery). Scoped to one app when an
34
+ * appId is given (or set as the SDK app context): `GET /public/collection/:c[/app/:appId]/functions`.
35
+ */
36
+ function list(collectionId: string, opts?: FunctionCallOptions): Promise<FunctionListResponse>;
37
+ }
@@ -0,0 +1,63 @@
1
+ // Client-side invocation of app SERVER FUNCTIONS.
2
+ //
3
+ // The function itself is authored server-side (one contract: `async (ctx, event) => result`,
4
+ // see docs/server-functions.md). THIS is how a page/UI actually calls one — the missing
5
+ // client half. The PUBLIC route runs a function inline on the caller's own authority (owner if
6
+ // signed in via authKit, else public); the ADMIN route runs on the admin surface (needs an admin
7
+ // session). The returned value is the function's own result (`event.body` is what you pass here).
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, appId) {
18
+ const c = encodeURIComponent(collectionId);
19
+ const n = encodeURIComponent(name);
20
+ const app = appId !== null && appId !== void 0 ? appId : getAppContext();
21
+ return app
22
+ ? `/${surface}/collection/${c}/app/${encodeURIComponent(app)}/functions/${n}`
23
+ : `/${surface}/collection/${c}/functions/${n}`; // deprecated flat alias
24
+ }
25
+ export var functions;
26
+ (function (functions) {
27
+ /**
28
+ * Call a PUBLIC app server function inline (surface `'public'`).
29
+ * App-scoped: `POST /public/collection/:c/app/:appId/functions/:name`.
30
+ *
31
+ * @example
32
+ * // App calling its own function (appId from initializeApi({ appId })):
33
+ * const { value } = await SL.functions.call<{ value: number }>(collectionId, 'pressCounter')
34
+ * // Or address another app explicitly:
35
+ * await SL.functions.call(collectionId, 'pressCounter', {}, { appId: 'my-counter-app' })
36
+ */
37
+ async function call(collectionId, name, body = {}, opts = {}) {
38
+ return post(fnPath('public', collectionId, name, opts.appId), body);
39
+ }
40
+ functions.call = call;
41
+ /**
42
+ * Call an ADMIN app server function (surface `'admin'`; requires an admin session).
43
+ * App-scoped: `POST /admin/collection/:c/app/:appId/functions/:name`.
44
+ */
45
+ async function callAdmin(collectionId, name, body = {}, opts = {}) {
46
+ return post(fnPath('admin', collectionId, name, opts.appId), body);
47
+ }
48
+ functions.callAdmin = callAdmin;
49
+ /**
50
+ * List the public functions available for a collection (discovery). Scoped to one app when an
51
+ * appId is given (or set as the SDK app context): `GET /public/collection/:c[/app/:appId]/functions`.
52
+ */
53
+ async function list(collectionId, opts = {}) {
54
+ var _a;
55
+ const c = encodeURIComponent(collectionId);
56
+ const app = (_a = opts.appId) !== null && _a !== void 0 ? _a : getAppContext();
57
+ const path = app
58
+ ? `/public/collection/${c}/app/${encodeURIComponent(app)}/functions`
59
+ : `/public/collection/${c}/functions`;
60
+ return request(path);
61
+ }
62
+ functions.list = list;
63
+ })(functions || (functions = {}));
@@ -28,6 +28,7 @@ export { qr } from "./qr.js";
28
28
  export { template } from "./template.js";
29
29
  export { interactions } from "./interactions.js";
30
30
  export { analytics } from "./analytics.js";
31
+ export { functions } from "./functions.js";
31
32
  export { location } from "./location.js";
32
33
  export * as realtime from "./realtime.js";
33
34
  export { tags } from "./tags.js";
package/dist/api/index.js CHANGED
@@ -30,6 +30,7 @@ export { qr } from "./qr.js";
30
30
  export { template } from "./template.js";
31
31
  export { interactions } from "./interactions.js";
32
32
  export { analytics } from "./analytics.js";
33
+ export { functions } from "./functions.js";
33
34
  export { location } from "./location.js";
34
35
  import * as realtime_1 from "./realtime.js";
35
36
  export { realtime_1 as realtime };
@@ -1,6 +1,6 @@
1
1
  # Smartlinks API Summary
2
2
 
3
- Version: 2.0.19 | Generated: 2026-09-24T19:51:28.993Z
3
+ Version: 2.0.21 | Generated: 2026-09-25T17:47:14.766Z
4
4
 
5
5
  This is a concise summary of all available API functions and types.
6
6
 
@@ -135,6 +135,7 @@ The Smartlinks SDK is organized into the following namespaces:
135
135
  - **config** - Functions for config operations
136
136
  - **containers** - Functions for containers operations
137
137
  - **facets** - Functions for facets operations
138
+ - **functions** - Functions for functions operations
138
139
  - **http** - Functions for http operations
139
140
  - **integrations** - Functions for integrations operations
140
141
  - **jobs** - Functions for jobs operations
@@ -165,6 +166,12 @@ Reset the diagnostics counters (does not touch the cache itself).
165
166
  **isProxyEnabled**() → `boolean`
166
167
  Return whether proxy mode is currently enabled.
167
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
+
168
175
  **initializeApi**(options: {
169
176
  baseURL: string
170
177
  apiKey?: string
@@ -8943,6 +8950,31 @@ type VerifyTokenResponse = {
8943
8950
  }
8944
8951
  ```
8945
8952
 
8953
+ ### functions (api)
8954
+
8955
+ **FunctionListEntry** (interface)
8956
+ ```typescript
8957
+ interface FunctionListEntry {
8958
+ name: string; visibility?: string; trigger?: string
8959
+ }
8960
+ ```
8961
+
8962
+ **FunctionListResponse** (interface)
8963
+ ```typescript
8964
+ interface FunctionListResponse {
8965
+ functions: FunctionListEntry[]
8966
+ }
8967
+ ```
8968
+
8969
+ **FunctionCallOptions** (interface)
8970
+ ```typescript
8971
+ interface FunctionCallOptions {
8972
+ appId?: string
8973
+ }
8974
+ ```
8975
+
8976
+ **FunctionCallResult** = `any`
8977
+
8946
8978
  ### sequence (api)
8947
8979
 
8948
8980
  **AllocateSequenceInput** (interface)
@@ -10435,6 +10467,23 @@ Update a form for a collection (admin only).
10435
10467
  **remove**(collectionId: string, formId: string) → `Promise<void>`
10436
10468
  Delete a form for a collection (admin only).
10437
10469
 
10470
+ ### functions
10471
+
10472
+ **call**(collectionId: string,
10473
+ name: string,
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' })
10477
+
10478
+ **callAdmin**(collectionId: string,
10479
+ name: string,
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`.
10483
+
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`.
10486
+
10438
10487
  ### http
10439
10488
 
10440
10489
  **get**(path: string) → `Promise<T>`
@@ -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. Resolves only refs you declared via `secrets:<ref>`. */
188
- secrets: { get(ref: string): Promise<string | null> }
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,50 @@ 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
+ If no appId is available (not set on init, none passed), the call falls back to the **deprecated
252
+ flat path** `/collection/:c/functions/:name`, which the server resolves by bare name and **rejects
253
+ with `409 AMBIGUOUS_FUNCTION`** when more than one installed app defines that name (a first-party
254
+ builtin still wins). Always prefer an appId.
255
+
256
+ > There is no such thing as an app-less function. First-party builtins (e.g. the `functions.ping`
257
+ > diagnostic) belong to the reserved `smartlinks` app. A "global" function is just an app enabled on
258
+ > the `global` collection and called at `/collection/global/app/:appId/functions/:name` — same two
259
+ > facets (collection + app), with `global` as the sentinel collection.
260
+
214
261
  ---
215
262
 
216
263
  ## Runtime — what your function can use
@@ -262,6 +309,8 @@ Prefer the platform primitives above over a dependency; when you do need one, pi
262
309
  | Hash / HMAC-sign / verify / encrypt | `crypto.subtle` (Web Crypto) | — |
263
310
  | Random id / bytes | `crypto.randomUUID()` / `crypto.getRandomValues()` | — |
264
311
  | Read or write SmartLinks data | `ctx.sl.*` (records, products, attestations, …) | the matching `sl:<res>:<read\|write>` |
312
+ | 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` |
313
+ | 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
314
 
266
315
  Notes:
267
316
  - **Talking to the SmartLinks core is `ctx.sl`, not HTTP.** The common pattern — *validate /
@@ -318,14 +367,20 @@ other secrets, or any other collection.
318
367
 
319
368
  ## Invoking an http function
320
369
 
321
- An `http` function is called by POSTing to the collection's functions endpoint on the
370
+ An `http` function is called by POSTing to the app-scoped functions endpoint on the
322
371
  surface that matches its `visibility`:
323
372
 
324
373
  ```
325
- POST /admin/collection/:collectionId/functions/:name # visibility: admin (collection-admin auth)
326
- POST /public/collection/:collectionId/functions/:name # visibility: public (auth optional)
374
+ POST /admin/collection/:collectionId/app/:appId/functions/:name # visibility: admin (collection-admin auth)
375
+ POST /public/collection/:collectionId/app/:appId/functions/:name # visibility: public (auth optional)
376
+ GET /{admin|public}/collection/:collectionId/app/:appId/functions # list this app's functions on the surface
327
377
  ```
328
378
 
379
+ The bare-name form (`/collection/:c/functions/:name`, no `/app/:appId`) is a **deprecated alias**:
380
+ it resolves by name across all installed apps, lets a first-party builtin win, and returns
381
+ `409 AMBIGUOUS_FUNCTION` when two apps define the same name. Prefer the app-scoped route (the SDK
382
+ emits it automatically once an appId is set — see "Calling a function from your app").
383
+
329
384
  The request body is delivered to the handler as `event.body` (query string as
330
385
  `event.query`). A function only runs on its own surface — calling an `admin` function on
331
386
  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
@@ -29269,6 +29269,28 @@ components:
29269
29269
  additionalProperties: true
29270
29270
  required:
29271
29271
  - valid
29272
+ FunctionListEntry:
29273
+ type: object
29274
+ properties:
29275
+ name:
29276
+ type: object
29277
+ additionalProperties: true
29278
+ required:
29279
+ - name
29280
+ FunctionListResponse:
29281
+ type: object
29282
+ properties:
29283
+ functions:
29284
+ type: array
29285
+ items:
29286
+ $ref: "#/components/schemas/FunctionListEntry"
29287
+ required:
29288
+ - functions
29289
+ FunctionCallOptions:
29290
+ type: object
29291
+ properties:
29292
+ appId:
29293
+ type: string
29272
29294
  AllocateSequenceInput:
29273
29295
  type: object
29274
29296
  properties:
@@ -1,6 +1,6 @@
1
1
  # Smartlinks API Summary
2
2
 
3
- Version: 2.0.19 | Generated: 2026-09-24T19:51:28.993Z
3
+ Version: 2.0.21 | Generated: 2026-09-25T17:47:14.766Z
4
4
 
5
5
  This is a concise summary of all available API functions and types.
6
6
 
@@ -135,6 +135,7 @@ The Smartlinks SDK is organized into the following namespaces:
135
135
  - **config** - Functions for config operations
136
136
  - **containers** - Functions for containers operations
137
137
  - **facets** - Functions for facets operations
138
+ - **functions** - Functions for functions operations
138
139
  - **http** - Functions for http operations
139
140
  - **integrations** - Functions for integrations operations
140
141
  - **jobs** - Functions for jobs operations
@@ -165,6 +166,12 @@ Reset the diagnostics counters (does not touch the cache itself).
165
166
  **isProxyEnabled**() → `boolean`
166
167
  Return whether proxy mode is currently enabled.
167
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
+
168
175
  **initializeApi**(options: {
169
176
  baseURL: string
170
177
  apiKey?: string
@@ -8943,6 +8950,31 @@ type VerifyTokenResponse = {
8943
8950
  }
8944
8951
  ```
8945
8952
 
8953
+ ### functions (api)
8954
+
8955
+ **FunctionListEntry** (interface)
8956
+ ```typescript
8957
+ interface FunctionListEntry {
8958
+ name: string; visibility?: string; trigger?: string
8959
+ }
8960
+ ```
8961
+
8962
+ **FunctionListResponse** (interface)
8963
+ ```typescript
8964
+ interface FunctionListResponse {
8965
+ functions: FunctionListEntry[]
8966
+ }
8967
+ ```
8968
+
8969
+ **FunctionCallOptions** (interface)
8970
+ ```typescript
8971
+ interface FunctionCallOptions {
8972
+ appId?: string
8973
+ }
8974
+ ```
8975
+
8976
+ **FunctionCallResult** = `any`
8977
+
8946
8978
  ### sequence (api)
8947
8979
 
8948
8980
  **AllocateSequenceInput** (interface)
@@ -10435,6 +10467,23 @@ Update a form for a collection (admin only).
10435
10467
  **remove**(collectionId: string, formId: string) → `Promise<void>`
10436
10468
  Delete a form for a collection (admin only).
10437
10469
 
10470
+ ### functions
10471
+
10472
+ **call**(collectionId: string,
10473
+ name: string,
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' })
10477
+
10478
+ **callAdmin**(collectionId: string,
10479
+ name: string,
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`.
10483
+
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`.
10486
+
10438
10487
  ### http
10439
10488
 
10440
10489
  **get**(path: string) → `Promise<T>`
@@ -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. Resolves only refs you declared via `secrets:<ref>`. */
188
- secrets: { get(ref: string): Promise<string | null> }
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,50 @@ 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
+ If no appId is available (not set on init, none passed), the call falls back to the **deprecated
252
+ flat path** `/collection/:c/functions/:name`, which the server resolves by bare name and **rejects
253
+ with `409 AMBIGUOUS_FUNCTION`** when more than one installed app defines that name (a first-party
254
+ builtin still wins). Always prefer an appId.
255
+
256
+ > There is no such thing as an app-less function. First-party builtins (e.g. the `functions.ping`
257
+ > diagnostic) belong to the reserved `smartlinks` app. A "global" function is just an app enabled on
258
+ > the `global` collection and called at `/collection/global/app/:appId/functions/:name` — same two
259
+ > facets (collection + app), with `global` as the sentinel collection.
260
+
214
261
  ---
215
262
 
216
263
  ## Runtime — what your function can use
@@ -262,6 +309,8 @@ Prefer the platform primitives above over a dependency; when you do need one, pi
262
309
  | Hash / HMAC-sign / verify / encrypt | `crypto.subtle` (Web Crypto) | — |
263
310
  | Random id / bytes | `crypto.randomUUID()` / `crypto.getRandomValues()` | — |
264
311
  | Read or write SmartLinks data | `ctx.sl.*` (records, products, attestations, …) | the matching `sl:<res>:<read\|write>` |
312
+ | 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` |
313
+ | 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
314
 
266
315
  Notes:
267
316
  - **Talking to the SmartLinks core is `ctx.sl`, not HTTP.** The common pattern — *validate /
@@ -318,14 +367,20 @@ other secrets, or any other collection.
318
367
 
319
368
  ## Invoking an http function
320
369
 
321
- An `http` function is called by POSTing to the collection's functions endpoint on the
370
+ An `http` function is called by POSTing to the app-scoped functions endpoint on the
322
371
  surface that matches its `visibility`:
323
372
 
324
373
  ```
325
- POST /admin/collection/:collectionId/functions/:name # visibility: admin (collection-admin auth)
326
- POST /public/collection/:collectionId/functions/:name # visibility: public (auth optional)
374
+ POST /admin/collection/:collectionId/app/:appId/functions/:name # visibility: admin (collection-admin auth)
375
+ POST /public/collection/:collectionId/app/:appId/functions/:name # visibility: public (auth optional)
376
+ GET /{admin|public}/collection/:collectionId/app/:appId/functions # list this app's functions on the surface
327
377
  ```
328
378
 
379
+ The bare-name form (`/collection/:c/functions/:name`, no `/app/:appId`) is a **deprecated alias**:
380
+ it resolves by name across all installed apps, lets a first-party builtin win, and returns
381
+ `409 AMBIGUOUS_FUNCTION` when two apps define the same name. Prefer the app-scoped route (the SDK
382
+ emits it automatically once an appId is set — see "Calling a function from your app").
383
+
329
384
  The request body is delivered to the handler as `event.body` (query string as
330
385
  `event.query`). A function only runs on its own surface — calling an `admin` function on
331
386
  the public endpoint is a `403`. The response is `{ ok: true, result }` on success, or
package/openapi.yaml CHANGED
@@ -29269,6 +29269,28 @@ components:
29269
29269
  additionalProperties: true
29270
29270
  required:
29271
29271
  - valid
29272
+ FunctionListEntry:
29273
+ type: object
29274
+ properties:
29275
+ name:
29276
+ type: object
29277
+ additionalProperties: true
29278
+ required:
29279
+ - name
29280
+ FunctionListResponse:
29281
+ type: object
29282
+ properties:
29283
+ functions:
29284
+ type: array
29285
+ items:
29286
+ $ref: "#/components/schemas/FunctionListEntry"
29287
+ required:
29288
+ - functions
29289
+ FunctionCallOptions:
29290
+ type: object
29291
+ properties:
29292
+ appId:
29293
+ type: string
29272
29294
  AllocateSequenceInput:
29273
29295
  type: object
29274
29296
  properties:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proveanything/smartlinks",
3
- "version": "2.0.19",
3
+ "version": "2.0.21",
4
4
  "description": "Official JavaScript/TypeScript SDK for the Smartlinks API",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",