@particle-academy/fancy-flow 0.47.1 → 0.48.0
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/connectors.cjs +95 -0
- package/dist/connectors.cjs.map +1 -0
- package/dist/connectors.d.cts +122 -0
- package/dist/connectors.d.ts +122 -0
- package/dist/connectors.js +90 -0
- package/dist/connectors.js.map +1 -0
- package/package.json +11 -1
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/connectors.ts
|
|
4
|
+
var SELECTABLE_SANDBOX = ["credential", "base-url", "separate-account"];
|
|
5
|
+
function connectionFields(meta) {
|
|
6
|
+
const modes = [
|
|
7
|
+
{ value: "auto", label: "Auto \u2014 sandbox locally, live in production" },
|
|
8
|
+
{ value: "fake", label: "Fake \u2014 no credentials, no network" }
|
|
9
|
+
];
|
|
10
|
+
if (SELECTABLE_SANDBOX.includes(meta.sandbox)) {
|
|
11
|
+
modes.push({ value: "sandbox", label: sandboxLabel(meta.sandbox) });
|
|
12
|
+
}
|
|
13
|
+
modes.push({ value: "live", label: "Live \u2014 the real account" });
|
|
14
|
+
return [
|
|
15
|
+
{
|
|
16
|
+
type: "credential",
|
|
17
|
+
key: "connection",
|
|
18
|
+
label: `${meta.serviceTitle} connection`,
|
|
19
|
+
credentialType: `connector:${meta.service}`,
|
|
20
|
+
description: `Which configured ${meta.serviceTitle} connection to use. Credentials live in the host's configuration, never in the workflow \u2014 a graph is exported, committed and handed to agents.`
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
type: "select",
|
|
24
|
+
key: "mode",
|
|
25
|
+
label: "Environment",
|
|
26
|
+
options: modes,
|
|
27
|
+
default: "auto",
|
|
28
|
+
description: sandboxNote(meta)
|
|
29
|
+
}
|
|
30
|
+
];
|
|
31
|
+
}
|
|
32
|
+
function sandboxLabel(kind) {
|
|
33
|
+
return kind === "base-url" ? "Sandbox \u2014 the provider's separate test host" : kind === "separate-account" ? "Sandbox \u2014 your separate test account" : "Sandbox \u2014 the provider's test estate";
|
|
34
|
+
}
|
|
35
|
+
function sandboxNote(meta) {
|
|
36
|
+
if (meta.sandbox === "restricted-reach") {
|
|
37
|
+
return `${meta.serviceTitle} has no separate test estate \u2014 an unreviewed app posts only to its own developers, or privately. Same credentials, same endpoints: only the audience changes, and nothing here selects it. A restricted run looks exactly like a successful one, so confirm reach on the provider's own surface.`;
|
|
38
|
+
}
|
|
39
|
+
if (meta.sandbox === "unverified") {
|
|
40
|
+
return `Nobody has verified what test estate ${meta.serviceTitle} offers, so "sandbox" is not offered \u2014 and "auto" means fake locally and live in production. Find out before pointing a workflow at it.`;
|
|
41
|
+
}
|
|
42
|
+
if (meta.sandbox === "none") {
|
|
43
|
+
return `${meta.serviceTitle} has no sandbox estate, so "auto" means fake locally and live in production.`;
|
|
44
|
+
}
|
|
45
|
+
return "Auto follows the environment. Setting this explicitly overrides it everywhere, including in production.";
|
|
46
|
+
}
|
|
47
|
+
function defineConnectorKind(meta, kind) {
|
|
48
|
+
return {
|
|
49
|
+
...kind,
|
|
50
|
+
// `category` stays fancy-flow's own taxonomy — it describes what the node
|
|
51
|
+
// does to the GRAPH, which is what the palette groups by. Connector-ness is
|
|
52
|
+
// a separate axis and is carried separately; overloading one field with two
|
|
53
|
+
// meanings would make "show me the triggers" and "hide the connectors"
|
|
54
|
+
// impossible to ask at the same time.
|
|
55
|
+
category: kind.category ?? (meta.role === "trigger" ? "trigger" : "io"),
|
|
56
|
+
accent: kind.accent ?? DOMAIN_ACCENT[meta.domain],
|
|
57
|
+
configSchema: [...connectionFields(meta), ...kind.configSchema ?? []],
|
|
58
|
+
connector: meta
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function summarize(meta, config, detail) {
|
|
62
|
+
const mode = typeof config.mode === "string" ? config.mode : "auto";
|
|
63
|
+
const where = mode === "fake" ? " (faked \u2014 nothing leaves this machine)" : mode === "sandbox" ? " in the sandbox" : mode === "live" ? " on the live account" : "";
|
|
64
|
+
const what = detail?.trim() ? detail.trim() : meta.operation.replace(/_/g, " ");
|
|
65
|
+
return meta.role === "trigger" ? `When ${meta.serviceTitle} reports ${what}${where}` : `Then ${meta.serviceTitle} will ${what}${where}`;
|
|
66
|
+
}
|
|
67
|
+
function ingredients(fields) {
|
|
68
|
+
return fields;
|
|
69
|
+
}
|
|
70
|
+
var DOMAIN_ACCENT = {
|
|
71
|
+
payments: "#635bff",
|
|
72
|
+
commerce: "#96bf48",
|
|
73
|
+
messaging: "#4a154b",
|
|
74
|
+
email: "#0f9d58",
|
|
75
|
+
crm: "#00a1e0",
|
|
76
|
+
support: "#03363d",
|
|
77
|
+
storage: "#ff9900",
|
|
78
|
+
calendar: "#4285f4",
|
|
79
|
+
productivity: "#2f3437",
|
|
80
|
+
database: "#336791",
|
|
81
|
+
devtools: "#24292f",
|
|
82
|
+
analytics: "#f9a03c",
|
|
83
|
+
marketing: "#1877f2",
|
|
84
|
+
ai: "#d97757",
|
|
85
|
+
forms: "#262627",
|
|
86
|
+
hr: "#5c4ee5",
|
|
87
|
+
geo: "#34a853"
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
exports.connectionFields = connectionFields;
|
|
91
|
+
exports.defineConnectorKind = defineConnectorKind;
|
|
92
|
+
exports.ingredients = ingredients;
|
|
93
|
+
exports.summarize = summarize;
|
|
94
|
+
//# sourceMappingURL=connectors.cjs.map
|
|
95
|
+
//# sourceMappingURL=connectors.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/connectors.ts"],"names":[],"mappings":";;;AAwGA,IAAM,kBAAA,GAA6C,CAAC,YAAA,EAAc,UAAA,EAAY,kBAAkB,CAAA;AAsBzF,SAAS,iBAAiB,IAAA,EAAoC;AACnE,EAAA,MAAM,KAAA,GAAiD;AAAA,IACrD,EAAE,KAAA,EAAO,MAAA,EAAQ,KAAA,EAAO,iDAAA,EAA6C;AAAA,IACrE,EAAE,KAAA,EAAO,MAAA,EAAQ,KAAA,EAAO,wCAAA;AAAoC,GAC9D;AAIA,EAAA,IAAI,kBAAA,CAAmB,QAAA,CAAS,IAAA,CAAK,OAAO,CAAA,EAAG;AAC7C,IAAA,KAAA,CAAM,IAAA,CAAK,EAAE,KAAA,EAAO,SAAA,EAAW,OAAO,YAAA,CAAa,IAAA,CAAK,OAAO,CAAA,EAAG,CAAA;AAAA,EACpE;AACA,EAAA,KAAA,CAAM,KAAK,EAAE,KAAA,EAAO,MAAA,EAAQ,KAAA,EAAO,gCAA2B,CAAA;AAE9D,EAAA,OAAO;AAAA,IACL;AAAA,MACE,IAAA,EAAM,YAAA;AAAA,MACN,GAAA,EAAK,YAAA;AAAA,MACL,KAAA,EAAO,CAAA,EAAG,IAAA,CAAK,YAAY,CAAA,WAAA,CAAA;AAAA,MAC3B,cAAA,EAAgB,CAAA,UAAA,EAAa,IAAA,CAAK,OAAO,CAAA,CAAA;AAAA,MACzC,WAAA,EACE,CAAA,iBAAA,EAAoB,IAAA,CAAK,YAAY,CAAA,mJAAA;AAAA,KAEzC;AAAA,IACA;AAAA,MACE,IAAA,EAAM,QAAA;AAAA,MACN,GAAA,EAAK,MAAA;AAAA,MACL,KAAA,EAAO,aAAA;AAAA,MACP,OAAA,EAAS,KAAA;AAAA,MACT,OAAA,EAAS,MAAA;AAAA,MACT,WAAA,EAAa,YAAY,IAAI;AAAA;AAC/B,GACF;AACF;AAEA,SAAS,aAAa,IAAA,EAA2B;AAC/C,EAAA,OAAO,IAAA,KAAS,UAAA,GACZ,kDAAA,GACA,IAAA,KAAS,qBACP,2CAAA,GACA,2CAAA;AACR;AAWA,SAAS,YAAY,IAAA,EAA6B;AAChD,EAAA,IAAI,IAAA,CAAK,YAAY,kBAAA,EAAoB;AACvC,IAAA,OACE,CAAA,EAAG,KAAK,YAAY,CAAA,qSAAA,CAAA;AAAA,EAIxB;AAEA,EAAA,IAAI,IAAA,CAAK,YAAY,YAAA,EAAc;AACjC,IAAA,OACE,CAAA,qCAAA,EAAwC,KAAK,YAAY,CAAA,4IAAA,CAAA;AAAA,EAG7D;AAEA,EAAA,IAAI,IAAA,CAAK,YAAY,MAAA,EAAQ;AAC3B,IAAA,OAAO,CAAA,EAAG,KAAK,YAAY,CAAA,4EAAA,CAAA;AAAA,EAC7B;AAEA,EAAA,OAAO,yGAAA;AACT;AAUO,SAAS,mBAAA,CACd,MACA,IAAA,EACmD;AACnD,EAAA,OAAO;AAAA,IACL,GAAG,IAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMH,UAAU,IAAA,CAAK,QAAA,KAAa,IAAA,CAAK,IAAA,KAAS,YAAY,SAAA,GAAY,IAAA,CAAA;AAAA,IAClE,MAAA,EAAQ,IAAA,CAAK,MAAA,IAAU,aAAA,CAAc,KAAK,MAAM,CAAA;AAAA,IAChD,YAAA,EAAc,CAAC,GAAG,gBAAA,CAAiB,IAAI,GAAG,GAAI,IAAA,CAAK,YAAA,IAAgB,EAAG,CAAA;AAAA,IACtE,SAAA,EAAW;AAAA,GACb;AACF;AASO,SAAS,SAAA,CAAU,IAAA,EAAqB,MAAA,EAAiC,MAAA,EAAyB;AACvG,EAAA,MAAM,OAAO,OAAO,MAAA,CAAO,IAAA,KAAS,QAAA,GAAW,OAAO,IAAA,GAAO,MAAA;AAC7D,EAAA,MAAM,KAAA,GACJ,SAAS,MAAA,GACL,6CAAA,GACA,SAAS,SAAA,GACP,iBAAA,GACA,IAAA,KAAS,MAAA,GACP,sBAAA,GACA,EAAA;AAEV,EAAA,MAAM,IAAA,GAAO,MAAA,EAAQ,IAAA,EAAK,GAAI,MAAA,CAAO,IAAA,EAAK,GAAI,IAAA,CAAK,SAAA,CAAU,OAAA,CAAQ,IAAA,EAAM,GAAG,CAAA;AAE9E,EAAA,OAAO,KAAK,IAAA,KAAS,SAAA,GACjB,CAAA,KAAA,EAAQ,IAAA,CAAK,YAAY,CAAA,SAAA,EAAY,IAAI,CAAA,EAAG,KAAK,KACjD,CAAA,KAAA,EAAQ,IAAA,CAAK,YAAY,CAAA,MAAA,EAAS,IAAI,GAAG,KAAK,CAAA,CAAA;AACpD;AAGO,SAAS,YAAY,MAAA,EAAsC;AAChE,EAAA,OAAO,MAAA;AACT;AAEA,IAAM,aAAA,GAAiD;AAAA,EACrD,QAAA,EAAU,SAAA;AAAA,EACV,QAAA,EAAU,SAAA;AAAA,EACV,SAAA,EAAW,SAAA;AAAA,EACX,KAAA,EAAO,SAAA;AAAA,EACP,GAAA,EAAK,SAAA;AAAA,EACL,OAAA,EAAS,SAAA;AAAA,EACT,OAAA,EAAS,SAAA;AAAA,EACT,QAAA,EAAU,SAAA;AAAA,EACV,YAAA,EAAc,SAAA;AAAA,EACd,QAAA,EAAU,SAAA;AAAA,EACV,QAAA,EAAU,SAAA;AAAA,EACV,SAAA,EAAW,SAAA;AAAA,EACX,SAAA,EAAW,SAAA;AAAA,EACX,EAAA,EAAI,SAAA;AAAA,EACJ,KAAA,EAAO,SAAA;AAAA,EACP,EAAA,EAAI,SAAA;AAAA,EACJ,GAAA,EAAK;AACP,CAAA","file":"connectors.cjs","sourcesContent":["/**\n * The authoring surface every connector node shares.\n *\n * ## Why this is in fancy-flow and not in fancy-connector-core\n *\n * `fancy-connector-core` owns the WIRE — delivery, classification, retries,\n * estate selection — and deliberately knows nothing about flow. This module\n * builds a node's `ConfigField[]` and its `NodeKindDefinition`, which are\n * fancy-flow concepts, so putting it there would couple the runtime to the\n * editor and force a non-flow consumer to install one to make an HTTP call.\n *\n * It lived as vendored source in the node marketplace until 0.48.0, which was\n * fine for a node (a copy costs a consumer no dependency) and impossible for a\n * PACKAGE — a package cannot import vendored sandbox source, so every generated\n * `<provider>-ui` package carried its own copy of a file only this repo can\n * correctly change. At the scale the connector estate is heading for that is\n * the exact \"a copy cannot be upgraded\" problem the packages exist to solve.\n *\n * ## The model, and where it came from\n *\n * IFTTT's authoring UX is the one non-engineers actually complete, and three of\n * its four ideas map onto fancy-flow without distortion:\n *\n * 1. **Service first, then capability.** You pick Stripe, *then* pick what about\n * Stripe. Not a flat wall of four hundred nodes. Here that lives in the\n * registry — `connector.service` groups a provider's nodes, and the listing\n * tools narrow in two steps.\n * 2. **The connection is a thing, not a field.** You authorize a service once\n * and every applet reuses it. Here: `connection.ts` in the runtime, and the\n * single `connection` config field below.\n * 3. **Ingredients.** A trigger's output fields become named tokens you drop\n * into a downstream action's fields. fancy-flow already has the machinery —\n * `outputShape` on the kind, `availableVariables()` reading it off direct\n * predecessors — so a connector's job is simply to DECLARE its shape. See\n * `ingredients.ts`.\n * 4. **A sentence, not a form dump.** `summarize()` below.\n *\n * ## Where it does NOT map, and what we did instead\n *\n * An IFTTT applet is one trigger and one action, so \"the ingredients\" is\n * unambiguous: there is only one upstream. A fancy-flow graph branches, fans\n * out, merges and nests, so a connector node several hops downstream has no\n * single trigger to draw from — `availableVariables()` deliberately offers only\n * DIRECT predecessors, because a grandparent's field resolves to `null` at run\n * time and a suggestion that silently produces nothing is worse than no\n * suggestion.\n *\n * We did not paper over that. Connector triggers publish their event as a\n * single object with a declared shape, so the natural thing an author does —\n * carry it forward on the wire — keeps the ingredients available at each step.\n * Reaching further up the graph is a fancy-flow concern, recorded as a finding\n * rather than reimplemented per node.\n */\n\nimport type { ConfigField, NodeKindDefinition } from \"./registry/types\";\nimport type { OutputField } from \"./expressions/variables\";\n\n// Re-exported so a connector package needs ONE import rather than two entries.\nexport type { OutputField };\n\n/** The domains the catalogue is grouped by. Mirrors the plan's taxonomy. */\nexport type ConnectorDomain =\n | \"payments\"\n | \"commerce\"\n | \"messaging\"\n | \"email\"\n | \"crm\"\n | \"support\"\n | \"storage\"\n | \"calendar\"\n | \"productivity\"\n | \"database\"\n | \"devtools\"\n | \"analytics\"\n | \"marketing\"\n | \"ai\"\n | \"forms\"\n | \"hr\"\n | \"geo\";\n\n/** What a connector node does in the graph — IFTTT's \"this\" versus \"that\". */\nexport type ConnectorRole = \"trigger\" | \"action\" | \"search\";\n\n/**\n * How the provider exposes a test estate. Kept in step with the runtime's\n * `SandboxKind`, and `vendoring.test.ts` compares the two declarations — a\n * hand-maintained mirror with nothing checking it is the shape this repository\n * keeps finding.\n *\n * Two of these are answers people skip. `unverified` means NOBODY HAS CHECKED —\n * a real state, and the right one until somebody has. `restricted-reach` is the\n * dangerous one: same credentials, same endpoints, same estate, and only the\n * AUDIENCE restricted, so it looks exactly like a successful post nobody can\n * see. Neither is `none`, and neither can be selected as a mode.\n */\nexport type SandboxKind =\n | \"credential\"\n | \"base-url\"\n | \"separate-account\"\n | \"restricted-reach\"\n | \"none\"\n | \"unverified\";\n\n/** The kinds a `sandbox` mode can actually point at. Mirrors `sandboxIsSelectable`. */\nconst SELECTABLE_SANDBOX: readonly SandboxKind[] = [\"credential\", \"base-url\", \"separate-account\"];\n\nexport type ConnectorMeta = {\n service: string;\n serviceTitle: string;\n domain: ConnectorDomain;\n role: ConnectorRole;\n /** The provider's name for this operation, so search finds what people type. */\n operation: string;\n sandbox: SandboxKind;\n /** Link to the provider documentation this node was written against. */\n docs?: string;\n};\n\n/**\n * The two fields EVERY connector node carries, in the same order, with the same\n * keys.\n *\n * Uniformity is the feature. An agent that has configured one connector has\n * configured all of them, and a human who has learned where the sandbox switch\n * lives never has to look for it again.\n */\nexport function connectionFields(meta: ConnectorMeta): ConfigField[] {\n const modes: Array<{ value: string; label: string }> = [\n { value: \"auto\", label: \"Auto — sandbox locally, live in production\" },\n { value: \"fake\", label: \"Fake — no credentials, no network\" },\n ];\n\n // Only offer sandbox where one exists. A select listing a mode the provider\n // does not have is an invitation to pick it and then read an error.\n if (SELECTABLE_SANDBOX.includes(meta.sandbox)) {\n modes.push({ value: \"sandbox\", label: sandboxLabel(meta.sandbox) });\n }\n modes.push({ value: \"live\", label: \"Live — the real account\" });\n\n return [\n {\n type: \"credential\",\n key: \"connection\",\n label: `${meta.serviceTitle} connection`,\n credentialType: `connector:${meta.service}`,\n description:\n `Which configured ${meta.serviceTitle} connection to use. Credentials live in the host's ` +\n \"configuration, never in the workflow — a graph is exported, committed and handed to agents.\",\n },\n {\n type: \"select\",\n key: \"mode\",\n label: \"Environment\",\n options: modes,\n default: \"auto\",\n description: sandboxNote(meta),\n },\n ];\n}\n\nfunction sandboxLabel(kind: SandboxKind): string {\n return kind === \"base-url\"\n ? \"Sandbox — the provider's separate test host\"\n : kind === \"separate-account\"\n ? \"Sandbox — your separate test account\"\n : \"Sandbox — the provider's test estate\";\n}\n\n/**\n * What \"auto\" means for this provider, said where the author is choosing.\n *\n * The two non-selectable kinds get their own sentence rather than sharing\n * `none`'s. `restricted-reach` in particular has to be said HERE — on the field\n * somebody is filling in — because the failure it produces is a run that looks\n * completely successful and reached nobody, and by the time that is visible the\n * author has stopped looking at this screen.\n */\nfunction sandboxNote(meta: ConnectorMeta): string {\n if (meta.sandbox === \"restricted-reach\") {\n return (\n `${meta.serviceTitle} has no separate test estate — an unreviewed app posts only to its own developers, ` +\n \"or privately. Same credentials, same endpoints: only the audience changes, and nothing here selects it. \" +\n \"A restricted run looks exactly like a successful one, so confirm reach on the provider's own surface.\"\n );\n }\n\n if (meta.sandbox === \"unverified\") {\n return (\n `Nobody has verified what test estate ${meta.serviceTitle} offers, so \"sandbox\" is not offered — and ` +\n '\"auto\" means fake locally and live in production. Find out before pointing a workflow at it.'\n );\n }\n\n if (meta.sandbox === \"none\") {\n return `${meta.serviceTitle} has no sandbox estate, so \"auto\" means fake locally and live in production.`;\n }\n\n return \"Auto follows the environment. Setting this explicitly overrides it everywhere, including in production.\";\n}\n\n/**\n * Build a connector node's authoring surface.\n *\n * Prepends the shared connection fields, applies the domain accent, and stamps\n * the metadata the registry reads. A connector that hand-rolled these would\n * drift from its siblings in exactly the small ways that make a catalogue feel\n * like a pile.\n */\nexport function defineConnectorKind(\n meta: ConnectorMeta,\n kind: Omit<NodeKindDefinition, \"category\"> & { category?: NodeKindDefinition[\"category\"] },\n): NodeKindDefinition & { connector: ConnectorMeta } {\n return {\n ...kind,\n // `category` stays fancy-flow's own taxonomy — it describes what the node\n // does to the GRAPH, which is what the palette groups by. Connector-ness is\n // a separate axis and is carried separately; overloading one field with two\n // meanings would make \"show me the triggers\" and \"hide the connectors\"\n // impossible to ask at the same time.\n category: kind.category ?? (meta.role === \"trigger\" ? \"trigger\" : \"io\"),\n accent: kind.accent ?? DOMAIN_ACCENT[meta.domain],\n configSchema: [...connectionFields(meta), ...(kind.configSchema ?? [])],\n connector: meta,\n };\n}\n\n/**\n * One line describing what a configured node will do, in IFTTT's register.\n *\n * A configured connector should read as a sentence, not as a form dump — it is\n * what makes a canvas skimmable, and it is also what an agent quotes back to a\n * human when asking whether to proceed.\n */\nexport function summarize(meta: ConnectorMeta, config: Record<string, unknown>, detail?: string): string {\n const mode = typeof config.mode === \"string\" ? config.mode : \"auto\";\n const where =\n mode === \"fake\"\n ? \" (faked — nothing leaves this machine)\"\n : mode === \"sandbox\"\n ? \" in the sandbox\"\n : mode === \"live\"\n ? \" on the live account\"\n : \"\";\n\n const what = detail?.trim() ? detail.trim() : meta.operation.replace(/_/g, \" \");\n\n return meta.role === \"trigger\"\n ? `When ${meta.serviceTitle} reports ${what}${where}`\n : `Then ${meta.serviceTitle} will ${what}${where}`;\n}\n\n/** Declare a trigger's event fields — the \"ingredients\" downstream nodes pick from. */\nexport function ingredients(fields: OutputField[]): OutputField[] {\n return fields;\n}\n\nconst DOMAIN_ACCENT: Record<ConnectorDomain, string> = {\n payments: \"#635bff\",\n commerce: \"#96bf48\",\n messaging: \"#4a154b\",\n email: \"#0f9d58\",\n crm: \"#00a1e0\",\n support: \"#03363d\",\n storage: \"#ff9900\",\n calendar: \"#4285f4\",\n productivity: \"#2f3437\",\n database: \"#336791\",\n devtools: \"#24292f\",\n analytics: \"#f9a03c\",\n marketing: \"#1877f2\",\n ai: \"#d97757\",\n forms: \"#262627\",\n hr: \"#5c4ee5\",\n geo: \"#34a853\",\n};\n"]}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { C as ConfigField, N as NodeKindDefinition, O as OutputField } from './types-BO8TgI2M.cjs';
|
|
2
|
+
import 'react';
|
|
3
|
+
import './types-Jx1TwehV.cjs';
|
|
4
|
+
import '@xyflow/react';
|
|
5
|
+
import './pause-9iT4tCEV.cjs';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The authoring surface every connector node shares.
|
|
9
|
+
*
|
|
10
|
+
* ## Why this is in fancy-flow and not in fancy-connector-core
|
|
11
|
+
*
|
|
12
|
+
* `fancy-connector-core` owns the WIRE — delivery, classification, retries,
|
|
13
|
+
* estate selection — and deliberately knows nothing about flow. This module
|
|
14
|
+
* builds a node's `ConfigField[]` and its `NodeKindDefinition`, which are
|
|
15
|
+
* fancy-flow concepts, so putting it there would couple the runtime to the
|
|
16
|
+
* editor and force a non-flow consumer to install one to make an HTTP call.
|
|
17
|
+
*
|
|
18
|
+
* It lived as vendored source in the node marketplace until 0.48.0, which was
|
|
19
|
+
* fine for a node (a copy costs a consumer no dependency) and impossible for a
|
|
20
|
+
* PACKAGE — a package cannot import vendored sandbox source, so every generated
|
|
21
|
+
* `<provider>-ui` package carried its own copy of a file only this repo can
|
|
22
|
+
* correctly change. At the scale the connector estate is heading for that is
|
|
23
|
+
* the exact "a copy cannot be upgraded" problem the packages exist to solve.
|
|
24
|
+
*
|
|
25
|
+
* ## The model, and where it came from
|
|
26
|
+
*
|
|
27
|
+
* IFTTT's authoring UX is the one non-engineers actually complete, and three of
|
|
28
|
+
* its four ideas map onto fancy-flow without distortion:
|
|
29
|
+
*
|
|
30
|
+
* 1. **Service first, then capability.** You pick Stripe, *then* pick what about
|
|
31
|
+
* Stripe. Not a flat wall of four hundred nodes. Here that lives in the
|
|
32
|
+
* registry — `connector.service` groups a provider's nodes, and the listing
|
|
33
|
+
* tools narrow in two steps.
|
|
34
|
+
* 2. **The connection is a thing, not a field.** You authorize a service once
|
|
35
|
+
* and every applet reuses it. Here: `connection.ts` in the runtime, and the
|
|
36
|
+
* single `connection` config field below.
|
|
37
|
+
* 3. **Ingredients.** A trigger's output fields become named tokens you drop
|
|
38
|
+
* into a downstream action's fields. fancy-flow already has the machinery —
|
|
39
|
+
* `outputShape` on the kind, `availableVariables()` reading it off direct
|
|
40
|
+
* predecessors — so a connector's job is simply to DECLARE its shape. See
|
|
41
|
+
* `ingredients.ts`.
|
|
42
|
+
* 4. **A sentence, not a form dump.** `summarize()` below.
|
|
43
|
+
*
|
|
44
|
+
* ## Where it does NOT map, and what we did instead
|
|
45
|
+
*
|
|
46
|
+
* An IFTTT applet is one trigger and one action, so "the ingredients" is
|
|
47
|
+
* unambiguous: there is only one upstream. A fancy-flow graph branches, fans
|
|
48
|
+
* out, merges and nests, so a connector node several hops downstream has no
|
|
49
|
+
* single trigger to draw from — `availableVariables()` deliberately offers only
|
|
50
|
+
* DIRECT predecessors, because a grandparent's field resolves to `null` at run
|
|
51
|
+
* time and a suggestion that silently produces nothing is worse than no
|
|
52
|
+
* suggestion.
|
|
53
|
+
*
|
|
54
|
+
* We did not paper over that. Connector triggers publish their event as a
|
|
55
|
+
* single object with a declared shape, so the natural thing an author does —
|
|
56
|
+
* carry it forward on the wire — keeps the ingredients available at each step.
|
|
57
|
+
* Reaching further up the graph is a fancy-flow concern, recorded as a finding
|
|
58
|
+
* rather than reimplemented per node.
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
/** The domains the catalogue is grouped by. Mirrors the plan's taxonomy. */
|
|
62
|
+
type ConnectorDomain = "payments" | "commerce" | "messaging" | "email" | "crm" | "support" | "storage" | "calendar" | "productivity" | "database" | "devtools" | "analytics" | "marketing" | "ai" | "forms" | "hr" | "geo";
|
|
63
|
+
/** What a connector node does in the graph — IFTTT's "this" versus "that". */
|
|
64
|
+
type ConnectorRole = "trigger" | "action" | "search";
|
|
65
|
+
/**
|
|
66
|
+
* How the provider exposes a test estate. Kept in step with the runtime's
|
|
67
|
+
* `SandboxKind`, and `vendoring.test.ts` compares the two declarations — a
|
|
68
|
+
* hand-maintained mirror with nothing checking it is the shape this repository
|
|
69
|
+
* keeps finding.
|
|
70
|
+
*
|
|
71
|
+
* Two of these are answers people skip. `unverified` means NOBODY HAS CHECKED —
|
|
72
|
+
* a real state, and the right one until somebody has. `restricted-reach` is the
|
|
73
|
+
* dangerous one: same credentials, same endpoints, same estate, and only the
|
|
74
|
+
* AUDIENCE restricted, so it looks exactly like a successful post nobody can
|
|
75
|
+
* see. Neither is `none`, and neither can be selected as a mode.
|
|
76
|
+
*/
|
|
77
|
+
type SandboxKind = "credential" | "base-url" | "separate-account" | "restricted-reach" | "none" | "unverified";
|
|
78
|
+
type ConnectorMeta = {
|
|
79
|
+
service: string;
|
|
80
|
+
serviceTitle: string;
|
|
81
|
+
domain: ConnectorDomain;
|
|
82
|
+
role: ConnectorRole;
|
|
83
|
+
/** The provider's name for this operation, so search finds what people type. */
|
|
84
|
+
operation: string;
|
|
85
|
+
sandbox: SandboxKind;
|
|
86
|
+
/** Link to the provider documentation this node was written against. */
|
|
87
|
+
docs?: string;
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* The two fields EVERY connector node carries, in the same order, with the same
|
|
91
|
+
* keys.
|
|
92
|
+
*
|
|
93
|
+
* Uniformity is the feature. An agent that has configured one connector has
|
|
94
|
+
* configured all of them, and a human who has learned where the sandbox switch
|
|
95
|
+
* lives never has to look for it again.
|
|
96
|
+
*/
|
|
97
|
+
declare function connectionFields(meta: ConnectorMeta): ConfigField[];
|
|
98
|
+
/**
|
|
99
|
+
* Build a connector node's authoring surface.
|
|
100
|
+
*
|
|
101
|
+
* Prepends the shared connection fields, applies the domain accent, and stamps
|
|
102
|
+
* the metadata the registry reads. A connector that hand-rolled these would
|
|
103
|
+
* drift from its siblings in exactly the small ways that make a catalogue feel
|
|
104
|
+
* like a pile.
|
|
105
|
+
*/
|
|
106
|
+
declare function defineConnectorKind(meta: ConnectorMeta, kind: Omit<NodeKindDefinition, "category"> & {
|
|
107
|
+
category?: NodeKindDefinition["category"];
|
|
108
|
+
}): NodeKindDefinition & {
|
|
109
|
+
connector: ConnectorMeta;
|
|
110
|
+
};
|
|
111
|
+
/**
|
|
112
|
+
* One line describing what a configured node will do, in IFTTT's register.
|
|
113
|
+
*
|
|
114
|
+
* A configured connector should read as a sentence, not as a form dump — it is
|
|
115
|
+
* what makes a canvas skimmable, and it is also what an agent quotes back to a
|
|
116
|
+
* human when asking whether to proceed.
|
|
117
|
+
*/
|
|
118
|
+
declare function summarize(meta: ConnectorMeta, config: Record<string, unknown>, detail?: string): string;
|
|
119
|
+
/** Declare a trigger's event fields — the "ingredients" downstream nodes pick from. */
|
|
120
|
+
declare function ingredients(fields: OutputField[]): OutputField[];
|
|
121
|
+
|
|
122
|
+
export { type ConnectorDomain, type ConnectorMeta, type ConnectorRole, OutputField, type SandboxKind, connectionFields, defineConnectorKind, ingredients, summarize };
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { C as ConfigField, N as NodeKindDefinition, O as OutputField } from './types-DJAmfiJC.js';
|
|
2
|
+
import 'react';
|
|
3
|
+
import './types-Jx1TwehV.js';
|
|
4
|
+
import '@xyflow/react';
|
|
5
|
+
import './pause-9iT4tCEV.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The authoring surface every connector node shares.
|
|
9
|
+
*
|
|
10
|
+
* ## Why this is in fancy-flow and not in fancy-connector-core
|
|
11
|
+
*
|
|
12
|
+
* `fancy-connector-core` owns the WIRE — delivery, classification, retries,
|
|
13
|
+
* estate selection — and deliberately knows nothing about flow. This module
|
|
14
|
+
* builds a node's `ConfigField[]` and its `NodeKindDefinition`, which are
|
|
15
|
+
* fancy-flow concepts, so putting it there would couple the runtime to the
|
|
16
|
+
* editor and force a non-flow consumer to install one to make an HTTP call.
|
|
17
|
+
*
|
|
18
|
+
* It lived as vendored source in the node marketplace until 0.48.0, which was
|
|
19
|
+
* fine for a node (a copy costs a consumer no dependency) and impossible for a
|
|
20
|
+
* PACKAGE — a package cannot import vendored sandbox source, so every generated
|
|
21
|
+
* `<provider>-ui` package carried its own copy of a file only this repo can
|
|
22
|
+
* correctly change. At the scale the connector estate is heading for that is
|
|
23
|
+
* the exact "a copy cannot be upgraded" problem the packages exist to solve.
|
|
24
|
+
*
|
|
25
|
+
* ## The model, and where it came from
|
|
26
|
+
*
|
|
27
|
+
* IFTTT's authoring UX is the one non-engineers actually complete, and three of
|
|
28
|
+
* its four ideas map onto fancy-flow without distortion:
|
|
29
|
+
*
|
|
30
|
+
* 1. **Service first, then capability.** You pick Stripe, *then* pick what about
|
|
31
|
+
* Stripe. Not a flat wall of four hundred nodes. Here that lives in the
|
|
32
|
+
* registry — `connector.service` groups a provider's nodes, and the listing
|
|
33
|
+
* tools narrow in two steps.
|
|
34
|
+
* 2. **The connection is a thing, not a field.** You authorize a service once
|
|
35
|
+
* and every applet reuses it. Here: `connection.ts` in the runtime, and the
|
|
36
|
+
* single `connection` config field below.
|
|
37
|
+
* 3. **Ingredients.** A trigger's output fields become named tokens you drop
|
|
38
|
+
* into a downstream action's fields. fancy-flow already has the machinery —
|
|
39
|
+
* `outputShape` on the kind, `availableVariables()` reading it off direct
|
|
40
|
+
* predecessors — so a connector's job is simply to DECLARE its shape. See
|
|
41
|
+
* `ingredients.ts`.
|
|
42
|
+
* 4. **A sentence, not a form dump.** `summarize()` below.
|
|
43
|
+
*
|
|
44
|
+
* ## Where it does NOT map, and what we did instead
|
|
45
|
+
*
|
|
46
|
+
* An IFTTT applet is one trigger and one action, so "the ingredients" is
|
|
47
|
+
* unambiguous: there is only one upstream. A fancy-flow graph branches, fans
|
|
48
|
+
* out, merges and nests, so a connector node several hops downstream has no
|
|
49
|
+
* single trigger to draw from — `availableVariables()` deliberately offers only
|
|
50
|
+
* DIRECT predecessors, because a grandparent's field resolves to `null` at run
|
|
51
|
+
* time and a suggestion that silently produces nothing is worse than no
|
|
52
|
+
* suggestion.
|
|
53
|
+
*
|
|
54
|
+
* We did not paper over that. Connector triggers publish their event as a
|
|
55
|
+
* single object with a declared shape, so the natural thing an author does —
|
|
56
|
+
* carry it forward on the wire — keeps the ingredients available at each step.
|
|
57
|
+
* Reaching further up the graph is a fancy-flow concern, recorded as a finding
|
|
58
|
+
* rather than reimplemented per node.
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
/** The domains the catalogue is grouped by. Mirrors the plan's taxonomy. */
|
|
62
|
+
type ConnectorDomain = "payments" | "commerce" | "messaging" | "email" | "crm" | "support" | "storage" | "calendar" | "productivity" | "database" | "devtools" | "analytics" | "marketing" | "ai" | "forms" | "hr" | "geo";
|
|
63
|
+
/** What a connector node does in the graph — IFTTT's "this" versus "that". */
|
|
64
|
+
type ConnectorRole = "trigger" | "action" | "search";
|
|
65
|
+
/**
|
|
66
|
+
* How the provider exposes a test estate. Kept in step with the runtime's
|
|
67
|
+
* `SandboxKind`, and `vendoring.test.ts` compares the two declarations — a
|
|
68
|
+
* hand-maintained mirror with nothing checking it is the shape this repository
|
|
69
|
+
* keeps finding.
|
|
70
|
+
*
|
|
71
|
+
* Two of these are answers people skip. `unverified` means NOBODY HAS CHECKED —
|
|
72
|
+
* a real state, and the right one until somebody has. `restricted-reach` is the
|
|
73
|
+
* dangerous one: same credentials, same endpoints, same estate, and only the
|
|
74
|
+
* AUDIENCE restricted, so it looks exactly like a successful post nobody can
|
|
75
|
+
* see. Neither is `none`, and neither can be selected as a mode.
|
|
76
|
+
*/
|
|
77
|
+
type SandboxKind = "credential" | "base-url" | "separate-account" | "restricted-reach" | "none" | "unverified";
|
|
78
|
+
type ConnectorMeta = {
|
|
79
|
+
service: string;
|
|
80
|
+
serviceTitle: string;
|
|
81
|
+
domain: ConnectorDomain;
|
|
82
|
+
role: ConnectorRole;
|
|
83
|
+
/** The provider's name for this operation, so search finds what people type. */
|
|
84
|
+
operation: string;
|
|
85
|
+
sandbox: SandboxKind;
|
|
86
|
+
/** Link to the provider documentation this node was written against. */
|
|
87
|
+
docs?: string;
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* The two fields EVERY connector node carries, in the same order, with the same
|
|
91
|
+
* keys.
|
|
92
|
+
*
|
|
93
|
+
* Uniformity is the feature. An agent that has configured one connector has
|
|
94
|
+
* configured all of them, and a human who has learned where the sandbox switch
|
|
95
|
+
* lives never has to look for it again.
|
|
96
|
+
*/
|
|
97
|
+
declare function connectionFields(meta: ConnectorMeta): ConfigField[];
|
|
98
|
+
/**
|
|
99
|
+
* Build a connector node's authoring surface.
|
|
100
|
+
*
|
|
101
|
+
* Prepends the shared connection fields, applies the domain accent, and stamps
|
|
102
|
+
* the metadata the registry reads. A connector that hand-rolled these would
|
|
103
|
+
* drift from its siblings in exactly the small ways that make a catalogue feel
|
|
104
|
+
* like a pile.
|
|
105
|
+
*/
|
|
106
|
+
declare function defineConnectorKind(meta: ConnectorMeta, kind: Omit<NodeKindDefinition, "category"> & {
|
|
107
|
+
category?: NodeKindDefinition["category"];
|
|
108
|
+
}): NodeKindDefinition & {
|
|
109
|
+
connector: ConnectorMeta;
|
|
110
|
+
};
|
|
111
|
+
/**
|
|
112
|
+
* One line describing what a configured node will do, in IFTTT's register.
|
|
113
|
+
*
|
|
114
|
+
* A configured connector should read as a sentence, not as a form dump — it is
|
|
115
|
+
* what makes a canvas skimmable, and it is also what an agent quotes back to a
|
|
116
|
+
* human when asking whether to proceed.
|
|
117
|
+
*/
|
|
118
|
+
declare function summarize(meta: ConnectorMeta, config: Record<string, unknown>, detail?: string): string;
|
|
119
|
+
/** Declare a trigger's event fields — the "ingredients" downstream nodes pick from. */
|
|
120
|
+
declare function ingredients(fields: OutputField[]): OutputField[];
|
|
121
|
+
|
|
122
|
+
export { type ConnectorDomain, type ConnectorMeta, type ConnectorRole, OutputField, type SandboxKind, connectionFields, defineConnectorKind, ingredients, summarize };
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// src/connectors.ts
|
|
2
|
+
var SELECTABLE_SANDBOX = ["credential", "base-url", "separate-account"];
|
|
3
|
+
function connectionFields(meta) {
|
|
4
|
+
const modes = [
|
|
5
|
+
{ value: "auto", label: "Auto \u2014 sandbox locally, live in production" },
|
|
6
|
+
{ value: "fake", label: "Fake \u2014 no credentials, no network" }
|
|
7
|
+
];
|
|
8
|
+
if (SELECTABLE_SANDBOX.includes(meta.sandbox)) {
|
|
9
|
+
modes.push({ value: "sandbox", label: sandboxLabel(meta.sandbox) });
|
|
10
|
+
}
|
|
11
|
+
modes.push({ value: "live", label: "Live \u2014 the real account" });
|
|
12
|
+
return [
|
|
13
|
+
{
|
|
14
|
+
type: "credential",
|
|
15
|
+
key: "connection",
|
|
16
|
+
label: `${meta.serviceTitle} connection`,
|
|
17
|
+
credentialType: `connector:${meta.service}`,
|
|
18
|
+
description: `Which configured ${meta.serviceTitle} connection to use. Credentials live in the host's configuration, never in the workflow \u2014 a graph is exported, committed and handed to agents.`
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
type: "select",
|
|
22
|
+
key: "mode",
|
|
23
|
+
label: "Environment",
|
|
24
|
+
options: modes,
|
|
25
|
+
default: "auto",
|
|
26
|
+
description: sandboxNote(meta)
|
|
27
|
+
}
|
|
28
|
+
];
|
|
29
|
+
}
|
|
30
|
+
function sandboxLabel(kind) {
|
|
31
|
+
return kind === "base-url" ? "Sandbox \u2014 the provider's separate test host" : kind === "separate-account" ? "Sandbox \u2014 your separate test account" : "Sandbox \u2014 the provider's test estate";
|
|
32
|
+
}
|
|
33
|
+
function sandboxNote(meta) {
|
|
34
|
+
if (meta.sandbox === "restricted-reach") {
|
|
35
|
+
return `${meta.serviceTitle} has no separate test estate \u2014 an unreviewed app posts only to its own developers, or privately. Same credentials, same endpoints: only the audience changes, and nothing here selects it. A restricted run looks exactly like a successful one, so confirm reach on the provider's own surface.`;
|
|
36
|
+
}
|
|
37
|
+
if (meta.sandbox === "unverified") {
|
|
38
|
+
return `Nobody has verified what test estate ${meta.serviceTitle} offers, so "sandbox" is not offered \u2014 and "auto" means fake locally and live in production. Find out before pointing a workflow at it.`;
|
|
39
|
+
}
|
|
40
|
+
if (meta.sandbox === "none") {
|
|
41
|
+
return `${meta.serviceTitle} has no sandbox estate, so "auto" means fake locally and live in production.`;
|
|
42
|
+
}
|
|
43
|
+
return "Auto follows the environment. Setting this explicitly overrides it everywhere, including in production.";
|
|
44
|
+
}
|
|
45
|
+
function defineConnectorKind(meta, kind) {
|
|
46
|
+
return {
|
|
47
|
+
...kind,
|
|
48
|
+
// `category` stays fancy-flow's own taxonomy — it describes what the node
|
|
49
|
+
// does to the GRAPH, which is what the palette groups by. Connector-ness is
|
|
50
|
+
// a separate axis and is carried separately; overloading one field with two
|
|
51
|
+
// meanings would make "show me the triggers" and "hide the connectors"
|
|
52
|
+
// impossible to ask at the same time.
|
|
53
|
+
category: kind.category ?? (meta.role === "trigger" ? "trigger" : "io"),
|
|
54
|
+
accent: kind.accent ?? DOMAIN_ACCENT[meta.domain],
|
|
55
|
+
configSchema: [...connectionFields(meta), ...kind.configSchema ?? []],
|
|
56
|
+
connector: meta
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function summarize(meta, config, detail) {
|
|
60
|
+
const mode = typeof config.mode === "string" ? config.mode : "auto";
|
|
61
|
+
const where = mode === "fake" ? " (faked \u2014 nothing leaves this machine)" : mode === "sandbox" ? " in the sandbox" : mode === "live" ? " on the live account" : "";
|
|
62
|
+
const what = detail?.trim() ? detail.trim() : meta.operation.replace(/_/g, " ");
|
|
63
|
+
return meta.role === "trigger" ? `When ${meta.serviceTitle} reports ${what}${where}` : `Then ${meta.serviceTitle} will ${what}${where}`;
|
|
64
|
+
}
|
|
65
|
+
function ingredients(fields) {
|
|
66
|
+
return fields;
|
|
67
|
+
}
|
|
68
|
+
var DOMAIN_ACCENT = {
|
|
69
|
+
payments: "#635bff",
|
|
70
|
+
commerce: "#96bf48",
|
|
71
|
+
messaging: "#4a154b",
|
|
72
|
+
email: "#0f9d58",
|
|
73
|
+
crm: "#00a1e0",
|
|
74
|
+
support: "#03363d",
|
|
75
|
+
storage: "#ff9900",
|
|
76
|
+
calendar: "#4285f4",
|
|
77
|
+
productivity: "#2f3437",
|
|
78
|
+
database: "#336791",
|
|
79
|
+
devtools: "#24292f",
|
|
80
|
+
analytics: "#f9a03c",
|
|
81
|
+
marketing: "#1877f2",
|
|
82
|
+
ai: "#d97757",
|
|
83
|
+
forms: "#262627",
|
|
84
|
+
hr: "#5c4ee5",
|
|
85
|
+
geo: "#34a853"
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export { connectionFields, defineConnectorKind, ingredients, summarize };
|
|
89
|
+
//# sourceMappingURL=connectors.js.map
|
|
90
|
+
//# sourceMappingURL=connectors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/connectors.ts"],"names":[],"mappings":"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","file":"connectors.js","sourcesContent":["/**\n * The authoring surface every connector node shares.\n *\n * ## Why this is in fancy-flow and not in fancy-connector-core\n *\n * `fancy-connector-core` owns the WIRE — delivery, classification, retries,\n * estate selection — and deliberately knows nothing about flow. This module\n * builds a node's `ConfigField[]` and its `NodeKindDefinition`, which are\n * fancy-flow concepts, so putting it there would couple the runtime to the\n * editor and force a non-flow consumer to install one to make an HTTP call.\n *\n * It lived as vendored source in the node marketplace until 0.48.0, which was\n * fine for a node (a copy costs a consumer no dependency) and impossible for a\n * PACKAGE — a package cannot import vendored sandbox source, so every generated\n * `<provider>-ui` package carried its own copy of a file only this repo can\n * correctly change. At the scale the connector estate is heading for that is\n * the exact \"a copy cannot be upgraded\" problem the packages exist to solve.\n *\n * ## The model, and where it came from\n *\n * IFTTT's authoring UX is the one non-engineers actually complete, and three of\n * its four ideas map onto fancy-flow without distortion:\n *\n * 1. **Service first, then capability.** You pick Stripe, *then* pick what about\n * Stripe. Not a flat wall of four hundred nodes. Here that lives in the\n * registry — `connector.service` groups a provider's nodes, and the listing\n * tools narrow in two steps.\n * 2. **The connection is a thing, not a field.** You authorize a service once\n * and every applet reuses it. Here: `connection.ts` in the runtime, and the\n * single `connection` config field below.\n * 3. **Ingredients.** A trigger's output fields become named tokens you drop\n * into a downstream action's fields. fancy-flow already has the machinery —\n * `outputShape` on the kind, `availableVariables()` reading it off direct\n * predecessors — so a connector's job is simply to DECLARE its shape. See\n * `ingredients.ts`.\n * 4. **A sentence, not a form dump.** `summarize()` below.\n *\n * ## Where it does NOT map, and what we did instead\n *\n * An IFTTT applet is one trigger and one action, so \"the ingredients\" is\n * unambiguous: there is only one upstream. A fancy-flow graph branches, fans\n * out, merges and nests, so a connector node several hops downstream has no\n * single trigger to draw from — `availableVariables()` deliberately offers only\n * DIRECT predecessors, because a grandparent's field resolves to `null` at run\n * time and a suggestion that silently produces nothing is worse than no\n * suggestion.\n *\n * We did not paper over that. Connector triggers publish their event as a\n * single object with a declared shape, so the natural thing an author does —\n * carry it forward on the wire — keeps the ingredients available at each step.\n * Reaching further up the graph is a fancy-flow concern, recorded as a finding\n * rather than reimplemented per node.\n */\n\nimport type { ConfigField, NodeKindDefinition } from \"./registry/types\";\nimport type { OutputField } from \"./expressions/variables\";\n\n// Re-exported so a connector package needs ONE import rather than two entries.\nexport type { OutputField };\n\n/** The domains the catalogue is grouped by. Mirrors the plan's taxonomy. */\nexport type ConnectorDomain =\n | \"payments\"\n | \"commerce\"\n | \"messaging\"\n | \"email\"\n | \"crm\"\n | \"support\"\n | \"storage\"\n | \"calendar\"\n | \"productivity\"\n | \"database\"\n | \"devtools\"\n | \"analytics\"\n | \"marketing\"\n | \"ai\"\n | \"forms\"\n | \"hr\"\n | \"geo\";\n\n/** What a connector node does in the graph — IFTTT's \"this\" versus \"that\". */\nexport type ConnectorRole = \"trigger\" | \"action\" | \"search\";\n\n/**\n * How the provider exposes a test estate. Kept in step with the runtime's\n * `SandboxKind`, and `vendoring.test.ts` compares the two declarations — a\n * hand-maintained mirror with nothing checking it is the shape this repository\n * keeps finding.\n *\n * Two of these are answers people skip. `unverified` means NOBODY HAS CHECKED —\n * a real state, and the right one until somebody has. `restricted-reach` is the\n * dangerous one: same credentials, same endpoints, same estate, and only the\n * AUDIENCE restricted, so it looks exactly like a successful post nobody can\n * see. Neither is `none`, and neither can be selected as a mode.\n */\nexport type SandboxKind =\n | \"credential\"\n | \"base-url\"\n | \"separate-account\"\n | \"restricted-reach\"\n | \"none\"\n | \"unverified\";\n\n/** The kinds a `sandbox` mode can actually point at. Mirrors `sandboxIsSelectable`. */\nconst SELECTABLE_SANDBOX: readonly SandboxKind[] = [\"credential\", \"base-url\", \"separate-account\"];\n\nexport type ConnectorMeta = {\n service: string;\n serviceTitle: string;\n domain: ConnectorDomain;\n role: ConnectorRole;\n /** The provider's name for this operation, so search finds what people type. */\n operation: string;\n sandbox: SandboxKind;\n /** Link to the provider documentation this node was written against. */\n docs?: string;\n};\n\n/**\n * The two fields EVERY connector node carries, in the same order, with the same\n * keys.\n *\n * Uniformity is the feature. An agent that has configured one connector has\n * configured all of them, and a human who has learned where the sandbox switch\n * lives never has to look for it again.\n */\nexport function connectionFields(meta: ConnectorMeta): ConfigField[] {\n const modes: Array<{ value: string; label: string }> = [\n { value: \"auto\", label: \"Auto — sandbox locally, live in production\" },\n { value: \"fake\", label: \"Fake — no credentials, no network\" },\n ];\n\n // Only offer sandbox where one exists. A select listing a mode the provider\n // does not have is an invitation to pick it and then read an error.\n if (SELECTABLE_SANDBOX.includes(meta.sandbox)) {\n modes.push({ value: \"sandbox\", label: sandboxLabel(meta.sandbox) });\n }\n modes.push({ value: \"live\", label: \"Live — the real account\" });\n\n return [\n {\n type: \"credential\",\n key: \"connection\",\n label: `${meta.serviceTitle} connection`,\n credentialType: `connector:${meta.service}`,\n description:\n `Which configured ${meta.serviceTitle} connection to use. Credentials live in the host's ` +\n \"configuration, never in the workflow — a graph is exported, committed and handed to agents.\",\n },\n {\n type: \"select\",\n key: \"mode\",\n label: \"Environment\",\n options: modes,\n default: \"auto\",\n description: sandboxNote(meta),\n },\n ];\n}\n\nfunction sandboxLabel(kind: SandboxKind): string {\n return kind === \"base-url\"\n ? \"Sandbox — the provider's separate test host\"\n : kind === \"separate-account\"\n ? \"Sandbox — your separate test account\"\n : \"Sandbox — the provider's test estate\";\n}\n\n/**\n * What \"auto\" means for this provider, said where the author is choosing.\n *\n * The two non-selectable kinds get their own sentence rather than sharing\n * `none`'s. `restricted-reach` in particular has to be said HERE — on the field\n * somebody is filling in — because the failure it produces is a run that looks\n * completely successful and reached nobody, and by the time that is visible the\n * author has stopped looking at this screen.\n */\nfunction sandboxNote(meta: ConnectorMeta): string {\n if (meta.sandbox === \"restricted-reach\") {\n return (\n `${meta.serviceTitle} has no separate test estate — an unreviewed app posts only to its own developers, ` +\n \"or privately. Same credentials, same endpoints: only the audience changes, and nothing here selects it. \" +\n \"A restricted run looks exactly like a successful one, so confirm reach on the provider's own surface.\"\n );\n }\n\n if (meta.sandbox === \"unverified\") {\n return (\n `Nobody has verified what test estate ${meta.serviceTitle} offers, so \"sandbox\" is not offered — and ` +\n '\"auto\" means fake locally and live in production. Find out before pointing a workflow at it.'\n );\n }\n\n if (meta.sandbox === \"none\") {\n return `${meta.serviceTitle} has no sandbox estate, so \"auto\" means fake locally and live in production.`;\n }\n\n return \"Auto follows the environment. Setting this explicitly overrides it everywhere, including in production.\";\n}\n\n/**\n * Build a connector node's authoring surface.\n *\n * Prepends the shared connection fields, applies the domain accent, and stamps\n * the metadata the registry reads. A connector that hand-rolled these would\n * drift from its siblings in exactly the small ways that make a catalogue feel\n * like a pile.\n */\nexport function defineConnectorKind(\n meta: ConnectorMeta,\n kind: Omit<NodeKindDefinition, \"category\"> & { category?: NodeKindDefinition[\"category\"] },\n): NodeKindDefinition & { connector: ConnectorMeta } {\n return {\n ...kind,\n // `category` stays fancy-flow's own taxonomy — it describes what the node\n // does to the GRAPH, which is what the palette groups by. Connector-ness is\n // a separate axis and is carried separately; overloading one field with two\n // meanings would make \"show me the triggers\" and \"hide the connectors\"\n // impossible to ask at the same time.\n category: kind.category ?? (meta.role === \"trigger\" ? \"trigger\" : \"io\"),\n accent: kind.accent ?? DOMAIN_ACCENT[meta.domain],\n configSchema: [...connectionFields(meta), ...(kind.configSchema ?? [])],\n connector: meta,\n };\n}\n\n/**\n * One line describing what a configured node will do, in IFTTT's register.\n *\n * A configured connector should read as a sentence, not as a form dump — it is\n * what makes a canvas skimmable, and it is also what an agent quotes back to a\n * human when asking whether to proceed.\n */\nexport function summarize(meta: ConnectorMeta, config: Record<string, unknown>, detail?: string): string {\n const mode = typeof config.mode === \"string\" ? config.mode : \"auto\";\n const where =\n mode === \"fake\"\n ? \" (faked — nothing leaves this machine)\"\n : mode === \"sandbox\"\n ? \" in the sandbox\"\n : mode === \"live\"\n ? \" on the live account\"\n : \"\";\n\n const what = detail?.trim() ? detail.trim() : meta.operation.replace(/_/g, \" \");\n\n return meta.role === \"trigger\"\n ? `When ${meta.serviceTitle} reports ${what}${where}`\n : `Then ${meta.serviceTitle} will ${what}${where}`;\n}\n\n/** Declare a trigger's event fields — the \"ingredients\" downstream nodes pick from. */\nexport function ingredients(fields: OutputField[]): OutputField[] {\n return fields;\n}\n\nconst DOMAIN_ACCENT: Record<ConnectorDomain, string> = {\n payments: \"#635bff\",\n commerce: \"#96bf48\",\n messaging: \"#4a154b\",\n email: \"#0f9d58\",\n crm: \"#00a1e0\",\n support: \"#03363d\",\n storage: \"#ff9900\",\n calendar: \"#4285f4\",\n productivity: \"#2f3437\",\n database: \"#336791\",\n devtools: \"#24292f\",\n analytics: \"#f9a03c\",\n marketing: \"#1877f2\",\n ai: \"#d97757\",\n forms: \"#262627\",\n hr: \"#5c4ee5\",\n geo: \"#34a853\",\n};\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@particle-academy/fancy-flow",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.48.0",
|
|
4
4
|
"description": "Workflow editor + runner. Six built-in node kits (trigger / action / decision / output / note / subgraph), tokenized theme, topological execution with per-node status. React-flow bundled; consumers npm install fancy-flow and get nothing extra.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -43,6 +43,16 @@
|
|
|
43
43
|
"default": "./dist/registry.cjs"
|
|
44
44
|
}
|
|
45
45
|
},
|
|
46
|
+
"./connectors": {
|
|
47
|
+
"import": {
|
|
48
|
+
"types": "./dist/connectors.d.ts",
|
|
49
|
+
"default": "./dist/connectors.js"
|
|
50
|
+
},
|
|
51
|
+
"require": {
|
|
52
|
+
"types": "./dist/connectors.d.cts",
|
|
53
|
+
"default": "./dist/connectors.cjs"
|
|
54
|
+
}
|
|
55
|
+
},
|
|
46
56
|
"./schema": {
|
|
47
57
|
"import": {
|
|
48
58
|
"types": "./dist/schema/index.d.ts",
|