target-supabase-sdk 0.2.16 → 0.2.18

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.
Files changed (60) hide show
  1. package/README.md +20 -0
  2. package/dist/browser.d.ts +2 -0
  3. package/dist/browser.d.ts.map +1 -1
  4. package/dist/browser.js +1 -1
  5. package/dist/chunks/{task-queue-Gi4DSq74.js → task-queue-DufG7PlX.js} +162 -2
  6. package/dist/chunks/task-queue-DufG7PlX.js.map +1 -0
  7. package/dist/node/telegram/api-client.d.ts +12 -0
  8. package/dist/node/telegram/api-client.d.ts.map +1 -0
  9. package/dist/node/telegram/chunk-locator.d.ts +10 -0
  10. package/dist/node/telegram/chunk-locator.d.ts.map +1 -0
  11. package/dist/node/telegram/chunk.d.ts +4 -0
  12. package/dist/node/telegram/chunk.d.ts.map +1 -0
  13. package/dist/node/telegram/index.d.ts +8 -0
  14. package/dist/node/telegram/index.d.ts.map +1 -0
  15. package/dist/node/telegram/storage-provider.d.ts +32 -0
  16. package/dist/node/telegram/storage-provider.d.ts.map +1 -0
  17. package/dist/node/telegram/types.d.ts +53 -0
  18. package/dist/node/telegram/types.d.ts.map +1 -0
  19. package/dist/node.d.ts +4 -0
  20. package/dist/node.d.ts.map +1 -1
  21. package/dist/node.js +503 -8
  22. package/dist/node.js.map +1 -1
  23. package/dist/shared/media/index.d.ts +14 -0
  24. package/dist/shared/media/index.d.ts.map +1 -0
  25. package/dist/shared/media/media.build.d.ts +20 -0
  26. package/dist/shared/media/media.build.d.ts.map +1 -0
  27. package/dist/shared/media/media.interface.d.ts +38 -0
  28. package/dist/shared/media/media.interface.d.ts.map +1 -0
  29. package/dist/shared/media/media.mime.d.ts +4 -0
  30. package/dist/shared/media/media.mime.d.ts.map +1 -0
  31. package/dist/shared/media/media.name.d.ts +6 -0
  32. package/dist/shared/media/media.name.d.ts.map +1 -0
  33. package/dist/shared/media/media.query.d.ts +8 -0
  34. package/dist/shared/media/media.query.d.ts.map +1 -0
  35. package/dist/shared/media/node/index.d.ts +9 -0
  36. package/dist/shared/media/node/index.d.ts.map +1 -0
  37. package/dist/shared/media/node/media.access.d.ts +40 -0
  38. package/dist/shared/media/node/media.access.d.ts.map +1 -0
  39. package/dist/shared/media/node/media.service.d.ts +30 -0
  40. package/dist/shared/media/node/media.service.d.ts.map +1 -0
  41. package/package.json +8 -1
  42. package/src/browser.ts +11 -0
  43. package/src/node/telegram/api-client.ts +146 -0
  44. package/src/node/telegram/chunk-locator.ts +23 -0
  45. package/src/node/telegram/chunk.ts +33 -0
  46. package/src/node/telegram/index.ts +20 -0
  47. package/src/node/telegram/storage-provider.ts +182 -0
  48. package/src/node/telegram/types.ts +82 -0
  49. package/src/node.ts +36 -0
  50. package/src/shared/media/README.md +109 -0
  51. package/src/shared/media/index.ts +19 -0
  52. package/src/shared/media/media.build.ts +69 -0
  53. package/src/shared/media/media.interface.ts +45 -0
  54. package/src/shared/media/media.mime.ts +64 -0
  55. package/src/shared/media/media.name.ts +33 -0
  56. package/src/shared/media/media.query.ts +13 -0
  57. package/src/shared/media/node/index.ts +13 -0
  58. package/src/shared/media/node/media.access.ts +148 -0
  59. package/src/shared/media/node/media.service.ts +129 -0
  60. package/dist/chunks/task-queue-Gi4DSq74.js.map +0 -1
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Media Link — browser-safe barrel only.
3
+ *
4
+ * Node-only code lives in `./node/` and is exported from `src/node.ts`,
5
+ * never from this file (see README.md).
6
+ */
7
+ export type { BuildMediaLinkDraftInput } from "./media.build";
8
+ export { buildMediaLinkDraft } from "./media.build";
9
+ export type { MediaOriginal, MediaValue } from "./media.interface";
10
+ export { isMediaValue, MEDIA_CONTENT_HASH_ALGORITHM, MEDIA_CONTENT_HASH_PREFIX_LENGTH, MEDIA_VALUES, } from "./media.interface";
11
+ export { guessMediaMime } from "./media.mime";
12
+ export { buildMediaNameFromLocator } from "./media.name";
13
+ export { mediaLinkDedupFilters } from "./media.query";
14
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/shared/media/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,YAAY,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAC;AAC9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACpD,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACnE,OAAO,EACH,YAAY,EACZ,4BAA4B,EAC5B,gCAAgC,EAChC,YAAY,GACf,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAC;AACzD,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1,20 @@
1
+ import type { TargetDraft } from "../../core.interface";
2
+ import type { Link } from "../../link/link.interface";
3
+ import { type MediaValue } from "./media.interface";
4
+ export interface BuildMediaLinkDraftInput {
5
+ value: MediaValue;
6
+ name: string;
7
+ storageProvider: string;
8
+ locator: string;
9
+ contentHash: string;
10
+ size: number;
11
+ mimeType: string;
12
+ description?: string;
13
+ tagList?: string[];
14
+ }
15
+ /**
16
+ * Assemble a media {@link Link} draft (`category=link`, `loaderKey === value`).
17
+ * Does not validate filesystem or write to Supabase.
18
+ */
19
+ export declare function buildMediaLinkDraft(input: BuildMediaLinkDraftInput): TargetDraft<Link>;
20
+ //# sourceMappingURL=media.build.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"media.build.d.ts","sourceRoot":"","sources":["../../../src/shared/media/media.build.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAExD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,2BAA2B,CAAC;AACtD,OAAO,EAAoC,KAAK,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAEtF,MAAM,WAAW,wBAAwB;IACrC,KAAK,EAAE,UAAU,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,wBAAwB,GAAG,WAAW,CAAC,IAAI,CAAC,CA+CtF"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Media Link protocol — Link rows with value/loaderKey in {@link MEDIA_VALUES}.
3
+ * No separate Target category; category remains `link`.
4
+ */
5
+ /** Fixed digest algorithm for single-file media `contentHash`. */
6
+ export declare const MEDIA_CONTENT_HASH_ALGORITHM: "sha256";
7
+ /** Hex prefix length for {@link buildMediaNameFromLocator}. */
8
+ export declare const MEDIA_CONTENT_HASH_PREFIX_LENGTH = 6;
9
+ export declare const MEDIA_VALUES: readonly ["video", "audio", "image", "image-list"];
10
+ /** Discriminator stored in both `Link.value` and `details.loaderKey`. */
11
+ export type MediaValue = (typeof MEDIA_VALUES)[number];
12
+ /**
13
+ * `Link.details.original` for media Links.
14
+ *
15
+ * - `locator` — absolute filesystem path, or (future) network URL keyed by `storageProvider`.
16
+ * - `image-list`: `locator` is an absolute directory path; only the **immediate** children
17
+ * (one level, non-recursive) are counted. `contentHash` and `mimeType` are `""`;
18
+ * `size` is the number of files in that directory (not bytes).
19
+ * - other values: `size` is byte length; `contentHash` is SHA-256 hex; `mimeType` from the file.
20
+ */
21
+ export interface MediaOriginal {
22
+ storageProvider: string;
23
+ locator: string;
24
+ /**
25
+ * SHA-256 hex of file bytes, or `""` when `value === "image-list"`.
26
+ * Algorithm is always {@link MEDIA_CONTENT_HASH_ALGORITHM}.
27
+ */
28
+ contentHash: string;
29
+ /**
30
+ * Single-file media: byte length.
31
+ * `image-list`: number of files in the directory (one level).
32
+ */
33
+ size: number;
34
+ /** e.g. `video/mp4`, or `""` when `value === "image-list"`. */
35
+ mimeType: string;
36
+ }
37
+ export declare function isMediaValue(value: string): value is MediaValue;
38
+ //# sourceMappingURL=media.interface.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"media.interface.d.ts","sourceRoot":"","sources":["../../../src/shared/media/media.interface.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,kEAAkE;AAClE,eAAO,MAAM,4BAA4B,EAAG,QAAiB,CAAC;AAE9D,+DAA+D;AAC/D,eAAO,MAAM,gCAAgC,IAAI,CAAC;AAElD,eAAO,MAAM,YAAY,oDAAqD,CAAC;AAE/E,yEAAyE;AACzE,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC;AAEvD;;;;;;;;GAQG;AACH,MAAM,WAAW,aAAa;IAC1B,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,+DAA+D;IAC/D,QAAQ,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,UAAU,CAE/D"}
@@ -0,0 +1,4 @@
1
+ import type { MediaValue } from "./media.interface";
2
+ /** Guess MIME from path extension; `null` when unknown. */
3
+ export declare function guessMediaMime(locator: string, value: Exclude<MediaValue, "image-list">): string | null;
4
+ //# sourceMappingURL=media.mime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"media.mime.d.ts","sourceRoot":"","sources":["../../../src/shared/media/media.mime.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAkDpD,2DAA2D;AAC3D,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,UAAU,EAAE,YAAY,CAAC,GAAG,MAAM,GAAG,IAAI,CAYvG"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * `{basename}.{hash6}.{ext}` from an absolute (or path-like) locator and full content hash.
3
+ * Does not touch the filesystem.
4
+ */
5
+ export declare function buildMediaNameFromLocator(locator: string, contentHash: string): string;
6
+ //# sourceMappingURL=media.name.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"media.name.d.ts","sourceRoot":"","sources":["../../../src/shared/media/media.name.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CA0BtF"}
@@ -0,0 +1,8 @@
1
+ import type { QueryFilter } from "../../core.interface";
2
+ import type { MediaValue } from "./media.interface";
3
+ /** Dedup key: `category=link` + `value` + `name` (matches register / find). */
4
+ export declare function mediaLinkDedupFilters(input: {
5
+ value: MediaValue;
6
+ name: string;
7
+ }): QueryFilter[];
8
+ //# sourceMappingURL=media.query.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"media.query.d.ts","sourceRoot":"","sources":["../../../src/shared/media/media.query.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAExD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAEpD,+EAA+E;AAC/E,wBAAgB,qBAAqB,CAAC,KAAK,EAAE;IAAE,KAAK,EAAE,UAAU,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,WAAW,EAAE,CAO/F"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Node-only media surface — for `src/node.ts` / `target-supabase-sdk/node` only.
3
+ * Never re-export from `../index.ts` or `browser.ts`.
4
+ */
5
+ export type { CheckMediaAvailabilityInput, MediaAvailabilityResult } from "./media.access";
6
+ export { checkMediaAvailability, probeMediaOriginal } from "./media.access";
7
+ export type { FindMediaLinkInput, RegisterMediaLinkInput, RegisterMediaLinkResult, } from "./media.service";
8
+ export { findMediaLink, registerMediaLink } from "./media.service";
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/shared/media/node/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,YAAY,EAAE,2BAA2B,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAC3F,OAAO,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAC5E,YAAY,EACR,kBAAkB,EAClB,sBAAsB,EACtB,uBAAuB,GAC1B,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,40 @@
1
+ import { type MediaOriginal, type MediaValue } from "../media.interface";
2
+ export interface CheckMediaAvailabilityInput {
3
+ value: MediaValue;
4
+ storageProvider: string;
5
+ locator: string;
6
+ /**
7
+ * This host's storage id (e.g. from `LOCAL_STORAGE_PROVIDER`).
8
+ * Local path checks only run when it equals `storageProvider`.
9
+ */
10
+ localStorageProvider: string;
11
+ }
12
+ export interface MediaAvailabilityResult {
13
+ ok: boolean;
14
+ /** Absolute path when the locator was resolved on this host. */
15
+ absolutePath?: string;
16
+ reason?: string;
17
+ }
18
+ /**
19
+ * Fast local reachability check for a media locator.
20
+ *
21
+ * - Requires `storageProvider === localStorageProvider` (cross-host → not ok).
22
+ * - `locator` must be an absolute path.
23
+ * - `image-list`: directory exists; only immediate children are considered.
24
+ * - other values: path exists and is a non-empty file.
25
+ *
26
+ * **Node-only** — under `shared/media/node/`; do not import from the browser entry.
27
+ */
28
+ export declare function checkMediaAvailability(input: CheckMediaAvailabilityInput): Promise<MediaAvailabilityResult>;
29
+ /**
30
+ * Derive `contentHash` / `size` / `mimeType` for {@link MediaOriginal} after availability succeeds.
31
+ *
32
+ * **Node-only** — under `shared/media/node/`; do not import from the browser entry.
33
+ */
34
+ export declare function probeMediaOriginal(input: {
35
+ value: MediaValue;
36
+ storageProvider: string;
37
+ locator: string;
38
+ absolutePath: string;
39
+ }): Promise<MediaOriginal>;
40
+ //# sourceMappingURL=media.access.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"media.access.d.ts","sourceRoot":"","sources":["../../../../src/shared/media/node/media.access.ts"],"names":[],"mappings":"AAKA,OAAO,EAAgC,KAAK,aAAa,EAAE,KAAK,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAGvG,MAAM,WAAW,2BAA2B;IACxC,KAAK,EAAE,UAAU,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,oBAAoB,EAAE,MAAM,CAAC;CAChC;AAED,MAAM,WAAW,uBAAuB;IACpC,EAAE,EAAE,OAAO,CAAC;IACZ,gEAAgE;IAChE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AA+BD;;;;;;;;;GASG;AACH,wBAAsB,sBAAsB,CAAC,KAAK,EAAE,2BAA2B,GAAG,OAAO,CAAC,uBAAuB,CAAC,CA0CjH;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,KAAK,EAAE;IAC5C,KAAK,EAAE,UAAU,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;CACxB,GAAG,OAAO,CAAC,aAAa,CAAC,CA4BzB"}
@@ -0,0 +1,30 @@
1
+ import type { Link } from "../../../link/link.interface";
2
+ import { type MediaValue } from "../media.interface";
3
+ export interface FindMediaLinkInput {
4
+ value: MediaValue;
5
+ name: string;
6
+ }
7
+ /** Dedup lookup: same `category` + `value` + `name`. */
8
+ export declare function findMediaLink(input: FindMediaLinkInput): Promise<Link | null>;
9
+ export interface RegisterMediaLinkInput {
10
+ value: MediaValue;
11
+ name: string;
12
+ storageProvider: string;
13
+ locator: string;
14
+ /** This host id for availability (e.g. `process.env.LOCAL_STORAGE_PROVIDER`). */
15
+ localStorageProvider: string;
16
+ description?: string;
17
+ tagList?: string[];
18
+ }
19
+ export interface RegisterMediaLinkResult {
20
+ id: string;
21
+ created: boolean;
22
+ }
23
+ /**
24
+ * Register a media Link: dedup → availability → probe → `postTarget`.
25
+ * Idempotent when `category`+`value`+`name` already exist.
26
+ *
27
+ * **Node-only** — under `shared/media/node/`; do not import from the browser entry.
28
+ */
29
+ export declare function registerMediaLink(input: RegisterMediaLinkInput): Promise<RegisterMediaLinkResult>;
30
+ //# sourceMappingURL=media.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"media.service.d.ts","sourceRoot":"","sources":["../../../../src/shared/media/node/media.service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,8BAA8B,CAAC;AAGzD,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAMnE,MAAM,WAAW,kBAAkB;IAC/B,KAAK,EAAE,UAAU,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CAChB;AAED,wDAAwD;AACxD,wBAAsB,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CAYnF;AAED,MAAM,WAAW,sBAAsB;IACnC,KAAK,EAAE,UAAU,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,iFAAiF;IACjF,oBAAoB,EAAE,MAAM,CAAC;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,uBAAuB;IACpC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,OAAO,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAAC,uBAAuB,CAAC,CA4EvG"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "target-supabase-sdk",
3
- "version": "0.2.16",
3
+ "version": "0.2.18",
4
4
  "description": "Supabase client initializer and data access APIs",
