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
|
@@ -22,52 +22,12 @@
|
|
|
22
22
|
* Not a domain model (→ ADR 0002): "Telegram signed this string" is a fact
|
|
23
23
|
* about a transport, the same kind of fact as a bearer token being well-formed.
|
|
24
24
|
*/
|
|
25
|
-
import {
|
|
26
|
-
/**
|
|
27
|
-
* The Telegram user record, as our surface names things.
|
|
28
|
-
*
|
|
29
|
-
* Telegram's wire fields are `snake_case`; every other name this package
|
|
30
|
-
* publishes is `camelCase`, and a surface that switches convention because of
|
|
31
|
-
* where a value came from makes the caller remember which is which. The
|
|
32
|
-
* untouched wire pairs stay available in `raw`.
|
|
33
|
-
*/
|
|
34
|
-
declare const TelegramInitDataUserSchema: z.ZodPipe<z.ZodObject<{
|
|
35
|
-
id: z.ZodInt;
|
|
36
|
-
is_bot: z.ZodOptional<z.ZodBoolean>;
|
|
37
|
-
first_name: z.ZodString;
|
|
38
|
-
last_name: z.ZodOptional<z.ZodString>;
|
|
39
|
-
username: z.ZodOptional<z.ZodString>;
|
|
40
|
-
language_code: z.ZodOptional<z.ZodString>;
|
|
41
|
-
is_premium: z.ZodOptional<z.ZodBoolean>;
|
|
42
|
-
allows_write_to_pm: z.ZodOptional<z.ZodBoolean>;
|
|
43
|
-
photo_url: z.ZodOptional<z.ZodString>;
|
|
44
|
-
}, z.core.$strip>, z.ZodTransform<{
|
|
45
|
-
id: number;
|
|
46
|
-
firstName: string;
|
|
47
|
-
isBot?: boolean | undefined;
|
|
48
|
-
lastName?: string | undefined;
|
|
49
|
-
username?: string | undefined;
|
|
50
|
-
languageCode?: string | undefined;
|
|
51
|
-
isPremium?: boolean | undefined;
|
|
52
|
-
allowsWriteToPm?: boolean | undefined;
|
|
53
|
-
photoUrl?: string | undefined;
|
|
54
|
-
}, {
|
|
55
|
-
id: number;
|
|
56
|
-
is_bot?: boolean | undefined;
|
|
57
|
-
first_name: string;
|
|
58
|
-
last_name?: string | undefined;
|
|
59
|
-
username?: string | undefined;
|
|
60
|
-
language_code?: string | undefined;
|
|
61
|
-
is_premium?: boolean | undefined;
|
|
62
|
-
allows_write_to_pm?: boolean | undefined;
|
|
63
|
-
photo_url?: string | undefined;
|
|
64
|
-
}>>;
|
|
65
|
-
export type TelegramInitDataUser = z.infer<typeof TelegramInitDataUserSchema>;
|
|
25
|
+
import { type TelegramUser } from './user.js';
|
|
66
26
|
export interface TelegramInitData {
|
|
67
27
|
/** Absent when the Mini App was opened somewhere Telegram sends no user. */
|
|
68
|
-
user?:
|
|
28
|
+
user?: TelegramUser;
|
|
69
29
|
/** Whose chat the Mini App was opened from, when Telegram named one. */
|
|
70
|
-
receiver?:
|
|
30
|
+
receiver?: TelegramUser;
|
|
71
31
|
/** When Telegram signed this string. */
|
|
72
32
|
authDate: Date;
|
|
73
33
|
/** How old the signature was at verification time. */
|
|
@@ -130,5 +90,4 @@ export interface VerifyTelegramInitDataOptions {
|
|
|
130
90
|
* being configured wrong, and it throws.
|
|
131
91
|
*/
|
|
132
92
|
export declare function verifyTelegramInitData(options: VerifyTelegramInitDataOptions): Promise<TelegramInitDataVerification>;
|
|
133
|
-
export {};
|
|
134
93
|
//# sourceMappingURL=init-data.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init-data.d.ts","sourceRoot":"","sources":["../../src/telegram/init-data.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;
|
|
1
|
+
{"version":3,"file":"init-data.d.ts","sourceRoot":"","sources":["../../src/telegram/init-data.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAGH,OAAO,EAAqB,KAAK,YAAY,EAAE,MAAM,QAAQ,CAAC;AAI9D,MAAM,WAAW,gBAAgB;IAC/B,4EAA4E;IAC5E,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,wEAAwE;IACxE,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,wCAAwC;IACxC,QAAQ,EAAE,IAAI,CAAC;IACf,sDAAsD;IACtD,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;;OAMG;IACH,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACvC;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,uBAAuB,GAC/B,cAAc,GACd,oBAAoB,GACpB,WAAW,GACX,SAAS,CAAC;AAEd,MAAM,MAAM,4BAA4B,GACpC;IAAE,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAA;CAAE,GACzD;IACE,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,uBAAuB,CAAC;IACzC,0EAA0E;IAC1E,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B,CAAC;AAEN,MAAM,WAAW,6BAA6B;IAC5C,yDAAyD;IACzD,QAAQ,EAAE,MAAM,CAAC;IACjB,mEAAmE;IACnE,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gEAAgE;IAChE,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAUD;;;;;;;;GAQG;AACH,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,4BAA4B,CAAC,CAuEvC"}
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* What to post stays the application's. This owns the queue, the pacing, the
|
|
14
14
|
* topics, 429s and the bounded way down.
|
|
15
15
|
*/
|
|
16
|
+
import { type TelegramOperatorDedupe } from './operator-dedupe.js';
|
|
16
17
|
import { type TelegramSendFailure } from './send-failure.js';
|
|
17
18
|
export type TelegramChatId = number | string;
|
|
18
19
|
export interface TelegramOperatorMessage {
|
|
@@ -66,24 +67,6 @@ export interface TelegramOperatorChannelConfig<TTopic extends string> {
|
|
|
66
67
|
*/
|
|
67
68
|
readonly dedupe?: TelegramOperatorDedupe<TTopic>;
|
|
68
69
|
}
|
|
69
|
-
export interface TelegramOperatorDedupe<TTopic extends string> {
|
|
70
|
-
/** Default 600 000 — ten minutes. */
|
|
71
|
-
readonly windowMs?: number;
|
|
72
|
-
/** Messages sent per window, whatever their fingerprint. Default 20. */
|
|
73
|
-
readonly maxPerWindow?: number;
|
|
74
|
-
/**
|
|
75
|
-
* What makes two messages the same. Default: the topic and the text with
|
|
76
|
-
* numbers, UUIDs and long identifiers blanked — they differ between two
|
|
77
|
-
* occurrences of one failure without making it another.
|
|
78
|
-
*/
|
|
79
|
-
readonly fingerprint?: (text: string, topic: TTopic | undefined) => string;
|
|
80
|
-
/** The line added to a message after `count` of its repeats were held back. */
|
|
81
|
-
readonly repeatedLine?: (count: number) => string;
|
|
82
|
-
/** The line added to the first message after `count` were held back by the budget. */
|
|
83
|
-
readonly overBudgetLine?: (count: number) => string;
|
|
84
|
-
/** Default `Date.now`. */
|
|
85
|
-
readonly now?: () => number;
|
|
86
|
-
}
|
|
87
70
|
export interface TelegramOperatorChannel<TTopic extends string> {
|
|
88
71
|
/** Queue one message. Returns at once and never throws. */
|
|
89
72
|
post(text: string, topic?: TTopic): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"operator-channel.d.ts","sourceRoot":"","sources":["../../src/telegram/operator-channel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAKH,OAAO,EAA+B,KAAK,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAEvF,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,MAAM,CAAC;AAyB7C,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAChC,iDAAiD;IACjD,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,oBAAoB,CAAC,MAAM,SAAS,MAAM;IACzD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;;;;OAMG;IACH,QAAQ,CAAC,MAAM,EAAE,UAAU,GAAG,SAAS,GAAG,UAAU,GAAG,QAAQ,GAAG,UAAU,GAAG,aAAa,CAAC;IAC7F,QAAQ,CAAC,OAAO,CAAC,EAAE,mBAAmB,CAAC;CACxC;AAED,MAAM,WAAW,6BAA6B,CAAC,MAAM,SAAS,MAAM;IAClE,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAChC,2FAA2F;IAC3F,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;IAC5D,mFAAmF;IACnF,QAAQ,CAAC,IAAI,EAAE,CAAC,OAAO,EAAE,uBAAuB,EAAE,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3F,4DAA4D;IAC5D,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,gGAAgG;IAChG,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,4DAA4D;IAC5D,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,oDAAoD;IACpD,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,oBAAoB,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC;IAClE,sDAAsD;IACtD,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9E;;;;OAIG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/C,gFAAgF;IAChF,QAAQ,CAAC,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7C;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,sBAAsB,CAAC,MAAM,CAAC,CAAC;CAClD;AAED,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"operator-channel.d.ts","sourceRoot":"","sources":["../../src/telegram/operator-channel.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repeats and storms held back before they reach a person.
|
|
3
|
+
*
|
|
4
|
+
* One failure in a loop is a hundred identical alerts, and a hundred alerts
|
|
5
|
+
* is a muted chat — after which the one that mattered is not read either. So
|
|
6
|
+
* the same message, by fingerprint, goes once per window and the next one
|
|
7
|
+
* that goes says how many were held back; past a budget per window the rest
|
|
8
|
+
* are counted and the count rides on the first message after. The operator
|
|
9
|
+
* channel uses it through its `dedupe` option; it is exported for senders of
|
|
10
|
+
* their own.
|
|
11
|
+
*/
|
|
12
|
+
export interface TelegramOperatorDedupe<TTopic extends string> {
|
|
13
|
+
/** Default 600 000 — ten minutes. */
|
|
14
|
+
readonly windowMs?: number;
|
|
15
|
+
/** Messages sent per window, whatever their fingerprint. Default 20. */
|
|
16
|
+
readonly maxPerWindow?: number;
|
|
17
|
+
/**
|
|
18
|
+
* What makes two messages the same. Default: the topic and the text with
|
|
19
|
+
* numbers, UUIDs and long identifiers blanked — they differ between two
|
|
20
|
+
* occurrences of one failure without making it another.
|
|
21
|
+
*/
|
|
22
|
+
readonly fingerprint?: (text: string, topic: TTopic | undefined) => string;
|
|
23
|
+
/** The line added to a message after `count` of its repeats were held back. */
|
|
24
|
+
readonly repeatedLine?: (count: number) => string;
|
|
25
|
+
/** The line added to the first message after `count` were held back by the budget. */
|
|
26
|
+
readonly overBudgetLine?: (count: number) => string;
|
|
27
|
+
/** Default `Date.now`. */
|
|
28
|
+
readonly now?: () => number;
|
|
29
|
+
}
|
|
30
|
+
/** Send `text` — the message with its held-back counts — or hold it back, with why. */
|
|
31
|
+
export type TelegramOperatorDedupeVerdict = {
|
|
32
|
+
readonly send: true;
|
|
33
|
+
readonly text: string;
|
|
34
|
+
} | {
|
|
35
|
+
readonly send: false;
|
|
36
|
+
readonly reason: 'repeated' | 'over-budget';
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* The dedupe window on its own, for an application that sends alerts itself —
|
|
40
|
+
* its own bot, its own retry — and wants only the decision: whether this
|
|
41
|
+
* message goes, and the text it goes with.
|
|
42
|
+
*/
|
|
43
|
+
export declare function createTelegramOperatorDedupe<TTopic extends string = never>(options?: TelegramOperatorDedupe<TTopic>): (text: string, topic?: TTopic) => TelegramOperatorDedupeVerdict;
|
|
44
|
+
//# sourceMappingURL=operator-dedupe.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operator-dedupe.d.ts","sourceRoot":"","sources":["../../src/telegram/operator-dedupe.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,WAAW,sBAAsB,CAAC,MAAM,SAAS,MAAM;IAC3D,qCAAqC;IACrC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,wEAAwE;IACxE,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B;;;;OAIG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,KAAK,MAAM,CAAC;IAC3E,+EAA+E;IAC/E,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAClD,sFAAsF;IACtF,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IACpD,0BAA0B;IAC1B,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CAC7B;AAUD,uFAAuF;AACvF,MAAM,MAAM,6BAA6B,GACrC;IAAE,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9C;IAAE,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,UAAU,GAAG,aAAa,CAAA;CAAE,CAAC;AAE1E;;;;GAIG;AACH,wBAAgB,4BAA4B,CAAC,MAAM,SAAS,MAAM,GAAG,KAAK,EACxE,OAAO,GAAE,sBAAsB,CAAC,MAAM,CAAM,GAC3C,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,6BAA6B,CAwCjE"}
|
|
@@ -63,6 +63,12 @@ export interface TelegramSendFailure {
|
|
|
63
63
|
*/
|
|
64
64
|
evidence: 'parameters' | 'status' | 'description' | 'none';
|
|
65
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* Every reason, for a consumer that maps each one to its own code or text. A
|
|
68
|
+
* `Record<TelegramSendFailureReason, …>` beside it fails to compile when a
|
|
69
|
+
* release adds a reason, instead of falling through to a default at run time.
|
|
70
|
+
*/
|
|
71
|
+
export declare const TELEGRAM_SEND_FAILURE_REASONS: readonly TelegramSendFailureReason[];
|
|
66
72
|
/**
|
|
67
73
|
* Classify one refused send.
|
|
68
74
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"send-failure.d.ts","sourceRoot":"","sources":["../../src/telegram/send-failure.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAIH;;;;;;;;;GASG;AACH,MAAM,MAAM,yBAAyB,GACjC,iBAAiB,GACjB,kBAAkB,GAClB,gBAAgB,GAChB,aAAa,GACb,cAAc,GACd,iBAAiB,GACjB,cAAc,GACd,SAAS,CAAC;AAEd,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,yBAAyB,CAAC;IAClC,6DAA6D;IAC7D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;OAMG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,wDAAwD;IACxD,SAAS,EAAE,OAAO,CAAC;IACnB;;;;;;;;OAQG;IACH,oBAAoB,EAAE,OAAO,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,EAAE,YAAY,GAAG,QAAQ,GAAG,aAAa,GAAG,MAAM,CAAC;CAC5D;
|
|
1
|
+
{"version":3,"file":"send-failure.d.ts","sourceRoot":"","sources":["../../src/telegram/send-failure.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAIH;;;;;;;;;GASG;AACH,MAAM,MAAM,yBAAyB,GACjC,iBAAiB,GACjB,kBAAkB,GAClB,gBAAgB,GAChB,aAAa,GACb,cAAc,GACd,iBAAiB,GACjB,cAAc,GACd,SAAS,CAAC;AAEd,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,yBAAyB,CAAC;IAClC,6DAA6D;IAC7D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;OAMG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,wDAAwD;IACxD,SAAS,EAAE,OAAO,CAAC;IACnB;;;;;;;;OAQG;IACH,oBAAoB,EAAE,OAAO,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,EAAE,YAAY,GAAG,QAAQ,GAAG,aAAa,GAAG,MAAM,CAAC;CAC5D;AA6CD;;;;GAIG;AACH,eAAO,MAAM,6BAA6B,EAAE,SAAS,yBAAyB,EAC7B,CAAC;AAyClD;;;;;GAKG;AACH,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,OAAO,GAAG,mBAAmB,CAyD/E;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,mBAAmB,GAC3B,cAAc,GACd,cAAc,GACd,gBAAgB,GAChB,eAAe,GACf,OAAO,CAAC;AAeZ,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,OAAO,GAAG,mBAAmB,CAM/E"}
|
|
@@ -34,7 +34,11 @@ export interface TelegramUpdateFailure {
|
|
|
34
34
|
export type TelegramUpdateStoreStep = 'claim' | 'renew' | 'settle' | 'sweep';
|
|
35
35
|
export interface TelegramUpdateIntakeConfig<TUpdate extends TelegramUpdateEnvelope> {
|
|
36
36
|
readonly store: TelegramUpdateStore;
|
|
37
|
-
/**
|
|
37
|
+
/**
|
|
38
|
+
* Handle one update — with grammY, `(update) => bot.handleUpdate(update)`.
|
|
39
|
+
* A grammY `BotError` is unwrapped: `retry`, `onFailure` and the stored
|
|
40
|
+
* error see what the middleware threw.
|
|
41
|
+
*/
|
|
38
42
|
readonly handle: (update: TUpdate) => unknown;
|
|
39
43
|
/**
|
|
40
44
|
* After a failed attempt: milliseconds until the next, or `false` to
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"update-intake.d.ts","sourceRoot":"","sources":["../../src/telegram/update-intake.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,WAAW,CAAC;AAE1D,mFAAmF;AACnF,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,kEAAkE;IAClE,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,MAAM,uBAAuB,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;AAE7E,MAAM,WAAW,0BAA0B,CAAC,OAAO,SAAS,sBAAsB;IAChF,QAAQ,CAAC,KAAK,EAAE,mBAAmB,CAAC;IACpC
|
|
1
|
+
{"version":3,"file":"update-intake.d.ts","sourceRoot":"","sources":["../../src/telegram/update-intake.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,WAAW,CAAC;AAE1D,mFAAmF;AACnF,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,kEAAkE;IAClE,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,MAAM,uBAAuB,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;AAE7E,MAAM,WAAW,0BAA0B,CAAC,OAAO,SAAS,sBAAsB;IAChF,QAAQ,CAAC,KAAK,EAAE,mBAAmB,CAAC;IACpC;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC;IAC9C;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,KAAK,MAAM,GAAG,KAAK,CAAC;IACrE,iBAAiB;IACjB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,qGAAqG;IACrG,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,kGAAkG;IAClG,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,sBAAsB;IACtB,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,2FAA2F;IAC3F,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,yCAAyC;IACzC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,4BAA4B;IAC5B,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,qBAAqB,KAAK,IAAI,CAAC;IAC9D,6EAA6E;IAC7E,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,uBAAuB,KAAK,IAAI,CAAC;IAChF,0BAA0B;IAC1B,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,oBAAoB;IACnC,kFAAkF;IAClF,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,wBAAwB,CAAC,CAAC;IACxD,iEAAiE;IACjE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,kEAAkE;IAClE,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IACzB,0CAA0C;IAC1C,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,2DAA2D;IAC3D,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAsED,wBAAgB,0BAA0B,CAAC,OAAO,SAAS,sBAAsB,EAC/E,MAAM,EAAE,0BAA0B,CAAC,OAAO,CAAC,GAC1C,oBAAoB,CAiJtB"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The rules of `TelegramUpdateStore`, as a check any store can be put
|
|
3
|
+
* through — the ones shipped here and an application's own over its ORM.
|
|
4
|
+
*
|
|
5
|
+
* A store is five conditional statements, and each is easy to write almost
|
|
6
|
+
* right: a claim that does not check the lease, a settle that does not check
|
|
7
|
+
* the attempt, a claim read and then written in two statements so that two
|
|
8
|
+
* processes both take one update. Nothing fails in a test with one process;
|
|
9
|
+
* the update simply runs twice in production. So the check runs every rule on
|
|
10
|
+
* a fresh store, and races claims against each other.
|
|
11
|
+
*
|
|
12
|
+
* It does not depend on a test framework: it returns what was violated, and
|
|
13
|
+
* an empty list is a store that keeps the rules.
|
|
14
|
+
*/
|
|
15
|
+
import type { TelegramUpdateStore } from './update-store.js';
|
|
16
|
+
/**
|
|
17
|
+
* Put a store through every rule, each on a fresh store from `make`. Resolves
|
|
18
|
+
* with the violations — `"<rule>: <what happened>"` — empty when the store
|
|
19
|
+
* keeps them all. A store that throws violates the rule it threw in.
|
|
20
|
+
*/
|
|
21
|
+
export declare function checkTelegramUpdateStore(make: () => TelegramUpdateStore | Promise<TelegramUpdateStore>): Promise<string[]>;
|
|
22
|
+
//# sourceMappingURL=update-store-check.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"update-store-check.d.ts","sourceRoot":"","sources":["../../src/telegram/update-store-check.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAqG1D;;;;GAIG;AACH,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,MAAM,mBAAmB,GAAG,OAAO,CAAC,mBAAmB,CAAC,GAC7D,OAAO,CAAC,MAAM,EAAE,CAAC,CAYnB"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The update store in Postgres, through whatever client the application
|
|
3
|
+
* already holds — one function that runs a statement with `$n` parameters and
|
|
4
|
+
* returns its rows:
|
|
5
|
+
*
|
|
6
|
+
* - Bun: `(text, parameters) => sql.unsafe(text, [...parameters])`
|
|
7
|
+
* - Prisma: `(text, parameters) => prisma.$queryRawUnsafe(text, ...parameters)`
|
|
8
|
+
* - `pg`: `(text, parameters) => pool.query(text, [...parameters]).then((result) => result.rows)`
|
|
9
|
+
*
|
|
10
|
+
* Each rule of `TelegramUpdateStore` is one conditional statement, so a claim
|
|
11
|
+
* is atomic across every process sharing the table; every statement returns
|
|
12
|
+
* rows, so a client that has only a "query" call needs nothing else.
|
|
13
|
+
* Parameters carry explicit casts: a driver that sends a JS number as a
|
|
14
|
+
* double still lands in a `bigint` column.
|
|
15
|
+
*/
|
|
16
|
+
import type { TelegramUpdateStore } from './update-store.js';
|
|
17
|
+
/** Run one statement with `$1…$n` parameters; resolve with the rows it returned. */
|
|
18
|
+
export type TelegramPostgresQuery = (text: string, parameters: readonly (string | number | null)[]) => Promise<readonly unknown[]>;
|
|
19
|
+
export interface PostgresTelegramUpdateStoreConfig {
|
|
20
|
+
readonly query: TelegramPostgresQuery;
|
|
21
|
+
/** Default `telegram_updates`. */
|
|
22
|
+
readonly table?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Create the table on first use when it is missing. Default `true`; set
|
|
25
|
+
* `false` when migrations own the schema — `postgresTelegramUpdateStoreSchema`
|
|
26
|
+
* is the statement to put in one.
|
|
27
|
+
*/
|
|
28
|
+
readonly createTable?: boolean;
|
|
29
|
+
}
|
|
30
|
+
/** The table and its index, for a migration. Safe to run twice. */
|
|
31
|
+
export declare function postgresTelegramUpdateStoreSchema(table?: string): string;
|
|
32
|
+
export declare function postgresTelegramUpdateStore(config: PostgresTelegramUpdateStoreConfig): TelegramUpdateStore;
|
|
33
|
+
//# sourceMappingURL=update-store-postgres.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"update-store-postgres.d.ts","sourceRoot":"","sources":["../../src/telegram/update-store-postgres.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAA4B,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAEpF,oFAAoF;AACpF,MAAM,MAAM,qBAAqB,GAAG,CAClC,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,SAAS,CAAC,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC,EAAE,KAC5C,OAAO,CAAC,SAAS,OAAO,EAAE,CAAC,CAAC;AAEjC,MAAM,WAAW,iCAAiC;IAChD,QAAQ,CAAC,KAAK,EAAE,qBAAqB,CAAC;IACtC,kCAAkC;IAClC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB;;;;OAIG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;CAChC;AAaD,mEAAmE;AACnE,wBAAgB,iCAAiC,CAAC,KAAK,SAAqB,GAAG,MAAM,CAapF;AAqBD,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,iCAAiC,GACxC,mBAAmB,CAyGrB"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A Telegram user in one shape, wherever it came from.
|
|
3
|
+
*
|
|
4
|
+
* Telegram sends a user in `snake_case` twice over — the Bot API's `from` on
|
|
5
|
+
* every update, the Mini App's signed `user` — and every other name this
|
|
6
|
+
* package publishes is `camelCase`. An application with one `ensureAccount`
|
|
7
|
+
* receives both, and a surface that switched convention by source would make
|
|
8
|
+
* it write the same translation twice. So both pass through this schema and
|
|
9
|
+
* arrive as the same `TelegramUser`.
|
|
10
|
+
*/
|
|
11
|
+
import { z } from 'zod';
|
|
12
|
+
declare const TelegramUserSchema: z.ZodPipe<z.ZodObject<{
|
|
13
|
+
id: z.ZodInt;
|
|
14
|
+
is_bot: z.ZodOptional<z.ZodBoolean>;
|
|
15
|
+
first_name: z.ZodString;
|
|
16
|
+
last_name: z.ZodOptional<z.ZodString>;
|
|
17
|
+
username: z.ZodOptional<z.ZodString>;
|
|
18
|
+
language_code: z.ZodOptional<z.ZodString>;
|
|
19
|
+
is_premium: z.ZodOptional<z.ZodBoolean>;
|
|
20
|
+
allows_write_to_pm: z.ZodOptional<z.ZodBoolean>;
|
|
21
|
+
photo_url: z.ZodOptional<z.ZodString>;
|
|
22
|
+
}, z.core.$strip>, z.ZodTransform<{
|
|
23
|
+
id: number;
|
|
24
|
+
firstName: string;
|
|
25
|
+
isBot?: boolean | undefined;
|
|
26
|
+
lastName?: string | undefined;
|
|
27
|
+
username?: string | undefined;
|
|
28
|
+
languageCode?: string | undefined;
|
|
29
|
+
isPremium?: boolean | undefined;
|
|
30
|
+
allowsWriteToPm?: boolean | undefined;
|
|
31
|
+
photoUrl?: string | undefined;
|
|
32
|
+
}, {
|
|
33
|
+
id: number;
|
|
34
|
+
is_bot?: boolean | undefined;
|
|
35
|
+
first_name: string;
|
|
36
|
+
last_name?: string | undefined;
|
|
37
|
+
username?: string | undefined;
|
|
38
|
+
language_code?: string | undefined;
|
|
39
|
+
is_premium?: boolean | undefined;
|
|
40
|
+
allows_write_to_pm?: boolean | undefined;
|
|
41
|
+
photo_url?: string | undefined;
|
|
42
|
+
}>>;
|
|
43
|
+
/** A Telegram user as this package names it; fields Telegram did not send are absent. */
|
|
44
|
+
export type TelegramUser = z.infer<typeof TelegramUserSchema>;
|
|
45
|
+
/**
|
|
46
|
+
* Telegram's user record — grammY's `ctx.from`, a Bot API `User`, a Mini App's
|
|
47
|
+
* `user` — as a `TelegramUser`; `undefined` when it is not one.
|
|
48
|
+
*/
|
|
49
|
+
export declare function parseTelegramUser(value: unknown): TelegramUser | undefined;
|
|
50
|
+
export {};
|
|
51
|
+
//# sourceMappingURL=user.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user.d.ts","sourceRoot":"","sources":["../../src/telegram/user.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,QAAA,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwBnB,CAAC;AAEN,yFAAyF;AACzF,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,YAAY,GAAG,SAAS,CAG1E"}
|
package/llms/files.txt
CHANGED
|
@@ -29,5 +29,8 @@ available from `stitchkit/contract`.
|
|
|
29
29
|
| `writeFileAtomic` / `writeFileAtomicSync` / `WriteFileAtomicOptions` | function / _type_ | replace a file atomically: a random staging name created exclusively (never through a planted link), the mode set on the descriptor before the file is visible (default `0o600`, not masked by the umask), `fsync`, rename; a failure leaves the target and no staging file. The asynchronous form keeps the event loop running |
|
|
30
30
|
| `withExclusiveLock` / `ExclusiveLockOptions` / `ExclusiveLock` / `ExclusiveLockOwner` | function / _type_ | run work under an exclusive lock between processes — a file recording its owner (pid, host, machine identity, time and nullable kernel process identity); waits up to `timeoutMs` (default 10 s) and stops on `signal`; a dead owner on this machine is taken over, a live, slow or foreign one never is; an ownerless lock only after `ownerlessGraceMs` |
|
|
31
31
|
| `ExclusiveLockError` | class | `LOCK_TIMEOUT` naming the resource and its holder, or `LOCK_ABORTED` with the signal's reason |
|
|
32
|
+
| `createChunkSpool` | function | the server half of a chunked upload: parts on disk under `owner` + client-minted `uploadId`, each with a size+sha256 receipt; idempotent `open` and `put`, the first writer of a part wins across processes, `assemble` in order once every part is in, `sweep` of untouched uploads — [guide](../guide/client.md#chunked-uploads) |
|
|
33
|
+
| `ChunkSpool` / `ChunkSpoolConfig` / `ChunkSpoolKey` / `ChunkSpoolOpen` / `ChunkSpoolPart` / `ChunkSpoolAssembly` | _type_ | the spool, its directory, part size, file limit, meta schema and age; an upload's key; its declaration; one part; the assembled parts with a concatenating `stream()` |
|
|
34
|
+
| `ChunkSpoolErrorCode` | _type_ | the `AppError` codes a spool refuses with, each with its 4xx status: `UPLOAD_INVALID`, `UPLOAD_TOO_LARGE`, `UPLOAD_NOT_FOUND`, `UPLOAD_CONFLICT`, `UPLOAD_CHUNK_OUT_OF_RANGE`, `UPLOAD_CHUNK_SIZE`, `UPLOAD_INCOMPLETE` |
|
|
32
35
|
|
|
33
36
|
---
|
package/llms/observability.txt
CHANGED
|
@@ -577,6 +577,18 @@ A payload goes into an audit row only after `sanitizePayload`:
|
|
|
577
577
|
`tokenizer`) survive. This applies wherever a key exists — an object field and
|
|
578
578
|
a `Map` entry alike, at any depth. A `Set` member has no key, so nothing there
|
|
579
579
|
is masked by name;
|
|
580
|
+
- **secrets are masked by their shape** in every string, whatever key holds
|
|
581
|
+
it — the secret half of a Telegram bot token (`123456:[redacted]`: the bot's
|
|
582
|
+
id stays, it tells two bots apart), the password in
|
|
583
|
+
`postgresql://user:password@host` (user, host and path stay), and the value
|
|
584
|
+
of a query parameter named as a secret (`?access_token=`, `?api-key=`,
|
|
585
|
+
`?key=`, `?sig=`). A URL in an error message or a request log line is the
|
|
586
|
+
usual way a token reaches a journal;
|
|
587
|
+
- **an error keeps what it says about itself** — its own fields (`error_code`,
|
|
588
|
+
`parameters`, a provider's `status`) are written beside `name`, `message`,
|
|
589
|
+
`stack` and `cause`, masked by the same rules; a field holding a class
|
|
590
|
+
instance other than an error (grammY's `BotError.ctx`) is named, not written,
|
|
591
|
+
so one error cannot push its line past the bound and lose the stack;
|
|
580
592
|
- **binary blobs** (`Uint8Array`, `Blob`, `FormData`) collapse to metadata —
|
|
581
593
|
never the bytes;
|
|
582
594
|
- the result is **capped** — anything over the byte limit becomes a preview.
|
|
@@ -889,7 +901,7 @@ audit event. See the [Observability guide](../guide/observability.md).
|
|
|
889
901
|
| Export | Kind | Summary |
|
|
890
902
|
|--------|------|---------|
|
|
891
903
|
| `sanitizePayload` | function | redact secrets and cap size — [guide](../guide/observability.md#sanitisation) |
|
|
892
|
-
| `redact` | function | mask secret-named keys, drop binary blobs |
|
|
904
|
+
| `redact` | function | mask secret-named keys and secrets recognised by shape in every string — the secret half of a bot token, a password in an address, a query parameter named as a secret; drop binary blobs; an error keeps its own fields (`error_code`, `parameters`), a class instance on one is named, not written |
|
|
893
905
|
| `truncatePreview` | function | cap a value by serialised size |
|
|
894
906
|
| `measureSize` | function | item count + byte size of a result |
|
|
895
907
|
| `JsonValue` | _type_ | a JSON-serialisable value |
|
package/llms/server.4.txt
CHANGED
|
@@ -357,6 +357,11 @@ Three things it does that a hand-written check usually does not:
|
|
|
357
357
|
- `raw` keeps every signed pair, so a field Telegram adds after this release
|
|
358
358
|
does not need a release to reach.
|
|
359
359
|
|
|
360
|
+
The user arrives `camelCase` — `firstName`, `languageCode` — as `TelegramUser`.
|
|
361
|
+
A bot meets the same person as grammY's `ctx.from`, in Telegram's `snake_case`;
|
|
362
|
+
`parseTelegramUser(ctx.from)` gives the same `TelegramUser`, so one
|
|
363
|
+
`ensureAccount(user)` serves the Mini App and the chat.
|
|
364
|
+
|
|
360
365
|
`maxAgeSeconds` is optional and there is no default: only the application knows
|
|
361
366
|
how long its own session is worth. Omitting it means the string never expires,
|
|
362
367
|
which is a decision rather than an oversight.
|
|
@@ -386,6 +391,17 @@ and our payload is wrong, which is the case a list of substrings quietly counts
|
|
|
386
391
|
against the user. An unrecognised refusal leaves the recipient reachable: losing
|
|
387
392
|
a working subscriber forever costs more than one wasted send.
|
|
388
393
|
|
|
394
|
+
`TELEGRAM_SEND_FAILURE_REASONS` lists every reason. A table keyed by
|
|
395
|
+
`TelegramSendFailureReason` — error codes, the text a person reads — fails to
|
|
396
|
+
compile when a release adds one, instead of falling through to a default:
|
|
397
|
+
|
|
398
|
+
```ts
|
|
399
|
+
const codes: Record<TelegramSendFailureReason, string> = {
|
|
400
|
+
'blocked-by-user': 'E_BLOCKED',
|
|
401
|
+
/* … every reason … */
|
|
402
|
+
}
|
|
403
|
+
```
|
|
404
|
+
|
|
389
405
|
The rest of what a bot is assembled from — its resources, journal, operator
|
|
390
406
|
channel, broadcasts and local Bot API files — is in [Telegram bots](telegram.md).
|
|
391
407
|
|
package/llms/stitchkit.2.txt
CHANGED
|
@@ -352,8 +352,8 @@ non-flat field in a `GET` input, and a missing or invalid multipart file. → AD
|
|
|
352
352
|
0148.
|
|
353
353
|
|
|
354
354
|
Abort and timeout do not emit `network_error` and are not retried. The same
|
|
355
|
-
options work for query, JSON, multipart and raw-response calls.
|
|
356
|
-
|
|
355
|
+
options work for query, JSON, multipart and raw-response calls. A body call
|
|
356
|
+
also takes `onUploadProgress` — see [Upload progress](#upload-progress).
|
|
357
357
|
|
|
358
358
|
An injected transport failure is normalized to `UNKNOWN_ERROR` while its exact
|
|
359
359
|
object remains available as `ApiError.cause`. This is how a bounded adapter can
|
|
@@ -368,6 +368,51 @@ For an endpoint without contract arguments, pass only the options object:
|
|
|
368
368
|
await api.health.withOptions({ signal: controller.signal })
|
|
369
369
|
```
|
|
370
370
|
|
|
371
|
+
### Upload progress
|
|
372
|
+
|
|
373
|
+
A call with a body can report it leaving:
|
|
374
|
+
|
|
375
|
+
```ts
|
|
376
|
+
await api.recordings.upload.withOptions(
|
|
377
|
+
{ file, title: 'Take 3' },
|
|
378
|
+
{
|
|
379
|
+
signal,
|
|
380
|
+
onUploadProgress: ({ sentBytes, totalBytes, attempt }) => {
|
|
381
|
+
bar.value = sentBytes / totalBytes
|
|
382
|
+
},
|
|
383
|
+
},
|
|
384
|
+
)
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
- `sentBytes` counts bytes of the encoded body handed to the network — not
|
|
388
|
+
bytes the server accepted. `totalBytes` is the encoded length, multipart
|
|
389
|
+
boundaries included. The first report is `0`, values only grow, and the last
|
|
390
|
+
one of an attempt equals `totalBytes`.
|
|
391
|
+
- A transport retry (`HttpClientConfig.retry`) counts again from `0` under the
|
|
392
|
+
next `attempt`.
|
|
393
|
+
- Without the option nothing changes: the request takes the path it always did.
|
|
394
|
+
- A `GET`, `HEAD`, `DELETE` or streaming endpoint refuses the option with a
|
|
395
|
+
`TypeError` at the call — it has no body to count, or a live response the
|
|
396
|
+
browser route would buffer. A listener that throws does not fail the upload.
|
|
397
|
+
|
|
398
|
+
Fetch has no upload event, so the count comes from one of two routes, chosen
|
|
399
|
+
per call:
|
|
400
|
+
|
|
401
|
+
| Where the client runs | Route | What counts |
|
|
402
|
+
|---|---|---|
|
|
403
|
+
| Bun, Node, SSR, an injected `fetch`, a unix socket | the body is encoded once and sent as a stream of 64 KiB pieces with an exact `content-length` | a piece counts when the transport pulls it |
|
|
404
|
+
| a browser on its own fetch | `XMLHttpRequest`, with the same headers, credentials, errors, cancellation and timeout | `xhr.upload` progress events |
|
|
405
|
+
|
|
406
|
+
The browser route exists because a streaming request body is not portable:
|
|
407
|
+
Chromium sends one only over HTTP/2 and Safari not at all. Two consequences of
|
|
408
|
+
XHR: `credentials: 'omit'` is refused (XHR cannot drop same-origin cookies),
|
|
409
|
+
and the response is read whole before the call resolves. An injected `fetch`
|
|
410
|
+
always takes the stream route — it owns the I/O, and a transport that buffers
|
|
411
|
+
the stream before sending reports the whole body at once.
|
|
412
|
+
|
|
413
|
+
A counted body is encoded in memory once. A file larger than one request
|
|
414
|
+
belongs in [chunked uploads](#chunked-uploads), whose parts are sized for it.
|
|
415
|
+
|
|
371
416
|
### Injected delivery adapters
|
|
372
417
|
|
|
373
418
|
`ClientFetch` is the narrow composition seam for application-owned delivery.
|
|
@@ -465,6 +510,116 @@ Every method now requires `tenantId`, and the callback sees it as a `string`.
|
|
|
465
510
|
The batch form delegates to the same single-contract client runtime, including
|
|
466
511
|
HTTP exposure filtering, multipart, raw responses and output validation.
|
|
467
512
|
|
|
513
|
+
## Chunked uploads
|
|
514
|
+
|
|
515
|
+
A file larger than one request goes as three calls of the application's own
|
|
516
|
+
contract — open, parts, finish — driven by `uploadInChunks` on the client and
|
|
517
|
+
held by `createChunkSpool` (`stitchkit/files`) on the server. The contract stays
|
|
518
|
+
the application's: its fields, its limits, what happens to the finished file.
|
|
519
|
+
|
|
520
|
+
```ts
|
|
521
|
+
// shared: one part size for both halves
|
|
522
|
+
export const CHUNK_BYTES = 4 * 1024 * 1024
|
|
523
|
+
|
|
524
|
+
// contract
|
|
525
|
+
init: {
|
|
526
|
+
method: 'POST', path: '/uploads', desc: 'Open an upload',
|
|
527
|
+
input: z.object({ uploadId: z.string(), totalBytes: z.number(), chunkCount: z.number(), name: z.string() }),
|
|
528
|
+
output: z.object({ finished: Accepted.optional() }),
|
|
529
|
+
},
|
|
530
|
+
chunk: {
|
|
531
|
+
method: 'PUT', path: '/uploads/:uploadId/chunks/:index', desc: 'Send one part',
|
|
532
|
+
params: z.object({ uploadId: z.string(), index: z.coerce.number() }),
|
|
533
|
+
multipart: { files: { bytes: {} } },
|
|
534
|
+
output: z.object({ state: z.enum(['stored', 'repeated']) }),
|
|
535
|
+
},
|
|
536
|
+
finalize: {
|
|
537
|
+
method: 'POST', path: '/uploads/:uploadId/finalize', desc: 'Assemble the file',
|
|
538
|
+
params: z.object({ uploadId: z.string() }),
|
|
539
|
+
output: Accepted,
|
|
540
|
+
},
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
```ts
|
|
544
|
+
// server
|
|
545
|
+
import { createChunkSpool } from 'stitchkit/files'
|
|
546
|
+
|
|
547
|
+
const spool = createChunkSpool({
|
|
548
|
+
directory: env.UPLOAD_SPOOL_DIR,
|
|
549
|
+
chunkBytes: CHUNK_BYTES,
|
|
550
|
+
maxFileBytes: 1024 * 1024 * 1024,
|
|
551
|
+
meta: z.object({ name: z.string() }),
|
|
552
|
+
})
|
|
553
|
+
|
|
554
|
+
implement(contract, {
|
|
555
|
+
init: async ({ input, ctx }) => {
|
|
556
|
+
const done = await findFinished(input.uploadId) // the app's own record
|
|
557
|
+
if (done) return { finished: done }
|
|
558
|
+
await spool.open({ owner: ctx.user.id, ...input, meta: { name: input.name } })
|
|
559
|
+
return {}
|
|
560
|
+
},
|
|
561
|
+
chunk: async ({ params, files, ctx }) => ({
|
|
562
|
+
state: await spool.put({ owner: ctx.user.id, ...params, bytes: files.bytes }),
|
|
563
|
+
}),
|
|
564
|
+
finalize: async ({ params, ctx }) => {
|
|
565
|
+
const key = { owner: ctx.user.id, uploadId: params.uploadId }
|
|
566
|
+
const file = await spool.assemble(key) // file.stream(), file.chunkPaths, file.meta
|
|
567
|
+
const accepted = await store(file)
|
|
568
|
+
await spool.discard(key)
|
|
569
|
+
return accepted
|
|
570
|
+
},
|
|
571
|
+
})
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
```ts
|
|
575
|
+
// client — Bun, Node or a browser
|
|
576
|
+
import { uploadInChunks } from 'stitchkit'
|
|
577
|
+
|
|
578
|
+
const accepted = await uploadInChunks({
|
|
579
|
+
file,
|
|
580
|
+
chunkBytes: CHUNK_BYTES,
|
|
581
|
+
signal,
|
|
582
|
+
onProgress: ({ sentBytes, totalBytes }) => draw(sentBytes / totalBytes),
|
|
583
|
+
init: async (start, options) => {
|
|
584
|
+
const opened = await api.uploads.init.withOptions({ ...start, name: file.name }, options)
|
|
585
|
+
return opened.finished ? { finished: opened.finished } : undefined
|
|
586
|
+
},
|
|
587
|
+
chunk: (part, options) => api.uploads.chunk.withOptions(part, options),
|
|
588
|
+
finalize: (upload, options) => api.uploads.finalize.withOptions(upload, options),
|
|
589
|
+
})
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
The rules the two halves share:
|
|
593
|
+
|
|
594
|
+
- **The client mints the upload id** (default `crypto.randomUUID()`), so a
|
|
595
|
+
repeated `init` is the same upload. The spool keys it by `owner` too — the
|
|
596
|
+
same id from another owner is another upload.
|
|
597
|
+
- **A part is idempotent.** The spool keeps a size+sha256 receipt per index:
|
|
598
|
+
the same bytes again answer `'repeated'`, other bytes `UPLOAD_CONFLICT`
|
|
599
|
+
(409). So the driver repeats `init` and a part on a failure that may pass — no
|
|
600
|
+
answer (network, timeout), `429`, `5xx` — up to `retries` times (default 3)
|
|
601
|
+
with a doubling pause from `retryDelayMs` (default 1 s). A refusal is final.
|
|
602
|
+
- **`finalize` is never repeated.** After it the application owns the file and
|
|
603
|
+
the parts are gone; a lost answer is recovered by `init` returning
|
|
604
|
+
`{ finished }`, which the driver hands back without sending a byte.
|
|
605
|
+
- **Cancellation is checked between parts**, so a cancelled upload never
|
|
606
|
+
reaches `finalize`.
|
|
607
|
+
- **Every part but the last is exactly `chunkBytes`** and `chunkCount` must add
|
|
608
|
+
up; the spool refuses anything else with a 4xx `AppError`
|
|
609
|
+
(`ChunkSpoolErrorCode`), which a handler can let through as the client's
|
|
610
|
+
typed error.
|
|
611
|
+
- **Progress counts file bytes**, inside a part too — the driver passes
|
|
612
|
+
`onUploadProgress` into the part call, so the [upload
|
|
613
|
+
progress](#upload-progress) route of the client applies. A repeated part
|
|
614
|
+
counts again from its start.
|
|
615
|
+
- The first writer of a part wins across processes: its data file is named by
|
|
616
|
+
its hash and its receipt is published by an exclusive link. `sweep()` removes
|
|
617
|
+
uploads untouched for `staleAfterMs` (default 24 h); run it on a schedule.
|
|
618
|
+
|
|
619
|
+
Part size is a latency decision: every part costs a round trip, so on a
|
|
620
|
+
200 ms link a smaller part buys smoother progress at the price of a slower
|
|
621
|
+
upload.
|
|
622
|
+
|
|
468
623
|
## Contract URL builders
|
|
469
624
|
|
|
470
625
|
Browser-native consumers such as `<img src>`, downloads and navigation need a
|
package/llms/stitchkit.3.txt
CHANGED
|
@@ -41,7 +41,10 @@ The browser-and-server entrypoint. Re-exports everything from
|
|
|
41
41
|
| `UrlBuilderConfig` | _type_ | explicit `{ baseUrl }` source for a URL builder |
|
|
42
42
|
| `ClientConfig` | _type_ | config for `createClient`'s bare-fetch mode (2nd arg, no `HttpClient`) |
|
|
43
43
|
| `ClientFetch` | _type_ | injectable Fetch-compatible transport used by framework and application-owned adapters |
|
|
44
|
-
| `ClientRequestOptions` | _type_ | per-call `{ signal
|
|
44
|
+
| `ClientRequestOptions` | _type_ | per-call `{ signal?, onUploadProgress? }` passed through an endpoint callable's `.withOptions(...)`; caller abort is distinct from timeout — [guide](../guide/client.md#per-call-cancellation) |
|
|
45
|
+
| `UploadProgress` | _type_ | `{ sentBytes, totalBytes, attempt }` heard by `onUploadProgress`: bytes of the encoded body handed to the network, first `0`, last `totalBytes`, counted again on each transport retry — [guide](../guide/client.md#upload-progress) |
|
|
46
|
+
| `uploadInChunks` | function | send a file larger than one request as `init` → parts → `finalize` through three calls the app owns; client-minted id, repeats of a part on a failure that may pass, `finalize` never repeated, cancel checked between parts, progress in file bytes — [guide](../guide/client.md#chunked-uploads) |
|
|
47
|
+
| `ChunkedUploadConfig` / `ChunkedUploadStart` / `ChunkedUploadPart` / `ChunkedUploadProgress` | _type_ | the driver's file, part size, retries and three calls; what `init` declares; one part; progress `{ sentBytes, totalBytes, index, chunkCount, attempt }` |
|
|
45
48
|
| `ContractClientConfig` | _type_ | per-tenant / resource-scoped client config — dynamic `pathPrefix` + `stripPrefixKeys` ([guide](../guide/client.md#contractclientconfig--per-tenant--resource-scoped-clients)) |
|
|
46
49
|
| `contractEndpointMatchers` | function | compile exact pathname matchers for selected HTTP contract operations and expected-401 policy |
|
|
47
50
|
| `PathPrefixArgs` | _type_ | required string-valued keys exposed to a typed dynamic `pathPrefix` callback |
|
|
@@ -54,7 +57,7 @@ The browser-and-server entrypoint. Re-exports everything from
|
|
|
54
57
|
| `ConfiguredHttpClient` | _type_ | a framework-created `HttpClient` carrying its readonly `baseUrl` for URL builders |
|
|
55
58
|
| `HttpClientConfig` | _type_ | config for `createHttpClient`; retry `limit` counts retries after the initial attempt (default 2 = at most 3 GET attempts), with `statusCodes: []` by default; `fetch` installs an explicit transport and is mutually exclusive with the legacy Bun-only `unix` option — [details](../guide/client.md#createhttpclient) |
|
|
56
59
|
| `UnauthorizedMatcher` | _type_ | exact `(pathname) => boolean` policy accepted by `suppressUnauthorizedFor` |
|
|
57
|
-
| `RequestOptions` | _type_ | per-call options — params, timeout, response type |
|
|
60
|
+
| `RequestOptions` | _type_ | per-call options — params, timeout, response type, `onUploadProgress` |
|
|
58
61
|
| `HeaderProvider` | _type_ | static or per-request headers |
|
|
59
62
|
| `ApiEvent` | _type_ | a client event — `unauthorized` / `network_error` / `logout` |
|
|
60
63
|
| `ApiEventListener` | _type_ | an `ApiEvent` handler |
|