@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.
- package/dist/docs/API_SUMMARY.md +23 -1
- package/dist/docs/iframe-responder.md +310 -308
- package/dist/docs/server-functions.md +34 -0
- package/dist/http.d.ts +5 -0
- package/dist/http.js +28 -4
- package/dist/openapi.yaml +32 -0
- package/dist/testing/index.d.ts +3 -1
- package/dist/testing/index.js +1 -0
- package/dist/types/appManifest.d.ts +31 -0
- package/docs/API_SUMMARY.md +23 -1
- package/docs/iframe-responder.md +310 -308
- package/docs/server-functions.md +34 -0
- package/openapi.yaml +32 -0
- package/package.json +1 -1
|
@@ -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:
|
package/dist/testing/index.d.ts
CHANGED
|
@@ -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). */
|
package/dist/testing/index.js
CHANGED
|
@@ -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> {
|
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.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
|
|