@proveanything/smartlinks 2.0.40 → 2.0.41

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.
@@ -324,6 +324,40 @@ name (a first-party builtin still wins). Always prefer an appId.
324
324
 
325
325
  ---
326
326
 
327
+ ## Inputs — data handed to your function
328
+
329
+ Declare what your function needs and the platform fetches it **before** the run: read it from `ctx.inputs` instead of calling `ctx.sl` first. One less round trip — and for functions running in the isolated runner, one less network hop.
330
+
331
+ ```jsonc
332
+ // app.manifest.json → functions.definitions[]
333
+ {
334
+ "name": "onProductChanged",
335
+ "trigger": { "type": "event", "eventTypes": ["product.updated"] },
336
+ "capabilities": ["sl:products:read", "sl:data:read"],
337
+ "inputs": { "appConfig": true } // entity is on by default for event triggers
338
+ }
339
+ ```
340
+
341
+ ```js
342
+ export async function onProductChanged(ctx, event) {
343
+ const product = ctx.inputs.entity // the product that changed
344
+ const { interactionIds } = ctx.inputs.appConfig || {} // this app's config for the collection
345
+ }
346
+ ```
347
+
348
+ | Input | What you get | Needs | Triggers |
349
+ |---|---|---|---|
350
+ | `entity` | The record the event is about: the product, your app's record, or the interaction event. **On by default**; `"entity": false` opts out. | read on that resource (`sl:products:read`, `sl:records:read`) | event |
351
+ | `collection` | The collection's public record | — | all |
352
+ | `appConfig` | Your app's config for this collection (where setup like `interactionIds` lives) | `sl:data:read` | all |
353
+ | `product` | A product named by the request: `{ "from": "body.productId" }`, `"query.<field>"` or `"path"` | `sl:products:read` | http |
354
+
355
+ - **Same rules as `ctx.sl`.** Inputs are fetched with your function's authority and capabilities, scoped to the collection it runs in — a product from another collection is never fetched.
356
+ - **Never an error.** Not found, not allowed or larger than 256 KB → that input is `null`. Check before you use it.
357
+ - **Current state.** Event inputs are fetched when your function runs, so you see the record as it is now; `event` still carries the ids.
358
+ - **Checked at publish.** An unknown input, a bad `from`, or a missing capability fails validation.
359
+ - **Testing:** pass `inputs` to the test context — `createFunctionTestContext({ def, inputs: { entity: { id: 'p1', name: 'Kettle' } } })`.
360
+
327
361
  ## Exposing a function to the AI agent
328
362
 
329
363
  An `http` function can be offered to the AI agent as a **callable tool**, alongside the built-in
