@h-ai/reach 1.0.0-beta.21 → 1.0.0-beta.23

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 CHANGED
@@ -175,7 +175,7 @@ Production rejects console providers and requires SMTP, SMS or an actual API ser
175
175
 
176
176
  Use `reach.send({ ..., sensitive: true, expiresAt, idempotencyKey })` for passwords, OTPs and reset tokens. expiresAt is Unix milliseconds; stable operation keys must contain no credentials. A shared ledger stores only metadata receipts/status and redacted destinations, never bodies, variables or credential hashes. There is no delayed credential queue. Success returns receiptId; DND/expired messages are not sent, and uncertain delivery is never automatically repeated. Explicit development console output is isolated from persistent observers. Production Console restrictions remain. Normal messages may use deferPolicy: reject to avoid a second queue.
177
177
 
178
- `migrateReach({ dryRun: true })` previews persistence preparation performed during repository initialization. Stop on failure, back up and stop conflicting writers first, preserve the sending ledger and never treat schema preparation as permission to replay credentials.
178
+ `reach.init()` creates and validates the send log and its queue identifier constraint. Incompatible tables cause initialization to fail. Preserve send receipts and reconcile uncertain outcomes before attempting further credential delivery.
179
179
 
180
180
  ### API acknowledgements and destination boundaries
181
181
 
