@proveanything/smartlinks 2.0.55 → 2.0.57

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.
@@ -29,9 +29,20 @@ export interface FlowConnection {
29
29
  defaultHeaders?: Record<string, string>;
30
30
  auth?: FlowConnectionAuth;
31
31
  }
32
+ /** A flow driven by a connector app (docs/connectors.md). */
33
+ export interface FlowConnectorRef {
34
+ /** The connector app (manifest `connector` block), installed on the collection. */
35
+ appId: string;
36
+ /** The preset the flow was created from, if any. */
37
+ preset?: string;
38
+ /** Per-flow settings, layered over the installation's. */
39
+ settings?: Record<string, unknown>;
40
+ }
32
41
  export interface IntegrationFlowConfig {
33
42
  connection?: FlowConnection;
34
43
  fieldMappings?: FieldMapping[];
44
+ /** Run this flow through a connector app's functions instead of the built-in mapper/transport. */
45
+ connector?: FlowConnectorRef;
35
46
  [key: string]: any;
36
47
  }
37
48
  export interface IntegrationFlow {
@@ -95,12 +106,43 @@ export interface RunFlowSummary {
95
106
  flowId: string;
96
107
  direction: FlowDirection;
97
108
  records: number;
98
- sent: number;
109
+ /** Outbound: records delivered. */
110
+ sent?: number;
99
111
  failed: number;
112
+ /** Inbound: products created / updated, and records the connector skipped. */
113
+ created?: number;
114
+ updated?: number;
115
+ skipped?: number;
100
116
  status: RunStatus;
101
117
  /** Id of the persisted run row (deep-link into its detail). */
102
118
  runId?: string;
103
119
  }
120
+ /** One installed connector app, as the integrations console sees it. */
121
+ export interface InstalledConnector {
122
+ appId: string;
123
+ name: string;
124
+ /** The release channel installed on this collection. */
125
+ channel: string;
126
+ version: string | null;
127
+ connector: import('./appManifest.js').AppConnectorDeclaration;
128
+ /** Non-secret Connect-form values + installation settings. */
129
+ installation: {
130
+ connection: Record<string, unknown>;
131
+ settings: Record<string, unknown>;
132
+ };
133
+ /** Each secret the Connect form declares: whether it is set (+ masked hint). Never a value. */
134
+ secrets: Record<string, {
135
+ set: boolean;
136
+ hint?: string | null;
137
+ }>;
138
+ }
139
+ export interface SaveConnectorInstallationInput {
140
+ /** Non-secret fields only; secret fields are set with `secrets.put` for the connector's appId. */
141
+ connection?: Record<string, unknown>;
142
+ settings?: Record<string, unknown>;
143
+ }
144
+ /** Steps the console may run directly (send and fetch only run inside a flow). */
145
+ export type ConnectorConsoleStep = 'testConnection' | 'describeTarget' | 'sample' | 'transform';
104
146
  export type RunTrigger = 'manual' | 'event' | 'schedule' | 'test';
105
147
  export type FlowRunStatus = 'running' | 'success' | 'partial' | 'failed' | 'skipped';
106
148
  export type FlowRunItemStatus = 'sent' | 'failed' | 'skipped';
@@ -42,3 +42,47 @@ export interface AblyTokenRequest {
42
42
  * - collection:abc:users:active
43
43
  */
44
44
  export type RealtimeChannelPattern = string;
45
+ /** Where a realtime listener lives: the collection, and (for app channels) the app. */
46
+ export interface RealtimeScope {
47
+ collectionId: string;
48
+ appId?: string;
49
+ }
50
+ /** A message on a channel. `channel` is the logical name ("draw", "feed:admin"). */
51
+ export interface RealtimeMessage {
52
+ channel: string;
53
+ event: string;
54
+ data: unknown;
55
+ /** When it was sent (ms since epoch), when known. */
56
+ at?: number;
57
+ }
58
+ /** "This changed" — ids only; refetch through the normal API. */
59
+ export interface RealtimeChangeEvent {
60
+ /** 'product' | 'proof' | 'asset' | 'record' | 'collection' | 'collection.apps' | 'settings' | … */
61
+ type: string;
62
+ id?: string;
63
+ appId?: string;
64
+ recordType?: string;
65
+ productId?: string;
66
+ proofId?: string;
67
+ /** 'created' | 'updated' | 'deleted' (records) or 'create' | 'update' | 'delete' (products, proofs…) */
68
+ status?: string;
69
+ at: number;
70
+ }
71
+ /** Which changes to hear about — every field given must match. */
72
+ export interface RealtimeChangeFilter {
73
+ type?: string;
74
+ id?: string;
75
+ appId?: string;
76
+ recordType?: string;
77
+ productId?: string;
78
+ proofId?: string;
79
+ }
80
+ /** A channel an app declares in its manifest (`realtime.channels`). */
81
+ export interface RealtimeChannelDeclaration {
82
+ /** Lowercase letters, numbers, - and _; up to 40 characters. */
83
+ name: string;
84
+ /** Who may listen. Default 'admin'. */
85
+ subscribe?: "public" | "signedIn" | "admin";
86
+ /** Who may send: those levels, or 'server' (only the app's server functions). Default 'server'. */
87
+ publish?: "public" | "signedIn" | "admin" | "server";
88
+ }
@@ -1,6 +1,6 @@
1
1
  # Smartlinks API Summary
2
2
 
3
- Version: 2.0.55 | Generated: 2026-10-12T20:04:55.294Z
3
+ Version: 2.0.57 | Generated: 2026-10-13T11:42:19.247Z
4
4
 
5
5
  This is a concise summary of all available API functions and types.
6
6
 
@@ -65,6 +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/transform/send/fetch) that the integration engine and console call for one external provider
68
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
69
70
  - **[Deep Link Discovery](deep-link-discovery.md)** - Registering and discovering navigable app states for portal menus and AI orchestration
70
71
  - **[AI Guide Template](ai-guide-template.md)** - A sample for an app on how to build an AI setup guide
@@ -2850,12 +2851,159 @@ interface AppManifest {
2850
2851
  publicViews?: PublicView[];
2851
2852
  executor?: AppManifestExecutor;
2852
2853
  functions?: AppManifestFunctions;
2854
+ realtime?: { channels: RealtimeChannelDeclaration[] };
2853
2855
  data?: AppDataDeclaration;
2854
2856
  headless?: AppHeadlessDeclaration;
2857
+ connector?: AppConnectorDeclaration;
2855
2858
  [key: string]: any;
2856
2859
  }
2857
2860
  ```
2858
2861
 
2862
+ **ConnectorConnectionField** (interface)
2863
+ ```typescript
2864
+ interface ConnectorConnectionField {
2865
+ name: string
2866
+ label: string
2867
+ description?: string
2868
+ secret?: string
2869
+ required?: boolean
2870
+ type?: 'text' | 'url' | 'select' | 'number' | 'boolean'
2871
+ options?: Array<{ value: string; label: string }>
2872
+ placeholder?: string
2873
+ }
2874
+ ```
2875
+
2876
+ **ConnectorPreset** (interface)
2877
+ ```typescript
2878
+ interface ConnectorPreset {
2879
+ id: string
2880
+ title: string
2881
+ description?: string
2882
+ direction: 'inbound' | 'outbound'
2883
+ entity: string
2884
+ flow?: Record<string, unknown>
2885
+ }
2886
+ ```
2887
+
2888
+ **ConnectorPanel** (interface)
2889
+ ```typescript
2890
+ interface ConnectorPanel {
2891
+ id: string
2892
+ title: string
2893
+ component: string
2894
+ }
2895
+ ```
2896
+
2897
+ **AppConnectorDeclaration** (interface)
2898
+ ```typescript
2899
+ interface AppConnectorDeclaration {
2900
+ provider: string
2901
+ title?: string
2902
+ directions: Array<'inbound' | 'outbound'>
2903
+ entities: string[]
2904
+ connection?: { fields: ConnectorConnectionField[] }
2905
+ settings?: Record<string, unknown>
2906
+ presets?: ConnectorPreset[]
2907
+ functions: {
2908
+ testConnection?: string
2909
+ describeTarget?: string
2910
+ sample?: string
2911
+ transform?: string
2912
+ send?: string
2913
+ fetch?: string
2914
+ }
2915
+ panels?: ConnectorPanel[]
2916
+ aiGuide?: string
2917
+ }
2918
+ ```
2919
+
2920
+ **ConnectorInstallation** (interface)
2921
+ ```typescript
2922
+ interface ConnectorInstallation {
2923
+ collectionId: string
2924
+ appId: string
2925
+ flowId?: string | null
2926
+ connection: Record<string, unknown>
2927
+ settings: Record<string, unknown>
2928
+ }
2929
+ ```
2930
+
2931
+ **ConnectorTestResult** (interface)
2932
+ ```typescript
2933
+ interface ConnectorTestResult {
2934
+ ok: boolean
2935
+ message: string
2936
+ }
2937
+ ```
2938
+
2939
+ **ConnectorTargetField** (interface)
2940
+ ```typescript
2941
+ interface ConnectorTargetField {
2942
+ path: string
2943
+ label?: string
2944
+ type?: 'string' | 'number' | 'boolean' | 'date' | 'object' | 'array'
2945
+ required?: boolean
2946
+ description?: string
2947
+ enum?: string[]
2948
+ }
2949
+ ```
2950
+
2951
+ **ConnectorTargetSchema** (interface)
2952
+ ```typescript
2953
+ interface ConnectorTargetSchema {
2954
+ fields: ConnectorTargetField[]
2955
+ }
2956
+ ```
2957
+
2958
+ **ConnectorRecords** (interface)
2959
+ ```typescript
2960
+ interface ConnectorRecords {
2961
+ records: Array<Record<string, unknown>>
2962
+ nextCursor?: string | null
2963
+ }
2964
+ ```
2965
+
2966
+ **ConnectorTransformInput** (interface)
2967
+ ```typescript
2968
+ interface ConnectorTransformInput {
2969
+ installation: ConnectorInstallation
2970
+ records: Array<Record<string, unknown>>
2971
+ mapping: FieldMapping[]
2972
+ }
2973
+ ```
2974
+
2975
+ **ConnectorTransformResult** (interface)
2976
+ ```typescript
2977
+ interface ConnectorTransformResult {
2978
+ payloads: Array<unknown | null>
2979
+ skipped?: Array<{ index: number; reason: string }>
2980
+ }
2981
+ ```
2982
+
2983
+ **ConnectorSendInput** (interface)
2984
+ ```typescript
2985
+ interface ConnectorSendInput {
2986
+ installation: ConnectorInstallation
2987
+ payloads: unknown[]
2988
+ }
2989
+ ```
2990
+
2991
+ **ConnectorSendResult** (interface)
2992
+ ```typescript
2993
+ interface ConnectorSendResult {
2994
+ results: Array<{ index: number; ok: boolean; status?: number; error?: string; externalId?: string }>
2995
+ }
2996
+ ```
2997
+
2998
+ **ConnectorFetchInput** (interface)
2999
+ ```typescript
3000
+ interface ConnectorFetchInput {
3001
+ installation: ConnectorInstallation
3002
+ cursor?: string | null
3003
+ limit?: number
3004
+ }
3005
+ ```
3006
+
2859
3007
  **CollectionAppWidget** (interface)
2860
3008
  ```typescript
2861
3009
  interface CollectionAppWidget {
@@ -6898,11 +7046,21 @@ interface FlowConnection {
6898
7046
  }
6899
7047
  ```
6900
7048
 
7049
+ **FlowConnectorRef** (interface)
7050
+ ```typescript
7051
+ interface FlowConnectorRef {
7052
+ appId: string
7053
+ preset?: string
7054
+ settings?: Record<string, unknown>
7055
+ }
7056
+ ```
7057
+
6901
7058
  **IntegrationFlowConfig** (interface)
6902
7059
  ```typescript
6903
7060
  interface IntegrationFlowConfig {
6904
7061
  connection?: FlowConnection
6905
7062
  fieldMappings?: FieldMapping[]
7063
+ connector?: FlowConnectorRef
6906
7064
  [key: string]: any
6907
7065
  }
6908
7066
  ```
@@ -6981,13 +7139,37 @@ interface RunFlowSummary {
6981
7139
  flowId: string
6982
7140
  direction: FlowDirection
6983
7141
  records: number
6984
- sent: number
7142
+ sent?: number
6985
7143
  failed: number
7144
+ created?: number
7145
+ updated?: number
7146
+ skipped?: number
6986
7147
  status: RunStatus
6987
7148
  runId?: string
6988
7149
  }
6989
7150
  ```
6990
7151
 
7152
+ **InstalledConnector** (interface)
7153
+ ```typescript
7154
+ interface InstalledConnector {
7155
+ appId: string
7156
+ name: string
7157
+ channel: string
7158
+ version: string | null
7159
+ connector: import('./appManifest').AppConnectorDeclaration
7160
+ installation: { connection: Record<string, unknown>; settings: Record<string, unknown> }
7161
+ secrets: Record<string, { set: boolean; hint?: string | null }>
7162
+ }
7163
+ ```
7164
+
7165
+ **SaveConnectorInstallationInput** (interface)
7166
+ ```typescript
7167
+ interface SaveConnectorInstallationInput {
7168
+ connection?: Record<string, unknown>
7169
+ settings?: Record<string, unknown>
7170
+ }
7171
+ ```
7172
+
6991
7173
  **CapturedRequest** (interface)
6992
7174
  ```typescript
6993
7175
  interface CapturedRequest {
@@ -7192,6 +7374,8 @@ interface RecordTypesResponse {
7192
7374
 
7193
7375
  **UpdateFlowInput** = `Partial<Omit<CreateFlowInput, 'direction'>> & {`
7194
7376
 
7377
+ **ConnectorConsoleStep** = `'testConnection' | 'describeTarget' | 'sample' | 'transform'`
7378
+
7195
7379
  **RunTrigger** = `'manual' | 'event' | 'schedule' | 'test'`
7196
7380
 
7197
7381
  **FlowRunStatus** = `'running' | 'success' | 'partial' | 'failed' | 'skipped'`
@@ -9141,6 +9325,59 @@ interface AblyTokenRequest {
9141
9325
  }
9142
9326
  ```
9143
9327
 
9328
+ **RealtimeScope** (interface)
9329
+ ```typescript
9330
+ interface RealtimeScope {
9331
+ collectionId: string
9332
+ appId?: string
9333
+ }
9334
+ ```
9335
+
9336
+ **RealtimeMessage** (interface)
9337
+ ```typescript
9338
+ interface RealtimeMessage {
9339
+ channel: string
9340
+ event: string
9341
+ data: unknown
9342
+ at?: number
9343
+ }
9344
+ ```
9345
+
9346
+ **RealtimeChangeEvent** (interface)
9347
+ ```typescript
9348
+ interface RealtimeChangeEvent {
9349
+ type: string
9350
+ id?: string
9351
+ appId?: string
9352
+ recordType?: string
9353
+ productId?: string
9354
+ proofId?: string
9355
+ status?: string
9356
+ at: number
9357
+ }
9358
+ ```
9359
+
9360
+ **RealtimeChangeFilter** (interface)
9361
+ ```typescript
9362
+ interface RealtimeChangeFilter {
9363
+ type?: string
9364
+ id?: string
9365
+ appId?: string
9366
+ recordType?: string
9367
+ productId?: string
9368
+ proofId?: string
9369
+ }
9370
+ ```
9371
+
9372
+ **RealtimeChannelDeclaration** (interface)
9373
+ ```typescript
9374
+ interface RealtimeChannelDeclaration {
9375
+ name: string
9376
+ subscribe?: "public" | "signedIn" | "admin"
9377
+ publish?: "public" | "signedIn" | "admin" | "server"
9378
+ }
9379
+ ```
9380
+
9144
9381
  **RealtimeChannelPattern** = `string`
9145
9382
 
9146
9383
  ### research
@@ -11453,6 +11690,20 @@ Enable enhanced logging. POST /integrations/logging/:connectionId (omit ttlMinut
11453
11690
  **cancelLogging**(collectionId: string, connectionId: string) → `Promise<`
11454
11691
  Cancel enhanced logging now. DELETE /integrations/logging/:connectionId
11455
11692
 
11693
+ **listConnectors**(collectionId: string) → `Promise<`
11694
+ The collection's installed connector apps. GET /integrations/connectors
11695
+
11696
+ **saveConnectorInstallation**(collectionId: string,
11697
+ appId: string,
11698
+ input: SaveConnectorInstallationInput) → `Promise<`
11699
+ Save a connector's non-secret Connect-form values and/or settings. PUT /integrations/connectors/:appId/installation
11700
+
11701
+ **runConnectorStep**(collectionId: string,
11702
+ appId: string,
11703
+ step: ConnectorConsoleStep,
11704
+ input: Record<string, unknown> & { flowId?: string } = {}) → `Promise<T>`
11705
+ Run a connector step from the console: `testConnection` → ConnectorTestResult, `describeTarget` → ConnectorTargetSchema, `sample` → ConnectorRecords, `transform` (a dry run) → ConnectorTransformResult. The server adds the installation; pass `flowId` to layer that flow's settings. POST /integrations/connectors/:appId/steps/:step
11706
+
11456
11707
  ### interactions
11457
11708
 
11458
11709
  **query**(collectionId: string,
@@ -12168,11 +12419,20 @@ Reset rate limit for a user
12168
12419
 
12169
12420
  ### realtime
12170
12421
 
12422
+ **channel**(scope: RealtimeScope & { appId: string }, name: string) → `void`
12423
+ One of your app's declared channels (manifest `realtime.channels`). ```ts const draw = realtime.channel({ collectionId, appId }, "draw") const off = draw.subscribe("status", (m) => setStatus(m.data)) await draw.publish("status", { phase: "spinning" }) // if the channel lets this viewer publish ```
12424
+
12425
+ **onChange**(scope: RealtimeScope, filter: RealtimeChangeFilter, onChangeFn: (change: RealtimeChangeEvent) → `void`
12426
+ Something changed: refetch it. IDs only — never data. Admins of the collection see every change; everyone else sees changes to public things (public app records, products). Filter by `type` ('product' | 'proof' | 'asset' | 'record' | 'collection' | 'collection.apps' | 'settings'), `id`, `appId`, `recordType`, `productId`, `proofId`. ```ts const off = realtime.onChange({ collectionId }, { type: "record", appId, recordType: "entry" }, () => refetch()) ```
12427
+
12428
+ **onMine**(scope: RealtimeScope, onMessage: (msg: RealtimeMessage) → `void`
12429
+ Messages for the signed-in person only (e.g. "scan" — a tag they just scanned on another device).
12430
+
12171
12431
  **getPublicToken**(params: RealtimeTokenRequest) → `Promise<AblyTokenRequest>`
12172
- Get an Ably token for public (user-scoped) real-time communication. This endpoint returns an Ably TokenRequest that can be used to initialize an Ably client with appropriate permissions for the specified collection and optional app. Requires user authentication (Bearer token). ```ts const tokenRequest = await realtime.getPublicToken({ collectionId: 'my-collection-id', appId: 'my-app-id' }) // Use with Ably const ably = new Ably.Realtime.Promise({ authCallback: async (data, callback) => { callback(null, tokenRequest) } }) // Subscribe to channels matching the pattern const channel = ably.channels.get('collection:my-collection-id:app:my-app-id:chat:general') await channel.subscribe('message', (msg) => console.log(msg.data)) ```
12432
+ `collection:{collectionId}:*` (subscribe; publish only for the collection's admins).
12173
12433
 
12174
12434
  **getAdminToken**() → `Promise<AblyTokenRequest>`
12175
- Get an Ably token for admin real-time communication. This endpoint returns an Ably TokenRequest that can be used to initialize an Ably client with admin permissions to receive system notifications and alerts. Admin users get subscribe-only (read-only) access to the interaction:{userId} channel pattern. Requires admin authentication (Bearer token). ```ts const tokenRequest = await realtime.getAdminToken() // Use with Ably const ably = new Ably.Realtime.Promise({ authCallback: async (data, callback) => { callback(null, tokenRequest) } }) // Subscribe to admin interaction channel const userId = 'my-user-id' const channel = ably.channels.get(`interaction:${userId}`) await channel.subscribe((message) => { console.log('Admin notification:', message.data) }) ```
12435
+ (subscribe only).
12176
12436
 
12177
12437
  ### research
12178
12438
 
@@ -436,6 +436,13 @@ this is just the manifest shape.
436
436
  | `definitions[].authority` | string | `caller` \| `collection` — whose authority it runs as |
437
437
  | `definitions[].capabilities` | string[] | Least-privilege grants (`sl:<res>:<read\|write>`, `network[:<host>]`, `secrets:<ref>`) |
438
438
 
439
+ #### `connector`
440
+
441
+ Marks the app as an integration **connector** for one external provider: the integrations console
442
+ lists it and draws its connect form and settings, and the integration engine calls its server
443
+ functions (`testConnection`, `describeTarget`, `sample`, `transform`, `send`, `fetch`). The full
444
+ contract is in the **[Connectors guide](connectors.md)**.
445
+
439
446
  ---
440
447
 
441
448
  ## `app.admin.json`
@@ -0,0 +1,204 @@
1
+ # Integration connectors
2
+
3
+ A **connector** is an app that knows one external provider (a PIM, a content network, a shop
4
+ platform…). It is installed on a collection like any app, versioned and released like any app, and
5
+ it adds a manifest `connector` block plus a handful of [server functions](server-functions.md) the
6
+ integration engine and the integrations console call by convention.
7
+
8
+ Three layers, each with one job:
9
+
10
+ | Layer | Holds |
11
+ |---|---|
12
+ | **Engine** (the API) | [Integration flows](integrations.md), runs and their logs, triggers (events, schedule, Run now), the secret store, the deterministic field mapper, generic transports (HTTP, Event Hub). No provider code. |
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: its connect form, settings, target schema, transform, send/fetch, and any provider-specific panels. |
15
+
16
+ A flow that only posts JSON to a webhook or an Event Hub needs no connector; it uses the engine's
17
+ generic transports (see [integrations.md](integrations.md)).
18
+
19
+ **No AI at run time.** The AI helps an admin *author* a mapping in the console (from the connector's
20
+ target schema plus real sample records); a person approves it, and it is saved as data and runs
21
+ deterministically. A connector's `transform` must be deterministic too.
22
+
23
+ ---
24
+
25
+ ## The manifest block
26
+
27
+ ```jsonc
28
+ // app.manifest.json
29
+ "connector": {
30
+ "provider": "oneworldsync",
31
+ "title": "1WorldSync",
32
+ "directions": ["inbound"],
33
+ "entities": ["product"],
34
+ "connection": {
35
+ "fields": [
36
+ { "name": "appId", "label": "App ID", "secret": "oneworldsync-app-id", "required": true },
37
+ { "name": "secretKey", "label": "Secret key", "secret": "oneworldsync-secret-key", "required": true },
38
+ { "name": "environment", "label": "Environment", "type": "select",
39
+ "options": [ { "value": "preprod", "label": "Pre-production" }, { "value": "production", "label": "Production" } ] }
40
+ ]
41
+ },
42
+ "settings": { // JSON Schema → a form in the console
43
+ "type": "object",
44
+ "properties": { "targetMarket": { "type": "string", "default": "GB" } }
45
+ },
46
+ "presets": [
47
+ { "id": "import-products", "title": "Import products", "direction": "inbound", "entity": "product" }
48
+ ],
49
+ "functions": { // names of this app's server functions
50
+ "testConnection": "testConnection",
51
+ "describeTarget": "describeTarget",
52
+ "sample": "sample",
53
+ "fetch": "fetch"
54
+ },
55
+ "panels": [ { "id": "explorer", "title": "Explorer", "component": "Explorer" } ],
56
+ "aiGuide": "connector-ai-guide.md"
57
+ },
58
+ "functions": {
59
+ "files": { "js": { "umd": "dist/functions.umd.js" } },
60
+ "definitions": [
61
+ { "name": "testConnection", "trigger": { "type": "http", "method": "POST" },
62
+ "visibility": "admin", "authority": "collection",
63
+ "capabilities": ["network:content1-api.1worldsync.com", "secrets:oneworldsync-app-id", "secrets:oneworldsync-secret-key"] }
64
+ // …one definition per function named above
65
+ ]
66
+ }
67
+ ```
68
+
69
+ Types: `AppConnectorDeclaration` and friends in `@proveanything/smartlinks` (`src/types/appManifest.ts`).
70
+
71
+ | Field | Meaning |
72
+ |---|---|
73
+ | `provider` | Stable provider key (`syndigo`, `oneworldsync`). |
74
+ | `title` | Provider display name; defaults to the app name. |
75
+ | `directions` | `inbound` (provider → SmartLinks), `outbound` (SmartLinks → provider), or both. |
76
+ | `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. |
78
+ | `settings` | JSON Schema for installation settings; the console renders it as a form. |
79
+ | `presets[]` | Ready-made flows ("Products → provider") with flow defaults. |
80
+ | `functions` | Which of this app's server functions implement each step (below). Fill in only what applies. |
81
+ | `panels[]` | Provider-specific screens the console mounts (an API explorer, a status page). |
82
+ | `aiGuide` | A guide the mapping assistant reads: the provider's quirks, required fields, good defaults. |
83
+
84
+ ---
85
+
86
+ ## The functions
87
+
88
+ Each step names one of the app's server functions. Declare them as **http POST, `visibility:
89
+ "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
+ (`functions.callAdmin(collectionId, name, input, { appId })`). The input arrives as `event.body`;
92
+ return the result as JSON. Capabilities still cap what each one can do: declare the provider's hosts
93
+ (`network:<host>`) and the secrets it reads (`secrets:<name>`).
94
+
95
+ | Step | Called by | Input (`event.body`) | Returns |
96
+ |---|---|---|---|
97
+ | `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* |
99
+ | `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
+ | `fetch` | Every inbound run | `ConnectorFetchInput` `{ installation, cursor, limit }` | `ConnectorRecords` `{ records, nextCursor }`: one page per call |
103
+
104
+ What happens when a step is left out:
105
+
106
+ - **No `transform`:** the engine's own mapper runs the flow's `fieldMappings` (direct, static, Liquid
107
+ template).
108
+ - **No `send`:** the flow's own transport: HTTP (`config.connection`), or Azure Event Hub with
109
+ `config.transport: 'eventhub'` and `config.eventHub`.
110
+ - **No `describeTarget`:** the mapper offers free-text target paths.
111
+
112
+ ### `installation`
113
+
114
+ Every step receives the installation it serves:
115
+
116
+ ```ts
117
+ interface ConnectorInstallation {
118
+ collectionId: string
119
+ appId: string // the connector app
120
+ flowId?: string | null // when called for a flow
121
+ connection: Record<string, unknown> // non-secret Connect-form fields
122
+ settings: Record<string, unknown> // installation settings, with the flow's overrides on top
123
+ }
124
+ ```
125
+
126
+ Secrets are never in it. Read them in the function with `ctx.secrets.get('<name>')`, which needs the
127
+ matching `secrets:<name>` capability.
128
+
129
+ ### Example: `fetch`
130
+
131
+ ```ts
132
+ // src/functions/index.ts (the app's functions bundle)
133
+ import type { ConnectorFetchInput, ConnectorRecords } from '@proveanything/smartlinks'
134
+
135
+ export async function fetch(ctx: any, event: any): Promise<ConnectorRecords> {
136
+ const { installation, cursor, limit = 50 } = event.body as ConnectorFetchInput
137
+ const appId = await ctx.secrets.get('provider-app-id')
138
+ const key = await ctx.secrets.get('provider-secret-key')
139
+ const res = await ctx.fetch(buildSignedUrl(installation, { cursor, limit }, appId, key))
140
+ const page = await res.json()
141
+ return { records: page.items, nextCursor: page.next ?? null }
142
+ }
143
+ ```
144
+
145
+ ---
146
+
147
+ ## Where configuration lives
148
+
149
+ | What | Where |
150
+ |---|---|
151
+ | Connect-form values (non-secret), installation settings | The connector app's config on the collection: `appConfiguration` for `appId`, under `connector.connection` and `connector.settings`. |
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`. |
154
+
155
+ ```ts
156
+ await integrations.createFlow(collectionId, {
157
+ appId: 'core-integrations',
158
+ direction: 'inbound',
159
+ name: 'Import products from 1WorldSync',
160
+ status: 'active',
161
+ schedule: '0 * * * *',
162
+ targetEntity: 'product',
163
+ config: {
164
+ connector: { appId: 'oneworldsync', preset: 'import-products' },
165
+ fieldMappings: [ { targetPath: 'name', sourcePath: 'item.tradeItemDescription', transformType: 'direct' } ],
166
+ },
167
+ })
168
+ ```
169
+
170
+ ---
171
+
172
+ ## From the console
173
+
174
+ ```ts
175
+ import { integrations, secrets } from '@proveanything/smartlinks'
176
+
177
+ const { connectors } = await integrations.listConnectors(collectionId)
178
+ // → [{ appId, name, version, connector, installation: { connection, settings }, secrets: { 'provider-key': { set, hint } } }]
179
+
180
+ await secrets.put(collectionId, 'provider-key', value, { appId }) // a secret field
181
+ await integrations.saveConnectorInstallation(collectionId, appId, { connection: { environment: 'production' } })
182
+ const test = await integrations.runConnectorStep(collectionId, appId, 'testConnection')
183
+ const target = await integrations.runConnectorStep(collectionId, appId, 'describeTarget')
184
+ const dryRun = await integrations.runConnectorStep(collectionId, appId, 'transform', { records, mapping })
185
+ ```
186
+
187
+ `send` and `fetch` only run inside a flow. A flow run's summary carries `sent` (outbound) or
188
+ `created` / `updated` / `skipped` (inbound), and every run, inbound or outbound, is in the run history.
189
+
190
+ ---
191
+
192
+ ## Panels
193
+
194
+ 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 host-side and passes it the
196
+ collection and the installation. Panels are admin-only and call the connector's own functions.
197
+
198
+ ---
199
+
200
+ ## Who can build connectors
201
+
202
+ First-party connectors run in-process, like any app server function. Third-party connectors will run
203
+ in the isolated runner (see [server-functions.md](server-functions.md)) once that opens up; the contract
204
+ does not change.