@proveanything/smartlinks 2.0.57 → 2.0.58
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/integrations.d.ts +2 -1
- package/dist/api/integrations.js +2 -1
- package/dist/docs/API_SUMMARY.md +27 -10
- package/dist/docs/app-manifest.md +1 -1
- package/dist/docs/connectors.md +66 -29
- package/dist/docs/site-seo.md +69 -68
- package/dist/docs/websites.md +91 -0
- package/dist/openapi.yaml +36 -8
- package/dist/site.d.ts +32 -4
- package/dist/site.js +48 -5
- package/dist/types/appManifest.d.ts +41 -10
- package/dist/types/integrations.d.ts +2 -2
- package/docs/API_SUMMARY.md +27 -10
- package/docs/app-manifest.md +1 -1
- package/docs/connectors.md +66 -29
- package/docs/site-seo.md +69 -68
- package/docs/websites.md +91 -0
- package/openapi.yaml +36 -8
- package/package.json +1 -1
package/dist/site.d.ts
CHANGED
|
@@ -1,10 +1,38 @@
|
|
|
1
1
|
declare global {
|
|
2
2
|
var __SL_READY__: boolean | undefined;
|
|
3
|
+
var __SMARTLINKS_SITE__: SiteContext | undefined;
|
|
3
4
|
}
|
|
5
|
+
/** The site a page is being served as. */
|
|
6
|
+
export interface SiteContext {
|
|
7
|
+
/** The hostname this request arrived on (an automatic address, a chosen name or a custom domain). */
|
|
8
|
+
host: string;
|
|
9
|
+
/** The collection the site belongs to: pass it to every SDK call. */
|
|
10
|
+
collectionId: string;
|
|
11
|
+
appId: string;
|
|
12
|
+
/** The release channel being served ('stable' on a live address; 'dev' etc. on a test address). */
|
|
13
|
+
channel: string;
|
|
14
|
+
/** Where search engines consolidate the site (custom domain > chosen name > automatic address). */
|
|
15
|
+
canonicalHost: string;
|
|
16
|
+
}
|
|
17
|
+
type HeaderSource = {
|
|
18
|
+
headers: Headers | Record<string, string | string[] | undefined>;
|
|
19
|
+
} | Headers;
|
|
20
|
+
/**
|
|
21
|
+
* On the server: the site this request is rendering. The SmartLinks site router sets it on every
|
|
22
|
+
* request it hands to your site's server (and drops any value a visitor sends). Null outside the
|
|
23
|
+
* site runtime, e.g. in local development without the router: fall back to your dev collection.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* // TanStack Start server function / loader
|
|
27
|
+
* const site = SL.site.fromRequest(getRequest())
|
|
28
|
+
* const products = await SL.product.list(site!.collectionId)
|
|
29
|
+
*/
|
|
30
|
+
export declare function fromRequest(request: HeaderSource): SiteContext | null;
|
|
31
|
+
/** In the browser: the site the page is served as (the router puts it in the page). Null elsewhere. */
|
|
32
|
+
export declare function current(): SiteContext | null;
|
|
4
33
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* without it, it waits for the network to go idle (capped). Call once per page, after your data
|
|
8
|
-
* renders. Harmless anywhere else.
|
|
34
|
+
* @deprecated Sites are server-rendered: crawlers get the finished page from the server, so nothing
|
|
35
|
+
* waits for this signal any more. Kept as a harmless no-op signal for older pages.
|
|
9
36
|
*/
|
|
10
37
|
export declare function ready(): void;
|
|
38
|
+
export {};
|
package/dist/site.js
CHANGED
|
@@ -1,11 +1,54 @@
|
|
|
1
1
|
// =============================================================================
|
|
2
|
-
// site — for apps served as websites.
|
|
2
|
+
// site — for apps served as websites (server-rendered on the SmartLinks site runtime).
|
|
3
3
|
// =============================================================================
|
|
4
|
+
function headerOf(source, name) {
|
|
5
|
+
var _a, _b;
|
|
6
|
+
const headers = typeof Headers !== 'undefined' && source instanceof Headers ? source : source.headers;
|
|
7
|
+
if (!headers)
|
|
8
|
+
return null;
|
|
9
|
+
if (typeof headers.get === 'function')
|
|
10
|
+
return headers.get(name);
|
|
11
|
+
const v = (_a = headers[name]) !== null && _a !== void 0 ? _a : headers[name.toLowerCase()];
|
|
12
|
+
return Array.isArray(v) ? (_b = v[0]) !== null && _b !== void 0 ? _b : null : v !== null && v !== void 0 ? v : null;
|
|
13
|
+
}
|
|
14
|
+
function parse(raw) {
|
|
15
|
+
try {
|
|
16
|
+
const v = typeof raw === 'string' ? JSON.parse(raw) : raw;
|
|
17
|
+
if (!v || typeof v !== 'object' || typeof v.collectionId !== 'string')
|
|
18
|
+
return null;
|
|
19
|
+
const s = v;
|
|
20
|
+
return {
|
|
21
|
+
host: String(s.host || ''),
|
|
22
|
+
collectionId: String(s.collectionId),
|
|
23
|
+
appId: String(s.appId || ''),
|
|
24
|
+
channel: String(s.channel || 'stable'),
|
|
25
|
+
canonicalHost: String(s.canonicalHost || s.host || ''),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
catch (_a) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* On the server: the site this request is rendering. The SmartLinks site router sets it on every
|
|
34
|
+
* request it hands to your site's server (and drops any value a visitor sends). Null outside the
|
|
35
|
+
* site runtime, e.g. in local development without the router: fall back to your dev collection.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* // TanStack Start server function / loader
|
|
39
|
+
* const site = SL.site.fromRequest(getRequest())
|
|
40
|
+
* const products = await SL.product.list(site!.collectionId)
|
|
41
|
+
*/
|
|
42
|
+
export function fromRequest(request) {
|
|
43
|
+
return parse(headerOf(request, 'x-sl-site'));
|
|
44
|
+
}
|
|
45
|
+
/** In the browser: the site the page is served as (the router puts it in the page). Null elsewhere. */
|
|
46
|
+
export function current() {
|
|
47
|
+
return parse(globalThis.__SMARTLINKS_SITE__);
|
|
48
|
+
}
|
|
4
49
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* without it, it waits for the network to go idle (capped). Call once per page, after your data
|
|
8
|
-
* renders. Harmless anywhere else.
|
|
50
|
+
* @deprecated Sites are server-rendered: crawlers get the finished page from the server, so nothing
|
|
51
|
+
* waits for this signal any more. Kept as a harmless no-op signal for older pages.
|
|
9
52
|
*/
|
|
10
53
|
export function ready() {
|
|
11
54
|
if (typeof globalThis === 'undefined')
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
/// <reference types="node" />
|
|
3
3
|
import type { AppDataDeclaration, AppHeadlessDeclaration } from './headless.js';
|
|
4
4
|
import type { RealtimeChannelDeclaration } from './realtime.js';
|
|
5
|
-
import type { FieldMapping } from './integrations.js';
|
|
6
5
|
/**
|
|
7
6
|
* A bundle (widget or container) as returned by the collection widgets endpoint.
|
|
8
7
|
*
|
|
@@ -831,7 +830,9 @@ export interface AppManifest {
|
|
|
831
830
|
/**
|
|
832
831
|
* This app is an integration CONNECTOR for one external provider (Syndigo, OneWorldSync, …): the
|
|
833
832
|
* integrations console lists it, draws its connect form and settings, and the integration engine
|
|
834
|
-
* calls its server functions to test, describe, sample,
|
|
833
|
+
* calls its server functions to test, describe, sample, shape, serialize, send and fetch. The
|
|
834
|
+
* customer's mapping is NOT the connector's job: core maps between the connector's provider-neutral
|
|
835
|
+
* documents and SmartLinks (docs/connectors.md).
|
|
835
836
|
* @see AppConnectorDeclaration · docs/connectors.md
|
|
836
837
|
*/
|
|
837
838
|
connector?: AppConnectorDeclaration;
|
|
@@ -877,6 +878,9 @@ export interface ConnectorPanel {
|
|
|
877
878
|
* The connector contract. Every function named here is one of this app's server functions
|
|
878
879
|
* (manifest `functions.definitions`); the engine calls it with collection authority, capped by the
|
|
879
880
|
* capabilities the function declares. A connector fills in only what it needs.
|
|
881
|
+
*
|
|
882
|
+
* A connector is an ADAPTER: it talks to the provider and converts between the provider's format and
|
|
883
|
+
* a clean, provider-neutral document. Each customer's mapping, routing and rules live in core.
|
|
880
884
|
*/
|
|
881
885
|
export interface AppConnectorDeclaration {
|
|
882
886
|
/** Stable provider key, e.g. 'syndigo', 'oneworldsync'. */
|
|
@@ -892,6 +896,7 @@ export interface AppConnectorDeclaration {
|
|
|
892
896
|
};
|
|
893
897
|
/** Per-installation settings: a JSON Schema the console renders as a form. */
|
|
894
898
|
settings?: Record<string, unknown>;
|
|
899
|
+
/** Ready-made flows; a preset's `flow.config.fieldMappings` is a suggested mapping the customer can edit. */
|
|
895
900
|
presets?: ConnectorPreset[];
|
|
896
901
|
/** Names of this app's server functions implementing each step. */
|
|
897
902
|
functions: {
|
|
@@ -901,8 +906,18 @@ export interface AppConnectorDeclaration {
|
|
|
901
906
|
describeTarget?: string;
|
|
902
907
|
/** → ConnectorRecords: real records for the mapper, the AI assistant and dry runs (inbound). */
|
|
903
908
|
sample?: string;
|
|
904
|
-
/**
|
|
905
|
-
|
|
909
|
+
/**
|
|
910
|
+
* Inbound: (ConnectorShapeInput) → ConnectorShapeResult. Provider records → provider-neutral
|
|
911
|
+
* documents, the same for every customer (no customer mapping). Deterministic — no AI, network or
|
|
912
|
+
* clock. Omit to hand core the provider records as they are.
|
|
913
|
+
*/
|
|
914
|
+
shape?: string;
|
|
915
|
+
/**
|
|
916
|
+
* Outbound: (ConnectorSerializeInput) → ConnectorSerializeResult. Documents core has mapped to this
|
|
917
|
+
* provider's fields (`describeTarget` paths) → the provider's wire format (an envelope, a batch).
|
|
918
|
+
* Deterministic. Omit to send the mapped documents as they are.
|
|
919
|
+
*/
|
|
920
|
+
serialize?: string;
|
|
906
921
|
/** (ConnectorSendInput) → ConnectorSendResult. Omit to use a generic transport (HTTP, Event Hub). */
|
|
907
922
|
send?: string;
|
|
908
923
|
/** (ConnectorFetchInput) → ConnectorRecords: inbound pull, one page per call. */
|
|
@@ -951,15 +966,31 @@ export interface ConnectorRecords {
|
|
|
951
966
|
/** Inbound paging: pass back to the next fetch; absent when there's no more. */
|
|
952
967
|
nextCursor?: string | null;
|
|
953
968
|
}
|
|
954
|
-
|
|
969
|
+
/** Inbound: provider records, as fetch / sample returned them. */
|
|
970
|
+
export interface ConnectorShapeInput {
|
|
955
971
|
installation: ConnectorInstallation;
|
|
956
|
-
/** SmartLinks records (outbound) or provider records (inbound). */
|
|
957
972
|
records: Array<Record<string, unknown>>;
|
|
958
|
-
/** The flow's saved mapping (`config.fieldMappings`) — the same shape the engine's mapper runs. */
|
|
959
|
-
mapping: FieldMapping[];
|
|
960
973
|
}
|
|
961
|
-
export interface
|
|
962
|
-
/** One
|
|
974
|
+
export interface ConnectorShapeResult {
|
|
975
|
+
/** One provider-neutral document per record, in the same order (null = skip it, with a reason). */
|
|
976
|
+
documents: Array<Record<string, unknown> | null>;
|
|
977
|
+
skipped?: Array<{
|
|
978
|
+
index: number;
|
|
979
|
+
reason: string;
|
|
980
|
+
}>;
|
|
981
|
+
}
|
|
982
|
+
/**
|
|
983
|
+
* Outbound: documents core has mapped to this provider's fields (keyed by `describeTarget` paths).
|
|
984
|
+
* A deleted SmartLinks entity arrives as `{ id, _deleted: true }`.
|
|
985
|
+
*/
|
|
986
|
+
export interface ConnectorSerializeInput {
|
|
987
|
+
installation: ConnectorInstallation;
|
|
988
|
+
documents: Array<Record<string, unknown>>;
|
|
989
|
+
/** The event that triggered the run, e.g. 'product.updated' (absent for manual / scheduled runs). */
|
|
990
|
+
eventType?: string;
|
|
991
|
+
}
|
|
992
|
+
export interface ConnectorSerializeResult {
|
|
993
|
+
/** One provider payload per document, in the same order (null = skip it, with a reason). */
|
|
963
994
|
payloads: Array<unknown | null>;
|
|
964
995
|
skipped?: Array<{
|
|
965
996
|
index: number;
|
|
@@ -141,8 +141,8 @@ export interface SaveConnectorInstallationInput {
|
|
|
141
141
|
connection?: Record<string, unknown>;
|
|
142
142
|
settings?: Record<string, unknown>;
|
|
143
143
|
}
|
|
144
|
-
/** Steps the console may run directly (send and fetch only run inside a flow). */
|
|
145
|
-
export type ConnectorConsoleStep = 'testConnection' | 'describeTarget' | 'sample' | '
|
|
144
|
+
/** Steps the console may run directly (send and fetch only run inside a flow; shape/serialize as dry runs). */
|
|
145
|
+
export type ConnectorConsoleStep = 'testConnection' | 'describeTarget' | 'sample' | 'shape' | 'serialize';
|
|
146
146
|
export type RunTrigger = 'manual' | 'event' | 'schedule' | 'test';
|
|
147
147
|
export type FlowRunStatus = 'running' | 'success' | 'partial' | 'failed' | 'skipped';
|
|
148
148
|
export type FlowRunItemStatus = 'sent' | 'failed' | 'skipped';
|
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.58 | Generated: 2026-10-13T14:08:01.791Z
|
|
4
4
|
|
|
5
5
|
This is a concise summary of all available API functions and types.
|
|
6
6
|
|
|
@@ -65,7 +65,7 @@ For detailed guides on specific features:
|
|
|
65
65
|
- **[Analytics Metadata Conventions](analytics-metadata-conventions.md)** - Standard recommended keys and conventions for analytics metadata fields
|
|
66
66
|
- **[Loyalty: Points, Members & Earning Rules](loyalty.md)** - Loyalty schemes, automatic point earning via interaction rules, member balances, transaction history, and manual adjustments
|
|
67
67
|
- **[Integrations](integrations.md)** - Inbound/outbound integration flows + the sealed-secret store; triggers (manual/event/schedule), field mappings, and the Syndigo/Event Hub outbound path
|
|
68
|
-
- **[Connectors](connectors.md)** - Connector apps: a manifest `connector` block + server functions (testConnection/describeTarget/sample/
|
|
68
|
+
- **[Connectors](connectors.md)** - Connector apps (adapters for one external provider): a manifest `connector` block + server functions (testConnection/describeTarget/sample/fetch/shape/serialize/send); core does the mapping
|
|
69
69
|
- **[AI Tools & Skills](ai-tools-and-skills.md)** - The AI capability catalog: skills apps invoke by name (e.g. research.brand), the tools the AI reaches for (web fetch/screenshot/brand assets/image gen), the agent loop, and how apps discover them
|
|
70
70
|
- **[Deep Link Discovery](deep-link-discovery.md)** - Registering and discovering navigable app states for portal menus and AI orchestration
|
|
71
71
|
- **[AI Guide Template](ai-guide-template.md)** - A sample for an app on how to build an AI setup guide
|
|
@@ -2908,7 +2908,8 @@ interface AppConnectorDeclaration {
|
|
|
2908
2908
|
testConnection?: string
|
|
2909
2909
|
describeTarget?: string
|
|
2910
2910
|
sample?: string
|
|
2911
|
-
|
|
2911
|
+
shape?: string
|
|
2912
|
+
serialize?: string
|
|
2912
2913
|
send?: string
|
|
2913
2914
|
fetch?: string
|
|
2914
2915
|
}
|
|
@@ -2963,18 +2964,34 @@ interface ConnectorRecords {
|
|
|
2963
2964
|
}
|
|
2964
2965
|
```
|
|
2965
2966
|
|
|
2966
|
-
**
|
|
2967
|
+
**ConnectorShapeInput** (interface)
|
|
2967
2968
|
```typescript
|
|
2968
|
-
interface
|
|
2969
|
+
interface ConnectorShapeInput {
|
|
2969
2970
|
installation: ConnectorInstallation
|
|
2970
2971
|
records: Array<Record<string, unknown>>
|
|
2971
|
-
mapping: FieldMapping[]
|
|
2972
2972
|
}
|
|
2973
2973
|
```
|
|
2974
2974
|
|
|
2975
|
-
**
|
|
2975
|
+
**ConnectorShapeResult** (interface)
|
|
2976
2976
|
```typescript
|
|
2977
|
-
interface
|
|
2977
|
+
interface ConnectorShapeResult {
|
|
2978
|
+
documents: Array<Record<string, unknown> | null>
|
|
2979
|
+
skipped?: Array<{ index: number; reason: string }>
|
|
2980
|
+
}
|
|
2981
|
+
```
|
|
2982
|
+
|
|
2983
|
+
**ConnectorSerializeInput** (interface)
|
|
2984
|
+
```typescript
|
|
2985
|
+
interface ConnectorSerializeInput {
|
|
2986
|
+
installation: ConnectorInstallation
|
|
2987
|
+
documents: Array<Record<string, unknown>>
|
|
2988
|
+
eventType?: string
|
|
2989
|
+
}
|
|
2990
|
+
```
|
|
2991
|
+
|
|
2992
|
+
**ConnectorSerializeResult** (interface)
|
|
2993
|
+
```typescript
|
|
2994
|
+
interface ConnectorSerializeResult {
|
|
2978
2995
|
payloads: Array<unknown | null>
|
|
2979
2996
|
skipped?: Array<{ index: number; reason: string }>
|
|
2980
2997
|
}
|
|
@@ -7374,7 +7391,7 @@ interface RecordTypesResponse {
|
|
|
7374
7391
|
|
|
7375
7392
|
**UpdateFlowInput** = `Partial<Omit<CreateFlowInput, 'direction'>> & {`
|
|
7376
7393
|
|
|
7377
|
-
**ConnectorConsoleStep** = `'testConnection' | 'describeTarget' | 'sample' | '
|
|
7394
|
+
**ConnectorConsoleStep** = `'testConnection' | 'describeTarget' | 'sample' | 'shape' | 'serialize'`
|
|
7378
7395
|
|
|
7379
7396
|
**RunTrigger** = `'manual' | 'event' | 'schedule' | 'test'`
|
|
7380
7397
|
|
|
@@ -11702,7 +11719,7 @@ Save a connector's non-secret Connect-form values and/or settings. PUT /integrat
|
|
|
11702
11719
|
appId: string,
|
|
11703
11720
|
step: ConnectorConsoleStep,
|
|
11704
11721
|
input: Record<string, unknown> & { flowId?: string } = {}) → `Promise<T>`
|
|
11705
|
-
Run a connector step from the console: `testConnection` → ConnectorTestResult, `describeTarget` → ConnectorTargetSchema, `sample` → ConnectorRecords, `
|
|
11722
|
+
Run a connector step from the console: `testConnection` → ConnectorTestResult, `describeTarget` → ConnectorTargetSchema, `sample` → ConnectorRecords, and dry runs of `shape` ({ records }) → ConnectorShapeResult and `serialize` ({ documents }) → ConnectorSerializeResult. The server adds the installation; pass `flowId` to layer that flow's settings. POST /integrations/connectors/:appId/steps/:step
|
|
11706
11723
|
|
|
11707
11724
|
### interactions
|
|
11708
11725
|
|
package/docs/app-manifest.md
CHANGED
|
@@ -440,7 +440,7 @@ this is just the manifest shape.
|
|
|
440
440
|
|
|
441
441
|
Marks the app as an integration **connector** for one external provider: the integrations console
|
|
442
442
|
lists it and draws its connect form and settings, and the integration engine calls its server
|
|
443
|
-
functions (`testConnection`, `describeTarget`, `sample`, `
|
|
443
|
+
functions (`testConnection`, `describeTarget`, `sample`, `fetch`, `shape`, `serialize`, `send`). It adapts the provider's format; core does each customer's mapping. The full
|
|
444
444
|
contract is in the **[Connectors guide](connectors.md)**.
|
|
445
445
|
|
|
446
446
|
---
|
package/docs/connectors.md
CHANGED
|
@@ -9,16 +9,22 @@ Three layers, each with one job:
|
|
|
9
9
|
|
|
10
10
|
| Layer | Holds |
|
|
11
11
|
|---|---|
|
|
12
|
-
| **Engine** (the API) | [Integration flows](integrations.md), runs and their logs, triggers (events, schedule, Run now), the secret store, the
|
|
12
|
+
| **Engine** (the API) | [Integration flows](integrations.md), runs and their logs, triggers (events, schedule, Run now), the secret store, **the mapping** (each customer's field mappings and rules), generic transports (HTTP, Event Hub). No provider code. |
|
|
13
13
|
| **Integrations console** (a core app) | The one admin screen: installed connectors, connect forms, settings, the mapper with AI assist, flows, runs. |
|
|
14
|
-
| **Connector apps** | One per provider:
|
|
14
|
+
| **Connector apps** | One per provider: an **adapter**. Talks to the provider and converts between its format and a clean, provider-neutral document. |
|
|
15
|
+
|
|
16
|
+
**A connector adapts; core maps.** A connector never holds a customer's mapping. It turns the
|
|
17
|
+
provider's records into provider-neutral documents (`shape`), or provider-ready payloads out of
|
|
18
|
+
documents core has already mapped (`serialize`). Every customer's mapping, routing and rules live in
|
|
19
|
+
core, so they work the same for every provider and can be replayed and traced. This is the line
|
|
20
|
+
SnapLogic (endpoint Snaps vs the Mapper), MuleSoft and Airbyte all draw.
|
|
15
21
|
|
|
16
22
|
A flow that only posts JSON to a webhook or an Event Hub needs no connector; it uses the engine's
|
|
17
23
|
generic transports (see [integrations.md](integrations.md)).
|
|
18
24
|
|
|
19
25
|
**No AI at run time.** The AI helps an admin *author* a mapping in the console (from the connector's
|
|
20
26
|
target schema plus real sample records); a person approves it, and it is saved as data and runs
|
|
21
|
-
deterministically. A connector's `
|
|
27
|
+
deterministically. A connector's `shape` and `serialize` must be deterministic too.
|
|
22
28
|
|
|
23
29
|
---
|
|
24
30
|
|
|
@@ -33,8 +39,10 @@ deterministically. A connector's `transform` must be deterministic too.
|
|
|
33
39
|
"entities": ["product"],
|
|
34
40
|
"connection": {
|
|
35
41
|
"fields": [
|
|
36
|
-
|
|
37
|
-
{ "name": "
|
|
42
|
+
// Optional: SmartLinks' own credentials are the default; a customer may enter theirs.
|
|
43
|
+
{ "name": "appId", "label": "App ID", "secret": "oneworldsync-app-id" },
|
|
44
|
+
{ "name": "secretKey", "label": "Secret key", "secret": "oneworldsync-secret-key" },
|
|
45
|
+
{ "name": "gln", "label": "GLN", "required": true },
|
|
38
46
|
{ "name": "environment", "label": "Environment", "type": "select",
|
|
39
47
|
"options": [ { "value": "preprod", "label": "Pre-production" }, { "value": "production", "label": "Production" } ] }
|
|
40
48
|
]
|
|
@@ -44,12 +52,14 @@ deterministically. A connector's `transform` must be deterministic too.
|
|
|
44
52
|
"properties": { "targetMarket": { "type": "string", "default": "GB" } }
|
|
45
53
|
},
|
|
46
54
|
"presets": [
|
|
47
|
-
{ "id": "import-products", "title": "Import products", "direction": "inbound", "entity": "product"
|
|
55
|
+
{ "id": "import-products", "title": "Import products", "direction": "inbound", "entity": "product",
|
|
56
|
+
"flow": { "config": { "fieldMappings": [ /* a suggested mapping the customer can edit */ ] } } }
|
|
48
57
|
],
|
|
49
58
|
"functions": { // names of this app's server functions
|
|
50
59
|
"testConnection": "testConnection",
|
|
51
60
|
"describeTarget": "describeTarget",
|
|
52
61
|
"sample": "sample",
|
|
62
|
+
"shape": "shape",
|
|
53
63
|
"fetch": "fetch"
|
|
54
64
|
},
|
|
55
65
|
"panels": [ { "id": "explorer", "title": "Explorer", "component": "Explorer" } ],
|
|
@@ -74,9 +84,9 @@ Types: `AppConnectorDeclaration` and friends in `@proveanything/smartlinks` (`sr
|
|
|
74
84
|
| `title` | Provider display name; defaults to the app name. |
|
|
75
85
|
| `directions` | `inbound` (provider → SmartLinks), `outbound` (SmartLinks → provider), or both. |
|
|
76
86
|
| `entities` | SmartLinks entities it maps, e.g. `["product"]`. |
|
|
77
|
-
| `connection.fields[]` | The console's **Connect** form. A field with `secret` is stored as this app's secret of that name (write-only; never returned to the browser). Other fields are saved in the installation's connection settings. |
|
|
87
|
+
| `connection.fields[]` | The console's **Connect** form. A field with `secret` is stored as this app's secret of that name on the collection (write-only; never returned to the browser). Other fields are saved in the installation's connection settings. |
|
|
78
88
|
| `settings` | JSON Schema for installation settings; the console renders it as a form. |
|
|
79
|
-
| `presets[]` | Ready-made flows ("Products → provider")
|
|
89
|
+
| `presets[]` | Ready-made flows ("Products → provider"). A preset's `flow.config.fieldMappings` is a **suggested** mapping: the connector's knowledge of the provider, which the customer accepts or edits in core. |
|
|
80
90
|
| `functions` | Which of this app's server functions implement each step (below). Fill in only what applies. |
|
|
81
91
|
| `panels[]` | Provider-specific screens the console mounts (an API explorer, a status page). |
|
|
82
92
|
| `aiGuide` | A guide the mapping assistant reads: the provider's quirks, required fields, good defaults. |
|
|
@@ -87,28 +97,37 @@ Types: `AppConnectorDeclaration` and friends in `@proveanything/smartlinks` (`sr
|
|
|
87
97
|
|
|
88
98
|
Each step names one of the app's server functions. Declare them as **http POST, `visibility:
|
|
89
99
|
"admin"`, `authority: "collection"`**: the engine calls them with no signed-in user (a product edit, a
|
|
90
|
-
schedule), and the console calls the same functions while an admin configures the flow
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
(`network:<host>`) and the secrets it reads (`secrets:<name>`).
|
|
100
|
+
schedule), and the console calls the same functions while an admin configures the flow. The input
|
|
101
|
+
arrives as `event.body`; return the result as JSON. Capabilities still cap what each one can do:
|
|
102
|
+
declare the provider's hosts (`network:<host>`) and the secrets it reads (`secrets:<name>`).
|
|
94
103
|
|
|
95
104
|
| Step | Called by | Input (`event.body`) | Returns |
|
|
96
105
|
|---|---|---|---|
|
|
97
106
|
| `testConnection` | Connect form, **Test** | `{ installation }` | `ConnectorTestResult` `{ ok, message }` |
|
|
98
|
-
| `describeTarget` | Mapper opens | `{ installation }` | `ConnectorTargetSchema` `{ fields: [{ path, label, type, required, enum }] }`: the provider's fields *for this customer
|
|
107
|
+
| `describeTarget` | Mapper opens | `{ installation }` | `ConnectorTargetSchema` `{ fields: [{ path, label, type, required, enum }] }`: the provider's fields *for this customer*. Outbound mappings target these paths. |
|
|
99
108
|
| `sample` | Mapper, AI assist, dry run | `{ installation, limit }` | `ConnectorRecords` `{ records }`: real provider records |
|
|
100
|
-
| `transform` | Every run | `ConnectorTransformInput` `{ installation, records, mapping }` | `ConnectorTransformResult` `{ payloads, skipped }`: one payload per record, same order; `null` skips one |
|
|
101
|
-
| `send` | Every outbound run | `ConnectorSendInput` `{ installation, payloads }` | `ConnectorSendResult` `{ results: [{ index, ok, status, error, externalId }] }` |
|
|
102
109
|
| `fetch` | Every inbound run | `ConnectorFetchInput` `{ installation, cursor, limit }` | `ConnectorRecords` `{ records, nextCursor }`: one page per call |
|
|
110
|
+
| `shape` | Every inbound run (and sample dry runs) | `ConnectorShapeInput` `{ installation, records }` | `ConnectorShapeResult` `{ documents, skipped }`: one provider-neutral document per record, same order; `null` skips one |
|
|
111
|
+
| `serialize` | Every outbound run | `ConnectorSerializeInput` `{ installation, documents, eventType? }`: documents core mapped to `describeTarget` paths; a deletion arrives as `{ id, _deleted: true }` | `ConnectorSerializeResult` `{ payloads, skipped }`: one provider payload per document, same order; `null` skips one |
|
|
112
|
+
| `send` | Every outbound run | `ConnectorSendInput` `{ installation, payloads }` | `ConnectorSendResult` `{ results: [{ index, ok, status, error, externalId }] }` |
|
|
103
113
|
|
|
104
|
-
|
|
114
|
+
**How a run flows:**
|
|
115
|
+
- **Inbound:** `fetch` (paged) → `shape` → core maps the documents with the flow's mapping → upsert.
|
|
116
|
+
- **Outbound:** gather the SmartLinks records → core maps them to the provider's fields → `serialize`
|
|
117
|
+
→ `send` (or a generic transport).
|
|
105
118
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
- **No `
|
|
119
|
+
**What happens when a step is left out:**
|
|
120
|
+
- **No `shape`:** core maps the provider records as they come.
|
|
121
|
+
- **No `serialize`:** the mapped documents are sent as they are.
|
|
122
|
+
- **No `send`:** the flow's own transport is used: HTTP (`config.connection`), or Azure Event Hub with
|
|
109
123
|
`config.transport: 'eventhub'` and `config.eventHub`.
|
|
110
124
|
- **No `describeTarget`:** the mapper offers free-text target paths.
|
|
111
125
|
|
|
126
|
+
**What `shape` should produce:** plain, predictable JSON a person can map without reading the
|
|
127
|
+
provider's documentation. Flatten the provider's envelopes, name things plainly, keep codes alongside
|
|
128
|
+
their meanings, and put repeated sections (ingredients, allergens, nutrients, images) in arrays. The
|
|
129
|
+
same provider record always gives the same document.
|
|
130
|
+
|
|
112
131
|
### `installation`
|
|
113
132
|
|
|
114
133
|
Every step receives the installation it serves:
|
|
@@ -124,13 +143,15 @@ interface ConnectorInstallation {
|
|
|
124
143
|
```
|
|
125
144
|
|
|
126
145
|
Secrets are never in it. Read them in the function with `ctx.secrets.get('<name>')`, which needs the
|
|
127
|
-
matching `secrets:<name>` capability.
|
|
146
|
+
matching `secrets:<name>` capability. A secret the customer set on their collection wins; otherwise the
|
|
147
|
+
connector's own app-level secret of that name (set in Forge) is used, so a connector can ship with
|
|
148
|
+
SmartLinks' credentials as the default.
|
|
128
149
|
|
|
129
|
-
### Example: `fetch`
|
|
150
|
+
### Example: `fetch` and `shape`
|
|
130
151
|
|
|
131
152
|
```ts
|
|
132
153
|
// src/functions/index.ts (the app's functions bundle)
|
|
133
|
-
import type { ConnectorFetchInput, ConnectorRecords } from '@proveanything/smartlinks'
|
|
154
|
+
import type { ConnectorFetchInput, ConnectorRecords, ConnectorShapeInput, ConnectorShapeResult } from '@proveanything/smartlinks'
|
|
134
155
|
|
|
135
156
|
export async function fetch(ctx: any, event: any): Promise<ConnectorRecords> {
|
|
136
157
|
const { installation, cursor, limit = 50 } = event.body as ConnectorFetchInput
|
|
@@ -140,6 +161,18 @@ export async function fetch(ctx: any, event: any): Promise<ConnectorRecords> {
|
|
|
140
161
|
const page = await res.json()
|
|
141
162
|
return { records: page.items, nextCursor: page.next ?? null }
|
|
142
163
|
}
|
|
164
|
+
|
|
165
|
+
export async function shape(_ctx: any, event: any): Promise<ConnectorShapeResult> {
|
|
166
|
+
const { records } = event.body as ConnectorShapeInput
|
|
167
|
+
return {
|
|
168
|
+
documents: records.map((r: any) => ({
|
|
169
|
+
gtin: r.item?.gtin,
|
|
170
|
+
name: r.item?.tradeItemDescription?.[0]?.value,
|
|
171
|
+
brand: r.item?.brandName,
|
|
172
|
+
ingredients: (r.item?.ingredients ?? []).map((i: any) => ({ name: i.name, percent: i.percentage })),
|
|
173
|
+
})),
|
|
174
|
+
}
|
|
175
|
+
}
|
|
143
176
|
```
|
|
144
177
|
|
|
145
178
|
---
|
|
@@ -148,9 +181,9 @@ export async function fetch(ctx: any, event: any): Promise<ConnectorRecords> {
|
|
|
148
181
|
|
|
149
182
|
| What | Where |
|
|
150
183
|
|---|---|
|
|
151
|
-
| Connect-form values (non-secret), installation settings | The connector app's config on the collection
|
|
152
|
-
| Credentials | The connector app's secrets, by the names its fields declare. |
|
|
153
|
-
| One flow's use of the connector | The flow: `config.connector = { appId, preset?, settings? }` (per-flow settings override the installation's), plus `config.fieldMappings
|
|
184
|
+
| Connect-form values (non-secret), installation settings | The connector app's config on the collection, in its admin-only zone (`admin.connector.connection`, `admin.connector.settings`). |
|
|
185
|
+
| Credentials | The connector app's secrets, by the names its fields declare (the customer's on their collection, else the connector's default). |
|
|
186
|
+
| One flow's use of the connector | The flow: `config.connector = { appId, preset?, settings? }` (per-flow settings override the installation's), plus `config.fieldMappings`, which core runs. |
|
|
154
187
|
|
|
155
188
|
```ts
|
|
156
189
|
await integrations.createFlow(collectionId, {
|
|
@@ -162,7 +195,8 @@ await integrations.createFlow(collectionId, {
|
|
|
162
195
|
targetEntity: 'product',
|
|
163
196
|
config: {
|
|
164
197
|
connector: { appId: 'oneworldsync', preset: 'import-products' },
|
|
165
|
-
|
|
198
|
+
// Maps the connector's shaped documents (not the raw provider records) onto the product.
|
|
199
|
+
fieldMappings: [ { targetPath: 'name', sourcePath: 'name', transformType: 'direct' } ],
|
|
166
200
|
},
|
|
167
201
|
})
|
|
168
202
|
```
|
|
@@ -181,7 +215,9 @@ await secrets.put(collectionId, 'provider-key', value, { appId }) /
|
|
|
181
215
|
await integrations.saveConnectorInstallation(collectionId, appId, { connection: { environment: 'production' } })
|
|
182
216
|
const test = await integrations.runConnectorStep(collectionId, appId, 'testConnection')
|
|
183
217
|
const target = await integrations.runConnectorStep(collectionId, appId, 'describeTarget')
|
|
184
|
-
const
|
|
218
|
+
const { records } = await integrations.runConnectorStep(collectionId, appId, 'sample', { limit: 5 })
|
|
219
|
+
const shaped = await integrations.runConnectorStep(collectionId, appId, 'shape', { records }) // dry run
|
|
220
|
+
const wire = await integrations.runConnectorStep(collectionId, appId, 'serialize', { documents }) // dry run
|
|
185
221
|
```
|
|
186
222
|
|
|
187
223
|
`send` and `fetch` only run inside a flow. A flow run's summary carries `sent` (outbound) or
|
|
@@ -192,8 +228,9 @@ const dryRun = await integrations.runConnectorStep(collectionId, appId, 'transfo
|
|
|
192
228
|
## Panels
|
|
193
229
|
|
|
194
230
|
A connector can contribute screens to the integrations console (an API explorer, say). Each
|
|
195
|
-
`panels[]` entry names a component exported from the app's widgets bundle; the console mounts it
|
|
196
|
-
collection and the installation. Panels are admin-only and call the
|
|
231
|
+
`panels[]` entry names a component exported from the app's widgets bundle; the console mounts it
|
|
232
|
+
host-side and passes it the collection and the installation. Panels are admin-only and call the
|
|
233
|
+
connector's own functions.
|
|
197
234
|
|
|
198
235
|
---
|
|
199
236
|
|