@teamlearners/clawops 0.32.1 → 0.34.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.
@@ -0,0 +1,269 @@
1
+ import { RequestSendMessagesSchema, SolapiMessageService } from 'solapi';
2
+ import { D as ClawOps } from '../client-CKaAfPlF.js';
3
+ export { a2 as SolapiBridgeError } from '../client-CKaAfPlF.js';
4
+ import 'zod';
5
+
6
+ /** 제네릭을 거쳐야 유니온(단건 | 배열)에 분배된다 */
7
+ type Elem<T> = T extends readonly (infer U)[] ? U : T;
8
+ type SolapiMessage = Elem<RequestSendMessagesSchema>;
9
+ type FallbackSource = 'customFields' | 'template' | 'text';
10
+ /**
11
+ * 문구 복원에 필요한 최소 모양.
12
+ *
13
+ * 발송 요청(`SolapiMessage`)과 조회 결과(`storedMessage`)를 둘 다 받는다 —
14
+ * 조회 결과의 `kakaoOptions` 는 서버 정규화 포맷이라 `Record<string, unknown>` 으로 온다.
15
+ */
16
+ interface FallbackInput {
17
+ type?: unknown;
18
+ text?: string | null;
19
+ customFields?: Record<string, string> | null;
20
+ kakaoOptions?: Record<string, unknown> | null;
21
+ }
22
+ type FallbackText = {
23
+ ok: true;
24
+ text: string;
25
+ source: FallbackSource;
26
+ } | {
27
+ ok: false;
28
+ reason: 'unresolved_variables';
29
+ unresolved: string[];
30
+ source: FallbackSource;
31
+ } | {
32
+ ok: false;
33
+ reason: 'no_template_content' | 'no_text';
34
+ source: FallbackSource;
35
+ };
36
+ interface ResolveOptions {
37
+ /** 대체 문구를 읽어올 customFields 키 */
38
+ field?: string;
39
+ /**
40
+ * 템플릿 본문 캐시. 호출자가 수명을 정한다.
41
+ *
42
+ * 값이 아니라 진행 중인 조회를 담는다 — 여러 건이 동시에 같은 템플릿을 찾을 때
43
+ * 완료를 기다리며 캐시가 비어 있으면 같은 요청이 그 수만큼 나간다.
44
+ */
45
+ cache?: Map<string, Promise<string | undefined>>;
46
+ }
47
+ declare const DEFAULT_FALLBACK_FIELD = "clawopsFallbackText";
48
+ /**
49
+ * 대체발송에 쓸 문구를 만든다.
50
+ *
51
+ * 솔라피는 이 문구를 콘솔(템플릿별 대체발송 설정)에만 두고 API 로 노출하지 않는다.
52
+ * 알림톡은 대개 `text` 없이 `variables` 만 보내므로 템플릿 본문을 조회해 직접 치환한다.
53
+ */
54
+ declare function resolveFallbackText(message: FallbackInput, solapi: SolapiMessageService, options?: ResolveOptions): Promise<FallbackText>;
55
+
56
+ /** 발송 시 심는 마커. 이 값이 있는 건만 대체발송 후보다 */
57
+ declare const FALLBACK_MARKER_FIELD = "clawopsFallback";
58
+ /**
59
+ * 기본 대상 코드 — **수신자 사유만**.
60
+ *
61
+ * 설정 오류(3101 발신프로필 무효·3105 미등록 템플릿·3106 유효하지 않은 채널)를 문자로 덮으면
62
+ * 알림톡이 깨진 걸 고객이 몇 달간 모른다. 3108(발송 가능 시간 아님)은 문자로 대체하면
63
+ * 21시 이후 발송이 되어 야간 규제에 걸린다. 둘 다 `onBlocked` 로 알리기만 한다.
64
+ */
65
+ declare const DEFAULT_FALLBACK_CODES: readonly ["3104", "3107", "3102"];
66
+ /** 다음 스윕이 어디서부터 볼지. 프로세스 밖에 저장할 수 있게 직렬화 가능한 값만 담는다 */
67
+ interface SweepCursor {
68
+ /** 마지막으로 본 갱신 시각 (ISO 8601) */
69
+ updatedAt: string;
70
+ /** 그 시각과 같은 messageId 들. 경계에서 같은 건을 두 번 집지 않기 위해 */
71
+ seen: readonly string[];
72
+ }
73
+ interface SweepFallbackEvent {
74
+ messageId: string;
75
+ to: string;
76
+ statusCode: string;
77
+ source: FallbackSource;
78
+ text: string;
79
+ }
80
+ type SweepBlockedEvent = {
81
+ messageId: string;
82
+ to: string;
83
+ statusCode: string;
84
+ } & (Extract<FallbackText, {
85
+ ok: false;
86
+ }>
87
+ /** 실패했지만 대체발송 대상 코드가 아니다 — 설정 오류·야간 등 */
88
+ | {
89
+ ok: false;
90
+ reason: 'code_not_eligible';
91
+ }
92
+ /** 문구는 만들었는데 ClawOps 가 거절했다. 다음 스윕이 다시 시도한다 */
93
+ | {
94
+ ok: false;
95
+ reason: 'send_rejected';
96
+ });
97
+ interface SweepOptions {
98
+ clawops: ClawOps;
99
+ solapi: SolapiMessageService;
100
+ /** 대체발송에 쓸 기본 발신번호. 조회 결과의 from 은 솔라피 발신번호라 쓰지 않는다 */
101
+ from: string;
102
+ /** 없으면 `lookbackMs` 만큼 거슬러 본다 */
103
+ cursor?: SweepCursor;
104
+ lookbackMs?: number;
105
+ /** 대체발송할 상태코드. 기본 `DEFAULT_FALLBACK_CODES` */
106
+ on?: readonly string[];
107
+ /** 훑을 메시지 타입. 기본 `DEFAULT_SWEEP_TYPES` (알림톡만) */
108
+ types?: readonly SweepMessageType[];
109
+ fallbackField?: string;
110
+ concurrency?: number;
111
+ /** 이미 처리한 건을 건너뛴다. 커서가 못 막는 재갱신을 여기서 막는다 */
112
+ skip?: (messageId: string) => boolean;
113
+ /** 템플릿 본문 캐시. 스윕 간 재사용하면 조회가 줄어든다 */
114
+ templateCache?: Map<string, Promise<string | undefined>>;
115
+ onFallback?: (event: SweepFallbackEvent) => void;
116
+ onBlocked?: (event: SweepBlockedEvent) => void;
117
+ }
118
+ interface SweepResult {
119
+ /** 다음 호출에 그대로 넘긴다 */
120
+ cursor: SweepCursor;
121
+ /** 조회한 총 건수 */
122
+ scanned: number;
123
+ /** 대체발송한 건수 */
124
+ sent: number;
125
+ /** 문구를 만들지 못해 보내지 못한 건수 */
126
+ blocked: number;
127
+ /**
128
+ * 페이지 상한에 걸려 구간을 다 못 봤다. 이때는 커서를 옮기지 않아 다음 스윕이 같은 곳을
129
+ * 다시 본다 — 물량이 계속 상한을 넘으면 주기를 줄이거나 lookback 을 좁혀야 한다
130
+ */
131
+ truncated: boolean;
132
+ /** 이번에 대체발송한 messageId. 호출자가 처리 집합에 넣는다 */
133
+ processed: string[];
134
+ }
135
+ /** 조회 API 가 실제로 받는 메시지 타입. 문자열로 두면 enum 을 벗어난 값이 런타임에야 터진다 */
136
+ type SweepMessageType = NonNullable<NonNullable<Parameters<SolapiMessageService['getMessages']>[0]>['type']>;
137
+ /**
138
+ * 알림톡 발송 실패(3XXX)를 찾아 문자로 대체발송한다.
139
+ *
140
+ * 접수 실패(1XXX)는 `send()` 응답에서 바로 잡히지만, 수신자가 카카오톡을 쓰지 않거나
141
+ * 알림톡을 차단한 경우(3104·3107)는 접수가 성공하고 **이통사 리포트에서만 판명된다.**
142
+ * 그래서 주기적으로 되짚어야 한다.
143
+ *
144
+ * 발송 시 심은 마커가 있는 건만 대상이라, 고객이 솔라피로 직접 보낸 알림톡은 건드리지 않는다.
145
+ */
146
+ declare function sweepFailedAlimtalk(options: SweepOptions): Promise<SweepResult>;
147
+
148
+ /** 우리가 접수하지 못한 건임을 알리는 코드. 솔라피 코드 체계와 겹치지 않는다 */
149
+ declare const CLAWOPS_FAILURE_STATUS_CODE = "CLAWOPS";
150
+ interface FallbackEvent {
151
+ to: string;
152
+ source: FallbackSource;
153
+ text: string;
154
+ /** 스윕에서 온 경우에만 채워진다 */
155
+ messageId?: string;
156
+ statusCode?: string;
157
+ }
158
+ type FallbackBlockedEvent = {
159
+ to: string;
160
+ messageId?: string;
161
+ statusCode?: string;
162
+ } & (Extract<FallbackText, {
163
+ ok: false;
164
+ }>
165
+ /** 스윕에서만 — 실패했지만 대체발송 대상 코드가 아니다 */
166
+ | {
167
+ ok: false;
168
+ reason: 'code_not_eligible';
169
+ }
170
+ /** 스윕에서만 — 문구는 만들었는데 ClawOps 가 거절했다 */
171
+ | {
172
+ ok: false;
173
+ reason: 'send_rejected';
174
+ });
175
+ /**
176
+ * 발송 실패 추적 방식.
177
+ *
178
+ * `polling`·`webhook` 은 일부러 넣지 않았다 — 유니온에 멤버를 추가하는 것은 하위호환이라
179
+ * 필요해지면 그때 붙이면 되고, 미리 선언하면 지키지 못할 약속이 된다.
180
+ */
181
+ type FallbackTracking =
182
+ /**
183
+ * 접수 실패(1XXX)만 대체발송. `send()` 응답으로 즉시 판정된다.
184
+ * 추적 옵션은 받지 않는다 — `mode` 없이 `lookbackMs` 를 써 놓고 스윕이 돈다고 오해하는 것을 막는다
185
+ */
186
+ {
187
+ mode?: undefined;
188
+ intervalMs?: never;
189
+ lookbackMs?: never;
190
+ on?: never;
191
+ initialCursor?: never;
192
+ onCursor?: never;
193
+ onError?: never;
194
+ }
195
+ /**
196
+ * 리포트를 주기적으로 훑어 발송 실패(3XXX)까지 대체발송한다.
197
+ * 수신자가 카카오톡을 쓰지 않거나 알림톡을 차단한 경우가 여기 해당한다.
198
+ */
199
+ | {
200
+ mode: 'sweep';
201
+ /** 스윕 주기. 기본 5분 */
202
+ intervalMs?: number;
203
+ /** 커서가 없을 때 거슬러 볼 구간. 기본 1시간 */
204
+ lookbackMs?: number;
205
+ /** 대체발송할 상태코드. 기본 3104·3107·3102 */
206
+ on?: readonly string[];
207
+ /** 훑을 메시지 타입. 기본은 알림톡만 — 친구톡까지 보려면 넓힌다 */
208
+ types?: SweepOptions['types'];
209
+ /** 프로세스 밖에 커서를 저장했다가 넘길 때 */
210
+ initialCursor?: SweepCursor;
211
+ onCursor?: (cursor: SweepCursor) => void;
212
+ /** 스윕 자체가 실패했을 때. 지정하지 않으면 조용히 다음 주기에 재시도한다 */
213
+ onError?: (error: unknown) => void;
214
+ };
215
+ type FallbackConfig = boolean
216
+ /**
217
+ * 끈 상태. 다른 옵션은 받지 않는다 —
218
+ * `{ enabled: false, mode: 'sweep' }` 처럼 써 놓고 스윕이 돈다고 오해하는 것을 막는다
219
+ */
220
+ | {
221
+ enabled: false;
222
+ mode?: never;
223
+ field?: never;
224
+ onFallback?: never;
225
+ onBlocked?: never;
226
+ } | ({
227
+ enabled: true;
228
+ /** 대체 문구를 읽어올 customFields 키 */
229
+ field?: string;
230
+ onFallback?: (event: FallbackEvent) => void;
231
+ onBlocked?: (event: FallbackBlockedEvent) => void;
232
+ } & FallbackTracking);
233
+ interface ClawOpsMessageServiceOptions {
234
+ /** ClawOps 클라이언트 */
235
+ clawops: ClawOps;
236
+ /** 문자 기본 발신번호. ClawOps 에 등록된 번호여야 한다 */
237
+ from: string;
238
+ /** 알림톡·RCS 등을 계속 쓸 때 넘긴다. 없으면 문자 전용 모드 */
239
+ solapi?: SolapiMessageService;
240
+ /**
241
+ * 알림톡 실패 시 문자로 대체발송.
242
+ * 기본은 접수 실패(1XXX)만 — 발송 실패(3XXX)까지 잡으려면 `mode: 'sweep'` 을 켠다.
243
+ */
244
+ fallback?: FallbackConfig;
245
+ /** 동시에 띄우는 ClawOps 요청 수. 기본 10 */
246
+ concurrency?: number;
247
+ }
248
+ /** solapi 는 send 만 노출하고 나머지는 감춘 문자 전용 서비스 */
249
+ type ClawOpsTextOnlyService = Pick<SolapiMessageService, 'send'>;
250
+ interface ClawOpsMessageServiceConstructor {
251
+ /** solapi 를 넘기면 SolapiMessageService 와 완전히 동일한 타입 */
252
+ new (options: ClawOpsMessageServiceOptions & {
253
+ solapi: SolapiMessageService;
254
+ }): SolapiMessageService;
255
+ /** 안 넘기면 문자 전용. 솔라피 기능은 타입에 나타나지 않는다 */
256
+ new (options: ClawOpsMessageServiceOptions & {
257
+ solapi?: undefined;
258
+ }): ClawOpsTextOnlyService;
259
+ /** 있는지 런타임에만 아는 경우. 두 경우 모두에서 안전한 문자 전용 타입을 준다 */
260
+ new (options: ClawOpsMessageServiceOptions): ClawOpsTextOnlyService;
261
+ }
262
+ /**
263
+ * 솔라피 코드를 그대로 두고 문자만 ClawOps 로 보낸다.
264
+ *
265
+ * `new` 의 결과 타입은 `SolapiMessageService` 그 자체라 기존 코드의 타입 자리에 그대로 들어간다.
266
+ */
267
+ declare const ClawOpsMessageService: ClawOpsMessageServiceConstructor;
268
+
269
+ export { CLAWOPS_FAILURE_STATUS_CODE, ClawOpsMessageService, type ClawOpsMessageServiceOptions, type ClawOpsTextOnlyService, DEFAULT_FALLBACK_CODES, DEFAULT_FALLBACK_FIELD, FALLBACK_MARKER_FIELD, type FallbackBlockedEvent, type FallbackConfig, type FallbackEvent, type FallbackSource, type FallbackText, type FallbackTracking, type SolapiMessage, type SweepCursor, type SweepOptions, type SweepResult, resolveFallbackText, sweepFailedAlimtalk };