package/dist/http.d.ts CHANGED
@@ -71,6 +71,11 @@ export declare function initializeApi(options: {
71
71
  awaitAuth?: boolean;
72
72
  /** How long (ms) to wait for the async token before letting requests proceed anyway. Default 8000. */
73
73
  awaitAuthTimeoutMs?: number;
74
+ /**
75
+ * proxyMode only: how long (ms) a request waits for the parent host's reply before failing with a
76
+ * 504 SmartlinksApiError (errorCode PROXY_TIMEOUT). Default 90000. 0 = wait indefinitely.
77
+ */
78
+ proxyTimeoutMs?: number;
74
79
  iframeAutoResize?: boolean;
75
80
  logger?: Logger;
76
81
  /**
package/dist/http.js CHANGED
@@ -31,6 +31,10 @@ let baseURL = null;
31
31
  let apiKey = undefined;
32
32
  let bearerToken = undefined;
33
33
  let proxyMode = false;
34
+ // How long a request through the parent (proxyMode) waits for the host's reply. Without a limit, a host
35
+ // that never answers (no responder, a dropped message) leaves the call pending forever: no network
36
+ // request in the frame, no error. 90 s covers the longest platform call (a 60 s function deadline).
37
+ let proxyTimeoutMs = 90000;
34
38
  let ngrokSkipBrowserWarning = false;
35
39
  let extraHeadersGlobal = {};
36
40
  /**
@@ -522,6 +526,8 @@ export function initializeApi(options) {
522
526
  });
523
527
  }
524
528
  proxyMode = !!options.proxyMode;
529
+ if (options.proxyTimeoutMs !== undefined)
530
+ proxyTimeoutMs = Math.max(0, Number(options.proxyTimeoutMs) || 0);
525
531
  // Auto-enable ngrok skip header if domain contains .ngrok.io and user did not explicitly set the flag.
526
532
  // Infer ngrok usage from common domains (.ngrok.io or .ngrok-free.dev)
527
533
  const inferredNgrok = /(\.ngrok\.io|\.ngrok-free\.dev)(\b|\/)/i.test(baseURL);
@@ -1082,11 +1088,30 @@ async function proxyRequest(method, path, body, headers, options) {
1082
1088
  };
1083
1089
  logDebug('[smartlinks] proxy:postMessage', { id, method, path, headers: headers ? redactHeaders(headers) : undefined, hasBody: !!body });
1084
1090
  return new Promise((resolve, reject) => {
1085
- proxyPending[id] = { resolve, reject };
1091
+ proxyPending[id] = withProxyTimeout(id, `${method} ${path}`, resolve, reject);
1086
1092
  window.parent.postMessage(msg, "*");
1087
- // Optionally: add a timeout here to reject if no response
1088
1093
  });
1089
1094
  }
1095
+ /**
1096
+ * Wrap a pending proxy request's resolve/reject with the proxy timeout (proxyTimeoutMs): if the host
1097
+ * hasn't answered by then, the request fails with a 504 PROXY_TIMEOUT instead of hanging forever.
1098
+ */
1099
+ function withProxyTimeout(id, what, resolve, reject) {
1100
+ if (!(proxyTimeoutMs > 0))
1101
+ return { resolve, reject };
1102
+ const timer = setTimeout(() => {
1103
+ if (!proxyPending[id])
1104
+ return;
1105
+ delete proxyPending[id];
1106
+ const message = `The host page did not answer ${what} within ${proxyTimeoutMs >= 1000 ? `${Math.round(proxyTimeoutMs / 1000)}s` : `${proxyTimeoutMs}ms`} (proxyMode). The parent may not be handling SmartLinks proxy requests.`;
1107
+ logDebug('[smartlinks] proxy:timeout', { id, what });
1108
+ reject(new SmartlinksApiError(message, 504, { code: 504, errorCode: 'PROXY_TIMEOUT', message }));
1109
+ }, proxyTimeoutMs);
1110
+ return {
1111
+ resolve: (data) => { clearTimeout(timer); resolve(data); },
1112
+ reject: (err) => { clearTimeout(timer); reject(err); },
1113
+ };
1114
+ }
1090
1115
  /**
1091
1116
  * Upload a FormData payload via proxy with progress events using chunked postMessage.
1092
1117
  * Parent is expected to implement the counterpart protocol.
@@ -1794,8 +1819,7 @@ export async function sendCustomProxyMessage(request, params) {
1794
1819
  };
1795
1820
  logDebug('[smartlinks] proxy:custom postMessage', { id, request, params: safeBodyPreview(params) });
1796
1821
  return new Promise((resolve, reject) => {
1797
- proxyPending[id] = { resolve, reject };
1822
+ proxyPending[id] = withProxyTimeout(id, `custom request ${request}`, resolve, reject);
1798
1823
  window.parent.postMessage(msg, "*");
1799
- // Optionally: add a timeout here to reject if no response
1800
1824
  });
1801
1825
  }
package/dist/openapi.yaml CHANGED
@@ -19124,6 +19124,8 @@ components:
19124
19124
  enum:
19125
19125
  - collection
19126
19126
  - global
19127
+ inputs:
19128
+ $ref: "#/components/schemas/AppFunctionInputs"
19127
19129
  apiVersion:
19128
19130
  type: string
19129
19131
  handler:
@@ -19233,6 +19235,8 @@ components:
19233
19235
  fetch:
19234
19236
  type: object
19235
19237
  additionalProperties: true
19238
+ inputs:
19239
+ $ref: "#/components/schemas/ServerFunctionInputs"
19236
19240
  required:
19237
19241
  - collectionId
19238
19242
  - appId
@@ -19240,6 +19244,34 @@ components:
19240
19244
  - secrets
19241
19245
  - caller
19242
19246
  - fetch
19247
+ - inputs
19248
+ AppFunctionInputs:
19249
+ type: object
19250
+ properties:
19251
+ entity:
19252
+ type: boolean
19253
+ collection:
19254
+ type: boolean
19255
+ appConfig:
19256
+ type: boolean
19257
+ product:
19258
+ type: object
19259
+ additionalProperties: true
19260
+ ServerFunctionInputs:
19261
+ type: object
19262
+ properties:
19263
+ entity:
19264
+ type: object
19265
+ additionalProperties: true
19266
+ collection:
19267
+ type: object
19268
+ additionalProperties: true
19269
+ appConfig:
19270
+ type: object
19271
+ additionalProperties: true
19272
+ product:
19273
+ type: object
19274
+ additionalProperties: true
19243
19275
  ServerFunctionHttpEvent:
19244
19276
  type: object
19245
19277
  properties:
@@ -1,4 +1,4 @@
1
- import type { AppFunctionDef, ServerFunctionContext } from '../types/appManifest.js';
1
+ import type { AppFunctionDef, ServerFunctionContext, ServerFunctionInputs } from '../types/appManifest.js';
2
2
  export declare class CapabilityError extends Error {
3
3
  capability: string;
4
4
  code: string;
@@ -62,6 +62,8 @@ export interface CreateFunctionTestContextOptions {
62
62
  sl?: TestSlImpl;
63
63
  /** Backing fetch (defaults to global fetch). Still gated by the `network` capability. */
64
64
  fetch?: typeof fetch;
65
+ /** What the platform would have prefetched as `ctx.inputs` (e.g. `{ entity: product }`). Default `{}`. */
66
+ inputs?: ServerFunctionInputs;
65
67
  }
66
68
  export interface FunctionTestContext extends ServerFunctionContext {
67
69
  /** Captured log lines (also written via ctx.log). */
@@ -176,6 +176,7 @@ export function createFunctionTestContext(opts) {
176
176
  log: (message, data) => {
177
177
  logs.push(Object.assign({ at: new Date().toISOString(), message: String(message) }, (data ? { data } : {})));
178
178
  },
179
+ inputs: Object.freeze(Object.assign({}, (opts.inputs || {}))),
179
180
  logs,
180
181
  };
181
182
  }
@@ -268,6 +268,14 @@ export interface AppFunctionDef {
268
268
  * Per-call `{ scope }` and the `appData.global` / `appData.collection` handles override this.
269
269
  */
270
270
  dataScope?: 'collection' | 'global';
271
+ /**
272
+ * Data the platform fetches BEFORE the run and hands over as `ctx.inputs` — no round trip from your
273
+ * code (for isolated functions, no network hop). Same authority and capabilities as the equivalent
274
+ * `ctx.sl` read; a missing, denied or oversized (> 256 KB) input arrives as `null`, never an error.
275
+ * Event-trigger functions get `entity` (the record the event is about) by default; `entity: false`
276
+ * opts out. See server-functions.md → "Inputs".
277
+ */
278
+ inputs?: AppFunctionInputs;
271
279
  /** SDK/manifest API version this function targets (pinned for runtime compatibility). */
272
280
  apiVersion?: string;
273
281
  /** Exported handler name in the functions bundle. Defaults to `name`. */
@@ -403,6 +411,29 @@ export interface ServerFunctionContext {
403
411
  fetch: typeof fetch;
404
412
  /** Structured logging captured into run telemetry. */
405
413
  log: (message: string, data?: Record<string, any>) => void;
414
+ /** Data prefetched for this run, per the definition's `inputs` (read-only; `{}` when none). */
415
+ inputs: Readonly<ServerFunctionInputs>;
416
+ }
417
+ /** What a function asks to have prefetched (`AppFunctionDef.inputs`). */
418
+ export interface AppFunctionInputs {
419
+ /** Event triggers: the record the event is about — product (needs `sl:products:read`), app record of
420
+ * this app (`sl:records:read`), or the interaction event itself. On by default; `false` opts out. */
421
+ entity?: boolean;
422
+ /** The collection's public record. */
423
+ collection?: boolean;
424
+ /** This app's config for the collection — e.g. its `interactionIds`. Needs `sl:data:read`. */
425
+ appConfig?: boolean;
426
+ /** Http triggers: a product named by the request. Needs `sl:products:read`. */
427
+ product?: {
428
+ from: `body.${string}` | `query.${string}` | 'path';
429
+ };
430
+ }
431
+ /** `ctx.inputs` — what was prefetched for this run (keys you declared; `null` when unavailable). */
432
+ export interface ServerFunctionInputs {
433
+ entity?: Record<string, any> | null;
434
+ collection?: Record<string, any> | null;
435
+ appConfig?: Record<string, any> | null;
436
+ product?: Record<string, any> | null;
406
437
  }
407
438
  /** The HTTP request handed to an `http`-trigger function as `event`. */
408
439
  export interface ServerFunctionHttpEvent<TBody = any> {
@@ -1,6 +1,6 @@
1
1
  # Smartlinks API Summary
2
2
 
3
- Version: 2.0.40 | Generated: 2026-10-05T14:51:09.158Z
3
+ Version: 2.0.41 | Generated: 2026-10-06T07:00:42.791Z
4
4
 
5
5
  This is a concise summary of all available API functions and types.
6
6
 
@@ -2539,6 +2539,7 @@ interface AppFunctionDef {
2539
2539
  elevated?: boolean;
2540
2540
  capabilities?: string[];
2541
2541
  dataScope?: 'collection' | 'global';
2542
+ inputs?: AppFunctionInputs;
2542
2543
  apiVersion?: string;
2543
2544
  handler?: string;
2544
2545
  agent?: AppFunctionAgentExposure;
@@ -2622,6 +2623,27 @@ interface ServerFunctionContext {
2622
2623
  caller: ServerFunctionCaller;
2623
2624
  fetch: typeof fetch;
2624
2625
  log: (message: string, data?: Record<string, any>) => void;
2626
+ inputs: Readonly<ServerFunctionInputs>;
2627
+ }
2628
+ ```
2629
+
2630
+ **AppFunctionInputs** (interface)
2631
+ ```typescript
2632
+ interface AppFunctionInputs {
2633
+ entity?: boolean;
2634
+ collection?: boolean;
2635
+ appConfig?: boolean;
2636
+ product?: { from: `body.${string}` | `query.${string}` | 'path' };
2637
+ }
2638
+ ```
2639
+
2640
+ **ServerFunctionInputs** (interface)
2641
+ ```typescript
2642
+ interface ServerFunctionInputs {
2643
+ entity?: Record<string, any> | null;
2644
+ collection?: Record<string, any> | null;
2645
+ appConfig?: Record<string, any> | null;
2646
+ product?: Record<string, any> | null;
2625
2647
  }
2626
2648
  ```
2627
2649