{"version":3,"file":"notify.mjs","names":[],"sources":["../../../../../../../notifications/src/dispatch/notify.ts"],"sourcesContent":["/**\n * `notify` — ad-hoc, per-channel send facade.\n *\n * A Proxy keyed by the channel registry. `notify.<channel>(to, payload,\n * options?)` works for ANY channel name registered in\n * `NotificationChannels` — built-in or custom (via declaration merging).\n *\n * For multi-channel sends, use `defineNotification` — that's the reusable\n * pattern and the only mental model for \"send through several channels at\n * once\" (no inline multi-channel overload here on purpose).\n *\n * `notify.channel(name)` is the runtime escape when the channel name isn't a\n * compile-time literal.\n *\n * @example\n *   await notify.mail(user, { subject: \"Welcome\", html: \"<p>Hi!</p>\" });\n *   await notify.database(user, { type: \"welcome\", title: \"Welcome!\" });\n *   await notify.mail(\"guest@example.com\", { subject: \"…\", html: \"…\" });   // raw target\n *   await notify.channel(dynamicName).send(user, payload);                 // dynamic name\n */\nimport { getNotificationConfig } from \"../config\";\nimport type { ChannelName, Notifiable, NotificationChannels, SendOptions } from \"../types\";\nimport { newDispatchId } from \"./dispatch-id\";\nimport { dispatchChannel } from \"./dispatcher\";\nimport { emit } from \"./notifications-event-bus\";\n\n/**\n * Public shape of `notify`. The mapped portion gives `notify.<channel>` per\n * registered channel; `channel(name)` is the runtime escape.\n */\nexport type Notify = {\n  channel(name: string): {\n    send(\n      to: Notifiable | string,\n      payload: unknown,\n      options?: SendOptions,\n    ): Promise<void>;\n  };\n} & {\n  [C in ChannelName]: (\n    to: Notifiable | string,\n    payload: NotificationChannels[C],\n    options?: SendOptions,\n  ) => Promise<void>;\n};\n\n/**\n * Per-call dispatch for `notify.<channel>`. Mirrors `defineNotification`'s\n * pipeline but without rendering: payload is already concrete.\n *\n * Preferences gate runs ONLY when a notification `type` is known — explicit\n * via `options.type`, or implicit from a database payload's `type` field.\n * Without a type we have nothing to gate on, and ad-hoc sends bypass\n * preferences silently (use `defineNotification` if you want guaranteed\n * gating).\n */\nasync function notifyImpl(\n  channelName: string,\n  to: Notifiable | string,\n  payload: unknown,\n  options: SendOptions = {},\n): Promise<void> {\n  const isRawTarget = typeof to === \"string\";\n  const notifiable: Notifiable | undefined = isRawTarget ? undefined : to;\n\n  // Derive the type: explicit `options.type` wins, then the payload's\n  // `type` field (database channel convention).\n  let notificationType: string | undefined = options.type;\n  if (\n    notificationType === undefined &&\n    payload &&\n    typeof payload === \"object\" &&\n    \"type\" in payload &&\n    typeof (payload as { type: unknown }).type === \"string\"\n  ) {\n    notificationType = (payload as { type: string }).type;\n  }\n\n  // Preferences gate — only when notifiable + type are both available.\n  if (notifiable && notificationType && !options.force) {\n    const config = getNotificationConfig();\n\n    if (config.preferences) {\n      const allowed = await config.preferences.resolveChannels(\n        notifiable,\n        notificationType,\n        [channelName as ChannelName],\n      );\n\n      if (!allowed.includes(channelName as ChannelName)) {\n        await emit(\"skipped\", {\n          dispatchId: newDispatchId(),\n          channel: channelName,\n          notifiable,\n          reason: \"preference\",\n          options,\n        });\n        return;\n      }\n    }\n  }\n\n  await dispatchChannel({\n    channelName,\n    payload,\n    to: notifiable,\n    rawRoute: isRawTarget ? to : undefined,\n    notificationType,\n    mode: \"send\",\n    options,\n  });\n}\n\n/**\n * Build the `notify` proxy. `channel(name)` returns a `{ send }` object;\n * any other property access returns a one-shot dispatcher for that channel.\n */\nexport const notify: Notify = new Proxy({} as Notify, {\n  get(_target, prop) {\n    if (typeof prop === \"symbol\") {\n      return undefined;\n    }\n\n    // Don't masquerade as a thenable — awaiting or Promise-wrapping `notify`\n    // must NOT invoke notify.then(...) as a channel send. Also keep common\n    // serialization probes from being treated as channel names.\n    if (prop === \"then\" || prop === \"catch\" || prop === \"finally\" || prop === \"toJSON\") {\n      return undefined;\n    }\n\n    if (prop === \"channel\") {\n      return (name: string) => ({\n        send: (to: Notifiable | string, payload: unknown, options?: SendOptions) =>\n          notifyImpl(name, to, payload, options),\n      });\n    }\n\n    return (to: Notifiable | string, payload: unknown, options?: SendOptions) =>\n      notifyImpl(prop, to, payload, options);\n  },\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwDA,eAAe,WACb,aACA,IACA,SACA,UAAuB,CAAC,GACT;CACf,MAAM,cAAc,OAAO,OAAO;CAClC,MAAM,aAAqC,cAAc,SAAY;CAIrE,IAAI,mBAAuC,QAAQ;CACnD,IACE,qBAAqB,UACrB,WACA,OAAO,YAAY,YACnB,UAAU,WACV,OAAQ,QAA8B,SAAS,UAE/C,mBAAoB,QAA6B;CAInD,IAAI,cAAc,oBAAoB,CAAC,QAAQ,OAAO;EACpD,MAAM,SAAS,sBAAsB;EAErC,IAAI,OAAO,aAOT;OAAI,EAAC,MANiB,OAAO,YAAY,gBACvC,YACA,kBACA,CAAC,WAA0B,CAC7B,EAEY,CAAC,SAAS,WAA0B,GAAG;IACjD,MAAM,KAAK,WAAW;KACpB,YAAY,cAAc;KAC1B,SAAS;KACT;KACA,QAAQ;KACR;IACF,CAAC;IACD;GACF;;CAEJ;CAEA,MAAM,gBAAgB;EACpB;EACA;EACA,IAAI;EACJ,UAAU,cAAc,KAAK;EAC7B;EACA,MAAM;EACN;CACF,CAAC;AACH;;;;;AAMA,MAAa,SAAiB,IAAI,MAAM,CAAC,GAAa,EACpD,IAAI,SAAS,MAAM;CACjB,IAAI,OAAO,SAAS,UAClB;CAMF,IAAI,SAAS,UAAU,SAAS,WAAW,SAAS,aAAa,SAAS,UACxE;CAGF,IAAI,SAAS,WACX,QAAQ,UAAkB,EACxB,OAAO,IAAyB,SAAkB,YAChD,WAAW,MAAM,IAAI,SAAS,OAAO,EACzC;CAGF,QAAQ,IAAyB,SAAkB,YACjD,WAAW,MAAM,IAAI,SAAS,OAAO;AACzC,EACF,CAAC"}