stitchkit 0.100.3 → 0.101.1
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/CHANGELOG.md +96 -0
- package/dist/browser/chunked-upload.d.ts +80 -0
- package/dist/browser/chunked-upload.d.ts.map +1 -0
- package/dist/browser/client-executors.d.ts.map +1 -1
- package/dist/browser/client.d.ts.map +1 -1
- package/dist/browser/http.d.ts +3 -0
- package/dist/browser/http.d.ts.map +1 -1
- package/dist/browser/upload-progress.d.ts +26 -0
- package/dist/browser/upload-progress.d.ts.map +1 -0
- package/dist/contract/client-types.d.ts +19 -0
- package/dist/contract/client-types.d.ts.map +1 -1
- package/dist/entrypoints/application.js +2 -1
- package/dist/entrypoints/contract.d.ts +1 -1
- package/dist/entrypoints/contract.d.ts.map +1 -1
- package/dist/entrypoints/files.d.ts +1 -0
- package/dist/entrypoints/files.d.ts.map +1 -1
- package/dist/entrypoints/files.js +230 -0
- package/dist/entrypoints/index.d.ts +1 -0
- package/dist/entrypoints/index.d.ts.map +1 -1
- package/dist/entrypoints/index.js +90 -2
- package/dist/entrypoints/observability.js +2 -1
- package/dist/entrypoints/react.js +1 -1
- package/dist/entrypoints/remote.js +2 -2
- package/dist/entrypoints/telegram/screens.js +1 -1
- package/dist/entrypoints/telegram.d.ts +8 -4
- package/dist/entrypoints/telegram.d.ts.map +1 -1
- package/dist/entrypoints/telegram.js +277 -49
- package/dist/entrypoints/testing.js +2 -2
- package/dist/entrypoints/tools.js +2 -1
- package/dist/entrypoints/tracking.js +1 -1
- package/dist/files/chunk-spool.d.ts +66 -0
- package/dist/files/chunk-spool.d.ts.map +1 -0
- package/dist/{index-aaj97r4y.js → index-65jwqhcf.js} +27 -10
- package/dist/{index-6k1svmjh.js → index-e05cjgtf.js} +16 -13
- package/dist/{index-kzz6rq0w.js → index-e6c2spsd.js} +56 -18
- package/dist/{index-qjeggp8w.js → index-h43cepag.js} +139 -11
- package/dist/index-sznqf8fx.js +20 -0
- package/dist/internal/secret-shapes.d.ts +21 -0
- package/dist/internal/secret-shapes.d.ts.map +1 -0
- package/dist/observability/sanitize.d.ts.map +1 -1
- package/dist/telegram/bot-api.d.ts +7 -4
- package/dist/telegram/bot-api.d.ts.map +1 -1
- package/dist/telegram/init-data.d.ts +3 -44
- package/dist/telegram/init-data.d.ts.map +1 -1
- package/dist/telegram/operator-channel.d.ts +1 -18
- package/dist/telegram/operator-channel.d.ts.map +1 -1
- package/dist/telegram/operator-dedupe.d.ts +44 -0
- package/dist/telegram/operator-dedupe.d.ts.map +1 -0
- package/dist/telegram/send-failure.d.ts +6 -0
- package/dist/telegram/send-failure.d.ts.map +1 -1
- package/dist/telegram/update-intake.d.ts +5 -1
- package/dist/telegram/update-intake.d.ts.map +1 -1
- package/dist/telegram/update-store-check.d.ts +22 -0
- package/dist/telegram/update-store-check.d.ts.map +1 -0
- package/dist/telegram/update-store-postgres.d.ts +33 -0
- package/dist/telegram/update-store-postgres.d.ts.map +1 -0
- package/dist/telegram/user.d.ts +51 -0
- package/dist/telegram/user.d.ts.map +1 -0
- package/llms/files.txt +3 -0
- package/llms/observability.txt +13 -1
- package/llms/server.4.txt +16 -0
- package/llms/stitchkit.2.txt +157 -2
- package/llms/stitchkit.3.txt +5 -2
- package/llms/stitchkit.txt +3 -3
- package/llms/telegram.txt +91 -13
- package/llms/{upgrading-0.74.0-0.100.0.txt → upgrading-0.74.0-0.101.0.txt} +39 -3
- package/llms/upgrading.txt +1 -1
- package/llms-full.txt +324 -21
- package/llms.txt +6 -6
- package/package.json +1 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { type ChunkedUploadConfig, type ChunkedUploadPart, type ChunkedUploadProgress, type ChunkedUploadStart, uploadInChunks, } from '../browser/chunked-upload.js';
|
|
1
2
|
export { type ClientConfig, type ClientContract, type ClientFetch, type ClientRegistryValue, type ContractClientConfig, contractEndpointMatchers, createClient, createClients, createScopedClients, createScopedUrlBuilders, createUrlBuilder, createUrlBuilders, type PathPrefixArgs, type RegistryScope, type ScopeClientConfigs, type ScopedClientRegistry, type ScopedUrlBuilderRegistry, type UrlBuilderConfig, } from '../browser/client.js';
|
|
2
3
|
export { ApiError, type ApiEvent, type ApiEventListener, type ConfiguredHttpClient, createHttpClient, type HeaderProvider, type HttpClient, type HttpClientConfig, type RequestOptions, type UnauthorizedMatcher, } from '../browser/http.js';
|
|
3
4
|
export { createLiveStateController, type LiveStateController, type LiveStateControllerConfig, type LiveStateControllerError, type LiveStateControllerSnapshot, type LiveStateControllerStatus, LiveStateControllerStatusSchema, type LiveStateEventDecision, type LiveStatePhase, LiveStatePhaseSchema, type LiveStateSource, type LiveStateSourceOpenInput, type LiveStateSourceOpenResult, type LiveStateStopReason, LiveStateStopReasonSchema, type LiveStateSubscriberError, } from '../browser/live-state.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/entrypoints/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,wBAAwB,EACxB,YAAY,EACZ,aAAa,EACb,mBAAmB,EACnB,uBAAuB,EACvB,gBAAgB,EAChB,iBAAiB,EACjB,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EACzB,KAAK,wBAAwB,EAC7B,KAAK,gBAAgB,GACtB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,QAAQ,EACR,KAAK,QAAQ,EACb,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EACzB,gBAAgB,EAChB,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,mBAAmB,GACzB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,yBAAyB,EACzB,KAAK,mBAAmB,EACxB,KAAK,yBAAyB,EAC9B,KAAK,wBAAwB,EAC7B,KAAK,2BAA2B,EAChC,KAAK,yBAAyB,EAC9B,+BAA+B,EAC/B,KAAK,sBAAsB,EAC3B,KAAK,cAAc,EACnB,oBAAoB,EACpB,KAAK,eAAe,EACpB,KAAK,wBAAwB,EAC7B,KAAK,yBAAyB,EAC9B,KAAK,mBAAmB,EACxB,yBAAyB,EACzB,KAAK,wBAAwB,GAC9B,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EACV,yBAAyB,EACzB,mBAAmB,EACnB,uBAAuB,GACxB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,uBAAuB,EAC5B,iBAAiB,GAClB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,oBAAoB,EAAE,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAChF,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,KAAK,gBAAgB,EACrB,KAAK,YAAY,GAClB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,wBAAwB,EACxB,KAAK,kBAAkB,EACvB,KAAK,wBAAwB,GAC9B,MAAM,gCAAgC,CAAC;AACxC,YAAY,EACV,cAAc,EACd,qBAAqB,EACrB,cAAc,EACd,cAAc,EACd,oBAAoB,EACpB,yBAAyB,GAC1B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAClF,OAAO,EACL,KAAK,kBAAkB,EACvB,KAAK,eAAe,EACpB,WAAW,EACX,QAAQ,GACT,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,KAAK,OAAO,EACZ,KAAK,aAAa,EAClB,mBAAmB,EACnB,aAAa,GACd,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAKvD,OAAO,EAAE,cAAc,EAAE,KAAK,eAAe,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAGzF,OAAO,EACL,SAAS,EACT,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,EAChB,KAAK,YAAY,GAClB,MAAM,wBAAwB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/entrypoints/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,KAAK,qBAAqB,EAC1B,KAAK,kBAAkB,EACvB,cAAc,GACf,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,wBAAwB,EACxB,YAAY,EACZ,aAAa,EACb,mBAAmB,EACnB,uBAAuB,EACvB,gBAAgB,EAChB,iBAAiB,EACjB,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EACzB,KAAK,wBAAwB,EAC7B,KAAK,gBAAgB,GACtB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,QAAQ,EACR,KAAK,QAAQ,EACb,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EACzB,gBAAgB,EAChB,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,mBAAmB,GACzB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,yBAAyB,EACzB,KAAK,mBAAmB,EACxB,KAAK,yBAAyB,EAC9B,KAAK,wBAAwB,EAC7B,KAAK,2BAA2B,EAChC,KAAK,yBAAyB,EAC9B,+BAA+B,EAC/B,KAAK,sBAAsB,EAC3B,KAAK,cAAc,EACnB,oBAAoB,EACpB,KAAK,eAAe,EACpB,KAAK,wBAAwB,EAC7B,KAAK,yBAAyB,EAC9B,KAAK,mBAAmB,EACxB,yBAAyB,EACzB,KAAK,wBAAwB,GAC9B,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EACV,yBAAyB,EACzB,mBAAmB,EACnB,uBAAuB,GACxB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,uBAAuB,EAC5B,iBAAiB,GAClB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,oBAAoB,EAAE,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAChF,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,KAAK,gBAAgB,EACrB,KAAK,YAAY,GAClB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,wBAAwB,EACxB,KAAK,kBAAkB,EACvB,KAAK,wBAAwB,GAC9B,MAAM,gCAAgC,CAAC;AACxC,YAAY,EACV,cAAc,EACd,qBAAqB,EACrB,cAAc,EACd,cAAc,EACd,oBAAoB,EACpB,yBAAyB,GAC1B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAClF,OAAO,EACL,KAAK,kBAAkB,EACvB,KAAK,eAAe,EACpB,WAAW,EACX,QAAQ,GACT,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,KAAK,OAAO,EACZ,KAAK,aAAa,EAClB,mBAAmB,EACnB,aAAa,GACd,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAKvD,OAAO,EAAE,cAAc,EAAE,KAAK,eAAe,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAGzF,OAAO,EACL,SAAS,EACT,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,EAChB,KAAK,YAAY,GAClB,MAAM,wBAAwB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC"}
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
import {
|
|
9
9
|
ApiError2,
|
|
10
10
|
createHttpClient2
|
|
11
|
-
} from "../index-
|
|
11
|
+
} from "../index-h43cepag.js";
|
|
12
12
|
import"../index-m38ej7e5.js";
|
|
13
13
|
import {
|
|
14
14
|
DEFAULT_CONTRACT_STREAM_FRAME_BYTES2,
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
createUrlBuilder2,
|
|
25
25
|
createUrlBuilders2,
|
|
26
26
|
createScopedUrlBuilders2
|
|
27
|
-
} from "../index-
|
|
27
|
+
} from "../index-65jwqhcf.js";
|
|
28
28
|
import"../index-w0eqdhyd.js";
|
|
29
29
|
import {
|
|
30
30
|
LiveStatePhaseSchema2,
|
|
@@ -95,6 +95,93 @@ import {
|
|
|
95
95
|
zodIssues2,
|
|
96
96
|
formatZodError2
|
|
97
97
|
} from "../index-1eeqpadm.js";
|
|
98
|
+
|
|
99
|
+
// src/browser/chunked-upload.ts
|
|
100
|
+
function isRetryableUploadFailure(error) {
|
|
101
|
+
if (ApiError2.is(error)) {
|
|
102
|
+
if (error.status === 0)
|
|
103
|
+
return error.code === "UNKNOWN_ERROR" || error.code === "REQUEST_TIMEOUT";
|
|
104
|
+
return error.status === 429 || error.status >= 500;
|
|
105
|
+
}
|
|
106
|
+
return error instanceof TypeError;
|
|
107
|
+
}
|
|
108
|
+
async function uploadInChunks(config) {
|
|
109
|
+
const { file, chunkBytes, signal } = config;
|
|
110
|
+
if (!Number.isSafeInteger(chunkBytes) || chunkBytes <= 0) {
|
|
111
|
+
throw new TypeError("uploadInChunks: chunkBytes must be a positive integer");
|
|
112
|
+
}
|
|
113
|
+
if (file.size === 0)
|
|
114
|
+
throw new TypeError("uploadInChunks: the file is empty");
|
|
115
|
+
const totalBytes = file.size;
|
|
116
|
+
const chunkCount = Math.ceil(totalBytes / chunkBytes);
|
|
117
|
+
const uploadId = config.uploadId ?? crypto.randomUUID();
|
|
118
|
+
const retries = config.retries ?? 3;
|
|
119
|
+
const retryDelayMs = config.retryDelayMs ?? 1000;
|
|
120
|
+
const report = (sentBytes, index, attempt) => {
|
|
121
|
+
if (!config.onProgress)
|
|
122
|
+
return;
|
|
123
|
+
try {
|
|
124
|
+
config.onProgress({ sentBytes, totalBytes, index, chunkCount, attempt });
|
|
125
|
+
} catch {}
|
|
126
|
+
};
|
|
127
|
+
throwIfAborted(signal);
|
|
128
|
+
const opened = await repeating(() => config.init({ uploadId, totalBytes, chunkBytes, chunkCount }, { signal }), { retries, retryDelayMs, signal });
|
|
129
|
+
if (opened) {
|
|
130
|
+
report(totalBytes, chunkCount, 1);
|
|
131
|
+
return opened.finished;
|
|
132
|
+
}
|
|
133
|
+
report(0, 0, 1);
|
|
134
|
+
for (let index = 0;index < chunkCount; index += 1) {
|
|
135
|
+
throwIfAborted(signal);
|
|
136
|
+
const start = index * chunkBytes;
|
|
137
|
+
const bytes = file.slice(start, Math.min(totalBytes, start + chunkBytes));
|
|
138
|
+
await repeating((attempt) => config.chunk({ uploadId, index, bytes }, {
|
|
139
|
+
...signal && { signal },
|
|
140
|
+
onUploadProgress: (progress) => report(start + fileShare(progress, bytes.size), index, attempt)
|
|
141
|
+
}), { retries, retryDelayMs, signal });
|
|
142
|
+
report(start + bytes.size, index + 1, 1);
|
|
143
|
+
}
|
|
144
|
+
throwIfAborted(signal);
|
|
145
|
+
return config.finalize({ uploadId }, { signal });
|
|
146
|
+
}
|
|
147
|
+
function fileShare(progress, partBytes) {
|
|
148
|
+
if (progress.totalBytes === 0)
|
|
149
|
+
return 0;
|
|
150
|
+
return Math.floor(partBytes * progress.sentBytes / progress.totalBytes);
|
|
151
|
+
}
|
|
152
|
+
async function repeating(call, {
|
|
153
|
+
retries,
|
|
154
|
+
retryDelayMs,
|
|
155
|
+
signal
|
|
156
|
+
}) {
|
|
157
|
+
for (let attempt = 1;; attempt += 1) {
|
|
158
|
+
try {
|
|
159
|
+
return await call(attempt);
|
|
160
|
+
} catch (error) {
|
|
161
|
+
if (signal?.aborted || attempt > retries || !isRetryableUploadFailure(error))
|
|
162
|
+
throw error;
|
|
163
|
+
await pause(retryDelayMs * 2 ** (attempt - 1), signal);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
function throwIfAborted(signal) {
|
|
168
|
+
if (signal?.aborted) {
|
|
169
|
+
throw new ApiError2("REQUEST_ABORTED", 0, undefined, "Request was aborted");
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
function pause(ms, signal) {
|
|
173
|
+
return new Promise((resolve, reject) => {
|
|
174
|
+
const abort = () => {
|
|
175
|
+
clearTimeout(timer);
|
|
176
|
+
reject(new ApiError2("REQUEST_ABORTED", 0, undefined, "Request was aborted"));
|
|
177
|
+
};
|
|
178
|
+
const timer = setTimeout(() => {
|
|
179
|
+
signal?.removeEventListener("abort", abort);
|
|
180
|
+
resolve();
|
|
181
|
+
}, ms);
|
|
182
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
183
|
+
});
|
|
184
|
+
}
|
|
98
185
|
// src/browser/socket-io-trace.ts
|
|
99
186
|
var realtimeRequestTraces = new WeakMap;
|
|
100
187
|
function observeRealtimeRequestPhase(trace, phase) {
|
|
@@ -954,6 +1041,7 @@ export {
|
|
|
954
1041
|
rateLimited2 as rateLimited,
|
|
955
1042
|
resumableIterator,
|
|
956
1043
|
unauthorized2 as unauthorized,
|
|
1044
|
+
uploadInChunks,
|
|
957
1045
|
withToolView2 as withToolView,
|
|
958
1046
|
zodIssues2 as zodIssues
|
|
959
1047
|
};
|
|
@@ -19,12 +19,13 @@ import {
|
|
|
19
19
|
childSpan2,
|
|
20
20
|
resolvePropagationContext2
|
|
21
21
|
} from "../index-hby4qhax.js";
|
|
22
|
+
import"../index-sznqf8fx.js";
|
|
22
23
|
import {
|
|
23
24
|
redact2,
|
|
24
25
|
truncatePreview2,
|
|
25
26
|
sanitizePayload2,
|
|
26
27
|
measureSize2
|
|
27
|
-
} from "../index-
|
|
28
|
+
} from "../index-e6c2spsd.js";
|
|
28
29
|
import {
|
|
29
30
|
runWithRequestContext2,
|
|
30
31
|
getRequestContext2,
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import"../index-hby4qhax.js";
|
|
2
2
|
import {
|
|
3
3
|
ApiError2
|
|
4
|
-
} from "../index-
|
|
4
|
+
} from "../index-h43cepag.js";
|
|
5
5
|
import"../index-m38ej7e5.js";
|
|
6
6
|
import"../index-r5c4fedv.js";
|
|
7
7
|
import {
|
|
8
8
|
createClient2
|
|
9
|
-
} from "../index-
|
|
9
|
+
} from "../index-65jwqhcf.js";
|
|
10
10
|
import {
|
|
11
11
|
AppError2
|
|
12
12
|
} from "../index-n53mswt3.js";
|
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
export { callTelegramBotApi, TELEGRAM_BOT_TOKEN_PATTERN, type TelegramBotApiCall, TelegramBotApiError, } from '../telegram/bot-api.js';
|
|
1
|
+
export { callTelegramBotApi, redactTelegramBotToken, TELEGRAM_BOT_TOKEN_PATTERN, type TelegramBotApiCall, TelegramBotApiError, } from '../telegram/bot-api.js';
|
|
2
2
|
export { runTelegramBroadcast, type TelegramBroadcastConfig, type TelegramBroadcastMessage, type TelegramBroadcastOutcome, type TelegramBroadcastRecipient, type TelegramBroadcastReport, type TelegramBroadcastRunOutcome, type TelegramBroadcastSend, type TelegramBroadcastSenderConfig, telegramBroadcastSender, } from '../telegram/broadcast.js';
|
|
3
|
-
export { type TelegramInitData, type TelegramInitDataRefusal, type
|
|
3
|
+
export { type TelegramInitData, type TelegramInitDataRefusal, type TelegramInitDataVerification, type VerifyTelegramInitDataOptions, verifyTelegramInitData, } from '../telegram/init-data.js';
|
|
4
4
|
export { createTelegramLocalFiles, TelegramLocalFileError, type TelegramLocalFileRefusal, type TelegramLocalFiles, type TelegramLocalFilesCheck, type TelegramLocalFilesConfig, } from '../telegram/local-files.js';
|
|
5
|
-
export { createTelegramOperatorChannel, type TelegramChatId, type TelegramOperatorChannel, type TelegramOperatorChannelConfig, type
|
|
6
|
-
export {
|
|
5
|
+
export { createTelegramOperatorChannel, type TelegramChatId, type TelegramOperatorChannel, type TelegramOperatorChannelConfig, type TelegramOperatorDrop, type TelegramOperatorMessage, type TelegramOperatorSenderConfig, telegramOperatorSender, } from '../telegram/operator-channel.js';
|
|
6
|
+
export { createTelegramOperatorDedupe, type TelegramOperatorDedupe, type TelegramOperatorDedupeVerdict, } from '../telegram/operator-dedupe.js';
|
|
7
|
+
export { classifyTelegramSendFailure, TELEGRAM_SEND_FAILURE_REASONS, type TelegramSendFailure, type TelegramSendFailureReason, } from '../telegram/send-failure.js';
|
|
7
8
|
export { createTelegramUpdateIntake, type TelegramUpdateEnvelope, type TelegramUpdateFailure, type TelegramUpdateIntake, type TelegramUpdateIntakeConfig, type TelegramUpdateStoreStep, } from '../telegram/update-intake.js';
|
|
8
9
|
export { memoryTelegramUpdateStore, type StoredTelegramUpdate, type TelegramUpdateClaimOptions, type TelegramUpdateDueQuery, type TelegramUpdateSettlement, type TelegramUpdateState, type TelegramUpdateStore, } from '../telegram/update-store.js';
|
|
10
|
+
export { checkTelegramUpdateStore } from '../telegram/update-store-check.js';
|
|
11
|
+
export { type PostgresTelegramUpdateStoreConfig, postgresTelegramUpdateStore, postgresTelegramUpdateStoreSchema, type TelegramPostgresQuery, } from '../telegram/update-store-postgres.js';
|
|
9
12
|
export { type SqliteTelegramUpdateStoreConfig, sqliteTelegramUpdateStore, type TelegramSqliteDatabase, } from '../telegram/update-store-sqlite.js';
|
|
13
|
+
export { parseTelegramUser, type TelegramUser } from '../telegram/user.js';
|
|
10
14
|
export { type ClaimTelegramWebhookConfig, checkTelegramWebhook, claimTelegramWebhook, type ReceiveTelegramWebhookOptions, receiveTelegramWebhook, TELEGRAM_WEBHOOK_NONE, type TelegramUpdateAcceptance, type TelegramWebhookClaim, TelegramWebhookClaimError, type TelegramWebhookConfig, type TelegramWebhookRefusal, type TelegramWebhookState, telegramWebhookUrl, } from '../telegram/webhook.js';
|
|
11
15
|
//# sourceMappingURL=telegram.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"telegram.d.ts","sourceRoot":"","sources":["../../src/entrypoints/telegram.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,0BAA0B,EAC1B,KAAK,kBAAkB,EACvB,mBAAmB,GACpB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,oBAAoB,EACpB,KAAK,uBAAuB,EAC5B,KAAK,wBAAwB,EAC7B,KAAK,wBAAwB,EAC7B,KAAK,0BAA0B,EAC/B,KAAK,uBAAuB,EAC5B,KAAK,2BAA2B,EAChC,KAAK,qBAAqB,EAC1B,KAAK,6BAA6B,EAClC,uBAAuB,GACxB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,uBAAuB,EAC5B,KAAK,
|
|
1
|
+
{"version":3,"file":"telegram.d.ts","sourceRoot":"","sources":["../../src/entrypoints/telegram.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,sBAAsB,EACtB,0BAA0B,EAC1B,KAAK,kBAAkB,EACvB,mBAAmB,GACpB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,oBAAoB,EACpB,KAAK,uBAAuB,EAC5B,KAAK,wBAAwB,EAC7B,KAAK,wBAAwB,EAC7B,KAAK,0BAA0B,EAC/B,KAAK,uBAAuB,EAC5B,KAAK,2BAA2B,EAChC,KAAK,qBAAqB,EAC1B,KAAK,6BAA6B,EAClC,uBAAuB,GACxB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,uBAAuB,EAC5B,KAAK,4BAA4B,EACjC,KAAK,6BAA6B,EAClC,sBAAsB,GACvB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,wBAAwB,EACxB,sBAAsB,EACtB,KAAK,wBAAwB,EAC7B,KAAK,kBAAkB,EACvB,KAAK,uBAAuB,EAC5B,KAAK,wBAAwB,GAC9B,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,6BAA6B,EAC7B,KAAK,cAAc,EACnB,KAAK,uBAAuB,EAC5B,KAAK,6BAA6B,EAClC,KAAK,oBAAoB,EACzB,KAAK,uBAAuB,EAC5B,KAAK,4BAA4B,EACjC,sBAAsB,GACvB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EACL,4BAA4B,EAC5B,KAAK,sBAAsB,EAC3B,KAAK,6BAA6B,GACnC,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,2BAA2B,EAC3B,6BAA6B,EAC7B,KAAK,mBAAmB,EACxB,KAAK,yBAAyB,GAC/B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,0BAA0B,EAC1B,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EACzB,KAAK,0BAA0B,EAC/B,KAAK,uBAAuB,GAC7B,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,yBAAyB,EACzB,KAAK,oBAAoB,EACzB,KAAK,0BAA0B,EAC/B,KAAK,sBAAsB,EAC3B,KAAK,wBAAwB,EAC7B,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,GACzB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,wBAAwB,EAAE,MAAM,gCAAgC,CAAC;AAC1E,OAAO,EACL,KAAK,iCAAiC,EACtC,2BAA2B,EAC3B,iCAAiC,EACjC,KAAK,qBAAqB,GAC3B,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EACL,KAAK,+BAA+B,EACpC,yBAAyB,EACzB,KAAK,sBAAsB,GAC5B,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,iBAAiB,EAAE,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACxE,OAAO,EACL,KAAK,0BAA0B,EAC/B,oBAAoB,EACpB,oBAAoB,EACpB,KAAK,6BAA6B,EAClC,sBAAsB,EACtB,qBAAqB,EACrB,KAAK,wBAAwB,EAC7B,KAAK,oBAAoB,EACzB,yBAAyB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC3B,KAAK,oBAAoB,EACzB,kBAAkB,GACnB,MAAM,qBAAqB,CAAC"}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
|
+
BOT_TOKEN_SECRET
|
|
3
|
+
} from "../index-sznqf8fx.js";
|
|
4
|
+
import {
|
|
5
|
+
TELEGRAM_SEND_FAILURE_REASONS2,
|
|
2
6
|
classifyTelegramSendFailure2
|
|
3
|
-
} from "../index-
|
|
7
|
+
} from "../index-e05cjgtf.js";
|
|
4
8
|
import {
|
|
5
9
|
TELEGRAM_TEXT_LIMIT2,
|
|
6
10
|
truncateTelegramHtml2
|
|
@@ -11,7 +15,10 @@ import {
|
|
|
11
15
|
} from "../index-9jfdm5tg.js";
|
|
12
16
|
|
|
13
17
|
// src/telegram/bot-api.ts
|
|
14
|
-
var TELEGRAM_BOT_TOKEN_PATTERN =
|
|
18
|
+
var TELEGRAM_BOT_TOKEN_PATTERN = BOT_TOKEN_SECRET;
|
|
19
|
+
function redactTelegramBotToken(text) {
|
|
20
|
+
return text.replace(new RegExp(BOT_TOKEN_SECRET.source, "g"), "[redacted]");
|
|
21
|
+
}
|
|
15
22
|
var DEFAULT_API_ROOT = "https://api.telegram.org";
|
|
16
23
|
|
|
17
24
|
class TelegramBotApiError extends Error {
|
|
@@ -336,9 +343,6 @@ function telegramBroadcastSender(config) {
|
|
|
336
343
|
...config.fetch && { fetch: config.fetch }
|
|
337
344
|
});
|
|
338
345
|
}
|
|
339
|
-
// src/telegram/init-data.ts
|
|
340
|
-
import { z as z2 } from "zod";
|
|
341
|
-
|
|
342
346
|
// src/telegram/crypto.ts
|
|
343
347
|
var encoder = new TextEncoder;
|
|
344
348
|
async function hmacSha256(key, message) {
|
|
@@ -358,9 +362,9 @@ function digestsEqual(a, b) {
|
|
|
358
362
|
return difference === 0;
|
|
359
363
|
}
|
|
360
364
|
|
|
361
|
-
// src/telegram/
|
|
362
|
-
|
|
363
|
-
var
|
|
365
|
+
// src/telegram/user.ts
|
|
366
|
+
import { z as z2 } from "zod";
|
|
367
|
+
var TelegramUserSchema = z2.object({
|
|
364
368
|
id: z2.int(),
|
|
365
369
|
is_bot: z2.boolean().optional(),
|
|
366
370
|
first_name: z2.string(),
|
|
@@ -381,11 +385,21 @@ var TelegramInitDataUserSchema = z2.object({
|
|
|
381
385
|
...user.allows_write_to_pm !== undefined && { allowsWriteToPm: user.allows_write_to_pm },
|
|
382
386
|
...user.photo_url !== undefined && { photoUrl: user.photo_url }
|
|
383
387
|
}));
|
|
388
|
+
function parseTelegramUser(value) {
|
|
389
|
+
const parsed = TelegramUserSchema.safeParse(value);
|
|
390
|
+
return parsed.success ? parsed.data : undefined;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// src/telegram/init-data.ts
|
|
394
|
+
var encoder2 = new TextEncoder;
|
|
384
395
|
function optionalUser(raw, key) {
|
|
385
396
|
const encoded = raw[key];
|
|
386
397
|
if (encoded === undefined)
|
|
387
398
|
return;
|
|
388
|
-
|
|
399
|
+
const user = parseTelegramUser(JSON.parse(encoded));
|
|
400
|
+
if (user === undefined)
|
|
401
|
+
throw new TypeError(`initData ${key} is not a Telegram user`);
|
|
402
|
+
return user;
|
|
389
403
|
}
|
|
390
404
|
async function verifyTelegramInitData(options) {
|
|
391
405
|
if (!options.botToken)
|
|
@@ -531,42 +545,12 @@ function createTelegramLocalFiles(config) {
|
|
|
531
545
|
}
|
|
532
546
|
};
|
|
533
547
|
}
|
|
534
|
-
// src/telegram/operator-
|
|
535
|
-
var MASK = "[redacted]";
|
|
536
|
-
function masked(text, patterns) {
|
|
537
|
-
let output = text;
|
|
538
|
-
for (const pattern of patterns) {
|
|
539
|
-
output = output.replace(new RegExp(pattern.source, `${pattern.flags.replace(/[gy]/g, "")}g`), MASK);
|
|
540
|
-
}
|
|
541
|
-
return output;
|
|
542
|
-
}
|
|
543
|
-
function literal(value) {
|
|
544
|
-
return new RegExp(value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
545
|
-
}
|
|
546
|
-
function timer(milliseconds, signal) {
|
|
547
|
-
return new Promise((resolve, reject) => {
|
|
548
|
-
if (signal.aborted)
|
|
549
|
-
return reject(signal.reason);
|
|
550
|
-
const handle = setTimeout(() => {
|
|
551
|
-
signal.removeEventListener("abort", stop);
|
|
552
|
-
resolve();
|
|
553
|
-
}, milliseconds);
|
|
554
|
-
const stop = () => {
|
|
555
|
-
clearTimeout(handle);
|
|
556
|
-
reject(signal.reason);
|
|
557
|
-
};
|
|
558
|
-
signal.addEventListener("abort", stop, { once: true });
|
|
559
|
-
});
|
|
560
|
-
}
|
|
561
|
-
function fitted(text) {
|
|
562
|
-
const points = Array.from(text);
|
|
563
|
-
return points.length <= TELEGRAM_TEXT_LIMIT2 ? text : `${points.slice(0, TELEGRAM_TEXT_LIMIT2 - 1).join("")}…`;
|
|
564
|
-
}
|
|
548
|
+
// src/telegram/operator-dedupe.ts
|
|
565
549
|
function defaultFingerprint(text, topic) {
|
|
566
550
|
const blanked = text.replace(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, "#").replace(/\b[a-z0-9]{20,}\b/gi, "#").replace(/\d+/g, "#");
|
|
567
551
|
return `${topic ?? ""}\x00${blanked.slice(0, 300)}`;
|
|
568
552
|
}
|
|
569
|
-
function
|
|
553
|
+
function createTelegramOperatorDedupe(options = {}) {
|
|
570
554
|
const windowMs = options.windowMs ?? 600000;
|
|
571
555
|
const maxPerWindow = options.maxPerWindow ?? 20;
|
|
572
556
|
const fingerprint = options.fingerprint ?? defaultFingerprint;
|
|
@@ -610,6 +594,38 @@ function dedupeWindow(options) {
|
|
|
610
594
|
`) };
|
|
611
595
|
};
|
|
612
596
|
}
|
|
597
|
+
|
|
598
|
+
// src/telegram/operator-channel.ts
|
|
599
|
+
var MASK = "[redacted]";
|
|
600
|
+
function masked(text, patterns) {
|
|
601
|
+
let output = text;
|
|
602
|
+
for (const pattern of patterns) {
|
|
603
|
+
output = output.replace(new RegExp(pattern.source, `${pattern.flags.replace(/[gy]/g, "")}g`), MASK);
|
|
604
|
+
}
|
|
605
|
+
return output;
|
|
606
|
+
}
|
|
607
|
+
function literal(value) {
|
|
608
|
+
return new RegExp(value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
609
|
+
}
|
|
610
|
+
function timer(milliseconds, signal) {
|
|
611
|
+
return new Promise((resolve, reject) => {
|
|
612
|
+
if (signal.aborted)
|
|
613
|
+
return reject(signal.reason);
|
|
614
|
+
const handle = setTimeout(() => {
|
|
615
|
+
signal.removeEventListener("abort", stop);
|
|
616
|
+
resolve();
|
|
617
|
+
}, milliseconds);
|
|
618
|
+
const stop = () => {
|
|
619
|
+
clearTimeout(handle);
|
|
620
|
+
reject(signal.reason);
|
|
621
|
+
};
|
|
622
|
+
signal.addEventListener("abort", stop, { once: true });
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
function fitted(text) {
|
|
626
|
+
const points = Array.from(text);
|
|
627
|
+
return points.length <= TELEGRAM_TEXT_LIMIT2 ? text : `${points.slice(0, TELEGRAM_TEXT_LIMIT2 - 1).join("")}…`;
|
|
628
|
+
}
|
|
613
629
|
function createTelegramOperatorChannel(config) {
|
|
614
630
|
const maxQueued = config.maxQueued ?? 200;
|
|
615
631
|
const minIntervalMs = config.minIntervalMs ?? 3000;
|
|
@@ -620,7 +636,7 @@ function createTelegramOperatorChannel(config) {
|
|
|
620
636
|
...config.sensitivePatterns ?? [],
|
|
621
637
|
...[...config.sensitiveValues ?? []].filter((value) => value !== "").sort((left, right) => right.length - left.length).map(literal)
|
|
622
638
|
];
|
|
623
|
-
const admit = config.dedupe ?
|
|
639
|
+
const admit = config.dedupe ? createTelegramOperatorDedupe(config.dedupe) : undefined;
|
|
624
640
|
const queue = [];
|
|
625
641
|
const idle = new Set;
|
|
626
642
|
let lifetime = new AbortController;
|
|
@@ -809,6 +825,9 @@ function parsed(body) {
|
|
|
809
825
|
return;
|
|
810
826
|
}
|
|
811
827
|
}
|
|
828
|
+
function handlerError(error) {
|
|
829
|
+
return error instanceof Error && error.name === "BotError" && "ctx" in error && "error" in error ? error.error : error;
|
|
830
|
+
}
|
|
812
831
|
function positive(name, value) {
|
|
813
832
|
if (!Number.isSafeInteger(value) || value < 1) {
|
|
814
833
|
throw new RangeError(`[stitchkit] telegram update intake: ${name} is a positive integer`);
|
|
@@ -862,7 +881,8 @@ function createTelegramUpdateIntake(config) {
|
|
|
862
881
|
try {
|
|
863
882
|
await config.handle(transportResult(update));
|
|
864
883
|
settlement = { state: "completed", at: now() };
|
|
865
|
-
} catch (
|
|
884
|
+
} catch (thrown) {
|
|
885
|
+
const error = handlerError(thrown);
|
|
866
886
|
const delay = number >= maxAttempts ? false : retry(error, number);
|
|
867
887
|
const message = error instanceof Error ? error.message : String(error);
|
|
868
888
|
settlement = delay === false ? { state: "abandoned", at: now(), error: message } : { state: "failed", at: now(), retryAt: now() + delay, error: message };
|
|
@@ -1022,9 +1042,210 @@ function memoryTelegramUpdateStore() {
|
|
|
1022
1042
|
}
|
|
1023
1043
|
};
|
|
1024
1044
|
}
|
|
1025
|
-
// src/telegram/update-store-
|
|
1045
|
+
// src/telegram/update-store-check.ts
|
|
1046
|
+
function same(actual, expected) {
|
|
1047
|
+
return JSON.stringify(actual) === JSON.stringify(expected);
|
|
1048
|
+
}
|
|
1049
|
+
var claim = { maxAttempts: 2 };
|
|
1050
|
+
var RULES = {
|
|
1051
|
+
"one record per update_id": async (store, violated) => {
|
|
1052
|
+
if (!await store.add({ updateId: 7, body: "b", receivedAt: 0 }))
|
|
1053
|
+
violated("add of a new update returned false");
|
|
1054
|
+
if (await store.add({ updateId: 7, body: "other", receivedAt: 1 }))
|
|
1055
|
+
violated("add of a recorded update_id returned true");
|
|
1056
|
+
},
|
|
1057
|
+
"a live lease is not taken, a lapsed one is": async (store, violated) => {
|
|
1058
|
+
await store.add({ updateId: 7, body: "b", receivedAt: 0 });
|
|
1059
|
+
if (await store.claim(7, { ...claim, now: 10, leaseUntil: 100 }) !== 1)
|
|
1060
|
+
violated("the first claim was not attempt 1");
|
|
1061
|
+
if (await store.claim(7, { ...claim, now: 50, leaseUntil: 150 }) !== undefined)
|
|
1062
|
+
violated("a claim took an update under a live lease");
|
|
1063
|
+
if (!same(await store.due({ now: 50, pendingBefore: 50, limit: 10 }), []))
|
|
1064
|
+
violated("due listed an update under a live lease");
|
|
1065
|
+
if (!same(await store.due({ now: 101, pendingBefore: 0, limit: 10 }), [
|
|
1066
|
+
{ updateId: 7, body: "b" }
|
|
1067
|
+
]))
|
|
1068
|
+
violated("due did not list an update whose lease lapsed, with its body");
|
|
1069
|
+
if (await store.claim(7, { ...claim, now: 101, leaseUntil: 200 }) !== 2)
|
|
1070
|
+
violated("a lapsed lease was not taken as attempt 2");
|
|
1071
|
+
},
|
|
1072
|
+
"only the attempt holding the update renews or settles it": async (store, violated) => {
|
|
1073
|
+
await store.add({ updateId: 1, body: "b", receivedAt: 0 });
|
|
1074
|
+
await store.claim(1, { ...claim, now: 0, leaseUntil: 10 });
|
|
1075
|
+
await store.claim(1, { ...claim, now: 11, leaseUntil: 50 });
|
|
1076
|
+
if (await store.renew(1, 1, 500))
|
|
1077
|
+
violated("an attempt that lost its lease renewed it");
|
|
1078
|
+
if (!await store.renew(1, 2, 60))
|
|
1079
|
+
violated("the holding attempt could not renew");
|
|
1080
|
+
await store.settle(1, 1, { state: "completed", at: 20 });
|
|
1081
|
+
if ((await store.due({ now: 61, pendingBefore: 0, limit: 10 })).length !== 1)
|
|
1082
|
+
violated("an attempt that lost its lease settled the update");
|
|
1083
|
+
await store.settle(1, 2, { state: "completed", at: 30 });
|
|
1084
|
+
if (!same(await store.due({ now: 1000, pendingBefore: 1000, limit: 10 }), []))
|
|
1085
|
+
violated("a completed update is still due");
|
|
1086
|
+
},
|
|
1087
|
+
"a failed update is due at its retry, spent attempts abandon it": async (store, violated) => {
|
|
1088
|
+
await store.add({ updateId: 2, body: "b", receivedAt: 0 });
|
|
1089
|
+
await store.add({ updateId: 1, body: "a", receivedAt: 5 });
|
|
1090
|
+
await store.claim(2, { ...claim, now: 0, leaseUntil: 10 });
|
|
1091
|
+
await store.settle(2, 1, { state: "failed", at: 1, retryAt: 30, error: "x" });
|
|
1092
|
+
if (!same(await store.due({ now: 29, pendingBefore: 0, limit: 10 }), []))
|
|
1093
|
+
violated("a failed update was due before its retry, or a pending one before its grace");
|
|
1094
|
+
const due = await store.due({ now: 30, pendingBefore: 5, limit: 10 });
|
|
1095
|
+
if (!same(due.map((row) => row.updateId), [1, 2]))
|
|
1096
|
+
violated("due did not list a retry and a pending update in update_id order");
|
|
1097
|
+
if ((await store.due({ now: 30, pendingBefore: 5, limit: 1 })).length !== 1)
|
|
1098
|
+
violated("due ignored its limit");
|
|
1099
|
+
await store.claim(2, { ...claim, now: 30, leaseUntil: 40 });
|
|
1100
|
+
await store.settle(2, 2, { state: "failed", at: 31, retryAt: 35, error: "x" });
|
|
1101
|
+
if (await store.claim(2, { ...claim, now: 36, leaseUntil: 50 }) !== undefined)
|
|
1102
|
+
violated("an update past maxAttempts was claimed");
|
|
1103
|
+
if (!same(await store.due({ now: 100, pendingBefore: 0, limit: 10 }), []))
|
|
1104
|
+
violated("an update past maxAttempts was not abandoned");
|
|
1105
|
+
},
|
|
1106
|
+
"settled updates are forgotten after they are pruned": async (store, violated) => {
|
|
1107
|
+
await store.add({ updateId: 3, body: "b", receivedAt: 0 });
|
|
1108
|
+
await store.claim(3, { ...claim, now: 0, leaseUntil: 10 });
|
|
1109
|
+
await store.settle(3, 1, { state: "abandoned", at: 36, error: "x" });
|
|
1110
|
+
await store.add({ updateId: 4, body: "b", receivedAt: 0 });
|
|
1111
|
+
if (await store.prune(36) !== 0)
|
|
1112
|
+
violated("prune forgot an update settled at its bound");
|
|
1113
|
+
if (await store.prune(37) !== 1)
|
|
1114
|
+
violated("prune did not forget exactly the settled update before its bound");
|
|
1115
|
+
if (!await store.add({ updateId: 3, body: "b", receivedAt: 40 }))
|
|
1116
|
+
violated("a pruned update_id could not be recorded again");
|
|
1117
|
+
if (await store.add({ updateId: 4, body: "b", receivedAt: 40 }))
|
|
1118
|
+
violated("prune forgot an update that was never settled");
|
|
1119
|
+
},
|
|
1120
|
+
"of concurrent claims exactly one takes the update": async (store, violated) => {
|
|
1121
|
+
await store.add({ updateId: 9, body: "b", receivedAt: 0 });
|
|
1122
|
+
const taken = await Promise.all(Array.from({ length: 8 }, () => store.claim(9, { maxAttempts: 5, now: 1, leaseUntil: 100 })));
|
|
1123
|
+
if (taken.filter((attempt) => attempt !== undefined).length !== 1)
|
|
1124
|
+
violated(`${taken.filter((attempt) => attempt !== undefined).length} of 8 concurrent claims took one update`);
|
|
1125
|
+
}
|
|
1126
|
+
};
|
|
1127
|
+
async function checkTelegramUpdateStore(make) {
|
|
1128
|
+
const violations = [];
|
|
1129
|
+
for (const [name, rule] of Object.entries(RULES)) {
|
|
1130
|
+
try {
|
|
1131
|
+
await rule(await make(), (what) => violations.push(`${name}: ${what}`));
|
|
1132
|
+
} catch (error) {
|
|
1133
|
+
violations.push(`${name}: threw ${error instanceof Error ? error.message : String(error)}`);
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
return violations;
|
|
1137
|
+
}
|
|
1138
|
+
// src/telegram/update-store-postgres.ts
|
|
1026
1139
|
var TABLE_NAME = /^[A-Za-z_][A-Za-z0-9_]{0,62}$/;
|
|
1027
|
-
|
|
1140
|
+
function checkedTable(table) {
|
|
1141
|
+
if (!TABLE_NAME.test(table)) {
|
|
1142
|
+
throw new TypeError("[stitchkit] telegram update store: a table name is [A-Za-z_][A-Za-z0-9_]*");
|
|
1143
|
+
}
|
|
1144
|
+
return table;
|
|
1145
|
+
}
|
|
1146
|
+
function postgresTelegramUpdateStoreSchema(table = "telegram_updates") {
|
|
1147
|
+
const name = checkedTable(table);
|
|
1148
|
+
return `CREATE TABLE IF NOT EXISTS ${name} (
|
|
1149
|
+
update_id BIGINT PRIMARY KEY,
|
|
1150
|
+
body TEXT NOT NULL,
|
|
1151
|
+
state TEXT NOT NULL,
|
|
1152
|
+
attempts INTEGER NOT NULL,
|
|
1153
|
+
due_at BIGINT NOT NULL,
|
|
1154
|
+
received_at BIGINT NOT NULL,
|
|
1155
|
+
settled_at BIGINT,
|
|
1156
|
+
error TEXT
|
|
1157
|
+
);
|
|
1158
|
+
CREATE INDEX IF NOT EXISTS ${name}_due ON ${name} (state, due_at);`;
|
|
1159
|
+
}
|
|
1160
|
+
var CLAIMABLE = `(state = 'pending' OR (state = 'failed' AND due_at <= $1::bigint)
|
|
1161
|
+
OR (state = 'processing' AND due_at < $1::bigint))`;
|
|
1162
|
+
function integer(value) {
|
|
1163
|
+
if (typeof value === "number")
|
|
1164
|
+
return value;
|
|
1165
|
+
if (typeof value === "bigint" || typeof value === "string" && /^-?\d+$/.test(value)) {
|
|
1166
|
+
return Number(value);
|
|
1167
|
+
}
|
|
1168
|
+
return;
|
|
1169
|
+
}
|
|
1170
|
+
function field(row, name) {
|
|
1171
|
+
return typeof row === "object" && row !== null && name in row ? Reflect.get(row, name) : undefined;
|
|
1172
|
+
}
|
|
1173
|
+
function postgresTelegramUpdateStore(config) {
|
|
1174
|
+
const table = checkedTable(config.table ?? "telegram_updates");
|
|
1175
|
+
const { query } = config;
|
|
1176
|
+
let ready;
|
|
1177
|
+
const run = async (text, parameters) => {
|
|
1178
|
+
if (config.createTable !== false) {
|
|
1179
|
+
ready ??= postgresTelegramUpdateStoreSchema(table).split(";").map((statement) => statement.trim()).filter((statement) => statement !== "").reduce((previous, statement) => previous.then(() => query(statement, [])), Promise.resolve()).then(() => {
|
|
1180
|
+
return;
|
|
1181
|
+
}, (error) => {
|
|
1182
|
+
ready = undefined;
|
|
1183
|
+
throw error;
|
|
1184
|
+
});
|
|
1185
|
+
await ready;
|
|
1186
|
+
}
|
|
1187
|
+
return query(text, parameters);
|
|
1188
|
+
};
|
|
1189
|
+
return {
|
|
1190
|
+
async add(update) {
|
|
1191
|
+
const rows = await run(`INSERT INTO ${table} (update_id, body, state, attempts, due_at, received_at)
|
|
1192
|
+
VALUES ($1::bigint, $2::text, 'pending', 0, $3::bigint, $3::bigint)
|
|
1193
|
+
ON CONFLICT (update_id) DO NOTHING RETURNING update_id`, [update.updateId, update.body, update.receivedAt]);
|
|
1194
|
+
return rows.length > 0;
|
|
1195
|
+
},
|
|
1196
|
+
async claim(updateId, options) {
|
|
1197
|
+
const [row] = await run(`UPDATE ${table} SET state = 'processing', attempts = attempts + 1,
|
|
1198
|
+
due_at = $2::bigint, error = NULL
|
|
1199
|
+
WHERE update_id = $3::bigint AND attempts < $4::integer AND ${CLAIMABLE}
|
|
1200
|
+
RETURNING attempts`, [options.now, options.leaseUntil, updateId, options.maxAttempts]);
|
|
1201
|
+
const attempts = integer(field(row, "attempts"));
|
|
1202
|
+
if (attempts !== undefined)
|
|
1203
|
+
return attempts;
|
|
1204
|
+
await run(`UPDATE ${table} SET state = 'abandoned', settled_at = $1::bigint, error = 'attempts spent'
|
|
1205
|
+
WHERE update_id = $2::bigint AND attempts >= $3::integer AND ${CLAIMABLE}
|
|
1206
|
+
RETURNING update_id`, [options.now, updateId, options.maxAttempts]);
|
|
1207
|
+
return;
|
|
1208
|
+
},
|
|
1209
|
+
async renew(updateId, attempt, leaseUntil) {
|
|
1210
|
+
const rows = await run(`UPDATE ${table} SET due_at = $1::bigint
|
|
1211
|
+
WHERE update_id = $2::bigint AND attempts = $3::integer AND state = 'processing'
|
|
1212
|
+
RETURNING update_id`, [leaseUntil, updateId, attempt]);
|
|
1213
|
+
return rows.length > 0;
|
|
1214
|
+
},
|
|
1215
|
+
async settle(updateId, attempt, settlement) {
|
|
1216
|
+
await run(`UPDATE ${table} SET state = $1::text, due_at = COALESCE($2::bigint, due_at),
|
|
1217
|
+
settled_at = $3::bigint, error = $4::text
|
|
1218
|
+
WHERE update_id = $5::bigint AND attempts = $6::integer AND state = 'processing'
|
|
1219
|
+
RETURNING update_id`, [
|
|
1220
|
+
settlement.state,
|
|
1221
|
+
settlement.state === "failed" ? settlement.retryAt : null,
|
|
1222
|
+
settlement.state === "failed" ? null : settlement.at,
|
|
1223
|
+
settlement.state === "completed" ? null : settlement.error,
|
|
1224
|
+
updateId,
|
|
1225
|
+
attempt
|
|
1226
|
+
]);
|
|
1227
|
+
},
|
|
1228
|
+
async due(options) {
|
|
1229
|
+
const rows = await run(`SELECT update_id, body FROM ${table}
|
|
1230
|
+
WHERE (state = 'pending' AND received_at <= $2::bigint)
|
|
1231
|
+
OR (state <> 'pending' AND ${CLAIMABLE})
|
|
1232
|
+
ORDER BY update_id LIMIT $3::integer`, [options.now, options.pendingBefore, options.limit]);
|
|
1233
|
+
return rows.flatMap((row) => {
|
|
1234
|
+
const updateId = integer(field(row, "update_id"));
|
|
1235
|
+
const body = field(row, "body");
|
|
1236
|
+
return updateId !== undefined && typeof body === "string" ? [{ updateId, body }] : [];
|
|
1237
|
+
});
|
|
1238
|
+
},
|
|
1239
|
+
async prune(before) {
|
|
1240
|
+
const rows = await run(`DELETE FROM ${table} WHERE state IN ('completed', 'abandoned') AND settled_at < $1::bigint
|
|
1241
|
+
RETURNING update_id`, [before]);
|
|
1242
|
+
return rows.length;
|
|
1243
|
+
}
|
|
1244
|
+
};
|
|
1245
|
+
}
|
|
1246
|
+
// src/telegram/update-store-sqlite.ts
|
|
1247
|
+
var TABLE_NAME2 = /^[A-Za-z_][A-Za-z0-9_]{0,62}$/;
|
|
1248
|
+
var CLAIMABLE2 = `(state = 'pending' OR (state = 'failed' AND due_at <= ?1)
|
|
1028
1249
|
OR (state = 'processing' AND due_at < ?1))`;
|
|
1029
1250
|
function rowOf(row) {
|
|
1030
1251
|
if (typeof row !== "object" || row === null)
|
|
@@ -1035,7 +1256,7 @@ function rowOf(row) {
|
|
|
1035
1256
|
}
|
|
1036
1257
|
function sqliteTelegramUpdateStore(config) {
|
|
1037
1258
|
const table = config.table ?? "telegram_updates";
|
|
1038
|
-
if (!
|
|
1259
|
+
if (!TABLE_NAME2.test(table)) {
|
|
1039
1260
|
throw new TypeError("[stitchkit] telegram update store: a table name is [A-Za-z_][A-Za-z0-9_]*");
|
|
1040
1261
|
}
|
|
1041
1262
|
const database = config.database;
|
|
@@ -1053,14 +1274,14 @@ function sqliteTelegramUpdateStore(config) {
|
|
|
1053
1274
|
const insert = database.prepare(`INSERT OR IGNORE INTO ${table} (update_id, body, state, attempts, due_at, received_at)
|
|
1054
1275
|
VALUES (?1, ?2, 'pending', 0, ?3, ?3)`);
|
|
1055
1276
|
const take = database.prepare(`UPDATE ${table} SET state = 'processing', attempts = attempts + 1, due_at = ?2, error = NULL
|
|
1056
|
-
WHERE update_id = ?3 AND attempts < ?4 AND ${
|
|
1277
|
+
WHERE update_id = ?3 AND attempts < ?4 AND ${CLAIMABLE2} RETURNING attempts`);
|
|
1057
1278
|
const spend = database.prepare(`UPDATE ${table} SET state = 'abandoned', settled_at = ?1, error = 'attempts spent'
|
|
1058
|
-
WHERE update_id = ?2 AND attempts >= ?3 AND ${
|
|
1279
|
+
WHERE update_id = ?2 AND attempts >= ?3 AND ${CLAIMABLE2}`);
|
|
1059
1280
|
const renew = database.prepare(`UPDATE ${table} SET due_at = ?1 WHERE update_id = ?2 AND attempts = ?3 AND state = 'processing'`);
|
|
1060
1281
|
const settle = database.prepare(`UPDATE ${table} SET state = ?1, due_at = COALESCE(?2, due_at), settled_at = ?3, error = ?4
|
|
1061
1282
|
WHERE update_id = ?5 AND attempts = ?6 AND state = 'processing'`);
|
|
1062
1283
|
const due = database.prepare(`SELECT update_id, body FROM ${table}
|
|
1063
|
-
WHERE (state = 'pending' AND received_at <= ?2) OR (state <> 'pending' AND ${
|
|
1284
|
+
WHERE (state = 'pending' AND received_at <= ?2) OR (state <> 'pending' AND ${CLAIMABLE2})
|
|
1064
1285
|
ORDER BY update_id LIMIT ?3`);
|
|
1065
1286
|
const prune = database.prepare(`DELETE FROM ${table} WHERE state IN ('completed', 'abandoned') AND settled_at < ?1`);
|
|
1066
1287
|
return {
|
|
@@ -1199,19 +1420,26 @@ async function receiveTelegramWebhook(request, options) {
|
|
|
1199
1420
|
}
|
|
1200
1421
|
export {
|
|
1201
1422
|
TELEGRAM_BOT_TOKEN_PATTERN,
|
|
1423
|
+
TELEGRAM_SEND_FAILURE_REASONS2 as TELEGRAM_SEND_FAILURE_REASONS,
|
|
1202
1424
|
TELEGRAM_WEBHOOK_NONE,
|
|
1203
1425
|
TelegramBotApiError,
|
|
1204
1426
|
TelegramLocalFileError,
|
|
1205
1427
|
TelegramWebhookClaimError,
|
|
1206
1428
|
callTelegramBotApi,
|
|
1429
|
+
checkTelegramUpdateStore,
|
|
1207
1430
|
checkTelegramWebhook,
|
|
1208
1431
|
claimTelegramWebhook,
|
|
1209
1432
|
classifyTelegramSendFailure2 as classifyTelegramSendFailure,
|
|
1210
1433
|
createTelegramLocalFiles,
|
|
1211
1434
|
createTelegramOperatorChannel,
|
|
1435
|
+
createTelegramOperatorDedupe,
|
|
1212
1436
|
createTelegramUpdateIntake,
|
|
1213
1437
|
memoryTelegramUpdateStore,
|
|
1438
|
+
parseTelegramUser,
|
|
1439
|
+
postgresTelegramUpdateStore,
|
|
1440
|
+
postgresTelegramUpdateStoreSchema,
|
|
1214
1441
|
receiveTelegramWebhook,
|
|
1442
|
+
redactTelegramBotToken,
|
|
1215
1443
|
runTelegramBroadcast,
|
|
1216
1444
|
sqliteTelegramUpdateStore,
|
|
1217
1445
|
telegramBroadcastSender,
|