5
5
  "license": "MIT",
6
6
  "author": "KyLoc20",
@@ -69,8 +69,14 @@
69
69
  "@supabase/postgrest-js": "^1.19.0",
70
70
  "@supabase/supabase-js": "^2.50.0",
71
71
  "lodash-es": "^4.17.21",
72
+ "undici": "^7.10.0",
72
73
  "zod": "^3.23.0"
73
74
  },
75
+ "peerDependenciesMeta": {
76
+ "undici": {
77
+ "optional": true
78
+ }
79
+ },
74
80
  "devDependencies": {
75
81
  "@biomejs/biome": "^2.5.3",
76
82
  "@rollup/plugin-node-resolve": "^16.0.1",
@@ -84,6 +90,7 @@
84
90
  "tslib": "^2.8.1",
85
91
  "tsx": "^4.19.4",
86
92
  "typescript": "~5.7.2",
93
+ "undici": "^7.10.0",
87
94
  "zod": "^3.24.0"
88
95
  },
89
96
  "publishConfig": {
package/src/browser.ts CHANGED
@@ -223,6 +223,17 @@ export {
223
223
  parseLogBatchMeta,
224
224
  resolveLogMinLevel,
225
225
  } from "./shared/log";
226
+ export type { BuildMediaLinkDraftInput, MediaOriginal, MediaValue } from "./shared/media";
227
+ export {
228
+ buildMediaLinkDraft,
229
+ buildMediaNameFromLocator,
230
+ guessMediaMime,
231
+ isMediaValue,
232
+ MEDIA_CONTENT_HASH_ALGORITHM,
233
+ MEDIA_CONTENT_HASH_PREFIX_LENGTH,
234
+ MEDIA_VALUES,
235
+ mediaLinkDedupFilters,
236
+ } from "./shared/media";
226
237
  export { isHttpUrl, resolveFetchUrl } from "./shared/utils/fetch-url";
227
238
  export { getValueAtPath } from "./shared/utils/get-value-at-path";
228
239
  export type {
@@ -0,0 +1,146 @@
1
+ import { type Dispatcher, ProxyAgent } from "undici";
2
+ import {
3
+ createLogger,
4
+ type FetchInitFactory,
5
+ fetchBinaryWithRetry,
6
+ fetchWithRetry,
7
+ isRetryableHttpStatus,
8
+ } from "../../browser";
9
+ import type { ResolvedTelegramStorageOptions, TelegramApiResponse } from "./types";
10
+
11
+ const logger = createLogger({ module: "telegram" });
12
+
13
+ let cachedDispatcher: Dispatcher | undefined;
14
+ let cachedProxyUrl: string | undefined;
15
+
16
+ /** Per-request undici dispatcher when proxyUrl is set (Supabase stays direct). */
17
+ function telegramFetchDispatcher(proxyUrl: string | undefined): Dispatcher | undefined {
18
+ if (proxyUrl == null) {
19
+ return undefined;
20
+ }
21
+ if (cachedDispatcher != null && cachedProxyUrl === proxyUrl) {
22
+ return cachedDispatcher;
23
+ }
24
+ cachedDispatcher = new ProxyAgent(proxyUrl);
25
+ cachedProxyUrl = proxyUrl;
26
+ return cachedDispatcher;
27
+ }
28
+
29
+ function telegramNetworkHint(proxyUrl: string | undefined): string {
30
+ return proxyUrl
31
+ ? ` (proxy=${proxyUrl}, ensure Clash/V2Ray is running)`
32
+ : " (set proxyUrl / TELEGRAM_PROXY if api.telegram.org is blocked)";
33
+ }
34
+
35
+ export interface CallTelegramOptions {
36
+ timeoutMs?: number;
37
+ retryAttempts?: number;
38
+ retryBaseMs?: number;
39
+ }
40
+
41
+ function botApiUrl(options: ResolvedTelegramStorageOptions, method: string): string {
42
+ return `${options.apiBaseUrl}/bot${options.botToken}/${method}`;
43
+ }
44
+
45
+ export function telegramFileDownloadUrl(options: ResolvedTelegramStorageOptions, filePath: string): string {
46
+ const normalized = filePath.replace(/^\/+/, "");
47
+ return `${options.apiBaseUrl}/file/bot${options.botToken}/${normalized}`;
48
+ }
49
+
50
+ function sleep(ms: number): Promise<void> {
51
+ return new Promise((resolve) => setTimeout(resolve, ms));
52
+ }
53
+
54
+ function telegramBackoff(attempt: number, retryBaseMs: number): Promise<void> {
55
+ return sleep(retryBaseMs * 2 ** (attempt - 1));
56
+ }
57
+
58
+ /** Bot API JSON call — transport retry via SDK fetch; outer loop retries Telegram `{ ok: false }`. */
59
+ export async function callTelegramApi<T>(
60
+ options: ResolvedTelegramStorageOptions,
61
+ method: string,
62
+ initOrFactory: RequestInit | FetchInitFactory,
63
+ callOptions: CallTelegramOptions = {},
64
+ ): Promise<T> {
65
+ const timeoutMs = callOptions.timeoutMs ?? options.fetchTimeoutMs;
66
+ const maxAttempts = callOptions.retryAttempts ?? 1;
67
+ const retryBaseMs = callOptions.retryBaseMs ?? options.uploadRetryBaseMs;
68
+ const url = botApiUrl(options, method);
69
+ const label = `Telegram ${method}`;
70
+ const dispatcher = telegramFetchDispatcher(options.proxyUrl);
71
+ const hint = telegramNetworkHint(options.proxyUrl);
72
+
73
+ let lastError: Error | null = null;
74
+
75
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
76
+ const response = await fetchWithRetry(url, initOrFactory, {
77
+ label,
78
+ timeoutMs,
79
+ maxAttempts: attempt === 1 ? maxAttempts : 1,
80
+ retryBaseMs,
81
+ dispatcher,
82
+ hint,
83
+ logger,
84
+ });
85
+
86
+ let payload: TelegramApiResponse<T>;
87
+ try {
88
+ payload = (await response.json()) as TelegramApiResponse<T>;
89
+ } catch {
90
+ lastError = new Error(`${label}: invalid JSON response (HTTP ${response.status})`);
91
+ if (attempt < maxAttempts && isRetryableHttpStatus(response.status)) {
92
+ await telegramBackoff(attempt, retryBaseMs);
93
+ continue;
94
+ }
95
+ throw lastError;
96
+ }
97
+
98
+ if (!payload.ok) {
99
+ const description = payload.description ?? `HTTP ${response.status}`;
100
+ lastError = new Error(`${label}: ${description}`);
101
+ if (attempt < maxAttempts && isRetryableHttpStatus(response.status)) {
102
+ logger.warn("retrying after API error", {
103
+ topic: "telegram",
104
+ data: { method, attempt, description },
105
+ });
106
+ await telegramBackoff(attempt, retryBaseMs);
107
+ continue;
108
+ }
109
+ throw lastError;
110
+ }
111
+
112
+ if (payload.result == null) {
113
+ throw new Error(`${label}: empty result in response`);
114
+ }
115
+
116
+ if (attempt > 1) {
117
+ logger.info("request succeeded after retry", { topic: "telegram", data: { method, attempt } });
118
+ }
119
+
120
+ return payload.result;
121
+ }
122
+
123
+ throw lastError ?? new Error(`${label}: request failed`);
124
+ }
125
+
126
+ export async function telegramDownloadBinary(
127
+ options: ResolvedTelegramStorageOptions,
128
+ url: string,
129
+ label: string,
130
+ callOptions: CallTelegramOptions = {},
131
+ ): Promise<ArrayBuffer> {
132
+ const dispatcher = telegramFetchDispatcher(options.proxyUrl);
133
+ return fetchBinaryWithRetry(
134
+ url,
135
+ { method: "GET" },
136
+ {
137
+ label: `Telegram ${label}`,
138
+ timeoutMs: callOptions.timeoutMs ?? options.fetchTimeoutMs,
139
+ maxAttempts: callOptions.retryAttempts ?? 1,
140
+ retryBaseMs: callOptions.retryBaseMs ?? options.uploadRetryBaseMs,
141
+ dispatcher,
142
+ hint: telegramNetworkHint(options.proxyUrl),
143
+ logger,
144
+ },
145
+ );
146
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Telegram chunk URL encoding: `${messageId}|${fileId}`.
3
+ * Legacy parcels store bare `file_id` (no messageId → cannot deleteMessage).
4
+ */
5
+
6
+ const SEPARATOR = "|";
7
+
8
+ export function encodeTelegramChunkUrl(messageId: number, fileId: string): string {
9
+ return `${messageId}${SEPARATOR}${fileId}`;
10
+ }
11
+
12
+ export function parseTelegramChunkUrl(locator: string): { messageId: number | null; fileId: string } {
13
+ const idx = locator.indexOf(SEPARATOR);
14
+ if (idx <= 0) {
15
+ return { messageId: null, fileId: locator };
16
+ }
17
+ const mid = Number(locator.slice(0, idx));
18
+ const fileId = locator.slice(idx + 1);
19
+ if (!Number.isInteger(mid) || mid <= 0 || fileId === "") {
20
+ return { messageId: null, fileId: locator };
21
+ }
22
+ return { messageId: mid, fileId };
23
+ }
@@ -0,0 +1,33 @@
1
+ import { callTelegramApi, telegramDownloadBinary, telegramFileDownloadUrl } from "./api-client";
2
+ import type { ResolvedTelegramStorageOptions, TelegramFile } from "./types";
3
+
4
+ /** Download chunk bytes by Telegram `file_id` stored in Chunk.url. */
5
+ export async function downloadTelegramChunk(
6
+ options: ResolvedTelegramStorageOptions,
7
+ fileId: string,
8
+ ): Promise<ArrayBuffer> {
9
+ const file = await callTelegramApi<TelegramFile>(
10
+ options,
11
+ `getFile?file_id=${encodeURIComponent(fileId)}`,
12
+ { method: "GET" },
13
+ {
14
+ retryAttempts: options.uploadRetryAttempts,
15
+ retryBaseMs: options.uploadRetryBaseMs,
16
+ },
17
+ );
18
+
19
+ if (file.file_path == null || file.file_path === "") {
20
+ throw new Error(`Telegram getFile: missing file_path for file_id ${fileId}`);
21
+ }
22
+
23
+ return telegramDownloadBinary(
24
+ options,
25
+ telegramFileDownloadUrl(options, file.file_path),
26
+ `download:${fileId.slice(0, 12)}…`,
27
+ {
28
+ timeoutMs: options.uploadTimeoutMs,
29
+ retryAttempts: options.uploadRetryAttempts,
30
+ retryBaseMs: options.uploadRetryBaseMs,
31
+ },
32
+ );
33
+ }
@@ -0,0 +1,20 @@
1
+ export type { CallTelegramOptions } from "./api-client";
2
+ export { callTelegramApi, telegramDownloadBinary, telegramFileDownloadUrl } from "./api-client";
3
+ export { downloadTelegramChunk } from "./chunk";
4
+ export { encodeTelegramChunkUrl, parseTelegramChunkUrl } from "./chunk-locator";
5
+ export {
6
+ createTelegramStorageProvider,
7
+ PROVIDER_TELEGRAM,
8
+ type TelegramChunkDeleteResult,
9
+ type TelegramStorageProvider,
10
+ } from "./storage-provider";
11
+ export type {
12
+ ResolvedTelegramStorageOptions,
13
+ TelegramApiResponse,
14
+ TelegramDocument,
15
+ TelegramFile,
16
+ TelegramMessage,
17
+ TelegramStorageProviderOptions,
18
+ TelegramUploadResult,
19
+ } from "./types";
20
+ export { resolveTelegramStorageOptions } from "./types";