@prajwalshete/telegram-plugin 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (295) hide show
  1. package/CHANGELOG.md +87 -0
  2. package/LICENSE +21 -0
  3. package/README.md +109 -2
  4. package/dist/adapters/clock.d.ts +3 -0
  5. package/dist/adapters/clock.js +4 -0
  6. package/dist/adapters/console-log.d.ts +13 -0
  7. package/dist/adapters/console-log.js +41 -0
  8. package/dist/adapters/d1-db.d.ts +5 -0
  9. package/dist/adapters/d1-db.js +37 -0
  10. package/dist/adapters/ids.d.ts +5 -0
  11. package/dist/adapters/ids.js +25 -0
  12. package/dist/adapters/index.d.ts +7 -0
  13. package/dist/adapters/keyring.d.ts +50 -0
  14. package/dist/adapters/keyring.js +174 -0
  15. package/dist/adapters/metrics.d.ts +28 -0
  16. package/dist/adapters/metrics.js +48 -0
  17. package/dist/adapters/r2-files.d.ts +20 -0
  18. package/dist/adapters/r2-files.js +53 -0
  19. package/dist/bin/cli.d.ts +1 -0
  20. package/dist/bin/cli.js +45 -0
  21. package/dist/client/broadcasts.d.ts +35 -0
  22. package/dist/client/commands.d.ts +23 -0
  23. package/dist/client/endpoint.d.ts +34 -0
  24. package/dist/client/events.d.ts +33 -0
  25. package/dist/client/index.d.ts +11 -0
  26. package/dist/client/index.js +3 -0
  27. package/dist/client/schedules.d.ts +42 -0
  28. package/dist/client/schema.d.ts +181 -0
  29. package/dist/client/send.d.ts +15 -0
  30. package/dist/client/templates.d.ts +47 -0
  31. package/dist/contracts/bots.d.ts +129 -0
  32. package/dist/contracts/bots.js +70 -0
  33. package/dist/contracts/broadcasts.d.ts +116 -0
  34. package/dist/contracts/broadcasts.js +63 -0
  35. package/dist/contracts/chats.d.ts +97 -0
  36. package/dist/contracts/chats.js +66 -0
  37. package/dist/contracts/commands.d.ts +52 -0
  38. package/dist/contracts/commands.js +53 -0
  39. package/dist/contracts/common.d.ts +38 -0
  40. package/dist/contracts/common.js +41 -0
  41. package/dist/contracts/deliveries.d.ts +103 -0
  42. package/dist/contracts/deliveries.js +74 -0
  43. package/dist/contracts/destinations.d.ts +24 -0
  44. package/dist/contracts/destinations.js +28 -0
  45. package/dist/contracts/events.d.ts +165 -0
  46. package/dist/contracts/events.js +98 -0
  47. package/dist/contracts/features.d.ts +20 -0
  48. package/dist/contracts/features.js +21 -0
  49. package/dist/contracts/health.d.ts +55 -0
  50. package/dist/contracts/health.js +58 -0
  51. package/dist/contracts/index.d.ts +22 -0
  52. package/dist/contracts/index.js +22 -0
  53. package/dist/contracts/labels.d.ts +38 -0
  54. package/dist/contracts/labels.js +52 -0
  55. package/dist/contracts/links.d.ts +51 -0
  56. package/dist/contracts/links.js +50 -0
  57. package/dist/contracts/meta.d.ts +132 -0
  58. package/dist/contracts/meta.js +85 -0
  59. package/dist/contracts/permissions.d.ts +24 -0
  60. package/dist/contracts/permissions.js +27 -0
  61. package/dist/contracts/problems.d.ts +106 -0
  62. package/dist/contracts/problems.js +101 -0
  63. package/dist/contracts/recipients.d.ts +36 -0
  64. package/dist/contracts/recipients.js +22 -0
  65. package/dist/contracts/schedules.d.ts +276 -0
  66. package/dist/contracts/schedules.js +143 -0
  67. package/dist/contracts/send.d.ts +126 -0
  68. package/dist/contracts/send.js +69 -0
  69. package/dist/contracts/subscriptions.d.ts +38 -0
  70. package/dist/contracts/subscriptions.js +31 -0
  71. package/dist/contracts/templates.d.ts +358 -0
  72. package/dist/contracts/templates.js +174 -0
  73. package/dist/contracts/users.d.ts +15 -0
  74. package/dist/contracts/users.js +19 -0
  75. package/dist/core/guards.d.ts +22 -0
  76. package/dist/core/guards.js +47 -0
  77. package/dist/core/html/escape.d.ts +2 -0
  78. package/dist/core/html/escape.js +36 -0
  79. package/dist/core/html/tokenize.js +65 -0
  80. package/dist/core/index.d.ts +18 -0
  81. package/dist/core/pacing.d.ts +1 -0
  82. package/dist/core/pacing.js +86 -0
  83. package/dist/core/random.d.ts +2 -0
  84. package/dist/core/random.js +38 -0
  85. package/dist/core/recipients.d.ts +1 -0
  86. package/dist/core/recipients.js +86 -0
  87. package/dist/core/schedule/describe.d.ts +3 -0
  88. package/dist/core/schedule/describe.js +74 -0
  89. package/dist/core/schedule/index.d.ts +4 -0
  90. package/dist/core/schedule/policy.d.ts +1 -0
  91. package/dist/core/schedule/policy.js +37 -0
  92. package/dist/core/schedule/rule.d.ts +25 -0
  93. package/dist/core/schedule/rule.js +142 -0
  94. package/dist/core/schedule/tz.d.ts +2 -0
  95. package/dist/core/schedule/tz.js +59 -0
  96. package/dist/core/split/plain.d.ts +2 -0
  97. package/dist/core/split/plain.js +23 -0
  98. package/dist/core/split/split.d.ts +15 -0
  99. package/dist/core/split/split.js +158 -0
  100. package/dist/core/template/ast.d.ts +58 -0
  101. package/dist/core/template/ast.js +5 -0
  102. package/dist/core/template/expr.js +108 -0
  103. package/dist/core/template/filters.d.ts +20 -0
  104. package/dist/core/template/filters.js +127 -0
  105. package/dist/core/template/format.d.ts +17 -0
  106. package/dist/core/template/format.js +88 -0
  107. package/dist/core/template/index.d.ts +20 -0
  108. package/dist/core/template/index.js +29 -0
  109. package/dist/core/template/parse.d.ts +8 -0
  110. package/dist/core/template/parse.js +369 -0
  111. package/dist/core/template/render.d.ts +17 -0
  112. package/dist/core/template/render.js +118 -0
  113. package/dist/core/template/shape.d.ts +28 -0
  114. package/dist/core/template/shape.js +151 -0
  115. package/dist/core/template/tags.js +105 -0
  116. package/dist/core/template/validate.d.ts +10 -0
  117. package/dist/core/template/validate.js +144 -0
  118. package/dist/core/template/variables.d.ts +15 -0
  119. package/dist/core/template/variables.js +41 -0
  120. package/dist/do/index.d.ts +3 -0
  121. package/dist/do/index.js +3 -0
  122. package/dist/do/telegram-bot-do.d.ts +45 -0
  123. package/dist/do/telegram-bot-do.js +219 -0
  124. package/dist/engine/actions/callback.js +256 -0
  125. package/dist/engine/actions/tokens.d.ts +2 -0
  126. package/dist/engine/actions/tokens.js +51 -0
  127. package/dist/engine/bot-api.js +20 -0
  128. package/dist/engine/bot-features.js +39 -0
  129. package/dist/engine/chat-state.js +66 -0
  130. package/dist/engine/content/audience.js +54 -0
  131. package/dist/engine/content/buttons.d.ts +1 -0
  132. package/dist/engine/content/buttons.js +113 -0
  133. package/dist/engine/content/fanout.js +284 -0
  134. package/dist/engine/content/intersect.d.ts +6 -0
  135. package/dist/engine/content/intersect.js +26 -0
  136. package/dist/engine/content/providers.d.ts +6 -0
  137. package/dist/engine/content/providers.js +142 -0
  138. package/dist/engine/content/recipients.js +128 -0
  139. package/dist/engine/content/render.js +138 -0
  140. package/dist/engine/content/reply.js +156 -0
  141. package/dist/engine/content/report-files.js +78 -0
  142. package/dist/engine/content/template-rows.js +74 -0
  143. package/dist/engine/dialogs/dialog.d.ts +2 -0
  144. package/dist/engine/dialogs/dialog.js +375 -0
  145. package/dist/engine/dispatch/builtins.js +198 -0
  146. package/dist/engine/dispatch/commands.js +194 -0
  147. package/dist/engine/dispatch/context.d.ts +2 -0
  148. package/dist/engine/dispatch/context.js +194 -0
  149. package/dist/engine/dispatch/dispatcher.js +66 -0
  150. package/dist/engine/dispatch/handlers.d.ts +1 -0
  151. package/dist/engine/dispatch/handlers.js +103 -0
  152. package/dist/engine/dispatch/lifecycle.js +85 -0
  153. package/dist/engine/dispatch/link-chat.js +31 -0
  154. package/dist/engine/dispatch/linking.js +273 -0
  155. package/dist/engine/errors.d.ts +60 -0
  156. package/dist/engine/errors.js +72 -0
  157. package/dist/engine/events/filters.d.ts +5 -0
  158. package/dist/engine/events/filters.js +82 -0
  159. package/dist/engine/events/kick.js +30 -0
  160. package/dist/engine/events/payload.d.ts +3 -0
  161. package/dist/engine/events/payload.js +16 -0
  162. package/dist/engine/events/tick.js +208 -0
  163. package/dist/engine/i18n.d.ts +12 -0
  164. package/dist/engine/i18n.js +55 -0
  165. package/dist/engine/inbound/files.js +121 -0
  166. package/dist/engine/inbound/handler.js +46 -0
  167. package/dist/engine/jobs.d.ts +32 -0
  168. package/dist/engine/jobs.js +85 -0
  169. package/dist/engine/link-codes.js +46 -0
  170. package/dist/engine/metrics.js +12 -0
  171. package/dist/engine/options.d.ts +369 -0
  172. package/dist/engine/options.js +81 -0
  173. package/dist/engine/outbox/dead-letter.js +78 -0
  174. package/dist/engine/outbox/do-registry.d.ts +10 -0
  175. package/dist/engine/outbox/do-registry.js +10 -0
  176. package/dist/engine/outbox/do-transport.d.ts +33 -0
  177. package/dist/engine/outbox/do-transport.js +73 -0
  178. package/dist/engine/outbox/drain.d.ts +13 -0
  179. package/dist/engine/outbox/drain.js +381 -0
  180. package/dist/engine/outbox/enqueue.d.ts +1 -0
  181. package/dist/engine/outbox/enqueue.js +63 -0
  182. package/dist/engine/outbox/select.js +24 -0
  183. package/dist/engine/outbox/send.js +362 -0
  184. package/dist/engine/outbox/transport.d.ts +35 -0
  185. package/dist/engine/outbox/transport.js +175 -0
  186. package/dist/engine/outbox/wake.js +28 -0
  187. package/dist/engine/registry.d.ts +502 -0
  188. package/dist/engine/registry.js +258 -0
  189. package/dist/engine/rows.d.ts +25 -0
  190. package/dist/engine/rows.js +131 -0
  191. package/dist/engine/runtime.d.ts +95 -0
  192. package/dist/engine/runtime.js +149 -0
  193. package/dist/engine/schedules/fire.js +208 -0
  194. package/dist/engine/schedules/resume.js +75 -0
  195. package/dist/engine/schedules/rows.js +69 -0
  196. package/dist/engine/schedules/tick.js +30 -0
  197. package/dist/engine/schedules/wake.js +50 -0
  198. package/dist/engine/state.d.ts +11 -0
  199. package/dist/engine/state.js +78 -0
  200. package/dist/locales/en.js +339 -0
  201. package/dist/locales/en.json +341 -0
  202. package/dist/locales/mr.js +339 -0
  203. package/dist/locales/mr.json +341 -0
  204. package/dist/ports/db.d.ts +22 -0
  205. package/dist/ports/index.d.ts +4 -0
  206. package/dist/ports/ports.d.ts +257 -0
  207. package/dist/ports/sql.d.ts +39 -0
  208. package/dist/ports/sql.js +76 -0
  209. package/dist/renderers/csv.d.ts +14 -0
  210. package/dist/renderers/csv.js +51 -0
  211. package/dist/renderers/html.d.ts +44 -0
  212. package/dist/renderers/html.js +81 -0
  213. package/dist/renderers/index.d.ts +5 -0
  214. package/dist/renderers/index.js +4 -0
  215. package/dist/renderers/table.d.ts +30 -0
  216. package/dist/renderers/table.js +114 -0
  217. package/dist/routes/admin.js +146 -0
  218. package/dist/routes/app.d.ts +3111 -0
  219. package/dist/routes/app.js +9 -0
  220. package/dist/routes/broadcasts.js +37 -0
  221. package/dist/routes/commands.js +35 -0
  222. package/dist/routes/content.js +12 -0
  223. package/dist/routes/events.js +36 -0
  224. package/dist/routes/http.d.ts +30 -0
  225. package/dist/routes/http.js +223 -0
  226. package/dist/routes/me.js +59 -0
  227. package/dist/routes/schedules.js +62 -0
  228. package/dist/routes/send.js +24 -0
  229. package/dist/routes/templates.js +70 -0
  230. package/dist/routes/webhook.js +147 -0
  231. package/dist/schema/columns.js +307 -0
  232. package/dist/schema/ddl.d.ts +7 -0
  233. package/dist/schema/ddl.js +45 -0
  234. package/dist/schema/index.d.ts +4255 -0
  235. package/dist/schema/index.js +44 -0
  236. package/dist/schema/reference.sql +359 -0
  237. package/dist/schema/tables.d.ts +4243 -0
  238. package/dist/schema/tables.js +327 -0
  239. package/dist/schema/version.d.ts +5 -0
  240. package/dist/schema/version.js +7 -0
  241. package/dist/server/index.d.ts +55 -0
  242. package/dist/server/index.js +46 -0
  243. package/dist/server/plugin.d.ts +81 -0
  244. package/dist/server/plugin.js +247 -0
  245. package/dist/server/version.d.ts +2 -0
  246. package/dist/server/version.js +4 -0
  247. package/dist/services/ai-tool.d.ts +26 -0
  248. package/dist/services/ai-tool.js +148 -0
  249. package/dist/services/bots.js +300 -0
  250. package/dist/services/broadcasts.js +390 -0
  251. package/dist/services/chats.js +83 -0
  252. package/dist/services/cleanup.js +83 -0
  253. package/dist/services/commands.js +140 -0
  254. package/dist/services/deliveries.js +53 -0
  255. package/dist/services/destinations.js +91 -0
  256. package/dist/services/event-rules.js +229 -0
  257. package/dist/services/events.js +20 -0
  258. package/dist/services/idempotency.js +64 -0
  259. package/dist/services/links.js +200 -0
  260. package/dist/services/menu.js +95 -0
  261. package/dist/services/ops.js +90 -0
  262. package/dist/services/rekey.js +63 -0
  263. package/dist/services/reports.js +87 -0
  264. package/dist/services/schedules.js +214 -0
  265. package/dist/services/send.d.ts +14 -0
  266. package/dist/services/send.js +421 -0
  267. package/dist/services/subscriptions.js +28 -0
  268. package/dist/services/templates.js +259 -0
  269. package/dist/telegram/classify.d.ts +30 -0
  270. package/dist/telegram/classify.js +164 -0
  271. package/dist/telegram/client.d.ts +73 -0
  272. package/dist/telegram/client.js +135 -0
  273. package/dist/telegram/errors.d.ts +37 -0
  274. package/dist/telegram/errors.js +62 -0
  275. package/dist/telegram/inbound.d.ts +40 -0
  276. package/dist/telegram/inbound.js +72 -0
  277. package/dist/telegram/index.d.ts +8 -0
  278. package/dist/telegram/multipart.d.ts +5 -0
  279. package/dist/telegram/multipart.js +47 -0
  280. package/dist/telegram/types.d.ts +271 -0
  281. package/dist/telegram/update.d.ts +833 -0
  282. package/dist/telegram/update.js +129 -0
  283. package/dist/testing/fake-html.d.ts +9 -0
  284. package/dist/testing/fake-html.js +83 -0
  285. package/dist/testing/fake-telegram.d.ts +206 -0
  286. package/dist/testing/fake-telegram.js +856 -0
  287. package/dist/testing/index.d.ts +7 -0
  288. package/dist/testing/index.js +7 -0
  289. package/dist/testing/reference-sql.d.ts +4 -0
  290. package/dist/testing/reference-sql.js +8 -0
  291. package/dist/testing/test-plugin.d.ts +126 -0
  292. package/dist/testing/test-plugin.js +182 -0
  293. package/dist/testing/updates.d.ts +100 -0
  294. package/dist/testing/updates.js +212 -0
  295. package/package.json +113 -3
