変換関数

マッピングはリクエストから値を取り出します。一方、トランスフォーム関数はさらに一歩進んだ機能です。これはユーザー自身が記述したJavaScriptであり、Shopify Flowが実行される前のすべてのリクエストに対して実行されます。そのため、ペイロードの形式を変更したり、何かを検索したり、そのリクエストに対してワークフローを実行する価値があるかどうかを判断したり、あるいは呼び出し元に対して直接応答したりすることが可能です。

これは、Webhookの「**Function」**タブにあります。エディタが空の場合は、無効となっています。

関数の形状

The contractjavascript
export default async function transform(payload, ctx) {
  // payload - the parsed request body (JSON, form or XML)
  // return an object -> it becomes the payload for mapping and Flow
  // return null      -> nothing is sent to Flow, and nothing counts
  return { ...payload, source: "warehouse" }
}

ctxが提供する機能

ctx.request method、contentType、リクエスト headers(秘密情報は伏せてあります)、および query
ctx.webhook Webhookのidおよびname
ctx.shop お客様のmyshopifyドメイン
ctx.log(...) テストパネルと履歴で読み取れる行を出力します
await ctx.fetch(url, init) パブリックAPIを呼び出します
await ctx.shopify(query, variables) Admin GraphQL を使用してストアのデータを読み取ります
ctx.storage 実行の合間に小さな値を保持します(以下を参照してください)
ctx.respond(body, options) 電話に出た相手はすぐに応答します

ペイロードの再構成

最も一般的な用途:送信元の構造を、ワークフローで必要とする少数の値に変換することです。

Flatten and normalisejavascript
export default async function transform(payload) {
  const order = payload.data.attributes
  return {
    orderNumber: String(order.number),
    email: order.customer.email.toLowerCase(),
    total: Number(order.total_cents) / 100,
  }
}

不要なリクエストを削除する

null を返すと、リクエストはそこで停止します。つまり、Shopify Flow は実行されず、履歴への記録も行われず、プランの消費も発生しません。呼び出し元には、{ "skipped": true } を含む 200 が返されるため、再試行は行われません。

Only paid orders above 100javascript
export default async function transform(payload, ctx) {
  if (payload.status !== "paid" || payload.total < 100) {
    ctx.log("skipping", payload.id, payload.status, payload.total)
    return null
  }
  return payload
}

走行の合間に物事を覚えておくこと

ctx.storage これは、貴店の専用となる小規模なキーバリューストアです。「これ、以前見たことあるかな?」という確認を行うためのツールです。イベントが発生した際にIDを保存しておき、たとえ数日後であっても、同じIDを含む次のリクエストはスキップします。

お電話ください ~ですか
await ctx.storage.get(key) 保存された値、あるいはnull
await ctx.storage.set(key, value) 任意のJSON値をストアし、上書きします
await ctx.storage.delete(key) それを削除します。存在していた場合は、trueとなります。
await ctx.storage.list({ prefix, limit, cursor }) 各ページごとの主要な名称とサイズ
Skip events you have already seenjavascript
export default async function transform(payload, ctx) {
  const key = `seen:${payload.id}`
  if (await ctx.storage.get(key)) return null
  await ctx.storage.set(key, { at: Date.now() })
  return payload
}

ストアデータを活用した情報充実

ctx.shopify この機能は、ストアに対して読み取り専用のAdmin GraphQLクエリを実行するため、ワークフローは送信者がこれまでアクセスできなかったデータを用いて開始することができます。送信者には、許可された情報のみが表示されます。「Function」タブの「Storeデータへのアクセス」で、必要なスコープを許可してください。デフォルトでは何も許可されておらず、ミューテーションは拒否されます。

Look up a variant by SKUjavascript
export default async function transform(payload, ctx) {
  const data = await ctx.shopify(
    `query($q: String!) {
      productVariants(first: 1, query: $q) { nodes { id title price } }
    }`,
    { q: `sku:${payload.sku}` },
  )
  const variant = data.productVariants.nodes[0]
  if (!variant) return null
  return { sku: payload.sku, variantId: variant.id, price: variant.price }
}

ご自身で電話に出る

ctx.respond() すぐに応答を返します。これにより、Webhookは小さなAPIエンドポイントとして機能するようになります。つまり、何かを検証し、結果を算出し、1回のリクエストで応答を返すのです。返された内容にかかわらず、Shopify Flowは引き続き実行されます。

Reply to the caller and start a workflowjavascript
export default async function transform(payload, ctx) {
  const ok = typeof payload.email === "string" && payload.email.includes("@")
  ctx.respond({ accepted: ok }, { status: ok ? 200 : 422 })
  return ok ? payload : null
}

テスト

[Function] タブの [Test] パネルでは、エディタ内のコード(保存されていない変更も含む)をサンプルペイロードに対して実行します。ここでは、戻り値、関数が送信するレスポンス、生成される Flow フィールド、ログ行、および実行時間が表示されます。 Shopify Flowは起動されず、履歴にも記録されず、プランの使用量にもカウントされません。ただし、ストレージへの呼び出しは実際に行われます。テスト実行では、本番のリクエストと同様に、同じストアからの読み取りおよび書き込みが行われます。

関数が失敗した場合

コードで例外が発生したり、タイムアウトしたり、使用できない結果が返されたりした場合、そのリクエストは「422」として拒否され、その失敗はエラー内容とログ行とともに「History」に記録されます。これにより、何が起きたかを確認できます。コードを修正し、そのエントリを再生してください。再生時には新しいバージョンが実行されます。

制限

  • 1回の実行につき5秒、64MB、そして50,000文字のコードです。
  • npmパッケージは使用できません。ctx.fetchが唯一の解決策ですが、内部アドレスやプライベートアドレスは拒否されます。
  • ctx.shopify 1回の実行につき10回の呼び出しが可能で、クエリのみを対象とし、付与されたスコープに限定されます。
  • Shopify Flowに到達するすべての実行は通常のリクエストとして扱われ、ご利用のプランのカウント対象となります。スキップされたリクエストはカウント対象外となります。