@proveanything/smartlinks 2.0.57 → 2.0.60

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,91 @@
1
+ # Websites: server-rendered on the SmartLinks site runtime
2
+
3
+ A website built on SmartLinks is an app whose public side is a **server-rendered site** at its own
4
+ address: an automatic `*.smartlinks.host` name, a chosen name, or the business's own domain. Every
5
+ page arrives as finished HTML, so search engines, AI crawlers and link previews see the real
6
+ content, and visitors see it before any JavaScript runs.
7
+
8
+ ## How a request is served
9
+
10
+ ```
11
+ visitor / crawler / agent
12
+ └─► SmartLinks site router (edge)
13
+ ├─ /robots.txt, /sitemap.xml, /llms.txt made by the platform (site-seo.md)
14
+ ├─ a file in your build (has an extension) served from the build, cached at the edge
15
+ └─ anything else your site's server renders it
16
+ • runs in an isolated worker, close to the SmartLinks API
17
+ • may call the SmartLinks API and the hosts you declare, nothing else
18
+ • request header x-sl-site says which site it is (SL.site.fromRequest)
19
+ ```
20
+
21
+ The router also adds the canonical or noindex header, the security headers, and
22
+ `window.__SMARTLINKS_SITE__` to every HTML page (read in the browser with `SL.site.current()` or
23
+ `SL.readContext()`).
24
+
25
+ ## The build
26
+
27
+ The website template (TanStack Start) produces this; you don't write it by hand.
28
+
29
+ ```
30
+ dist/
31
+ ├── index.html? / assets/… the client build (hashed files: cached for a year)
32
+ ├── sitemap-paths.txt your pages, "<path> [Title]" per line (site-seo.md)
33
+ ├── _server/ the server bundle: one ES module entry + its chunks
34
+ │ └── index.mjs exports default { fetch(request, env, ctx) } (WinterTC)
35
+ └── app.manifest.json
36
+ ```
37
+
38
+ Manifest:
39
+
40
+ ```jsonc
41
+ "site": {
42
+ "server": {
43
+ "main": "_server/index.mjs", // the server entry, inside the build
44
+ "compatibilityFlags": ["nodejs_compat"] // allowed: nodejs_compat, nodejs_compat_v2, nodejs_als
45
+ },
46
+ "network": ["api.example-weather.com"] // other hosts the server may call (optional)
47
+ }
48
+ ```
49
+
50
+ On publish, the server folder becomes the site's server for that channel. Promote copies the same
51
+ bytes. `_server/` is never served to visitors.
52
+
53
+ ## The server's rules
54
+
55
+ - **Which site:** `const site = SL.site.fromRequest(request)` gives `{ host, collectionId, appId,
56
+ channel, canonicalHost }`. One build serves every site it's installed on, so always take the
57
+ collection from here, never from a constant.
58
+ - **Data:** call the SmartLinks API with the SDK as you would in the browser. The server reads what
59
+ an anonymous visitor could read on that site, and only that collection. It holds no keys and no
60
+ signed-in user.
61
+ - **Writes** (contact forms, entries, sign-ups) go to your app's **server functions**
62
+ (server-functions.md), called from the browser as today. Their capabilities, secrets and limits
63
+ apply.
64
+ - **Other hosts:** only those in `site.network`. Anything else is refused (`403 Blocked`).
65
+ - **Limits per request:** a CPU-time budget and a cap on outgoing calls. A page that needs more is
66
+ doing work that belongs in a server function or a scheduled job.
67
+ - **No secrets** in the server bundle and no Node built-ins beyond `nodejs_compat`.
68
+
69
+ ## Caching
70
+
71
+ The router keeps a rendered page at the edge when your response says it may:
72
+
73
+ | Your response | At the edge |
74
+ |---|---|
75
+ | `Cache-Control: public, s-maxage=300` (the template's default for pages) | kept up to 5 minutes |
76
+ | `private`, `no-store`, `no-cache`, or a `Set-Cookie` | never kept |
77
+ | no `Cache-Control` | never kept |
78
+
79
+ Cached pages are keyed by the release and the collection's **content generation**: when products,
80
+ public app records, app settings or the collection change, the generation moves and every page of
81
+ the collection's sites re-renders on its next visit (within about 30 seconds). Publishing a release
82
+ does the same. Tracking parameters (`utm_*`, `gclid`, `fbclid`…) don't create separate entries.
83
+
84
+ Keep pages the same for every visitor. Anything personal (a signed-in owner's products, a basket)
85
+ renders in the browser after load.
86
+
87
+ ## Locally
88
+
89
+ `npm run dev` runs the site with your dev collection (`.env`: `VITE_SL_COLLECTION_ID`), because
90
+ there's no router to say which site it is: `SL.site.fromRequest()` is null, so fall back to it.
91
+ Forge's preview runs the same server code the site runtime does.
package/dist/openapi.yaml CHANGED
@@ -19766,7 +19766,9 @@ components:
19766
19766
  type: string
19767
19767
  sample:
19768
19768
  type: string
19769
- transform:
19769
+ shape:
19770
+ type: string
19771
+ serialize:
19770
19772
  type: string
19771
19773
  send:
19772
19774
  type: string
@@ -19778,6 +19780,11 @@ components:
19778
19780
  $ref: "#/components/schemas/ConnectorPanel"
19779
19781
  aiGuide:
19780
19782
  type: string
19783
+ recordId:
19784
+ type: string
19785
+ limits:
19786
+ type: object
19787
+ additionalProperties: true
19781
19788
  required:
19782
19789
  - provider
19783
19790
  - directions
@@ -19860,7 +19867,7 @@ components:
19860
19867
  type: string
19861
19868
  required:
19862
19869
  - records
19863
- ConnectorTransformInput:
19870
+ ConnectorShapeInput:
19864
19871
  type: object
19865
19872
  properties:
19866
19873
  installation:
@@ -19870,15 +19877,40 @@ components:
19870
19877
  items:
19871
19878
  type: object
19872
19879
  additionalProperties: true
19873
- mapping:
19880
+ required:
19881
+ - installation
19882
+ - records
19883
+ ConnectorShapeResult:
19884
+ type: object
19885
+ properties:
19886
+ documents:
19874
19887
  type: array
19875
19888
  items:
19876
- $ref: "#/components/schemas/FieldMapping"
19889
+ type: object
19890
+ additionalProperties: true
19891
+ skipped:
19892
+ type: array
19893
+ items:
19894
+ type: object
19895
+ additionalProperties: true
19896
+ required:
19897
+ - documents
19898
+ ConnectorSerializeInput:
19899
+ type: object
19900
+ properties:
19901
+ installation:
19902
+ $ref: "#/components/schemas/ConnectorInstallation"
19903
+ documents:
19904
+ type: array
19905
+ items:
19906
+ type: object
19907
+ additionalProperties: true
19908
+ eventType:
19909
+ type: string
19877
19910
  required:
19878
19911
  - installation
19879
- - records
19880
- - mapping
19881
- ConnectorTransformResult:
19912
+ - documents
19913
+ ConnectorSerializeResult:
19882
19914
  type: object
19883
19915
  properties:
19884
19916
  payloads:
@@ -19906,12 +19938,26 @@ components:
19906
19938
  type: object
19907
19939
  properties:
19908
19940
  results:
19909
- type: array
19910
- items:
19911
- type: object
19912
- additionalProperties: true
19941
+ type: object
19942
+ additionalProperties: true
19943
+ index:
19944
+ type: number
19945
+ ok:
19946
+ type: boolean
19947
+ status:
19948
+ type: number
19949
+ error:
19950
+ type: string
19951
+ externalId:
19952
+ type: string
19953
+ retryable:
19954
+ type: boolean
19955
+ retryAfterSeconds:
19956
+ type: number
19913
19957
  required:
19914
19958
  - results
19959
+ - index
19960
+ - ok
19915
19961
  ConnectorFetchInput:
19916
19962
  type: object
19917
19963
  properties:
@@ -26251,6 +26297,12 @@ components:
26251
26297
  type: string
26252
26298
  totalSynced:
26253
26299
  type: number
26300
+ circuitOpenedAt:
26301
+ type: string
26302
+ circuitProbeAt:
26303
+ type: string
26304
+ circuitReason:
26305
+ type: string
26254
26306
  required:
26255
26307
  - id
26256
26308
  - orgId
@@ -26267,6 +26319,47 @@ components:
26267
26319
  - createdBy
26268
26320
  - createdAt
26269
26321
  - updatedAt
26322
+ FailedRecord:
26323
+ type: object
26324
+ properties:
26325
+ id:
26326
+ type: string
26327
+ entityId:
26328
+ type: string
26329
+ externalId:
26330
+ type: string
26331
+ status:
26332
+ type: string
26333
+ enum:
26334
+ - failed
26335
+ - dead
26336
+ attempts:
26337
+ type: number
26338
+ nextAttemptAt:
26339
+ type: string
26340
+ lastError:
26341
+ type: string
26342
+ updatedAt:
26343
+ type: string
26344
+ required:
26345
+ - id
26346
+ - status
26347
+ - attempts
26348
+ FailedRecordList:
26349
+ type: object
26350
+ properties:
26351
+ direction:
26352
+ $ref: "#/components/schemas/FlowDirection"
26353
+ items:
26354
+ type: array
26355
+ items:
26356
+ $ref: "#/components/schemas/FailedRecord"
26357
+ nextCursor:
26358
+ type: string
26359
+ required:
26360
+ - direction
26361
+ - items
26362
+ - nextCursor
26270
26363
  CreateFlowInput:
26271
26364
  type: object
26272
26365
  properties:
@@ -26338,6 +26431,8 @@ components:
26338
26431
  type: number
26339
26432
  skipped:
26340
26433
  type: number
26434
+ held:
26435
+ type: number
26341
26436
  status:
26342
26437
  $ref: "#/components/schemas/RunStatus"
26343
26438
  runId:
@@ -26734,7 +26829,8 @@ components:
26734
26829
  - testConnection
26735
26830
  - describeTarget
26736
26831
  - sample
26737
- - transform
26832
+ - shape
26833
+ - serialize
26738
26834
  RunTrigger:
26739
26835
  type: string
26740
26836
  enum:
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
- * Tell the platform this page has finished rendering its content (data loaded, head tags set). The
6
- * platform's page renderer takes its snapshot for search engines and AI crawlers at this point;
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
- * Tell the platform this page has finished rendering its content (data loaded, head tags set). The
6
- * platform's page renderer takes its snapshot for search engines and AI crawlers at this point;
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, transform, send and fetch.
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
- /** (ConnectorTransformInput) → ConnectorTransformResult. Must be deterministic — no AI at run time. */
905
- transform?: string;
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. */
@@ -911,6 +926,16 @@ export interface AppConnectorDeclaration {
911
926
  panels?: ConnectorPanel[];
912
927
  /** Path (under the app's public files) of a guide the AI mapping assistant reads. */
913
928
  aiGuide?: string;
929
+ /**
930
+ * Path of the provider's own id in a fetched record (e.g. 'gtin', 'item.id'). Inbound records are
931
+ * landed and matched to SmartLinks entities by it, so a re-import updates rather than duplicates.
932
+ * Default 'id'; a flow can override it with `config.upsert.externalIdPath`.
933
+ */
934
+ recordId?: string;
935
+ /** Requests per second toward the provider, shared by every SmartLinks worker. */
936
+ limits?: {
937
+ requestsPerSecond?: number;
938
+ };
914
939
  }
915
940
  /**
916
941
  * What every connector function receives about the installation it serves. The engine assembles it
@@ -951,15 +976,31 @@ export interface ConnectorRecords {
951
976
  /** Inbound paging: pass back to the next fetch; absent when there's no more. */
952
977
  nextCursor?: string | null;
953
978
  }
954
- export interface ConnectorTransformInput {
979
+ /** Inbound: provider records, as fetch / sample returned them. */
980
+ export interface ConnectorShapeInput {
955
981
  installation: ConnectorInstallation;
956
- /** SmartLinks records (outbound) or provider records (inbound). */
957
982
  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
983
  }
961
- export interface ConnectorTransformResult {
962
- /** One payload per input record, in the same order (null = skip that record, with a reason). */
984
+ export interface ConnectorShapeResult {
985
+ /** One provider-neutral document per record, in the same order (null = skip it, with a reason). */
986
+ documents: Array<Record<string, unknown> | null>;
987
+ skipped?: Array<{
988
+ index: number;
989
+ reason: string;
990
+ }>;
991
+ }
992
+ /**
993
+ * Outbound: documents core has mapped to this provider's fields (keyed by `describeTarget` paths).
994
+ * A deleted SmartLinks entity arrives as `{ id, _deleted: true }`.
995
+ */
996
+ export interface ConnectorSerializeInput {
997
+ installation: ConnectorInstallation;
998
+ documents: Array<Record<string, unknown>>;
999
+ /** The event that triggered the run, e.g. 'product.updated' (absent for manual / scheduled runs). */
1000
+ eventType?: string;
1001
+ }
1002
+ export interface ConnectorSerializeResult {
1003
+ /** One provider payload per document, in the same order (null = skip it, with a reason). */
963
1004
  payloads: Array<unknown | null>;
964
1005
  skipped?: Array<{
965
1006
  index: number;
@@ -977,6 +1018,10 @@ export interface ConnectorSendResult {
977
1018
  status?: number;
978
1019
  error?: string;
979
1020
  externalId?: string;
1021
+ /** Worth retrying? Default: by status (408/425/429/5xx/none yes; other 4xx no). */
1022
+ retryable?: boolean;
1023
+ /** The provider asked to wait this long before retrying (e.g. its Retry-After). */
1024
+ retryAfterSeconds?: number;
980
1025
  }>;
981
1026
  }
982
1027
  export interface ConnectorFetchInput {
@@ -71,6 +71,32 @@ export interface IntegrationFlow {
71
71
  lastPollAt?: string | null;
72
72
  lastCursor?: string | null;
73
73
  totalSynced?: number | null;
74
+ /**
75
+ * Circuit breaker: set while the flow has paused itself because its destination / provider keeps
76
+ * failing. Nothing is sent or fetched (edits are held as retries) until a probe gets through —
77
+ * `circuitProbeAt` is the next one — or someone resumes it (`integrations.resumeFlow`).
78
+ */
79
+ circuitOpenedAt?: string | null;
80
+ circuitProbeAt?: string | null;
81
+ circuitReason?: string | null;
82
+ }
83
+ /** A record that failed: retried on its own with backoff ('failed'), or given up on ('dead'). */
84
+ export interface FailedRecord {
85
+ /** Outbound: the SmartLinks entity id. Inbound: the landed record's id (pass it to retryFailed). */
86
+ id: string;
87
+ entityId?: string | null;
88
+ /** Inbound: the provider's id for the record. */
89
+ externalId?: string;
90
+ status: 'failed' | 'dead';
91
+ attempts: number;
92
+ nextAttemptAt?: string | null;
93
+ lastError?: string | null;
94
+ updatedAt?: string;
95
+ }
96
+ export interface FailedRecordList {
97
+ direction: FlowDirection;
98
+ items: FailedRecord[];
99
+ nextCursor: string | null;
74
100
  }
75
101
  export interface CreateFlowInput {
76
102
  appId: string;
@@ -109,10 +135,12 @@ export interface RunFlowSummary {
109
135
  /** Outbound: records delivered. */
110
136
  sent?: number;
111
137
  failed: number;
112
- /** Inbound: products created / updated, and records the connector skipped. */
138
+ /** Inbound: products created / updated, and records the connector skipped (outbound: unchanged). */
113
139
  created?: number;
114
140
  updated?: number;
115
141
  skipped?: number;
142
+ /** Outbound: changes held because the destination is paused (circuit open); sent after it recovers. */
143
+ held?: number;
116
144
  status: RunStatus;
117
145
  /** Id of the persisted run row (deep-link into its detail). */
118
146
  runId?: string;
@@ -141,8 +169,8 @@ export interface SaveConnectorInstallationInput {
141
169
  connection?: Record<string, unknown>;
142
170
  settings?: Record<string, unknown>;
143
171
  }
144
- /** Steps the console may run directly (send and fetch only run inside a flow). */
145
- export type ConnectorConsoleStep = 'testConnection' | 'describeTarget' | 'sample' | 'transform';
172
+ /** Steps the console may run directly (send and fetch only run inside a flow; shape/serialize as dry runs). */
173
+ export type ConnectorConsoleStep = 'testConnection' | 'describeTarget' | 'sample' | 'shape' | 'serialize';
146
174
  export type RunTrigger = 'manual' | 'event' | 'schedule' | 'test';
147
175
  export type FlowRunStatus = 'running' | 'success' | 'partial' | 'failed' | 'skipped';
148
176
  export type FlowRunItemStatus = 'sent' | 'failed' | 'skipped';