@h-ai/reach 0.1.0-alpha.66 → 0.1.0-alpha.67
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/README.en.md +41 -54
- package/README.ja.md +42 -55
- package/README.md +42 -55
- package/package.json +4 -4
package/README.en.md
CHANGED
|
@@ -4,16 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
[Framework home](../../README.en.md) · Related modules: [@h-ai/cache](../cache/README.en.md) · [@h-ai/core](../core/README.en.md) · [@h-ai/reldb](../reldb/README.en.md)
|
|
6
6
|
|
|
7
|
+
## Introduction
|
|
8
|
+
|
|
7
9
|
A notification module supporting multiple providers (email, SMS, API callbacks) at once. Templates bind to providers for scenario-based routing. Includes do-not-disturb (DND) policies and configuration-defined templates.
|
|
8
10
|
|
|
9
11
|
Template sends need only `template`, `to` and `vars`; the provider comes from the template. An explicit `provider` still overrides it. Direct sends require a provider; omitting it returns a failed HaiResult.
|
|
10
12
|
|
|
11
|
-
##
|
|
12
|
-
|
|
13
|
-
- `@h-ai/reldb`: optional persistence for delivery logs and templates; enabled automatically when initialized.
|
|
14
|
-
- `@h-ai/cache`: optional distributed locks for DND delay; enabled automatically when initialized.
|
|
15
|
-
|
|
16
|
-
## Supported providers
|
|
13
|
+
## Core features
|
|
17
14
|
|
|
18
15
|
| Provider | Channel | Description |
|
|
19
16
|
| ------------ | ------- | -------------------------------------------------- |
|
|
@@ -22,9 +19,16 @@ Template sends need only `template`, `to` and `vars`; the provider comes from th
|
|
|
22
19
|
| `aliyun-sms` | SMS | Alibaba Cloud SMS through HTTP API, without an SDK |
|
|
23
20
|
| `api` | General | HTTP API callback (generic webhook) |
|
|
24
21
|
|
|
25
|
-
##
|
|
22
|
+
## Getting started
|
|
23
|
+
|
|
24
|
+
### Dependencies
|
|
25
|
+
|
|
26
|
+
- `@h-ai/reldb`: optional persistence for delivery logs and templates; enabled automatically when initialized.
|
|
27
|
+
- `@h-ai/cache`: optional distributed locks for DND delay; enabled automatically when initialized.
|
|
26
28
|
|
|
27
|
-
###
|
|
29
|
+
### Quick start
|
|
30
|
+
|
|
31
|
+
#### Installation
|
|
28
32
|
|
|
29
33
|
```bash
|
|
30
34
|
pnpm add @h-ai/reach
|
|
@@ -32,61 +36,44 @@ pnpm add @h-ai/reach
|
|
|
32
36
|
|
|
33
37
|
Install `nodemailer` for email delivery.
|
|
34
38
|
|
|
35
|
-
|
|
39
|
+
#### Initialize multiple providers
|
|
36
40
|
|
|
37
41
|
```ts
|
|
38
|
-
import { cache } from '@h-ai/cache'
|
|
39
42
|
import { reach } from '@h-ai/reach'
|
|
40
|
-
import { reldb } from '@h-ai/reldb'
|
|
41
|
-
|
|
42
|
-
// 1. 初始化可选依赖(按需)
|
|
43
|
-
await reldb.init({ type: 'sqlite', database: './data.db' }) // 可选,启用发送日志与模板持久化
|
|
44
|
-
await cache.init({ type: 'memory' }) // 可选,启用 DND delay 策略分布式锁
|
|
45
|
-
|
|
46
|
-
// 2. 初始化触达模块(自动检测已初始化的 reldb/cache 单例)
|
|
47
|
-
await reach.init({
|
|
48
|
-
providers: [
|
|
49
|
-
{ name: 'email', type: 'smtp', host: 'smtp.example.com', from: 'noreply@example.com' },
|
|
50
|
-
{ name: 'sms', type: 'aliyun-sms', accessKeyId: '...', accessKeySecret: '...', signName: '某某科技' },
|
|
51
|
-
{ name: 'webhook', type: 'api', url: 'https://api.example.com/notify' },
|
|
52
|
-
],
|
|
53
|
-
templates: [
|
|
54
|
-
{ name: 'welcome_email', provider: 'email', subject: '欢迎 {userName}', body: '亲爱的 {userName},欢迎!' },
|
|
55
|
-
{ name: 'sms_code', provider: 'sms', body: '验证码: {code},{minutes} 分钟内有效。' },
|
|
56
|
-
],
|
|
57
|
-
dnd: { enabled: true, strategy: 'delay', start: '22:00', end: '08:00' },
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
// 通过 provider 字段选择发送渠道
|
|
61
|
-
await reach.send({
|
|
62
|
-
to: 'user@example.com',
|
|
63
|
-
template: 'welcome_email',
|
|
64
|
-
vars: { userName: '张三' },
|
|
65
|
-
})
|
|
66
43
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
provider: '
|
|
70
|
-
to: '13800138000',
|
|
71
|
-
extra: { templateCode: 'SMS_123456' },
|
|
72
|
-
vars: { code: '123456' },
|
|
44
|
+
const initialized = await reach.init({
|
|
45
|
+
providers: [{ name: 'local', type: 'console' }],
|
|
46
|
+
templates: [{ name: 'welcome', provider: 'local', subject: 'Welcome', body: 'Hello {userName}' }],
|
|
73
47
|
})
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
48
|
+
if (!initialized.success)
|
|
49
|
+
throw new Error(initialized.error.message)
|
|
50
|
+
try {
|
|
51
|
+
const result = await reach.send({
|
|
52
|
+
to: 'user@example.com',
|
|
53
|
+
template: 'welcome',
|
|
54
|
+
vars: { userName: 'Alice' },
|
|
55
|
+
})
|
|
56
|
+
if (!result.success)
|
|
57
|
+
throw new Error(result.error.message)
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
await reach.close()
|
|
61
|
+
}
|
|
77
62
|
```
|
|
78
63
|
|
|
64
|
+
This example uses the development Console provider. It records delivery metadata and does not send email or SMS. Configure SMTP, SMS or API providers below for real delivery; initialize optional persistence/shared cache when needed.
|
|
65
|
+
|
|
79
66
|
`reach.config` returns a **redacted configuration snapshot**. Embedded credentials in `api.url` / `endpoint`, `headers.Authorization`, SMTP `pass`, SMS keys, and other secrets are hidden automatically.
|
|
80
67
|
|
|
81
|
-
|
|
68
|
+
### Configuration
|
|
82
69
|
|
|
83
|
-
|
|
70
|
+
#### Console (development/tests)
|
|
84
71
|
|
|
85
72
|
```ts
|
|
86
73
|
const config = { name: 'dev', type: 'console' }
|
|
87
74
|
```
|
|
88
75
|
|
|
89
|
-
|
|
76
|
+
#### SMTP
|
|
90
77
|
|
|
91
78
|
| Field | Type | Required | Default | Description |
|
|
92
79
|
| -------- | --------- | -------- | ------- | ----------------------- |
|
|
@@ -99,7 +86,7 @@ const config = { name: 'dev', type: 'console' }
|
|
|
99
86
|
| `pass` | `string` | — | — | Authentication password |
|
|
100
87
|
| `from` | `string` | ✅ | — | Sender address |
|
|
101
88
|
|
|
102
|
-
|
|
89
|
+
#### Alibaba Cloud SMS
|
|
103
90
|
|
|
104
91
|
| Field | Type | Required | Default | Description |
|
|
105
92
|
| ----------------- | -------- | -------- | ----------------------- | -------------------- |
|
|
@@ -110,7 +97,7 @@ const config = { name: 'dev', type: 'console' }
|
|
|
110
97
|
| `signName` | `string` | ✅ | — | SMS signature |
|
|
111
98
|
| `endpoint` | `string` | — | `dysmsapi.aliyuncs.com` | API endpoint |
|
|
112
99
|
|
|
113
|
-
|
|
100
|
+
#### API callbacks
|
|
114
101
|
|
|
115
102
|
| Field | Type | Required | Default | Description |
|
|
116
103
|
| --------- | ------------------------ | -------- | ------- | ----------------------- |
|
|
@@ -123,7 +110,7 @@ const config = { name: 'dev', type: 'console' }
|
|
|
123
110
|
|
|
124
111
|
API callback addresses are trusted server configuration. Logs contain only the callback origin, not URL credentials, query parameters, recipients, or third-party failure bodies. For tenant/user-supplied addresses, applications must enforce a domain allowlist; enterprise deployments should also block internal metadata access through egress proxies or network policies.
|
|
125
112
|
|
|
126
|
-
|
|
113
|
+
#### DND (do not disturb)
|
|
127
114
|
|
|
128
115
|
| Field | Type | Required | Default | Description |
|
|
129
116
|
| ---------- | ---------------------- | -------- | --------- | ------------------------------------- |
|
|
@@ -132,7 +119,7 @@ API callback addresses are trusted server configuration. Logs contain only the c
|
|
|
132
119
|
| `start` | `string` | — | `00:00` | Start time (HH:mm) |
|
|
133
120
|
| `end` | `string` | — | `00:00` | End time (HH:mm) |
|
|
134
121
|
|
|
135
|
-
|
|
122
|
+
### Error handling
|
|
136
123
|
|
|
137
124
|
All operations return `HaiResult<T>`. Error codes are defined in `HaiReachError`:
|
|
138
125
|
|
|
@@ -156,7 +143,7 @@ if (!result.success) {
|
|
|
156
143
|
}
|
|
157
144
|
```
|
|
158
145
|
|
|
159
|
-
|
|
146
|
+
### Distributed locks
|
|
160
147
|
|
|
161
148
|
Across nodes, flushing pending DND-delay messages is protected by `@h-ai/cache` distributed locks, allowing only one node to flush at a time.
|
|
162
149
|
|
|
@@ -165,7 +152,7 @@ Across nodes, flushing pending DND-delay messages is protected by `@h-ai/cache`
|
|
|
165
152
|
- Without initialized `@h-ai/cache`, distributed locking is disabled automatically; single-node operation continues.
|
|
166
153
|
- A stable process-level owner identifier prevents releasing another owner's lock.
|
|
167
154
|
|
|
168
|
-
##
|
|
155
|
+
## Development and testing
|
|
169
156
|
|
|
170
157
|
```bash
|
|
171
158
|
pnpm --filter @h-ai/reach test
|
package/README.ja.md
CHANGED
|
@@ -4,16 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
[フレームワークのホーム](../../README.ja.md) · 関連モジュール:[@h-ai/cache](../cache/README.ja.md) · [@h-ai/core](../core/README.ja.md) · [@h-ai/reldb](../reldb/README.ja.md)
|
|
6
6
|
|
|
7
|
+
## 概要
|
|
8
|
+
|
|
7
9
|
メール・SMS・API 通知など複数の Provider を同時登録できる通知モジュールです。テンプレートと Provider の対応で用途に応じて配信します。通知抑制(DND)と設定ファイルによるテンプレート定義を備えます。
|
|
8
10
|
|
|
9
11
|
テンプレート送信は `template`、`to`、`vars` のみで利用でき、Provider はテンプレートから選ばれます。明示した `provider` は優先されます。直接送信は Provider が必須で、省略すると失敗 HaiResult を返します。
|
|
10
12
|
|
|
11
|
-
##
|
|
12
|
-
|
|
13
|
-
- `@h-ai/reldb`:送信ログ・テンプレートの永続化(任意)。初期化済みなら自動有効化。
|
|
14
|
-
- `@h-ai/cache`:DND delay 用の分散ロック(任意)。初期化済みなら自動有効化。
|
|
15
|
-
|
|
16
|
-
## 対応 Provider
|
|
13
|
+
## 主な機能
|
|
17
14
|
|
|
18
15
|
| Provider | チャネル | 説明 |
|
|
19
16
|
| ------------ | -------- | -------------------------------------------------- |
|
|
@@ -22,9 +19,16 @@
|
|
|
22
19
|
| `aliyun-sms` | SMS | Alibaba Cloud SMS(HTTP API を直接使用、SDK 不要) |
|
|
23
20
|
| `api` | 汎用 | HTTP API 通知(汎用 webhook) |
|
|
24
21
|
|
|
25
|
-
##
|
|
22
|
+
## 使い始める
|
|
23
|
+
|
|
24
|
+
### 依存関係
|
|
25
|
+
|
|
26
|
+
- `@h-ai/reldb`:送信ログ・テンプレートの永続化(任意)。初期化済みなら自動有効化。
|
|
27
|
+
- `@h-ai/cache`:DND delay 用の分散ロック(任意)。初期化済みなら自動有効化。
|
|
26
28
|
|
|
27
|
-
###
|
|
29
|
+
### クイックスタート
|
|
30
|
+
|
|
31
|
+
#### インストール
|
|
28
32
|
|
|
29
33
|
```bash
|
|
30
34
|
pnpm add @h-ai/reach
|
|
@@ -32,61 +36,44 @@ pnpm add @h-ai/reach
|
|
|
32
36
|
|
|
33
37
|
メール送信には `nodemailer` をインストールしてください。
|
|
34
38
|
|
|
35
|
-
|
|
39
|
+
#### 複数 Provider の初期化
|
|
36
40
|
|
|
37
41
|
```ts
|
|
38
|
-
import { cache } from '@h-ai/cache'
|
|
39
42
|
import { reach } from '@h-ai/reach'
|
|
40
|
-
import { reldb } from '@h-ai/reldb'
|
|
41
|
-
|
|
42
|
-
// 1. 初始化可选依赖(按需)
|
|
43
|
-
await reldb.init({ type: 'sqlite', database: './data.db' }) // 可选,启用发送日志与模板持久化
|
|
44
|
-
await cache.init({ type: 'memory' }) // 可选,启用 DND delay 策略分布式锁
|
|
45
|
-
|
|
46
|
-
// 2. 初始化触达模块(自动检测已初始化的 reldb/cache 单例)
|
|
47
|
-
await reach.init({
|
|
48
|
-
providers: [
|
|
49
|
-
{ name: 'email', type: 'smtp', host: 'smtp.example.com', from: 'noreply@example.com' },
|
|
50
|
-
{ name: 'sms', type: 'aliyun-sms', accessKeyId: '...', accessKeySecret: '...', signName: '某某科技' },
|
|
51
|
-
{ name: 'webhook', type: 'api', url: 'https://api.example.com/notify' },
|
|
52
|
-
],
|
|
53
|
-
templates: [
|
|
54
|
-
{ name: 'welcome_email', provider: 'email', subject: '欢迎 {userName}', body: '亲爱的 {userName},欢迎!' },
|
|
55
|
-
{ name: 'sms_code', provider: 'sms', body: '验证码: {code},{minutes} 分钟内有效。' },
|
|
56
|
-
],
|
|
57
|
-
dnd: { enabled: true, strategy: 'delay', start: '22:00', end: '08:00' },
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
// 通过 provider 字段选择发送渠道
|
|
61
|
-
await reach.send({
|
|
62
|
-
to: 'user@example.com',
|
|
63
|
-
template: 'welcome_email',
|
|
64
|
-
vars: { userName: '张三' },
|
|
65
|
-
})
|
|
66
43
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
provider: '
|
|
70
|
-
to: '13800138000',
|
|
71
|
-
extra: { templateCode: 'SMS_123456' },
|
|
72
|
-
vars: { code: '123456' },
|
|
44
|
+
const initialized = await reach.init({
|
|
45
|
+
providers: [{ name: 'local', type: 'console' }],
|
|
46
|
+
templates: [{ name: 'welcome', provider: 'local', subject: 'Welcome', body: 'Hello {userName}' }],
|
|
73
47
|
})
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
48
|
+
if (!initialized.success)
|
|
49
|
+
throw new Error(initialized.error.message)
|
|
50
|
+
try {
|
|
51
|
+
const result = await reach.send({
|
|
52
|
+
to: 'user@example.com',
|
|
53
|
+
template: 'welcome',
|
|
54
|
+
vars: { userName: 'Alice' },
|
|
55
|
+
})
|
|
56
|
+
if (!result.success)
|
|
57
|
+
throw new Error(result.error.message)
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
await reach.close()
|
|
61
|
+
}
|
|
77
62
|
```
|
|
78
63
|
|
|
64
|
+
この例は開発用 Console Provider で送信メタデータを記録し、実メールや SMS は送りません。実送信には下記の SMTP・SMS・API を設定し、必要に応じて永続化と共有キャッシュを初期化してください。
|
|
65
|
+
|
|
79
66
|
`reach.config` は**機密情報をマスクした設定スナップショット**を返します。`api.url` / `endpoint` 内の認証情報、`headers.Authorization`、SMTP の `pass`、SMS の鍵などは自動的に非表示になります。
|
|
80
67
|
|
|
81
|
-
|
|
68
|
+
### 設定
|
|
82
69
|
|
|
83
|
-
|
|
70
|
+
#### Console(開発・テスト用)
|
|
84
71
|
|
|
85
72
|
```ts
|
|
86
73
|
const config = { name: 'dev', type: 'console' }
|
|
87
74
|
```
|
|
88
75
|
|
|
89
|
-
|
|
76
|
+
#### SMTP
|
|
90
77
|
|
|
91
78
|
| フィールド | 型 | 必須 | 既定値 | 説明 |
|
|
92
79
|
| ---------- | --------- | ---- | ------ | ----------------------- |
|
|
@@ -99,7 +86,7 @@ const config = { name: 'dev', type: 'console' }
|
|
|
99
86
|
| `pass` | `string` | — | — | 認証パスワード |
|
|
100
87
|
| `from` | `string` | ✅ | — | 送信元アドレス |
|
|
101
88
|
|
|
102
|
-
|
|
89
|
+
#### Alibaba Cloud SMS
|
|
103
90
|
|
|
104
91
|
| フィールド | 型 | 必須 | 既定値 | 説明 |
|
|
105
92
|
| ----------------- | -------- | ---- | ----------------------- | --------------------- |
|
|
@@ -110,7 +97,7 @@ const config = { name: 'dev', type: 'console' }
|
|
|
110
97
|
| `signName` | `string` | ✅ | — | SMS 署名 |
|
|
111
98
|
| `endpoint` | `string` | — | `dysmsapi.aliyuncs.com` | API endpoint |
|
|
112
99
|
|
|
113
|
-
|
|
100
|
+
#### API 通知
|
|
114
101
|
|
|
115
102
|
| フィールド | 型 | 必須 | 既定値 | 説明 |
|
|
116
103
|
| ---------- | ------------------------ | ---- | ------- | ------------------------ |
|
|
@@ -123,7 +110,7 @@ const config = { name: 'dev', type: 'console' }
|
|
|
123
110
|
|
|
124
111
|
API 通知先は信頼できるサーバー設定です。ログには origin のみを記録し、URL 認証情報、クエリ、宛先、外部の失敗応答本文は残しません。テナント・ユーザー入力の URL にはアプリでドメインの allowlist を設け、企業環境では出口プロキシやネットワーク方針でも内部メタデータへの接続を防いでください。
|
|
125
112
|
|
|
126
|
-
|
|
113
|
+
#### DND(通知抑制)
|
|
127
114
|
|
|
128
115
|
| フィールド | 型 | 必須 | 既定値 | 説明 |
|
|
129
116
|
| ---------- | ---------------------- | ---- | --------- | --------------------------------------- |
|
|
@@ -132,7 +119,7 @@ API 通知先は信頼できるサーバー設定です。ログには origin
|
|
|
132
119
|
| `start` | `string` | — | `00:00` | 開始時刻(HH:mm) |
|
|
133
120
|
| `end` | `string` | — | `00:00` | 終了時刻(HH:mm) |
|
|
134
121
|
|
|
135
|
-
|
|
122
|
+
### エラー処理
|
|
136
123
|
|
|
137
124
|
すべての操作は `HaiResult<T>` を返します。エラーコードは `HaiReachError` で定義します:
|
|
138
125
|
|
|
@@ -156,7 +143,7 @@ if (!result.success) {
|
|
|
156
143
|
}
|
|
157
144
|
```
|
|
158
145
|
|
|
159
|
-
|
|
146
|
+
### 分散ロック
|
|
160
147
|
|
|
161
148
|
複数ノードでは DND delay の保留メッセージの flush を `@h-ai/cache` の分散ロックで保護し、同時に 1 ノードのみが実行します。
|
|
162
149
|
|
|
@@ -165,12 +152,12 @@ if (!result.success) {
|
|
|
165
152
|
- `@h-ai/cache` が未初期化なら分散ロックは自動無効化し、単一ノードで動作します。
|
|
166
153
|
- 安定したプロセス単位の owner 識別子で、ほかの所有者のロックの誤解除を防ぎます。
|
|
167
154
|
|
|
168
|
-
##
|
|
155
|
+
## 開発とテスト
|
|
169
156
|
|
|
170
157
|
```bash
|
|
171
158
|
pnpm --filter @h-ai/reach test
|
|
172
159
|
```
|
|
173
160
|
|
|
174
|
-
##
|
|
161
|
+
## ライセンス
|
|
175
162
|
|
|
176
163
|
Apache-2.0
|
package/README.md
CHANGED
|
@@ -4,16 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
[框架首页](../../README.md) · 相关模块:[@h-ai/cache](../cache/README.md) · [@h-ai/core](../core/README.md) · [@h-ai/reldb](../reldb/README.md)
|
|
6
6
|
|
|
7
|
+
## 简介
|
|
8
|
+
|
|
7
9
|
用户触达模块,支持同时注册多个 Provider(邮件、短信、API 回调等),通过模板绑定 Provider 实现按场景路由发送。内置免打扰(DND)机制,支持通过配置文件定义模板。
|
|
8
10
|
|
|
9
11
|
模板发送只需 `template`、`to` 和 `vars`,Provider 默认来自模板;显式 `provider` 仍可覆盖。无模板的直接发送必须提供 Provider,缺少时返回失败 HaiResult。
|
|
10
12
|
|
|
11
|
-
##
|
|
12
|
-
|
|
13
|
-
- `@h-ai/reldb` — 数据库(发送日志与模板持久化),可选;已初始化时自动启用持久化
|
|
14
|
-
- `@h-ai/cache` — 缓存(DND delay 策略分布式锁),可选;已初始化时自动启用分布式锁
|
|
15
|
-
|
|
16
|
-
## 支持的 Provider
|
|
13
|
+
## 核心功能清单
|
|
17
14
|
|
|
18
15
|
| Provider | 渠道 | 说明 |
|
|
19
16
|
| ------------ | ---- | ------------------------------------------- |
|
|
@@ -22,9 +19,16 @@
|
|
|
22
19
|
| `aliyun-sms` | 短信 | 阿里云短信服务(直接调用 HTTP API,无 SDK) |
|
|
23
20
|
| `api` | 通用 | HTTP API 回调(通用 webhook) |
|
|
24
21
|
|
|
25
|
-
##
|
|
22
|
+
## 使用入门
|
|
23
|
+
|
|
24
|
+
### 依赖
|
|
25
|
+
|
|
26
|
+
- `@h-ai/reldb` — 数据库(发送日志与模板持久化),可选;已初始化时自动启用持久化
|
|
27
|
+
- `@h-ai/cache` — 缓存(DND delay 策略分布式锁),可选;已初始化时自动启用分布式锁
|
|
26
28
|
|
|
27
|
-
###
|
|
29
|
+
### 快速开始
|
|
30
|
+
|
|
31
|
+
#### 安装
|
|
28
32
|
|
|
29
33
|
```bash
|
|
30
34
|
pnpm add @h-ai/reach
|
|
@@ -32,61 +36,44 @@ pnpm add @h-ai/reach
|
|
|
32
36
|
|
|
33
37
|
邮件发送需安装 `nodemailer`。
|
|
34
38
|
|
|
35
|
-
|
|
39
|
+
#### 多 Provider 初始化
|
|
36
40
|
|
|
37
41
|
```ts
|
|
38
|
-
import { cache } from '@h-ai/cache'
|
|
39
42
|
import { reach } from '@h-ai/reach'
|
|
40
|
-
import { reldb } from '@h-ai/reldb'
|
|
41
|
-
|
|
42
|
-
// 1. 初始化可选依赖(按需)
|
|
43
|
-
await reldb.init({ type: 'sqlite', database: './data.db' }) // 可选,启用发送日志与模板持久化
|
|
44
|
-
await cache.init({ type: 'memory' }) // 可选,启用 DND delay 策略分布式锁
|
|
45
|
-
|
|
46
|
-
// 2. 初始化触达模块(自动检测已初始化的 reldb/cache 单例)
|
|
47
|
-
await reach.init({
|
|
48
|
-
providers: [
|
|
49
|
-
{ name: 'email', type: 'smtp', host: 'smtp.example.com', from: 'noreply@example.com' },
|
|
50
|
-
{ name: 'sms', type: 'aliyun-sms', accessKeyId: '...', accessKeySecret: '...', signName: '某某科技' },
|
|
51
|
-
{ name: 'webhook', type: 'api', url: 'https://api.example.com/notify' },
|
|
52
|
-
],
|
|
53
|
-
templates: [
|
|
54
|
-
{ name: 'welcome_email', provider: 'email', subject: '欢迎 {userName}', body: '亲爱的 {userName},欢迎!' },
|
|
55
|
-
{ name: 'sms_code', provider: 'sms', body: '验证码: {code},{minutes} 分钟内有效。' },
|
|
56
|
-
],
|
|
57
|
-
dnd: { enabled: true, strategy: 'delay', start: '22:00', end: '08:00' },
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
// 通过 provider 字段选择发送渠道
|
|
61
|
-
await reach.send({
|
|
62
|
-
to: 'user@example.com',
|
|
63
|
-
template: 'welcome_email',
|
|
64
|
-
vars: { userName: '张三' },
|
|
65
|
-
})
|
|
66
43
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
provider: '
|
|
70
|
-
to: '13800138000',
|
|
71
|
-
extra: { templateCode: 'SMS_123456' },
|
|
72
|
-
vars: { code: '123456' },
|
|
44
|
+
const initialized = await reach.init({
|
|
45
|
+
providers: [{ name: 'local', type: 'console' }],
|
|
46
|
+
templates: [{ name: 'welcome', provider: 'local', subject: 'Welcome', body: 'Hello {userName}' }],
|
|
73
47
|
})
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
48
|
+
if (!initialized.success)
|
|
49
|
+
throw new Error(initialized.error.message)
|
|
50
|
+
try {
|
|
51
|
+
const result = await reach.send({
|
|
52
|
+
to: 'user@example.com',
|
|
53
|
+
template: 'welcome',
|
|
54
|
+
vars: { userName: 'Alice' },
|
|
55
|
+
})
|
|
56
|
+
if (!result.success)
|
|
57
|
+
throw new Error(result.error.message)
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
await reach.close()
|
|
61
|
+
}
|
|
77
62
|
```
|
|
78
63
|
|
|
64
|
+
以上示例使用开发 Console Provider,只记录发送元数据,不实际发邮件或短信。需要真实投递时,按下方配置 SMTP、短信或 API Provider;数据库持久化和共享缓存按需初始化。
|
|
65
|
+
|
|
79
66
|
`reach.config` 返回的是**脱敏后的配置快照**:`api.url` / `endpoint` 的内嵌凭证,以及 `headers.Authorization`、SMTP `pass`、短信密钥等敏感值会自动隐藏。
|
|
80
67
|
|
|
81
|
-
|
|
68
|
+
### 配置
|
|
82
69
|
|
|
83
|
-
|
|
70
|
+
#### Console(开发/测试)
|
|
84
71
|
|
|
85
72
|
```ts
|
|
86
73
|
const config = { name: 'dev', type: 'console' }
|
|
87
74
|
```
|
|
88
75
|
|
|
89
|
-
|
|
76
|
+
#### SMTP
|
|
90
77
|
|
|
91
78
|
| 字段 | 类型 | 必填 | 默认值 | 说明 |
|
|
92
79
|
| -------- | --------- | ---- | ------ | --------------- |
|
|
@@ -99,7 +86,7 @@ const config = { name: 'dev', type: 'console' }
|
|
|
99
86
|
| `pass` | `string` | — | — | 认证密码 |
|
|
100
87
|
| `from` | `string` | ✅ | — | 发件人地址 |
|
|
101
88
|
|
|
102
|
-
|
|
89
|
+
#### 阿里云短信
|
|
103
90
|
|
|
104
91
|
| 字段 | 类型 | 必填 | 默认值 | 说明 |
|
|
105
92
|
| ----------------- | -------- | ---- | ----------------------- | ------------------- |
|
|
@@ -110,7 +97,7 @@ const config = { name: 'dev', type: 'console' }
|
|
|
110
97
|
| `signName` | `string` | ✅ | — | 短信签名 |
|
|
111
98
|
| `endpoint` | `string` | — | `dysmsapi.aliyuncs.com` | API 端点 |
|
|
112
99
|
|
|
113
|
-
|
|
100
|
+
#### API 回调
|
|
114
101
|
|
|
115
102
|
| 字段 | 类型 | 必填 | 默认值 | 说明 |
|
|
116
103
|
| --------- | ------------------------ | ---- | ------- | ---------------- |
|
|
@@ -123,7 +110,7 @@ const config = { name: 'dev', type: 'console' }
|
|
|
123
110
|
|
|
124
111
|
API 回调地址属于服务端可信配置。日志只记录回调 origin,不记录 URL 凭据、查询参数、收件人或第三方失败响应体。若回调地址来自租户/用户输入,应用层必须配置域名 allowlist;企业部署还应通过出口代理或网络策略阻止访问内网元数据地址。
|
|
125
112
|
|
|
126
|
-
|
|
113
|
+
#### DND(免打扰)
|
|
127
114
|
|
|
128
115
|
| 字段 | 类型 | 必填 | 默认值 | 说明 |
|
|
129
116
|
| ---------- | ---------------------- | ---- | --------- | --------------------------------------- |
|
|
@@ -132,7 +119,7 @@ API 回调地址属于服务端可信配置。日志只记录回调 origin,不
|
|
|
132
119
|
| `start` | `string` | — | `00:00` | 开始时间(HH:mm 格式) |
|
|
133
120
|
| `end` | `string` | — | `00:00` | 结束时间(HH:mm 格式) |
|
|
134
121
|
|
|
135
|
-
|
|
122
|
+
### 错误处理
|
|
136
123
|
|
|
137
124
|
所有操作返回 `HaiResult<T>`,错误码定义在 `HaiReachError`:
|
|
138
125
|
|
|
@@ -156,7 +143,7 @@ if (!result.success) {
|
|
|
156
143
|
}
|
|
157
144
|
```
|
|
158
145
|
|
|
159
|
-
|
|
146
|
+
### 分布式锁
|
|
160
147
|
|
|
161
148
|
多节点部署时,DND(delay 策略)的 pending 消息 flush 操作通过 `@h-ai/cache` 分布式锁保护,确保同一时刻只有一个节点执行 flush。
|
|
162
149
|
|
|
@@ -165,12 +152,12 @@ if (!result.success) {
|
|
|
165
152
|
- 若 `@h-ai/cache` 未初始化,分布式锁自动禁用,不影响单节点运行
|
|
166
153
|
- 使用稳定的进程级 owner 标识,防止误释放他人锁
|
|
167
154
|
|
|
168
|
-
##
|
|
155
|
+
## 开发测试
|
|
169
156
|
|
|
170
157
|
```bash
|
|
171
158
|
pnpm --filter @h-ai/reach test
|
|
172
159
|
```
|
|
173
160
|
|
|
174
|
-
##
|
|
161
|
+
## 许可证
|
|
175
162
|
|
|
176
163
|
Apache-2.0
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@h-ai/reach",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.1.0-alpha.
|
|
4
|
+
"version": "0.1.0-alpha.67",
|
|
5
5
|
"description": "Hai Framework user reach module for email, SMS, and webhook channels.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"exports": {
|
|
@@ -23,9 +23,9 @@
|
|
|
23
23
|
"node": "^22.22.2 || ^24.15.0 || >=26.0.0"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@h-ai/cache": "0.1.0-alpha.
|
|
27
|
-
"@h-ai/core": "0.1.0-alpha.
|
|
28
|
-
"@h-ai/reldb": "0.1.0-alpha.
|
|
26
|
+
"@h-ai/cache": "0.1.0-alpha.67",
|
|
27
|
+
"@h-ai/core": "0.1.0-alpha.67",
|
|
28
|
+
"@h-ai/reldb": "0.1.0-alpha.67",
|
|
29
29
|
"zod": "^4.6.5"
|
|
30
30
|
},
|
|
31
31
|
"optionalDependencies": {
|