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,5 +1,5 @@
|
|
|
1
1
|
import"../index-hby4qhax.js";
|
|
2
|
-
import"../index-
|
|
2
|
+
import"../index-h43cepag.js";
|
|
3
3
|
import {
|
|
4
4
|
joinRoutePath
|
|
5
5
|
} from "../index-m38ej7e5.js";
|
|
@@ -7,7 +7,7 @@ import"../index-r5c4fedv.js";
|
|
|
7
7
|
import {
|
|
8
8
|
createClient2,
|
|
9
9
|
createClients2
|
|
10
|
-
} from "../index-
|
|
10
|
+
} from "../index-65jwqhcf.js";
|
|
11
11
|
import"../index-n53mswt3.js";
|
|
12
12
|
import"../index-q0th8q0z.js";
|
|
13
13
|
import {
|
|
@@ -57,9 +57,10 @@ import {
|
|
|
57
57
|
defineContract2
|
|
58
58
|
} from "../index-ck71tbcf.js";
|
|
59
59
|
import"../index-1bx1xv1k.js";
|
|
60
|
+
import"../index-sznqf8fx.js";
|
|
60
61
|
import {
|
|
61
62
|
redact2
|
|
62
|
-
} from "../index-
|
|
63
|
+
} from "../index-e6c2spsd.js";
|
|
63
64
|
import {
|
|
64
65
|
AsyncOperationCancelResultSchema2,
|
|
65
66
|
createAsyncOperationSnapshotSchema2,
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The server half of a chunked upload (`uploadInChunks` is the client half):
|
|
3
|
+
* parts land on disk under the upload's owner, each with a receipt, and come
|
|
4
|
+
* back in order once every part is in.
|
|
5
|
+
*
|
|
6
|
+
* - An upload is keyed by `owner` (whoever the application says sends it —
|
|
7
|
+
* a user, a user and a device) and the client-minted `uploadId`; the same id
|
|
8
|
+
* from another owner is another upload.
|
|
9
|
+
* - `open` is idempotent: the same declaration again is `'reopened'`, a
|
|
10
|
+
* different one is `UPLOAD_CONFLICT`.
|
|
11
|
+
* - A part's receipt is its size and sha256. The same bytes again are
|
|
12
|
+
* `'repeated'`; other bytes at the same index are `UPLOAD_CONFLICT`. The
|
|
13
|
+
* first writer wins across processes: a part's data file is named by its hash
|
|
14
|
+
* and its receipt is published by an exclusive link, so a loser can neither
|
|
15
|
+
* replace the receipt nor the bytes it names.
|
|
16
|
+
* - Every part but the last is exactly `chunkBytes` long.
|
|
17
|
+
* - `sweep` removes an upload nothing has touched for `staleAfterMs`.
|
|
18
|
+
*
|
|
19
|
+
* Refusals are `AppError`s with a 4xx status, so a handler that does not catch
|
|
20
|
+
* them answers the client with a typed envelope the driver does not repeat.
|
|
21
|
+
*/
|
|
22
|
+
import type { ZodType } from 'zod';
|
|
23
|
+
export type ChunkSpoolErrorCode = 'UPLOAD_INVALID' | 'UPLOAD_TOO_LARGE' | 'UPLOAD_NOT_FOUND' | 'UPLOAD_CONFLICT' | 'UPLOAD_CHUNK_OUT_OF_RANGE' | 'UPLOAD_CHUNK_SIZE' | 'UPLOAD_INCOMPLETE';
|
|
24
|
+
export interface ChunkSpoolConfig<TMeta> {
|
|
25
|
+
/** Where uploads in progress live; one directory per upload. */
|
|
26
|
+
readonly directory: string;
|
|
27
|
+
/** Bytes per part, the same number the client driver uses. */
|
|
28
|
+
readonly chunkBytes: number;
|
|
29
|
+
readonly maxFileBytes: number;
|
|
30
|
+
/** What `open` stores beside the upload — file name, type, the app's fields. */
|
|
31
|
+
readonly meta: ZodType<TMeta>;
|
|
32
|
+
/** Default 24 hours. */
|
|
33
|
+
readonly staleAfterMs?: number;
|
|
34
|
+
}
|
|
35
|
+
export interface ChunkSpoolKey {
|
|
36
|
+
readonly owner: string;
|
|
37
|
+
readonly uploadId: string;
|
|
38
|
+
}
|
|
39
|
+
export interface ChunkSpoolOpen<TMeta> extends ChunkSpoolKey {
|
|
40
|
+
readonly totalBytes: number;
|
|
41
|
+
readonly chunkCount: number;
|
|
42
|
+
readonly meta: TMeta;
|
|
43
|
+
}
|
|
44
|
+
export interface ChunkSpoolPart extends ChunkSpoolKey {
|
|
45
|
+
readonly index: number;
|
|
46
|
+
readonly bytes: Blob;
|
|
47
|
+
}
|
|
48
|
+
export interface ChunkSpoolAssembly<TMeta> {
|
|
49
|
+
readonly meta: TMeta;
|
|
50
|
+
readonly totalBytes: number;
|
|
51
|
+
/** The parts on disk, in order; valid until `discard`. */
|
|
52
|
+
readonly chunkPaths: readonly string[];
|
|
53
|
+
/** The whole file, part after part. */
|
|
54
|
+
stream(): ReadableStream<Uint8Array>;
|
|
55
|
+
}
|
|
56
|
+
export interface ChunkSpool<TMeta> {
|
|
57
|
+
open(upload: ChunkSpoolOpen<TMeta>): Promise<'opened' | 'reopened'>;
|
|
58
|
+
put(part: ChunkSpoolPart): Promise<'stored' | 'repeated'>;
|
|
59
|
+
/** Every part, checked against its receipt; `UPLOAD_INCOMPLETE` names the first missing. */
|
|
60
|
+
assemble(key: ChunkSpoolKey): Promise<ChunkSpoolAssembly<TMeta>>;
|
|
61
|
+
discard(key: ChunkSpoolKey): Promise<void>;
|
|
62
|
+
/** Remove uploads untouched for `staleAfterMs`; resolves with how many. */
|
|
63
|
+
sweep(): Promise<number>;
|
|
64
|
+
}
|
|
65
|
+
export declare function createChunkSpool<TMeta>(config: ChunkSpoolConfig<TMeta>): ChunkSpool<TMeta>;
|
|
66
|
+
//# sourceMappingURL=chunk-spool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunk-spool.d.ts","sourceRoot":"","sources":["../../src/files/chunk-spool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAgBH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC;AAInC,MAAM,MAAM,mBAAmB,GAC3B,gBAAgB,GAChB,kBAAkB,GAClB,kBAAkB,GAClB,iBAAiB,GACjB,2BAA2B,GAC3B,mBAAmB,GACnB,mBAAmB,CAAC;AAoBxB,MAAM,WAAW,gBAAgB,CAAC,KAAK;IACrC,gEAAgE;IAChE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,8DAA8D;IAC9D,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,gFAAgF;IAChF,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IAC9B,wBAAwB;IACxB,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAChC;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,cAAc,CAAC,KAAK,CAAE,SAAQ,aAAa;IAC1D,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;CACtB;AAED,MAAM,WAAW,cAAe,SAAQ,aAAa;IACnD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB,CAAC,KAAK;IACvC,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IACrB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,0DAA0D;IAC1D,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,uCAAuC;IACvC,MAAM,IAAI,cAAc,CAAC,UAAU,CAAC,CAAC;CACtC;AAED,MAAM,WAAW,UAAU,CAAC,KAAK;IAC/B,IAAI,CAAC,MAAM,EAAE,cAAc,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,QAAQ,GAAG,UAAU,CAAC,CAAC;IACpE,GAAG,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,QAAQ,GAAG,UAAU,CAAC,CAAC;IAC1D,4FAA4F;IAC5F,QAAQ,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC;IACjE,OAAO,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3C,2EAA2E;IAC3E,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;CAC1B;AAkED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,gBAAgB,CAAC,KAAK,CAAC,GAAG,UAAU,CAAC,KAAK,CAAC,CA4J1F"}
|
|
@@ -2,10 +2,12 @@ import {
|
|
|
2
2
|
RequestCancellationError,
|
|
3
3
|
createRequestCancellation,
|
|
4
4
|
responseTraceId,
|
|
5
|
+
uploadProgressRoute,
|
|
6
|
+
withUploadProgress,
|
|
5
7
|
ApiError2,
|
|
6
8
|
refuseLocally,
|
|
7
9
|
parseApiErrorBody
|
|
8
|
-
} from "./index-
|
|
10
|
+
} from "./index-h43cepag.js";
|
|
9
11
|
import {
|
|
10
12
|
parseTrailingWildcard
|
|
11
13
|
} from "./index-m38ej7e5.js";
|
|
@@ -541,7 +543,8 @@ function createFetchExecutor(endpoint, prefix, config, contractConfig) {
|
|
|
541
543
|
try {
|
|
542
544
|
return await cancellation.run(async (signal) => {
|
|
543
545
|
const body = endpoint.multipart ? buildMultipartForm(endpoint.multipart, plan.remainingArgs) : hasBody ? JSON.stringify(plan.remainingArgs) : undefined;
|
|
544
|
-
const
|
|
546
|
+
const deliver = options?.onUploadProgress ? withUploadProgress(executeFetch, options.onUploadProgress, uploadProgressRoute(config.fetch !== undefined)) : executeFetch;
|
|
547
|
+
const res = await deliver(url, {
|
|
545
548
|
method: endpoint.method,
|
|
546
549
|
headers,
|
|
547
550
|
credentials: config.credentials,
|
|
@@ -660,7 +663,7 @@ function createEndpointMethod(endpoint, execute, contractConfig) {
|
|
|
660
663
|
return Object.assign(method, {
|
|
661
664
|
withOptions: (...args) => {
|
|
662
665
|
refuseExtraWithOptionsArguments(endpoint, args.length, 2);
|
|
663
|
-
return settle(execute, readClientRequestArgs(args[0]), readClientRequestOptions(args[1]));
|
|
666
|
+
return settle(execute, readClientRequestArgs(args[0]), readClientRequestOptions(endpoint, args[1]));
|
|
664
667
|
}
|
|
665
668
|
});
|
|
666
669
|
}
|
|
@@ -668,7 +671,7 @@ function createEndpointMethod(endpoint, execute, contractConfig) {
|
|
|
668
671
|
return Object.assign(method, {
|
|
669
672
|
withOptions: (...args) => {
|
|
670
673
|
refuseExtraWithOptionsArguments(endpoint, args.length, 1);
|
|
671
|
-
return settle(execute, {}, readClientRequestOptions(args[0]));
|
|
674
|
+
return settle(execute, {}, readClientRequestOptions(endpoint, args[0]));
|
|
672
675
|
}
|
|
673
676
|
});
|
|
674
677
|
}
|
|
@@ -694,16 +697,30 @@ function readClientRequestArgs(requestArgs) {
|
|
|
694
697
|
}
|
|
695
698
|
return requestArgs ?? {};
|
|
696
699
|
}
|
|
697
|
-
function readClientRequestOptions(value) {
|
|
700
|
+
function readClientRequestOptions(endpoint, value) {
|
|
698
701
|
if (!isRecord(value))
|
|
699
702
|
throw new TypeError("Client request options must be an object");
|
|
700
|
-
const signal = value
|
|
701
|
-
if (signal
|
|
702
|
-
return {};
|
|
703
|
-
if (!isAbortSignal(signal)) {
|
|
703
|
+
const { signal, onUploadProgress } = value;
|
|
704
|
+
if (signal !== undefined && !isAbortSignal(signal)) {
|
|
704
705
|
throw new TypeError("Client request signal must be an AbortSignal");
|
|
705
706
|
}
|
|
706
|
-
|
|
707
|
+
if (onUploadProgress === undefined)
|
|
708
|
+
return signal === undefined ? {} : { signal };
|
|
709
|
+
if (typeof onUploadProgress !== "function") {
|
|
710
|
+
throw new TypeError("Client request onUploadProgress must be a function");
|
|
711
|
+
}
|
|
712
|
+
if (!endpointSendsBody(endpoint)) {
|
|
713
|
+
throw new TypeError(`${endpoint.method} ${endpoint.path}: onUploadProgress needs a request body — ` + "a GET, HEAD or DELETE sends none, and a streaming endpoint keeps its response live, " + "which the XMLHttpRequest route would buffer.");
|
|
714
|
+
}
|
|
715
|
+
return {
|
|
716
|
+
...signal !== undefined && { signal },
|
|
717
|
+
onUploadProgress: (progress) => {
|
|
718
|
+
onUploadProgress(progress);
|
|
719
|
+
}
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
function endpointSendsBody(endpoint) {
|
|
723
|
+
return endpoint.method !== "GET" && endpoint.method !== "HEAD" && endpoint.method !== "DELETE" && !(("stream" in endpoint) && endpoint.stream);
|
|
707
724
|
}
|
|
708
725
|
function isAbortSignal(value) {
|
|
709
726
|
return typeof value === "object" && value !== null && "aborted" in value && typeof value.aborted === "boolean" && "addEventListener" in value && typeof value.addEventListener === "function" && "removeEventListener" in value && typeof value.removeEventListener === "function";
|
|
@@ -14,16 +14,20 @@ var BY_DESCRIPTION = [
|
|
|
14
14
|
"message-invalid"
|
|
15
15
|
]
|
|
16
16
|
];
|
|
17
|
-
var
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
|
|
26
|
-
|
|
17
|
+
var TRAITS = {
|
|
18
|
+
"blocked-by-user": { retryable: false, recipientUnreachable: true },
|
|
19
|
+
"user-deactivated": { retryable: false, recipientUnreachable: true },
|
|
20
|
+
"chat-not-found": { retryable: false, recipientUnreachable: true },
|
|
21
|
+
"not-started": { retryable: false, recipientUnreachable: true },
|
|
22
|
+
"rate-limited": { retryable: true, recipientUnreachable: false },
|
|
23
|
+
"message-invalid": { retryable: false, recipientUnreachable: false },
|
|
24
|
+
"server-error": { retryable: true, recipientUnreachable: false },
|
|
25
|
+
unknown: { retryable: false, recipientUnreachable: false }
|
|
26
|
+
};
|
|
27
|
+
function isSendFailureReason(value) {
|
|
28
|
+
return Object.hasOwn(TRAITS, value);
|
|
29
|
+
}
|
|
30
|
+
var TELEGRAM_SEND_FAILURE_REASONS2 = Object.keys(TRAITS).filter(isSendFailureReason);
|
|
27
31
|
function numberAt(value, keys) {
|
|
28
32
|
if (!isRecord(value))
|
|
29
33
|
return;
|
|
@@ -85,8 +89,7 @@ function classifyTelegramSendFailure2(error) {
|
|
|
85
89
|
return {
|
|
86
90
|
reason,
|
|
87
91
|
...status !== undefined && { status },
|
|
88
|
-
|
|
89
|
-
recipientUnreachable: UNREACHABLE.has(reason),
|
|
92
|
+
...TRAITS[reason],
|
|
90
93
|
evidence: "description"
|
|
91
94
|
};
|
|
92
95
|
}
|
|
@@ -138,4 +141,4 @@ function classifyTelegramEditRefusal(error) {
|
|
|
138
141
|
return "other";
|
|
139
142
|
}
|
|
140
143
|
|
|
141
|
-
export { classifyTelegramSendFailure2, classifyTelegramEditRefusal };
|
|
144
|
+
export { TELEGRAM_SEND_FAILURE_REASONS2, classifyTelegramSendFailure2, classifyTelegramEditRefusal };
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {
|
|
2
|
+
maskSecretShapes
|
|
3
|
+
} from "./index-sznqf8fx.js";
|
|
1
4
|
import {
|
|
2
5
|
isUnsafeKey
|
|
3
6
|
} from "./index-zsarb5xp.js";
|
|
@@ -53,7 +56,7 @@ var DEFAULT_MAX_BYTES = 16000;
|
|
|
53
56
|
var DEFAULT_MAX_DEPTH = 20;
|
|
54
57
|
var DEFAULT_MAX_NODES = 20000;
|
|
55
58
|
var MASK = "[redacted]";
|
|
56
|
-
function secretMasker(options) {
|
|
59
|
+
function secretMasker(options, isSecretName) {
|
|
57
60
|
const values = [...options.sensitiveValues ?? []].filter((value) => value !== "").sort((left, right) => right.length - left.length);
|
|
58
61
|
const patterns = options.sensitiveUrlPatterns ?? [];
|
|
59
62
|
return (input) => {
|
|
@@ -66,20 +69,27 @@ function secretMasker(options) {
|
|
|
66
69
|
output = output.replace(global, MASK);
|
|
67
70
|
} catch {}
|
|
68
71
|
}
|
|
69
|
-
return output;
|
|
72
|
+
return maskSecretShapes(output, MASK, isSecretName);
|
|
70
73
|
};
|
|
71
74
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
75
|
+
var ERROR_CORE = new Set(["_type", "name", "message", "stack", "cause"]);
|
|
76
|
+
function isErrorFact(value) {
|
|
77
|
+
if (value === null || typeof value !== "object")
|
|
78
|
+
return typeof value !== "function";
|
|
79
|
+
if (Array.isArray(value) || value instanceof Error || value instanceof Date)
|
|
80
|
+
return true;
|
|
81
|
+
const prototype = Object.getPrototypeOf(value);
|
|
82
|
+
return prototype === Object.prototype || prototype === null;
|
|
83
|
+
}
|
|
84
|
+
function kindOf(value) {
|
|
85
|
+
if (typeof value === "function")
|
|
86
|
+
return "function";
|
|
87
|
+
const maker = isRecord(value) ? value.constructor : undefined;
|
|
88
|
+
return typeof maker === "function" && maker.name !== "" ? maker.name : "object";
|
|
89
|
+
}
|
|
90
|
+
function pathMatcher(patterns) {
|
|
91
|
+
const matchers = patterns.map((pattern) => pattern.split("."));
|
|
92
|
+
const matches = (matcher, path, patternIndex = 0, pathIndex = 0) => {
|
|
83
93
|
if (patternIndex === matcher.length)
|
|
84
94
|
return pathIndex === path.length;
|
|
85
95
|
const segment = matcher[patternIndex];
|
|
@@ -87,21 +97,32 @@ function redact2(value, options = {}) {
|
|
|
87
97
|
if (patternIndex + 1 === matcher.length)
|
|
88
98
|
return true;
|
|
89
99
|
for (let next = pathIndex;next <= path.length; next += 1) {
|
|
90
|
-
if (
|
|
100
|
+
if (matches(matcher, path, patternIndex + 1, next))
|
|
91
101
|
return true;
|
|
92
102
|
}
|
|
93
103
|
return false;
|
|
94
104
|
}
|
|
95
105
|
if (pathIndex === path.length || segment !== "*" && segment !== path[pathIndex])
|
|
96
106
|
return false;
|
|
97
|
-
return
|
|
107
|
+
return matches(matcher, path, patternIndex + 1, pathIndex + 1);
|
|
98
108
|
};
|
|
99
|
-
|
|
109
|
+
return (path) => matchers.some((matcher) => matches(matcher, path));
|
|
110
|
+
}
|
|
111
|
+
function redact2(value, options = {}) {
|
|
112
|
+
const custom = options.sensitiveKeys;
|
|
113
|
+
const isSensitive = custom === undefined ? isSensitiveKeyDefault : (key) => {
|
|
114
|
+
custom.lastIndex = 0;
|
|
115
|
+
return custom.test(key) || options.includeDefaultSensitiveKeys === true && isSensitiveKeyDefault(key);
|
|
116
|
+
};
|
|
117
|
+
const maxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH;
|
|
118
|
+
const maxStringLength = options.maxStringLength ?? Number.POSITIVE_INFINITY;
|
|
119
|
+
const maxCollectionLength = options.maxCollectionLength ?? Number.POSITIVE_INFINITY;
|
|
120
|
+
const isSensitivePath = pathMatcher(options.sensitivePaths ?? []);
|
|
100
121
|
const stringBoundFor = (key) => {
|
|
101
122
|
const byKey = key === undefined ? undefined : options.maxStringLengthByKey?.[key];
|
|
102
123
|
return byKey ?? maxStringLength;
|
|
103
124
|
};
|
|
104
|
-
const mask = secretMasker(options);
|
|
125
|
+
const mask = secretMasker(options, isSensitive);
|
|
105
126
|
const safeString = (input, key) => {
|
|
106
127
|
const bound = stringBoundFor(key);
|
|
107
128
|
const output = mask(input);
|
|
@@ -176,14 +197,31 @@ function redact2(value, options = {}) {
|
|
|
176
197
|
return "[unreadable]";
|
|
177
198
|
}
|
|
178
199
|
};
|
|
200
|
+
if (ancestors.has(input))
|
|
201
|
+
return "[circular]";
|
|
202
|
+
ancestors.add(input);
|
|
179
203
|
const stack = property("stack");
|
|
180
204
|
const cause = property("cause");
|
|
205
|
+
const own = {};
|
|
206
|
+
for (const key of Object.keys(input)) {
|
|
207
|
+
if (isUnsafeKey(key) || ERROR_CORE.has(key))
|
|
208
|
+
continue;
|
|
209
|
+
const childPath = [...path, key];
|
|
210
|
+
try {
|
|
211
|
+
const field = Reflect.get(input, key);
|
|
212
|
+
own[key] = isSensitive(key) || isSensitivePath(childPath) ? MASK : isErrorFact(field) ? walk(field, depth + 1, childPath) : `[${kindOf(field)}]`;
|
|
213
|
+
} catch {
|
|
214
|
+
own[key] = "[unreadable]";
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
ancestors.delete(input);
|
|
181
218
|
return {
|
|
182
219
|
_type: "error",
|
|
183
220
|
name: safeString(String(property("name")), "name"),
|
|
184
221
|
message: safeString(String(property("message")), "message"),
|
|
185
222
|
...stack !== undefined && { stack: safeString(String(stack), "stack") },
|
|
186
|
-
...cause !== undefined && { cause: walk(cause, depth + 1, [...path, "cause"]) }
|
|
223
|
+
...cause !== undefined && { cause: walk(cause, depth + 1, [...path, "cause"]) },
|
|
224
|
+
...own
|
|
187
225
|
};
|
|
188
226
|
}
|
|
189
227
|
if (Array.isArray(input)) {
|
|
@@ -144,6 +144,122 @@ function responseTraceId(response) {
|
|
|
144
144
|
return response?.headers.get(REQUEST_ID_HEADER) ?? undefined;
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
+
// src/browser/upload-progress.ts
|
|
148
|
+
var PIECE_BYTES = 64 * 1024;
|
|
149
|
+
function uploadProgressRoute(injectedTransport) {
|
|
150
|
+
return !injectedTransport && typeof globalThis.XMLHttpRequest === "function" ? "xhr" : "stream";
|
|
151
|
+
}
|
|
152
|
+
function withUploadProgress(transport, listener, route) {
|
|
153
|
+
let attempts = 0;
|
|
154
|
+
return async (input, init) => {
|
|
155
|
+
attempts += 1;
|
|
156
|
+
const attempt = attempts;
|
|
157
|
+
const request = new Request(input, init);
|
|
158
|
+
const headers = new Headers(request.headers);
|
|
159
|
+
const bytes = new Uint8Array(await request.arrayBuffer());
|
|
160
|
+
let reported = -1;
|
|
161
|
+
const report = (sentBytes) => {
|
|
162
|
+
if (sentBytes <= reported)
|
|
163
|
+
return;
|
|
164
|
+
reported = sentBytes;
|
|
165
|
+
try {
|
|
166
|
+
listener({ sentBytes, totalBytes: bytes.byteLength, attempt });
|
|
167
|
+
} catch {}
|
|
168
|
+
};
|
|
169
|
+
const signal = init?.signal ?? (input instanceof Request ? input.signal : undefined);
|
|
170
|
+
const credentials = init?.credentials ?? (input instanceof Request ? input.credentials : undefined);
|
|
171
|
+
report(0);
|
|
172
|
+
if (route === "xhr") {
|
|
173
|
+
return sendThroughXhr(request, headers, bytes, { signal, credentials, report });
|
|
174
|
+
}
|
|
175
|
+
headers.set("content-length", String(bytes.byteLength));
|
|
176
|
+
const counted = bytes.byteLength > 0 ? { body: countedBody(bytes, report), duplex: "half" } : {};
|
|
177
|
+
const outgoing = {
|
|
178
|
+
...init,
|
|
179
|
+
method: request.method,
|
|
180
|
+
headers,
|
|
181
|
+
body: undefined,
|
|
182
|
+
...counted,
|
|
183
|
+
...signal && { signal },
|
|
184
|
+
...credentials && { credentials }
|
|
185
|
+
};
|
|
186
|
+
return transport(request.url, outgoing);
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
function countedBody(bytes, report) {
|
|
190
|
+
let offset = 0;
|
|
191
|
+
return new ReadableStream({
|
|
192
|
+
pull(controller) {
|
|
193
|
+
if (offset >= bytes.byteLength) {
|
|
194
|
+
controller.close();
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
const end = Math.min(offset + PIECE_BYTES, bytes.byteLength);
|
|
198
|
+
controller.enqueue(bytes.slice(offset, end));
|
|
199
|
+
offset = end;
|
|
200
|
+
report(offset);
|
|
201
|
+
}
|
|
202
|
+
}, { highWaterMark: 0 });
|
|
203
|
+
}
|
|
204
|
+
var BROWSER_OWNED_HEADERS = new Set(["content-length", "host", "connection"]);
|
|
205
|
+
function sendThroughXhr(request, headers, bytes, { signal, credentials, report }) {
|
|
206
|
+
return new Promise((resolve, reject) => {
|
|
207
|
+
if (credentials === "omit") {
|
|
208
|
+
reject(new TypeError("onUploadProgress sends through XMLHttpRequest, which cannot omit same-origin cookies — use credentials 'same-origin' or 'include'"));
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
if (signal?.aborted) {
|
|
212
|
+
reject(abortReason(signal));
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
const xhr = new XMLHttpRequest;
|
|
216
|
+
xhr.open(request.method, request.url, true);
|
|
217
|
+
headers.forEach((value, name) => {
|
|
218
|
+
if (!BROWSER_OWNED_HEADERS.has(name))
|
|
219
|
+
xhr.setRequestHeader(name, value);
|
|
220
|
+
});
|
|
221
|
+
xhr.withCredentials = credentials === "include";
|
|
222
|
+
xhr.responseType = "arraybuffer";
|
|
223
|
+
const abort = () => xhr.abort();
|
|
224
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
225
|
+
const settle = () => signal?.removeEventListener("abort", abort);
|
|
226
|
+
xhr.upload.onprogress = (event) => report(Math.min(event.loaded, bytes.byteLength));
|
|
227
|
+
xhr.upload.onload = () => report(bytes.byteLength);
|
|
228
|
+
xhr.onload = () => {
|
|
229
|
+
settle();
|
|
230
|
+
if (xhr.status < 200 || xhr.status > 599) {
|
|
231
|
+
reject(new TypeError("Network request failed"));
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
report(bytes.byteLength);
|
|
235
|
+
resolve(responseOf(xhr));
|
|
236
|
+
};
|
|
237
|
+
xhr.onerror = () => {
|
|
238
|
+
settle();
|
|
239
|
+
reject(new TypeError("Network request failed"));
|
|
240
|
+
};
|
|
241
|
+
xhr.onabort = () => {
|
|
242
|
+
settle();
|
|
243
|
+
reject(signal ? abortReason(signal) : new DOMException("Aborted", "AbortError"));
|
|
244
|
+
};
|
|
245
|
+
xhr.send(bytes.byteLength > 0 ? bytes : null);
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
function abortReason(signal) {
|
|
249
|
+
return signal.reason ?? new DOMException("Aborted", "AbortError");
|
|
250
|
+
}
|
|
251
|
+
var NULL_BODY_STATUSES = new Set([101, 103, 204, 205, 304]);
|
|
252
|
+
function responseOf(xhr) {
|
|
253
|
+
const headers = new Headers;
|
|
254
|
+
for (const line of xhr.getAllResponseHeaders().trim().split(/[\r\n]+/)) {
|
|
255
|
+
const colon = line.indexOf(":");
|
|
256
|
+
if (colon > 0)
|
|
257
|
+
headers.append(line.slice(0, colon).trim(), line.slice(colon + 1).trim());
|
|
258
|
+
}
|
|
259
|
+
const body = xhr.response;
|
|
260
|
+
return new Response(NULL_BODY_STATUSES.has(xhr.status) || !(body instanceof ArrayBuffer) ? null : body, { status: xhr.status, statusText: xhr.statusText, headers });
|
|
261
|
+
}
|
|
262
|
+
|
|
147
263
|
// src/browser/http.ts
|
|
148
264
|
var API_ERROR_BRAND = Symbol.for("stitchkit.ApiError");
|
|
149
265
|
function messageForCode(code, message) {
|
|
@@ -225,6 +341,15 @@ function requestFailure(error, emit) {
|
|
|
225
341
|
const msg = error instanceof Error ? error.message : undefined;
|
|
226
342
|
return new ApiError2("UNKNOWN_ERROR", status, msg ? { message: msg } : undefined, msg, undefined, responseTraceId(response), { cause: error });
|
|
227
343
|
}
|
|
344
|
+
function errorBodyOf(data) {
|
|
345
|
+
if (typeof data !== "string")
|
|
346
|
+
return data ?? null;
|
|
347
|
+
try {
|
|
348
|
+
return JSON.parse(data);
|
|
349
|
+
} catch {
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
228
353
|
function createHttpClient2(config) {
|
|
229
354
|
assertTransportChoice(config);
|
|
230
355
|
let ssrCookies = null;
|
|
@@ -280,23 +405,26 @@ function createHttpClient2(config) {
|
|
|
280
405
|
emit({ type: "unauthorized" });
|
|
281
406
|
}
|
|
282
407
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
408
|
+
}
|
|
409
|
+
],
|
|
410
|
+
beforeError: [
|
|
411
|
+
({ error }) => {
|
|
412
|
+
if (!isHTTPError(error))
|
|
413
|
+
return error;
|
|
414
|
+
const body = errorBodyOf(error.data);
|
|
415
|
+
const parsed = body === null ? null : parseError(body);
|
|
416
|
+
if (!parsed)
|
|
417
|
+
return error;
|
|
418
|
+
return new ApiError2(parsed.code, error.response.status, parsed.details, parsed.message, parsed.hint, responseTraceId(error.response));
|
|
292
419
|
}
|
|
293
420
|
]
|
|
294
421
|
}
|
|
295
422
|
});
|
|
296
423
|
async function request(method, url, data, options = {}) {
|
|
297
424
|
const cancellation = createRequestCancellation(options.signal, options.timeout ?? config.timeout ?? 30000);
|
|
425
|
+
const transport = config.fetch ?? globalThis.fetch.bind(globalThis);
|
|
298
426
|
const kyOptions = {
|
|
299
|
-
fetch: createRetryAwareFetch(config.fetch
|
|
427
|
+
fetch: createRetryAwareFetch(options.onUploadProgress ? withUploadProgress(transport, options.onUploadProgress, uploadProgressRoute(config.fetch !== undefined || config.unix !== undefined)) : transport, config.unix),
|
|
300
428
|
timeout: false,
|
|
301
429
|
signal: cancellation.signal
|
|
302
430
|
};
|
|
@@ -339,4 +467,4 @@ function createHttpClient2(config) {
|
|
|
339
467
|
};
|
|
340
468
|
}
|
|
341
469
|
|
|
342
|
-
export { RequestCancellationError, createRequestCancellation, responseTraceId, ApiError2, refuseLocally, isAbortLikeError, parseApiErrorBody, createHttpClient2 };
|
|
470
|
+
export { RequestCancellationError, createRequestCancellation, responseTraceId, uploadProgressRoute, withUploadProgress, ApiError2, refuseLocally, isAbortLikeError, parseApiErrorBody, createHttpClient2 };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// src/internal/secret-shapes.ts
|
|
2
|
+
var BOT_TOKEN_SECRET = /(?<=\d{5,}:)[A-Za-z0-9_-]{30,}/;
|
|
3
|
+
var URL_PASSWORD = /(\b[a-z][a-z0-9+.-]*:\/\/[^\s/?#@:"'<>]*:)([^\s/?#@"'<>]+)(?=@)/gi;
|
|
4
|
+
var QUERY_PARAMETER = /([?&;])([^=&#\s"'<>]+)=([^&#\s"'<>]*)/g;
|
|
5
|
+
var QUERY_SECRET_NAMES = new Set(["key", "sig", "signature"]);
|
|
6
|
+
function decoded(name) {
|
|
7
|
+
try {
|
|
8
|
+
return decodeURIComponent(name);
|
|
9
|
+
} catch {
|
|
10
|
+
return name;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function maskSecretShapes(input, mask, isSecretName) {
|
|
14
|
+
return input.replace(new RegExp(BOT_TOKEN_SECRET.source, "g"), mask).replace(URL_PASSWORD, `$1${mask}`).replace(QUERY_PARAMETER, (whole, separator, name) => {
|
|
15
|
+
const plain = decoded(name);
|
|
16
|
+
return isSecretName(plain) || QUERY_SECRET_NAMES.has(plain.toLowerCase()) ? `${separator}${name}=${mask}` : whole;
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export { BOT_TOKEN_SECRET, maskSecretShapes };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secrets recognised by their shape, with no list of values to match.
|
|
3
|
+
*
|
|
4
|
+
* A journal line carries secrets nobody named: a request URL with a bot token
|
|
5
|
+
* in its path, a database address with its password, a callback with
|
|
6
|
+
* `?token=` — often another process's, so not in this one's environment. Each
|
|
7
|
+
* shape masks only the secret part, so the line still says which bot, which
|
|
8
|
+
* host, which parameter.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* The secret half of a Telegram bot token, after the bot's numeric id and the
|
|
12
|
+
* colon. The id is not a secret — it is the bot's public user id — and it is
|
|
13
|
+
* what tells two bots' failures apart in a journal.
|
|
14
|
+
*/
|
|
15
|
+
export declare const BOT_TOKEN_SECRET: RegExp;
|
|
16
|
+
/**
|
|
17
|
+
* Mask every shaped secret in `input` with `mask`. `isSecretName` decides a
|
|
18
|
+
* query parameter by its name, the same test a field name gets.
|
|
19
|
+
*/
|
|
20
|
+
export declare function maskSecretShapes(input: string, mask: string, isSecretName: (name: string) => boolean): string;
|
|
21
|
+
//# sourceMappingURL=secret-shapes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secret-shapes.d.ts","sourceRoot":"","sources":["../../src/internal/secret-shapes.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,QAAmC,CAAC;AAuBjE;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,GACtC,MAAM,CAUR"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../../src/observability/sanitize.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../../src/observability/sanitize.ts"],"names":[],"mappings":"AAQA,8EAA8E;AAC9E,MAAM,MAAM,SAAS,GACjB,MAAM,GACN,MAAM,GACN,OAAO,GACP,IAAI,GACJ,SAAS,EAAE,GACX;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAAC;AAEjC,+CAA+C;AAC/C,MAAM,WAAW,eAAe;IAC9B;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,wEAAwE;IACxE,2BAA2B,CAAC,EAAE,OAAO,CAAC;IACtC,8EAA8E;IAC9E,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,uFAAuF;IACvF,oBAAoB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC;;;;OAIG;IACH,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,6FAA6F;IAC7F,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,yEAAyE;IACzE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;;;;;;;;;OAaG;IACH,oBAAoB,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACxD,mEAAmE;IACnE,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,mCAAmC;AACnC,MAAM,WAAW,WAAW;IAC1B,wEAAwE;IACxE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,8BAA8B;IAC9B,aAAa,EAAE,MAAM,CAAC;IACtB,wEAAwE;IACxE,cAAc,CAAC,EAAE,IAAI,CAAC;CACvB;AAwJD;;;;;;GAMG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,GAAE,eAAoB,GAAG,SAAS,CA4L/E;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,SAAoB,GAAG,SAAS,CAgBzF;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,OAAO,EACd,OAAO,GAAE,eAAoB,GAC5B,SAAS,GAAG,IAAI,CAOlB;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,CAqBvD"}
|
|
@@ -8,13 +8,16 @@
|
|
|
8
8
|
* reads `error_code` and `parameters.retry_after` before it ever reads prose.
|
|
9
9
|
*/
|
|
10
10
|
/**
|
|
11
|
-
* The
|
|
11
|
+
* The secret half of a bot token — what follows the bot's numeric id and the
|
|
12
|
+
* colon. The id stays readable: it is the bot's public user id, and the only
|
|
13
|
+
* thing in a masked line that tells which bot failed.
|
|
12
14
|
*
|
|
13
|
-
* For masking, not for validation
|
|
14
|
-
* `
|
|
15
|
-
* written to the journal.
|
|
15
|
+
* For masking, not for validation. `redact` and every logger built on it mask
|
|
16
|
+
* it already; `redactTelegramBotToken` masks it in a string of your own.
|
|
16
17
|
*/
|
|
17
18
|
export declare const TELEGRAM_BOT_TOKEN_PATTERN: RegExp;
|
|
19
|
+
/** Every bot token in `text` with its secret half masked: `123456:[redacted]`. */
|
|
20
|
+
export declare function redactTelegramBotToken(text: string): string;
|
|
18
21
|
/** Telegram refused the call. Carries its answer as fields, never the token. */
|
|
19
22
|
export declare class TelegramBotApiError extends Error {
|
|
20
23
|
readonly method: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bot-api.d.ts","sourceRoot":"","sources":["../../src/telegram/bot-api.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;
|
|
1
|
+
{"version":3,"file":"bot-api.d.ts","sourceRoot":"","sources":["../../src/telegram/bot-api.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH;;;;;;;GAOG;AACH,eAAO,MAAM,0BAA0B,EAAE,MAAyB,CAAC;AAEnE,kFAAkF;AAClF,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE3D;AAID,gFAAgF;AAChF,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE;QAAE,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAE7F,YACE,MAAM,EAAE,MAAM,EACd,MAAM,EAAE;QACN,UAAU,EAAE,MAAM,CAAC;QACnB,WAAW,EAAE,MAAM,CAAC;QACpB,UAAU,CAAC,EAAE,mBAAmB,CAAC,YAAY,CAAC,CAAC;KAChD,EAQF;CACF;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACpD,iFAAiF;IACjF,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAC9B,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CAC/B;AAQD;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,OAAO,CAAC,CAuCnF"}
|