@@ -0,0 +1,39 @@
1
+ /** One SQL statement with positional `?` params. Producers return these so a host can batch them with its writes. */
2
+ export interface SqlFragment {
3
+ /** SQL text with `?` placeholders. */
4
+ readonly text: string;
5
+ /** Bound values, in placeholder order. */
6
+ readonly params: readonly SqlValue[];
7
+ }
8
+ /** Values D1 can bind. Booleans are bound as 0/1. */
9
+ export type SqlValue = string | number | bigint | null | ArrayBuffer | Uint8Array;
10
+ /** Anything that may appear inside `sql\`…\``. */
11
+ export type SqlInput = SqlValue | boolean | SqlFragment;
12
+ /** True for fragments made by `sql` (or its helpers); plain `{ text, params }` objects are not fragments. */
13
+ export declare function isSqlFragment(value: unknown): value is SqlFragment;
14
+ /**
15
+ * Tagged template that builds a parameterized statement. Every interpolated value becomes a `?` param;
16
+ * nested fragments (from `sql`, `sql.join`, `sql.ident`, `sql.raw`) are inlined with their params.
17
+ *
18
+ * ```ts
19
+ * sql`SELECT * FROM tg_chat WHERE bot_id = ${botId} AND status IN (${sql.join(statuses)})`
20
+ * ```
21
+ */
22
+ declare function sqlTag(strings: TemplateStringsArray, ...values: (SqlInput | undefined)[]): SqlFragment;
23
+ /** Join values or fragments with a separator (default `, `). Plain values become `?` params. */
24
+ declare function join(items: readonly SqlInput[], separator?: SqlFragment | string): SqlFragment;
25
+ /** A quoted identifier (table / column). Only `[A-Za-z_][A-Za-z0-9_]*` is accepted. */
26
+ declare function ident(name: string): SqlFragment;
27
+ /** Trusted SQL text with no params. Never pass user input. */
28
+ declare function raw(text: string): SqlFragment;
29
+ /**
30
+ * Tagged template that builds a parameterized `SqlFragment` (`undefined` and non-finite numbers throw, booleans
31
+ * bind as 0/1), plus `sql.join`, `sql.ident` (quoted identifier), `sql.raw` (trusted text) and `sql.empty`.
32
+ */
33
+ export declare const sql: typeof sqlTag & {
34
+ join: typeof join;
35
+ ident: typeof ident;
36
+ raw: typeof raw;
37
+ empty: SqlFragment;
38
+ };
39
+ export {};
@@ -0,0 +1,76 @@
1
+ // src/ports/sql.ts
2
+ const FRAGMENT = Symbol.for("cf-telegram-plugin.sql");
3
+ function make(text, params) {
4
+ return Object.freeze({
5
+ text,
6
+ params,
7
+ [FRAGMENT]: true
8
+ });
9
+ }
10
+ /** True for fragments made by `sql` (or its helpers); plain `{ text, params }` objects are not fragments. */
11
+ function isSqlFragment(value) {
12
+ return typeof value === "object" && value !== null && FRAGMENT in value;
13
+ }
14
+ function bindValue(value) {
15
+ if (value === void 0) throw new TypeError("sql: undefined cannot be bound (use null)");
16
+ if (typeof value === "boolean") return value ? 1 : 0;
17
+ if (typeof value === "number" && !Number.isFinite(value)) throw new TypeError("sql: non-finite number");
18
+ return value;
19
+ }
20
+ function append(texts, params, value) {
21
+ if (isSqlFragment(value)) {
22
+ texts.push(value.text);
23
+ params.push(...value.params);
24
+ } else {
25
+ texts.push("?");
26
+ params.push(bindValue(value));
27
+ }
28
+ }
29
+ /**
30
+ * Tagged template that builds a parameterized statement. Every interpolated value becomes a `?` param;
31
+ * nested fragments (from `sql`, `sql.join`, `sql.ident`, `sql.raw`) are inlined with their params.
32
+ *
33
+ * ```ts
34
+ * sql`SELECT * FROM tg_chat WHERE bot_id = ${botId} AND status IN (${sql.join(statuses)})`
35
+ * ```
36
+ */
37
+ function sqlTag(strings, ...values) {
38
+ const texts = [];
39
+ const params = [];
40
+ strings.forEach((s, i) => {
41
+ texts.push(s);
42
+ if (i < values.length) append(texts, params, values[i]);
43
+ });
44
+ return make(texts.join(""), params);
45
+ }
46
+ /** Join values or fragments with a separator (default `, `). Plain values become `?` params. */
47
+ function join(items, separator = ", ") {
48
+ const sep = typeof separator === "string" ? make(separator, []) : separator;
49
+ const texts = [];
50
+ const params = [];
51
+ items.forEach((item, i) => {
52
+ if (i > 0) append(texts, params, sep);
53
+ append(texts, params, item);
54
+ });
55
+ return make(texts.join(""), params);
56
+ }
57
+ /** A quoted identifier (table / column). Only `[A-Za-z_][A-Za-z0-9_]*` is accepted. */
58
+ function ident(name) {
59
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) throw new TypeError(`sql: invalid identifier ${name}`);
60
+ return make(`"${name}"`, []);
61
+ }
62
+ /** Trusted SQL text with no params. Never pass user input. */
63
+ function raw(text) {
64
+ return make(text, []);
65
+ }
66
+ /**
67
+ * Tagged template that builds a parameterized `SqlFragment` (`undefined` and non-finite numbers throw, booleans
68
+ * bind as 0/1), plus `sql.join`, `sql.ident` (quoted identifier), `sql.raw` (trusted text) and `sql.empty`.
69
+ */
70
+ const sql = Object.assign(sqlTag, {
71
+ join,
72
+ ident,
73
+ raw,
74
+ empty: make("", [])
75
+ });
76
+ export { isSqlFragment, sql };
@@ -0,0 +1,14 @@
1
+ import { RendererPort } from "../ports/ports.js";
2
+ /** Quotes a field when needed (comma, quote, CR/LF, edge spaces); defuses spreadsheet formulas in text. */
3
+ export declare function csvField(value: string, opts?: {
4
+ formulaGuard?: boolean;
5
+ }): string;
6
+ /** CSV text (CRLF line ends, trailing CRLF) from a header and string rows; every field goes through {@link csvField}. */
7
+ export declare function toCsv(header: readonly string[], rows: readonly (readonly string[])[]): string;
8
+ /** Options for {@link csvRenderer}. */
9
+ export interface CsvRendererOptions {
10
+ /** Write the UTF-8 BOM (default true). */
11
+ bom?: boolean;
12
+ }
13
+ /** Built-in `csv` renderer for `ports.renderers`: money in major units, local dates, `.csv` appended to the file name. */
14
+ export declare function csvRenderer(opts?: CsvRendererOptions): RendererPort;
@@ -0,0 +1,51 @@
1
+ import { rawCell, reportTable } from "./table.js";
2
+ // src/renderers/csv.ts
3
+ const BOM = new Uint8Array([
4
+ 239,
5
+ 187,
6
+ 191
7
+ ]);
8
+ /** Quotes a field when needed (comma, quote, CR/LF, edge spaces); defuses spreadsheet formulas in text. */
9
+ function csvField(value, opts = {}) {
10
+ let v = value;
11
+ if (opts.formulaGuard !== false && /^[=+\-@\t\r]/.test(v) && !/^[+-]?\d+(\.\d+)?$/.test(v)) v = `'${v}`;
12
+ return /[",\r\n]|^\s|\s$/.test(v) ? `"${v.replaceAll("\"", "\"\"")}"` : v;
13
+ }
14
+ /** CSV text (CRLF line ends, trailing CRLF) from a header and string rows; every field goes through {@link csvField}. */
15
+ function toCsv(header, rows) {
16
+ return `${[header, ...rows].map((r) => r.map((c) => csvField(c)).join(",")).join("\r\n")}\r\n`;
17
+ }
18
+ function withExt(name, ext) {
19
+ return name.toLowerCase().endsWith(`.${ext}`) ? name : `${name}.${ext}`;
20
+ }
21
+ /** Built-in `csv` renderer for `ports.renderers`: money in major units, local dates, `.csv` appended to the file name. */
22
+ function csvRenderer(opts = {}) {
23
+ return {
24
+ format: "csv",
25
+ mime: "text/csv",
26
+ async render(input) {
27
+ const table = reportTable(input.data, input.layout ?? null, input.locale);
28
+ const ctx = {
29
+ locale: input.locale,
30
+ tz: input.timezone,
31
+ currency: input.currency,
32
+ minorDigits: input.minorDigits,
33
+ labels: input.labels
34
+ };
35
+ const text = toCsv(table.columns.map((c) => c.label), table.rows.map((r) => r.map((v, i) => rawCell(v, table.columns[i]?.format ?? "text", ctx))));
36
+ const body = new TextEncoder().encode(text);
37
+ const bytes = opts.bom === false ? body : new Uint8Array(BOM.length + body.length);
38
+ if (opts.bom !== false) {
39
+ bytes.set(BOM, 0);
40
+ bytes.set(body, BOM.length);
41
+ }
42
+ return {
43
+ name: withExt(input.fileName, "csv"),
44
+ mime: "text/csv",
45
+ body: bytes,
46
+ size: bytes.byteLength
47
+ };
48
+ }
49
+ };
50
+ }
51
+ export { csvField, csvRenderer, toCsv };
@@ -0,0 +1,44 @@
1
+ import { RenderInput, RendererPort } from "../ports/ports.js";
2
+ import { CellContext, ReportTable } from "./table.js";
3
+ /** Input for {@link htmlReport}. */
4
+ export interface HtmlReportInput {
5
+ title: string;
6
+ /** Telegram HTML from the template (a safe subset of HTML). */
7
+ intro?: string | undefined;
8
+ /** Data table; omitted or column-less = no table. */
9
+ table?: ReportTable | undefined;
10
+ /** `lang` attribute (`en`, `mr`). */
11
+ lang: string;
12
+ /** Cell formatting context. */
13
+ cell: CellContext;
14
+ /** Extra CSS, for example `@font-face` with an embedded Noto Sans Devanagari. */
15
+ css?: string | undefined;
16
+ }
17
+ /** A self-contained HTML report (title, intro, table). Title and cells are escaped; `intro` and `css` are inserted as-is. */
18
+ export declare function htmlReport(input: HtmlReportInput): string;
19
+ /** The report document for a render input (title + template HTML + table from the layout). */
20
+ export declare function reportDocument(input: RenderInput, css?: string): string;
21
+ /** What an HTML → PDF function may return. A non-OK `Response` throws. */
22
+ export type PdfBytes = Uint8Array | ArrayBuffer | ReadableStream<Uint8Array> | Response;
23
+ /** Options for {@link htmlTemplatePdf}. */
24
+ export interface HtmlPdfOptions {
25
+ /** Extra CSS for the default document (fonts). */
26
+ css?: string;
27
+ /** Build the HTML yourself instead of `reportDocument`. */
28
+ document?: (input: RenderInput) => string;
29
+ }
30
+ /** A pdf renderer from any HTML → PDF function (Browser Rendering, an external service, a WASM engine). */
31
+ export declare function htmlTemplatePdf(render: (html: string) => Promise<PdfBytes>, opts?: HtmlPdfOptions): RendererPort;
32
+ /** The Browser Rendering binding surface used here (`env.BROWSER`). */
33
+ export interface BrowserRunBinding {
34
+ quickAction(action: "pdf", options: Record<string, unknown>): Promise<unknown>;
35
+ }
36
+ /** Type guard: does `v` look like a Browser Rendering binding (has `quickAction`)? */
37
+ export declare function isBrowserRunBinding(v: unknown): v is BrowserRunBinding;
38
+ /** Options for {@link browserRunPdf}. */
39
+ export interface BrowserRunPdfOptions extends HtmlPdfOptions {
40
+ /** Passed as `pdfOptions` (format, margins…). Default A4 with background graphics. */
41
+ pdfOptions?: Record<string, unknown>;
42
+ }
43
+ /** PDF through Browser Rendering (`env.BROWSER.quickAction("pdf", { html })`): Chromium shapes Devanagari. */
44
+ export declare function browserRunPdf(binding: BrowserRunBinding, opts?: BrowserRunPdfOptions): RendererPort;
@@ -0,0 +1,81 @@
1
+ import { escapeHtml } from "../core/html/escape.js";
2
+ import { displayCell, reportTable } from "./table.js";
3
+ // src/renderers/html.ts
4
+ const BASE_CSS = `
5
+ body { font-family: "Noto Sans", "Noto Sans Devanagari", system-ui, sans-serif; font-size: 11pt; color: #111; margin: 24px; }
6
+ h1 { font-size: 16pt; margin: 0 0 12px; }
7
+ .intro { margin: 0 0 16px; white-space: pre-wrap; }
8
+ table { border-collapse: collapse; width: 100%; }
9
+ th, td { border: 1px solid #bbb; padding: 4px 6px; text-align: left; vertical-align: top; }
10
+ th { background: #f1f1f1; }
11
+ td.num { text-align: right; white-space: nowrap; }
12
+ `;
13
+ /** A self-contained HTML report (title, intro, table). Title and cells are escaped; `intro` and `css` are inserted as-is. */
14
+ function htmlReport(input) {
15
+ const t = input.table;
16
+ const table = t && t.columns.length > 0 ? `<table><thead><tr>${t.columns.map((c) => `<th>${escapeHtml(c.label)}</th>`).join("")}</tr></thead><tbody>${t.rows.map((r) => `<tr>${r.map((v, i) => {
17
+ const fmt = t.columns[i]?.format ?? "text";
18
+ return `<td${fmt === "money" || fmt === "number" ? " class=\"num\"" : ""}>${escapeHtml(displayCell(v, fmt, input.cell))}</td>`;
19
+ }).join("")}</tr>`).join("")}</tbody></table>` : "";
20
+ const intro = input.intro ? `<div class="intro">${input.intro}</div>` : "";
21
+ return `<!doctype html><html lang="${escapeHtml(input.lang)}"><head><meta charset="utf-8"><title>${escapeHtml(input.title)}</title><style>${BASE_CSS}${input.css ?? ""}</style></head><body><h1>${escapeHtml(input.title)}</h1>${intro}${table}</body></html>`;
22
+ }
23
+ /** The report document for a render input (title + template HTML + table from the layout). */
24
+ function reportDocument(input, css) {
25
+ return htmlReport({
26
+ title: input.title ?? input.fileName.replace(/\.[a-z0-9]+$/i, ""),
27
+ intro: input.html,
28
+ table: reportTable(input.data, input.layout ?? null, input.locale),
29
+ lang: input.locale,
30
+ cell: {
31
+ locale: input.locale,
32
+ tz: input.timezone,
33
+ currency: input.currency,
34
+ minorDigits: input.minorDigits,
35
+ labels: input.labels
36
+ },
37
+ css
38
+ });
39
+ }
40
+ async function toBytes(out) {
41
+ if (out instanceof Uint8Array) return out;
42
+ if (out instanceof ArrayBuffer) return new Uint8Array(out);
43
+ if (out instanceof Response) {
44
+ if (!out.ok) throw new Error(`pdf renderer answered ${out.status}`);
45
+ return new Uint8Array(await out.arrayBuffer());
46
+ }
47
+ return new Uint8Array(await new Response(out).arrayBuffer());
48
+ }
49
+ /** A pdf renderer from any HTML → PDF function (Browser Rendering, an external service, a WASM engine). */
50
+ function htmlTemplatePdf(render, opts = {}) {
51
+ return {
52
+ format: "pdf",
53
+ mime: "application/pdf",
54
+ async render(input) {
55
+ const body = await toBytes(await render(opts.document ? opts.document(input) : reportDocument(input, opts.css)));
56
+ return {
57
+ name: input.fileName.toLowerCase().endsWith(".pdf") ? input.fileName : `${input.fileName}.pdf`,
58
+ mime: "application/pdf",
59
+ body,
60
+ size: body.byteLength
61
+ };
62
+ }
63
+ };
64
+ }
65
+ /** Type guard: does `v` look like a Browser Rendering binding (has `quickAction`)? */
66
+ function isBrowserRunBinding(v) {
67
+ return typeof v === "object" && v !== null && typeof v.quickAction === "function";
68
+ }
69
+ /** PDF through Browser Rendering (`env.BROWSER.quickAction("pdf", { html })`): Chromium shapes Devanagari. */
70
+ function browserRunPdf(binding, opts = {}) {
71
+ return htmlTemplatePdf(async (html) => {
72
+ return await binding.quickAction("pdf", {
73
+ html,
74
+ pdfOptions: opts.pdfOptions ?? {
75
+ format: "a4",
76
+ printBackground: true
77
+ }
78
+ });
79
+ }, opts);
80
+ }
81
+ export { browserRunPdf, htmlReport, htmlTemplatePdf, isBrowserRunBinding, reportDocument };
@@ -0,0 +1,5 @@
1
+ import { RenderInput, RenderedFile, RendererPort } from "../ports/ports.js";
2
+ import { CsvRendererOptions, csvField, csvRenderer, toCsv } from "./csv.js";
3
+ import { CellContext, ReportTable, TableColumn, displayCell, rawCell, reportTable } from "./table.js";
4
+ import { BrowserRunBinding, BrowserRunPdfOptions, HtmlPdfOptions, HtmlReportInput, PdfBytes, browserRunPdf, htmlReport, htmlTemplatePdf, isBrowserRunBinding, reportDocument } from "./html.js";
5
+ export { type BrowserRunBinding, type BrowserRunPdfOptions, type CellContext, type CsvRendererOptions, type HtmlPdfOptions, type HtmlReportInput, type PdfBytes, type RenderInput, type RenderedFile, type RendererPort, type ReportTable, type TableColumn, browserRunPdf, csvField, csvRenderer, displayCell, htmlReport, htmlTemplatePdf, isBrowserRunBinding, rawCell, reportDocument, reportTable, toCsv };
@@ -0,0 +1,4 @@
1
+ import { displayCell, rawCell, reportTable } from "./table.js";
2
+ import { csvField, csvRenderer, toCsv } from "./csv.js";
3
+ import { browserRunPdf, htmlReport, htmlTemplatePdf, isBrowserRunBinding, reportDocument } from "./html.js";
4
+ export { browserRunPdf, csvField, csvRenderer, displayCell, htmlReport, htmlTemplatePdf, isBrowserRunBinding, rawCell, reportDocument, reportTable, toCsv };
@@ -0,0 +1,30 @@
1
+ import { ReportColumnFormat } from "../contracts/templates.js";
2
+ /** One resolved report column: dotted field path, localized header, cell format. */
3
+ export interface TableColumn {
4
+ key: string;
5
+ label: string;
6
+ format: ReportColumnFormat;
7
+ }
8
+ /** A report as columns plus raw row values (from {@link reportTable}). */
9
+ export interface ReportTable {
10
+ columns: TableColumn[];
11
+ /** Raw values per row, in column order. */
12
+ rows: unknown[][];
13
+ }
14
+ /** Formatting context for cells: locale, IANA zone, ISO 4217 currency, minor-unit digits (default 2), yes/no labels. */
15
+ export interface CellContext {
16
+ locale: string;
17
+ tz: string;
18
+ currency?: string | undefined;
19
+ minorDigits?: number | undefined;
20
+ labels?: {
21
+ yes: string;
22
+ no: string;
23
+ } | undefined;
24
+ }
25
+ /** `layout` is `report_json.layout` (validated here; an invalid layout falls back to the defaults). */
26
+ export declare function reportTable(data: unknown, layoutInput: unknown, locale: string): ReportTable;
27
+ /** Machine-friendly cell text (CSV): plain numbers, money in major units, ISO-like local dates. */
28
+ export declare function rawCell(value: unknown, format: ReportColumnFormat, ctx: CellContext): string;
29
+ /** Human cell text (HTML / PDF): locale formatting through Intl. */
30
+ export declare function displayCell(value: unknown, format: ReportColumnFormat, ctx: CellContext): string;
@@ -0,0 +1,114 @@
1
+ import { ReportLayout } from "../contracts/templates.js";
2
+ import { localParts } from "../core/schedule/tz.js";
3
+ import { formatDateTime, formatMoney, formatNumber, toEpoch, toNumber } from "../core/template/format.js";
4
+ // src/renderers/table.ts
5
+ function at(value, path) {
6
+ let v = value;
7
+ for (const k of path.split(".")) {
8
+ if (v === null || typeof v !== "object" || !Object.hasOwn(v, k)) return void 0;
9
+ v = v[k];
10
+ }
11
+ return v;
12
+ }
13
+ function labelOf(label, locale) {
14
+ if (typeof label === "string") return label;
15
+ return label[locale] ?? label[locale.split("-")[0] ?? ""] ?? Object.values(label)[0] ?? "";
16
+ }
17
+ function rowsOf(data, path) {
18
+ if (Array.isArray(data)) return data;
19
+ if (path) {
20
+ const v = at(data, path);
21
+ return Array.isArray(v) ? v : [];
22
+ }
23
+ if (data !== null && typeof data === "object") {
24
+ for (const v of Object.values(data)) if (Array.isArray(v)) return v;
25
+ }
26
+ return [];
27
+ }
28
+ /** `layout` is `report_json.layout` (validated here; an invalid layout falls back to the defaults). */
29
+ function reportTable(data, layoutInput, locale) {
30
+ const layout = layoutInput ? ReportLayout.safeParse(layoutInput).data ?? null : null;
31
+ const rows = rowsOf(data, layout?.rows);
32
+ let columns;
33
+ if (layout?.columns) columns = layout.columns.map((c) => ({
34
+ key: c.key,
35
+ label: labelOf(c.label, locale),
36
+ format: c.format
37
+ }));
38
+ else {
39
+ const first = rows.find((r) => r !== null && typeof r === "object" && !Array.isArray(r));
40
+ columns = first ? Object.entries(first).filter(([, v]) => v === null || typeof v !== "object").map(([key, v]) => ({
41
+ key,
42
+ label: key,
43
+ format: typeof v === "number" ? "number" : "text"
44
+ })) : [];
45
+ }
46
+ return {
47
+ columns,
48
+ rows: rows.map((r) => columns.map((c) => at(r, c.key)))
49
+ };
50
+ }
51
+ const pad = (n) => String(n).padStart(2, "0");
52
+ /** Machine-friendly cell text (CSV): plain numbers, money in major units, ISO-like local dates. */
53
+ function rawCell(value, format, ctx) {
54
+ if (value === null || value === void 0) return "";
55
+ switch (format) {
56
+ case "money": {
57
+ const n = toNumber(value);
58
+ const digits = ctx.minorDigits ?? 2;
59
+ return n === null ? "" : (n / 10 ** digits).toFixed(digits);
60
+ }
61
+ case "number": {
62
+ const n = toNumber(value);
63
+ return n === null ? "" : String(n);
64
+ }
65
+ case "date":
66
+ case "datetime": {
67
+ const ms = toEpoch(value);
68
+ if (ms === null) return "";
69
+ const p = localParts(ms, ctx.tz);
70
+ const date = `${p.year}-${pad(p.month)}-${pad(p.day)}`;
71
+ return format === "date" ? date : `${date} ${pad(p.hour)}:${pad(p.minute)}`;
72
+ }
73
+ case "yesno": return value ? ctx.labels?.yes ?? "Yes" : ctx.labels?.no ?? "No";
74
+ default: return text(value);
75
+ }
76
+ }
77
+ /** Human cell text (HTML / PDF): locale formatting through Intl. */
78
+ function displayCell(value, format, ctx) {
79
+ if (value === null || value === void 0) return "";
80
+ const fctx = {
81
+ locale: ctx.locale,
82
+ tz: ctx.tz,
83
+ currency: ctx.currency,
84
+ minorDigits: ctx.minorDigits
85
+ };
86
+ switch (format) {
87
+ case "money": {
88
+ const n = toNumber(value);
89
+ return n === null ? "" : formatMoney(n, fctx);
90
+ }
91
+ case "number": {
92
+ const n = toNumber(value);
93
+ return n === null ? "" : formatNumber(n, fctx);
94
+ }
95
+ case "date":
96
+ case "datetime": {
97
+ const ms = toEpoch(value);
98
+ if (ms === null) return "";
99
+ return formatDateTime(ms, fctx, format === "date" ? { dateStyle: "medium" } : {
100
+ dateStyle: "medium",
101
+ timeStyle: "short"
102
+ });
103
+ }
104
+ default: return rawCell(value, format, ctx);
105
+ }
106
+ }
107
+ function text(value) {
108
+ if (typeof value === "string") return value;
109
+ if (typeof value === "number") return Number.isFinite(value) ? String(value) : "";
110
+ if (typeof value === "boolean" || typeof value === "bigint") return String(value);
111
+ if (Array.isArray(value)) return value.map(text).filter(Boolean).join(", ");
112
+ return "";
113
+ }
114
+ export { displayCell, rawCell, reportTable };
@@ -0,0 +1,146 @@
1
+ import { PageQuery } from "../contracts/common.js";
2
+ import { BotCreate, BotPatch, BotRotateToken } from "../contracts/bots.js";
3
+ import { DestinationSave } from "../contracts/destinations.js";
4
+ import { ChatPatch, ChatQuery } from "../contracts/chats.js";
5
+ import { DeliveryQuery } from "../contracts/deliveries.js";
6
+ import { StatsQuery } from "../contracts/health.js";
7
+ import { LinkCreate } from "../contracts/links.js";
8
+ import { PERMISSION_NAMES } from "../contracts/permissions.js";
9
+ import { SubscriptionSave } from "../contracts/subscriptions.js";
10
+ import { UserQuery } from "../contracts/users.js";
11
+ import { TelegramPluginError, featureDisabled } from "../engine/errors.js";
12
+ import { botDto, chatDto, deliveryDto, destinationDto } from "../engine/rows.js";
13
+ import { connectBot, deleteBot, getBot, listBots, patchBot, resetWebhook, rotateToken, testBot } from "../services/bots.js";
14
+ import { listChats, patchChat, requireChat, unlinkChat } from "../services/chats.js";
15
+ import { createDestination, deleteDestination, listDestinations, requireDestination, updateDestination } from "../services/destinations.js";
16
+ import { cancelDelivery, listDeliveries, requireDelivery, retryDelivery } from "../services/deliveries.js";
17
+ import { createLink } from "../services/links.js";
18
+ import { health, stats } from "../services/ops.js";
19
+ import { listSubscriptions, replaceSubscriptions } from "../services/subscriptions.js";
20
+ import { actorMiddleware, decodeCursor, errorHandler, featureGate, idParam, idempotencyMiddleware, pageOf, requirePermission, runtimeMiddleware, schemaGuard, skipIdempotencyStore, zJson, zQuery } from "./http.js";
21
+ import { contentRoutes } from "./content.js";
22
+ import { Hono } from "hono";
23
+ // src/routes/admin.ts
24
+ const BotListQuery = PageQuery;
25
+ const ChatListQuery = ChatQuery.extend(PageQuery.shape);
26
+ const DeliveryListQuery = DeliveryQuery.extend(PageQuery.shape);
27
+ function adminRoutes(deps, meta) {
28
+ const f = deps.features;
29
+ const app = new Hono();
30
+ app.onError(errorHandler(deps.errors));
31
+ app.use("/admin/*", runtimeMiddleware(deps), schemaGuard(deps), actorMiddleware, idempotencyMiddleware);
32
+ app.use("/admin/chats/:id/subscriptions", featureGate(f, "subscriptions"));
33
+ app.use("/admin/deliveries/*", featureGate(f, "ops"));
34
+ app.use("/admin/deliveries", featureGate(f, "ops"));
35
+ app.use("/admin/health", featureGate(f, "ops"));
36
+ app.use("/admin/stats", featureGate(f, "ops"));
37
+ return app.get("/admin/meta", requirePermission("telegram.view"), async (c) => {
38
+ const rt = c.var.tg;
39
+ const held = [];
40
+ for (const p of PERMISSION_NAMES) if (await rt.can(c.var.tgActor, p)) held.push(rt.perm(p));
41
+ return c.json({
42
+ ...meta(),
43
+ permissions: held
44
+ });
45
+ }).get("/admin/bots", requirePermission("telegram.bots.manage"), zQuery(BotListQuery), async (c) => {
46
+ const q = c.req.valid("query");
47
+ const rows = await listBots(c.var.tg, {
48
+ cursor: decodeCursor(q.cursor),
49
+ limit: q.limit
50
+ });
51
+ return c.json(pageOf(rows, q.limit, botDto));
52
+ }).post("/admin/bots", requirePermission("telegram.bots.manage"), zJson(BotCreate), async (c) => {
53
+ return c.json(await connectBot(c.var.tg, c.var.tgActor, c.req.valid("json")), 201);
54
+ }).get("/admin/bots/:id", requirePermission("telegram.bots.manage"), async (c) => {
55
+ return c.json(await getBot(c.var.tg, idParam(c)));
56
+ }).patch("/admin/bots/:id", requirePermission("telegram.bots.manage"), zJson(BotPatch), async (c) => {
57
+ return c.json(await patchBot(c.var.tg, c.var.tgActor, idParam(c), c.req.valid("json")));
58
+ }).delete("/admin/bots/:id", requirePermission("telegram.bots.manage"), async (c) => {
59
+ await deleteBot(c.var.tg, c.var.tgActor, idParam(c));
60
+ return c.json({ ok: true });
61
+ }).post("/admin/bots/:id/test", requirePermission("telegram.bots.manage"), async (c) => {
62
+ return c.json(await testBot(c.var.tg, idParam(c)));
63
+ }).post("/admin/bots/:id/rotate-token", requirePermission("telegram.bots.manage"), zJson(BotRotateToken), async (c) => {
64
+ return c.json(await rotateToken(c.var.tg, c.var.tgActor, idParam(c), c.req.valid("json").token));
65
+ }).post("/admin/bots/:id/webhook/reset", requirePermission("telegram.bots.manage"), async (c) => {
66
+ return c.json(await resetWebhook(c.var.tg, c.var.tgActor, idParam(c)));
67
+ }).get("/admin/chats", requirePermission("telegram.chats.manage"), zQuery(ChatListQuery), async (c) => {
68
+ const q = c.req.valid("query");
69
+ const rows = await listChats(c.var.tg, q, {
70
+ cursor: decodeCursor(q.cursor),
71
+ limit: q.limit
72
+ });
73
+ return c.json(pageOf(rows, q.limit, chatDto));
74
+ }).get("/admin/chats/:id", requirePermission("telegram.chats.manage"), async (c) => {
75
+ return c.json(chatDto(await requireChat(c.var.tg, idParam(c))));
76
+ }).patch("/admin/chats/:id", requirePermission("telegram.chats.manage"), zJson(ChatPatch), async (c) => {
77
+ return c.json(await patchChat(c.var.tg, c.var.tgActor, idParam(c), c.req.valid("json")));
78
+ }).delete("/admin/chats/:id", requirePermission("telegram.chats.manage"), async (c) => {
79
+ await unlinkChat(c.var.tg, c.var.tgActor, idParam(c));
80
+ return c.json({ ok: true });
81
+ }).get("/admin/chats/:id/subscriptions", requirePermission("telegram.chats.manage"), async (c) => {
82
+ const chat = await requireChat(c.var.tg, idParam(c));
83
+ return c.json({ items: await listSubscriptions(c.var.tg, [chat.id]) });
84
+ }).put("/admin/chats/:id/subscriptions", requirePermission("telegram.chats.manage"), zJson(SubscriptionSave), async (c) => {
85
+ const chat = await requireChat(c.var.tg, idParam(c));
86
+ const body = c.req.valid("json");
87
+ if (body.chatRef !== chat.id) throw new TelegramPluginError("validation_failed", "chatRef must match the path");
88
+ return c.json({ items: await replaceSubscriptions(c.var.tg, body) });
89
+ }).get("/admin/users", requirePermission("telegram.chats.manage"), zQuery(UserQuery), async (c) => {
90
+ const rt = c.var.tg;
91
+ const search = rt.users?.search;
92
+ if (!search) throw featureDisabled("users.search", "user search needs ports.users.search");
93
+ const q = c.req.valid("query");
94
+ const items = (await search.call(rt.users, q.q, { limit: q.limit })).slice(0, q.limit).map((u) => ({
95
+ id: u.id,
96
+ name: u.name,
97
+ role: u.role,
98
+ ...u.locale ? { locale: u.locale } : {}
99
+ }));
100
+ return c.json({ items });
101
+ }).post("/admin/links", requirePermission("telegram.chats.manage"), zJson(LinkCreate), async (c) => {
102
+ return c.json(await createLink(c.var.tg, c.var.tgActor, c.req.valid("json")), 201);
103
+ }).get("/admin/destinations", requirePermission("telegram.destinations.manage"), zQuery(PageQuery), async (c) => {
104
+ const q = c.req.valid("query");
105
+ const rows = await listDestinations(c.var.tg, {
106
+ cursor: decodeCursor(q.cursor),
107
+ limit: q.limit
108
+ });
109
+ return c.json(pageOf(rows, q.limit, destinationDto));
110
+ }).post("/admin/destinations", requirePermission("telegram.destinations.manage"), zJson(DestinationSave), async (c) => {
111
+ return c.json(await createDestination(c.var.tg, c.var.tgActor, c.req.valid("json")), 201);
112
+ }).get("/admin/destinations/:id", requirePermission("telegram.destinations.manage"), async (c) => {
113
+ return c.json(destinationDto(await requireDestination(c.var.tg, idParam(c))));
114
+ }).put("/admin/destinations/:id", requirePermission("telegram.destinations.manage"), zJson(DestinationSave), async (c) => {
115
+ return c.json(await updateDestination(c.var.tg, c.var.tgActor, idParam(c), c.req.valid("json")));
116
+ }).delete("/admin/destinations/:id", requirePermission("telegram.destinations.manage"), async (c) => {
117
+ await deleteDestination(c.var.tg, c.var.tgActor, idParam(c));
118
+ return c.json({ ok: true });
119
+ }).get("/admin/deliveries", requirePermission("telegram.ops.view"), zQuery(DeliveryListQuery), async (c) => {
120
+ const q = c.req.valid("query");
121
+ const withBody = await c.var.tg.can(c.var.tgActor, "telegram.ops.content");
122
+ const rows = await listDeliveries(c.var.tg, q, {
123
+ cursor: decodeCursor(q.cursor),
124
+ limit: q.limit
125
+ });
126
+ return c.json(pageOf(rows, q.limit, (r) => deliveryDto(r, withBody)));
127
+ }).get("/admin/deliveries/:id", requirePermission("telegram.ops.view"), async (c) => {
128
+ const withBody = await c.var.tg.can(c.var.tgActor, "telegram.ops.content");
129
+ return c.json(deliveryDto(await requireDelivery(c.var.tg, idParam(c)), withBody));
130
+ }).post("/admin/deliveries/:id/retry", requirePermission("telegram.ops.manage"), async (c) => {
131
+ const withBody = await c.var.tg.can(c.var.tgActor, "telegram.ops.content");
132
+ if (withBody) skipIdempotencyStore(c);
133
+ return c.json(deliveryDto(await retryDelivery(c.var.tg, c.var.tgActor, idParam(c)), withBody));
134
+ }).post("/admin/deliveries/:id/cancel", requirePermission("telegram.ops.manage"), async (c) => {
135
+ const withBody = await c.var.tg.can(c.var.tgActor, "telegram.ops.content");
136
+ if (withBody) skipIdempotencyStore(c);
137
+ return c.json(deliveryDto(await cancelDelivery(c.var.tg, c.var.tgActor, idParam(c)), withBody));
138
+ }).get("/admin/health", requirePermission("telegram.ops.view"), async (c) => {
139
+ return c.json(await health(c.var.tg));
140
+ }).get("/admin/stats", requirePermission("telegram.ops.view"), zQuery(StatsQuery), async (c) => {
141
+ const q = c.req.valid("query");
142
+ if (q.to <= q.from) throw new TelegramPluginError("validation_failed", "`to` must be after `from`");
143
+ return c.json(await stats(c.var.tg, q.from, q.to));
144
+ }).route("/", contentRoutes(deps));
145
+ }
146
+ export { adminRoutes };