package/README.ja.md CHANGED
@@ -175,7 +175,7 @@ if (state.data?.status === 'dead' && state.data.updatedAt !== undefined) {
175
175
 
176
176
  パスワード、OTP、リセットトークンは `reach.send({ ..., sensitive: true, expiresAt, idempotencyKey })` を使います。expiresAt は Unix ミリ秒、操作キーに秘密値を含めません。共有台帳は本文・変数・認証値のハッシュを保存せず、脱機密化した宛先と受領状態のみを保持します。遅延キューに入れず、DND/期限切れは送信せず、不明な配信を自動再送しません。成功時は receiptId を返します。明示した開発用コンソール出力は永続観測から分離し、本番 Console 制限は維持します。通常通知は deferPolicy: reject で二重キューを防げます。
177
177
 
178
- `migrateReach({ dryRun: true })` で永続構造を確認し、リポジトリ初期化で準備します。失敗時は停止し、事前バックアップと競合書込の停止を行い、送信台帳を保持します。構造準備を資格情報の再送許可と解釈しないでください。
178
+ `reach.init()` は送信ログとキュー識別子の制約を作成・検証し、構造が一致しない場合は初期化に失敗します。送信台帳を保存し、結果が不明な資格情報通知は永続レシートで状態を確認してください。
179
179
 
180
180
  ### API 応答と送信先の境界
181
181
 
package/README.md CHANGED
@@ -175,7 +175,7 @@ if (state.data?.status === 'dead' && state.data.updatedAt !== undefined) {
175
175
 
176
176
  发送密码、OTP 或重置令牌时使用 `reach.send({ ..., sensitive: true, expiresAt, idempotencyKey })`。expiresAt 是 Unix 毫秒,稳定操作键不可包含凭据。必须有共享发送账本:只持久化无载荷回执/状态,接收方脱敏,不存正文、变量或低熵凭据摘要,也不进入延迟队列。发送成功返回 receiptId;DND/过期不投递,异常或不明状态不自动重投。明确启用的开发控制台仍可显示业务允许的通知,持久采集观察器被隔离;生产 Console Provider 限制不变。普通通知可用 deferPolicy: reject 阻止二次排队。
177
177
 
178
- `migrateReach({ dryRun: true })` 预检持久结构,仓储初始化执行支持的准备步骤;失败时停止初始化。执行前备份并停止冲突写入,保留发送账本,不把结构准备作为重新发送凭据的理由。
178
+ `reach.init()` 创建并校验发送日志表及队列标识约束,结构不符时返回初始化失败。保留发送账本,按持久回执核对状态,避免重发结果不明的凭据通知。
179
179
 
180
180
  ### API 回执与目标边界
181
181
 
package/dist/index.d.ts CHANGED
@@ -1,8 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import * as _h_ai_core from '@h-ai/core';
3
3
  import { HaiResult } from '@h-ai/core';
4
- import * as _h_ai_reldb from '@h-ai/reldb';
5
- import { ReldbFunctions } from '@h-ai/reldb';
6
4
 
7
5
  /**
8
6
  * @h-ai/reach — 配置 Schema
@@ -1377,17 +1375,5 @@ interface ReachFunctions extends SendOperations {
1377
1375
  */
1378
1376
  declare const reach: ReachFunctions;
1379
1377
 
1380
- /**
1381
- * 预检或升级 Reach 队列。旧凭据相关载荷清除,待发凭据丢弃,保留发送状态和历史标识。
1382
- * @example
1383
- * ```ts
1384
- * import { migrateReach } from '@h-ai/reach'
1385
- * const plan = await migrateReach({ dryRun: true })
1386
- * ```
1387
- */
1388
- declare function migrateReach(options?: {
1389
- dryRun?: boolean;
1390
- }, db?: ReldbFunctions): Promise<_h_ai_core.HaiResult<_h_ai_reldb.SchemaMigrationPlan>>;
1391
-
1392
- export { AliyunSmsProviderConfigSchema, ApiProviderConfigSchema, ConsoleProviderConfigSchema, DndConfigSchema, HaiReachError, ProviderConfigSchema, ReachConfigSchema, ReachDeadRequeueSchema, ReachQueueConfigSchema, ReachQueueIdSchema, ReachQueueListSchema, ReachUnknownResolutionSchema, SmtpProviderConfigSchema, TemplateConfigSchema, migrateReach, reach };
1378
+ export { AliyunSmsProviderConfigSchema, ApiProviderConfigSchema, ConsoleProviderConfigSchema, DndConfigSchema, HaiReachError, ProviderConfigSchema, ReachConfigSchema, ReachDeadRequeueSchema, ReachQueueConfigSchema, ReachQueueIdSchema, ReachQueueListSchema, ReachUnknownResolutionSchema, SmtpProviderConfigSchema, TemplateConfigSchema, reach };
1393
1379
  export type { AliyunSmsProviderConfig, ApiProviderConfig, ConsoleProviderConfig, DndConfig, ProviderConfig, ReachChannel, ReachConfig, ReachConfigInput, ReachDeadRequeue, ReachFunctions, ReachGatewayHook, ReachInitOptions, ReachMessage, ReachQueueConfig, ReachQueueEntry, ReachQueueFailure, ReachQueueListOptions, ReachQueueOperations, ReachQueueStatus, ReachTemplate, ReachTemplateRegistry, ReachUnknownResolution, RenderedTemplate, SendOperations, SendResult, SmtpProviderConfig, TemplateConfig };
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ import { z } from 'zod';
2
2
  import { core, err, ok } from '@h-ai/core';
3
3
  import process from 'process';
4
4
  import { reldb, BaseReldbCrudRepository } from '@h-ai/reldb';
5
- import { randomUUID, createHash, createHmac } from 'crypto';
5
+ import { createHash, randomUUID, createHmac } from 'crypto';
6
6
  import { addAbortListener } from 'events';
7
7
  import { createRequire } from 'module';
8
8
  import { cache } from '@h-ai/cache';
@@ -11,6 +11,7 @@ import { cache } from '@h-ai/cache';
11
11
 
12
12
  // messages/en-US.json
13
13
  var en_US_default = {
14
+ reach_sendLogIndexRequired: "The send log table requires a unique index on receipt IDs",
14
15
  $schema: "https://inlang.com/schema/inlang-message-format",
15
16
  reach_notInitialized: "Reach module not initialized, call reach.init() first",
16
17
  reach_initFailed: "Reach module initialization failed: {error}",
@@ -70,6 +71,7 @@ var en_US_default = {
70
71
 
71
72
  // messages/ja-JP.json
72
73
  var ja_JP_default = {
74
+ reach_sendLogIndexRequired: "\u9001\u4FE1\u30ED\u30B0\u30C6\u30FC\u30D6\u30EB\u306B\u306F\u53D7\u4ED8 ID \u306E\u4E00\u610F\u30A4\u30F3\u30C7\u30C3\u30AF\u30B9\u304C\u5FC5\u8981\u3067\u3059",
73
75
  $schema: "https://inlang.com/schema/inlang-message-format",
74
76
  reach_notInitialized: "\u901A\u77E5\u30E2\u30B8\u30E5\u30FC\u30EB\u304C\u521D\u671F\u5316\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002\u5148\u306Breach.init()\u3092\u547C\u3073\u51FA\u3057\u3066\u304F\u3060\u3055\u3044",
75
77
  reach_initFailed: "\u901A\u77E5\u30E2\u30B8\u30E5\u30FC\u30EB\u306E\u521D\u671F\u5316\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
@@ -129,6 +131,7 @@ var ja_JP_default = {
129
131
 
130
132
  // messages/zh-CN.json
131
133
  var zh_CN_default = {
134
+ reach_sendLogIndexRequired: "\u53D1\u9001\u65E5\u5FD7\u8868\u5FC5\u987B\u4E3A\u56DE\u6267\u7F16\u53F7\u5EFA\u7ACB\u552F\u4E00\u7D22\u5F15",
132
135
  $schema: "https://inlang.com/schema/inlang-message-format",
133
136
  reach_notInitialized: "\u89E6\u8FBE\u6A21\u5757\u5C1A\u672A\u521D\u59CB\u5316\uFF0C\u8BF7\u5148\u8C03\u7528 reach.init()",
134
137
  reach_initFailed: "\u89E6\u8FBE\u6A21\u5757\u521D\u59CB\u5316\u5931\u8D25: {error}",
@@ -483,7 +486,7 @@ function createAliyunSmsProvider(gatewayHook) {
483
486
  }
484
487
  const templateCode = message.extra?.templateCode;
485
488
  if (!config.gateway)
486
- logger.debug("Sending SMS", { to: message.to, templateCode });
489
+ logger.debug("Sending SMS", { templateCode });
487
490
  try {
488
491
  const params = {
489
492
  AccessKeyId: config.accessKeyId,
@@ -525,14 +528,18 @@ function createAliyunSmsProvider(gatewayHook) {
525
528
  reachM("reach_smsSendFailed")
526
529
  );
527
530
  }
528
- logger.info("SMS sent", { to: message.to, bizId: body.BizId });
531
+ logger.info("SMS sent", { bizId: body.BizId });
529
532
  return ok({ success: true, messageId: body.BizId });
530
533
  } catch (error) {
531
534
  if (config.gateway) {
532
- logger.warn("SMS gateway request failed");
535
+ logger.warn("SMS gateway request failed", {
536
+ causeName: error instanceof Error ? error.name : void 0
537
+ });
533
538
  return err(HaiReachError.SEND_FAILED, reachM("reach_smsGatewayFailed"));
534
539
  }
535
- logger.error("SMS send failed", { to: message.to, error });
540
+ logger.warn("SMS send failed", {
541
+ causeName: error instanceof Error ? error.name : void 0
542
+ });
536
543
  return err(toReachError());
537
544
  }
538
545
  }
@@ -713,7 +720,6 @@ function createConsoleProvider() {
713
720
  async send(message) {
714
721
  logger3.debug("Sending message via console", {
715
722
  provider: message.provider,
716
- to: message.to,
717
723
  hasSubject: Boolean(message.subject),
718
724
  bodyLength: message.body?.length ?? 0,
719
725
  template: message.template,
@@ -725,6 +731,15 @@ function createConsoleProvider() {
725
731
  };
726
732
  }
727
733
  var logger4 = core.logger.child({ module: "reach", scope: "provider-smtp" });
734
+ function smtpErrorDiagnostics(error) {
735
+ const code = error && typeof error === "object" && "code" in error ? error.code : void 0;
736
+ const responseCode = error && typeof error === "object" && "responseCode" in error ? error.responseCode : void 0;
737
+ return {
738
+ causeName: error instanceof Error ? error.name : void 0,
739
+ causeCode: typeof code === "string" && /^[A-Z0-9_]{1,64}$/.test(code) ? code : void 0,
740
+ responseCode: typeof responseCode === "number" ? responseCode : void 0
741
+ };
742
+ }
728
743
  function toReachError3() {
729
744
  return {
730
745
  ...HaiReachError.SEND_FAILED,
@@ -759,7 +774,7 @@ function createSmtpProvider() {
759
774
  logger4.info("SMTP provider connected", { host: config.host, port: config.port });
760
775
  return ok(void 0);
761
776
  } catch (error) {
762
- logger4.error("SMTP connection failed", { error });
777
+ logger4.error("SMTP connection failed", smtpErrorDiagnostics(error));
763
778
  return err(toReachError3());
764
779
  }
765
780
  },
@@ -781,7 +796,7 @@ function createSmtpProvider() {
781
796
  reachM("reach_notInitialized")
782
797
  );
783
798
  }
784
- logger4.debug("Sending email", { to: message.to, subject: message.subject });
799
+ logger4.debug("Sending email");
785
800
  try {
786
801
  const sendMail = transporter.sendMail.bind(transporter);
787
802
  const info = await sendMail({
@@ -790,10 +805,10 @@ function createSmtpProvider() {
790
805
  subject: message.subject ?? "",
791
806
  html: message.body ?? ""
792
807
  });
793
- logger4.info("Email sent", { to: message.to, messageId: info.messageId });
808
+ logger4.info("Email sent");
794
809
  return ok({ success: true, messageId: info.messageId });
795
810
  } catch (error) {
796
- logger4.error("Email send failed", { to: message.to, error });
811
+ logger4.warn("Email send failed", smtpErrorDiagnostics(error));
797
812
  return err(toReachError3());
798
813
  }
799
814
  }
@@ -1313,7 +1328,7 @@ async function executeSend(message, providers2, templateRegistry2, dndConfig2, r
1313
1328
  logger6.info("Message persisted for DND delivery", { provider: providerName, queueId });
1314
1329
  return ok({ success: true, deferred: true, queueId });
1315
1330
  }
1316
- logger6.info("Message blocked by DND (discard strategy)", { provider: providerName, to: message.to });
1331
+ logger6.info("Message blocked by DND (discard strategy)", { provider: providerName });
1317
1332
  return err(
1318
1333
  HaiReachError.DND_BLOCKED,
1319
1334
  reachM("reach_dndBlocked")
@@ -1322,14 +1337,12 @@ async function executeSend(message, providers2, templateRegistry2, dndConfig2, r
1322
1337
  const provider = providers2.get(providerName);
1323
1338
  logger6.debug("Sending message", {
1324
1339
  provider: providerName,
1325
- to: preprocessed.data.to,
1326
1340
  template: message.template
1327
1341
  });
1328
1342
  const result = await provider.send(preprocessed.data);
1329
1343
  if (!result.success) {
1330
1344
  logger6.warn("Message send failed", {
1331
1345
  provider: providerName,
1332
- to: message.to,
1333
1346
  error: result.error.code
1334
1347
  });
1335
1348
  return result;
@@ -1342,133 +1355,6 @@ async function executeSend(message, providers2, templateRegistry2, dndConfig2, r
1342
1355
  }
1343
1356
  return result;
1344
1357
  }
1345
- function migrateReach(options = {}, db = reldb) {
1346
- const definitions = {
1347
- queue_id: db.config?.type === "mysql" ? "VARCHAR(255)" : "TEXT",
1348
- retry_safe: "BOOLEAN NOT NULL DEFAULT FALSE",
1349
- attempt_count: "BIGINT NOT NULL DEFAULT 0",
1350
- max_attempts: "BIGINT NOT NULL DEFAULT 5",
1351
- next_attempt_at: "BIGINT NOT NULL DEFAULT 0",
1352
- retry_delay_ms: "BIGINT NOT NULL DEFAULT 60000",
1353
- max_retry_delay_ms: "BIGINT NOT NULL DEFAULT 3600000",
1354
- last_failure: "TEXT"
1355
- };
1356
- const steps = [
1357
- {
1358
- version: 1,
1359
- name: "durable-delivery-ledger",
1360
- async needed(sql) {
1361
- const found = await db.schemaColumns("hai_reach_send_log", sql);
1362
- return found.success ? ok(found.data.length > 0 && Object.keys(definitions).some((name) => !found.data.includes(name))) : found;
1363
- },
1364
- async apply(sql) {
1365
- const found = await db.schemaColumns("hai_reach_send_log", sql);
1366
- if (!found.success)
1367
- return found;
1368
- for (const [name, definition] of Object.entries(definitions)) {
1369
- if (found.data.includes(name))
1370
- continue;
1371
- const added = await sql.execute(`ALTER TABLE hai_reach_send_log ADD COLUMN ${name} ${definition}`);
1372
- if (!added.success)
1373
- return added;
1374
- }
1375
- return ok(void 0);
1376
- }
1377
- },
1378
- {
1379
- version: 2,
1380
- name: "legacy-delivery-isolation",
1381
- async needed(sql) {
1382
- const found = await db.schemaColumns("hai_reach_send_log", sql);
1383
- if (!found.success || !found.data.length) {
1384
- return found.success ? ok(false) : found;
1385
- }
1386
- if (!found.data.includes("queue_id"))
1387
- return ok(true);
1388
- const row = await sql.get("SELECT id FROM hai_reach_send_log WHERE queue_id IS NULL AND status IN ('pending','processing','unknown') LIMIT 1");
1389
- return row.success ? ok(Boolean(row.data)) : row;
1390
- },
1391
- async apply(sql) {
1392
- const isolated = await sql.execute("UPDATE hai_reach_send_log SET status='unknown',processing_owner=NULL,locked_until=NULL WHERE status='processing' AND queue_id IS NULL");
1393
- if (!isolated.success)
1394
- return isolated;
1395
- while (true) {
1396
- const rows = await sql.query("SELECT id FROM hai_reach_send_log WHERE queue_id IS NULL AND status IN ('pending','unknown') ORDER BY id LIMIT 100");
1397
- if (!rows.success)
1398
- return rows;
1399
- for (const row of rows.data) {
1400
- const saved = await sql.execute("UPDATE hai_reach_send_log SET queue_id=? WHERE id=? AND queue_id IS NULL", [
1401
- randomUUID(),
1402
- row.id
1403
- ]);
1404
- if (!saved.success)
1405
- return saved;
1406
- }
1407
- if (rows.data.length < 100)
1408
- return ok(void 0);
1409
- }
1410
- }
1411
- },
1412
- {
1413
- version: 3,
1414
- name: "credential-payload-retirement",
1415
- async needed(sql) {
1416
- const found = await db.schemaColumns("hai_reach_send_log", sql);
1417
- if (!found.success || !found.data.length) {
1418
- return found.success ? ok(false) : found;
1419
- }
1420
- const ledger = await db.schemaColumns("hai_schema_migrations", sql);
1421
- if (!ledger.success)
1422
- return ledger;
1423
- const version = ledger.data.length ? await sql.get("SELECT version FROM hai_schema_migrations WHERE module=?", ["reach"]) : ok(null);
1424
- if (!version.success)
1425
- return version;
1426
- if (Number(version.data?.version ?? 0) >= 3)
1427
- return ok(false);
1428
- const row = await sql.get("SELECT id FROM hai_reach_send_log WHERE subject IS NOT NULL OR body IS NOT NULL OR template IS NOT NULL OR vars_json IS NOT NULL OR extra_json IS NOT NULL LIMIT 1");
1429
- return row.success ? ok(Boolean(row.data)) : row;
1430
- },
1431
- async apply(sql) {
1432
- return sql.execute("UPDATE hai_reach_send_log SET body=NULL,subject=NULL,vars_json=NULL,extra_json=NULL,template=NULL,status=CASE WHEN status IN ('pending','processing') THEN 'discarded' WHEN status='sending' THEN 'unknown' ELSE status END,processing_owner=NULL,locked_until=NULL WHERE subject IS NOT NULL OR body IS NOT NULL OR template IS NOT NULL OR vars_json IS NOT NULL OR extra_json IS NOT NULL");
1433
- }
1434
- },
1435
- {
1436
- version: 4,
1437
- name: "unique-operation-receipts",
1438
- async needed(sql) {
1439
- const columns = await db.schemaColumns("hai_reach_send_log", sql);
1440
- if (!columns.success || !columns.data.length) {
1441
- return columns.success ? ok(false) : columns;
1442
- }
1443
- if (!columns.data.includes("queue_id"))
1444
- return ok(true);
1445
- if (db.config?.type === "sqlite") {
1446
- const indexes = await sql.query("PRAGMA index_list(hai_reach_send_log)");
1447
- if (!indexes.success)
1448
- return indexes;
1449
- for (const index of indexes.data) {
1450
- if (!index.unique || !/^\w+$/.test(index.name))
1451
- continue;
1452
- const fields = await sql.query(`PRAGMA index_info(${index.name})`);
1453
- if (!fields.success)
1454
- return fields;
1455
- if (fields.data.length === 1 && fields.data[0]?.name === "queue_id")
1456
- return ok(false);
1457
- }
1458
- return ok(true);
1459
- }
1460
- const found = db.config?.type === "mysql" ? await sql.get("SELECT index_name FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name=? AND non_unique=0 GROUP BY index_name HAVING COUNT(*)=1 AND MAX(column_name)=?", ["hai_reach_send_log", "queue_id"]) : await sql.get("SELECT i.indexrelid FROM pg_index i JOIN pg_class t ON t.oid=i.indrelid JOIN pg_namespace n ON n.oid=t.relnamespace JOIN pg_attribute a ON a.attrelid=t.oid AND a.attnum=ANY(i.indkey) WHERE t.relname=? AND n.nspname=current_schema() AND i.indisunique AND i.indnatts=1 AND a.attname=?", ["hai_reach_send_log", "queue_id"]);
1461
- return found.success ? ok(!found.data) : found;
1462
- },
1463
- async apply(sql) {
1464
- return sql.execute("CREATE UNIQUE INDEX uq_hai_reach_queue_v1 ON hai_reach_send_log(queue_id)");
1465
- }
1466
- }
1467
- ];
1468
- return db.migrateSchema("reach", steps, options);
1469
- }
1470
-
1471
- // src/repositories/reach-repository-send-log.ts
1472
1358
  var TABLE_NAME = "hai_reach_send_log";
1473
1359
  var DEFAULT_CLAIM_BATCH_SIZE = 100;
1474
1360
  var DEFAULT_CLAIM_LOCK_MS = 10 * 60 * 1e3;
@@ -1692,11 +1578,8 @@ function resetSendLogRepoSingleton() {
1692
1578
  async function createSendLogRepository(db) {
1693
1579
  if (sendLogRepoInstance && sendLogRepoSqlOps === db.sql)
1694
1580
  return ok(sendLogRepoInstance);
1695
- const migration = await migrateReach({}, db);
1696
- if (!migration.success)
1697
- return migration;
1698
1581
  const repo = new DbSendLogRepository(db);
1699
- const initResult = await repo.count();
1582
+ const initResult = await repo.ready();
1700
1583
  if (!initResult.success) {
1701
1584
  return err(
1702
1585
  HaiReachError.SEND_FAILED,
@@ -1711,6 +1594,17 @@ async function createSendLogRepository(db) {
1711
1594
  return ok(repo);
1712
1595
  }
1713
1596
  var DbSendLogRepository = class extends BaseReldbCrudRepository {
1597
+ /** 唯一回执约束保证多个节点不能为同一操作排入不同的发送记录。 */
1598
+ async ready() {
1599
+ const schema = await this.ensureSchema();
1600
+ if (!schema.success)
1601
+ return schema;
1602
+ const type = this.db.config?.type;
1603
+ const index = type === "sqlite" ? await this.db.sql.get('SELECT i.name FROM pragma_index_list(?) i JOIN pragma_index_info(i.name) f WHERE i."unique"=1 AND i.partial=0 GROUP BY i.name HAVING COUNT(*)=1 AND MAX(f.name)=?', [TABLE_NAME, "queue_id"]) : type === "mysql" ? await this.db.sql.get("SELECT index_name FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name=? AND non_unique=0 GROUP BY index_name HAVING COUNT(*)=1 AND MAX(column_name)=?", [TABLE_NAME, "queue_id"]) : await this.db.sql.get("SELECT i.indexrelid FROM pg_index i JOIN pg_class t ON t.oid=i.indrelid JOIN pg_namespace n ON n.oid=t.relnamespace JOIN pg_attribute a ON a.attrelid=t.oid AND a.attnum=i.indkey[0] WHERE t.relname=? AND n.nspname=current_schema() AND i.indisunique AND i.indisvalid AND i.indpred IS NULL AND i.indnkeyatts=1 AND a.attname=?", [TABLE_NAME, "queue_id"]);
1604
+ if (!index.success)
1605
+ return index;
1606
+ return index.data ? ok(void 0) : err(HaiReachError.SEND_FAILED, reachM("reach_sendLogIndexRequired"));
1607
+ }
1714
1608
  /** 共享后端取真正执行时钟;本地 SQLite 保留可控的单进程测试时钟。 */
1715
1609
  clockSql(localNow) {
1716
1610
  if (this.db.config?.type === "postgresql")
@@ -2587,6 +2481,6 @@ var reach = {
2587
2481
  }
2588
2482
  };
2589
2483
 
2590
- export { AliyunSmsProviderConfigSchema, ApiProviderConfigSchema, ConsoleProviderConfigSchema, DndConfigSchema, HaiReachError, ProviderConfigSchema, ReachConfigSchema, ReachDeadRequeueSchema, ReachQueueConfigSchema, ReachQueueIdSchema, ReachQueueListSchema, ReachUnknownResolutionSchema, SmtpProviderConfigSchema, TemplateConfigSchema, migrateReach, reach };
2484
+ export { AliyunSmsProviderConfigSchema, ApiProviderConfigSchema, ConsoleProviderConfigSchema, DndConfigSchema, HaiReachError, ProviderConfigSchema, ReachConfigSchema, ReachDeadRequeueSchema, ReachQueueConfigSchema, ReachQueueIdSchema, ReachQueueListSchema, ReachUnknownResolutionSchema, SmtpProviderConfigSchema, TemplateConfigSchema, reach };
2591
2485
  //# sourceMappingURL=index.js.map
2592
2486
  //# sourceMappingURL=index.js.map