@adriangalilea/utils 7.1.0 → 9.0.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 (134) hide show
  1. package/README.md +95 -56
  2. package/dist/bot/access-control.d.ts +89 -31
  3. package/dist/bot/access-control.d.ts.map +1 -1
  4. package/dist/bot/access-control.js +10 -28
  5. package/dist/bot/access-control.js.map +1 -1
  6. package/dist/bot/callbacks.d.ts +0 -13
  7. package/dist/bot/callbacks.d.ts.map +1 -1
  8. package/dist/bot/callbacks.js +0 -16
  9. package/dist/bot/callbacks.js.map +1 -1
  10. package/dist/bot/create.d.ts.map +1 -1
  11. package/dist/bot/create.js +8 -5
  12. package/dist/bot/create.js.map +1 -1
  13. package/dist/bot/kit.d.ts +4 -9
  14. package/dist/bot/kit.d.ts.map +1 -1
  15. package/dist/bot/kit.js +0 -1
  16. package/dist/bot/kit.js.map +1 -1
  17. package/dist/bot/lang.d.ts +46 -0
  18. package/dist/bot/lang.d.ts.map +1 -0
  19. package/dist/bot/lang.js +29 -0
  20. package/dist/bot/lang.js.map +1 -0
  21. package/dist/bot/language.d.ts +0 -2
  22. package/dist/bot/language.d.ts.map +1 -1
  23. package/dist/bot/language.js +0 -12
  24. package/dist/bot/language.js.map +1 -1
  25. package/dist/bot/llm.d.ts +1 -2
  26. package/dist/bot/llm.d.ts.map +1 -1
  27. package/dist/bot/llm.js +1 -2
  28. package/dist/bot/llm.js.map +1 -1
  29. package/dist/bot/menu.d.ts +7 -0
  30. package/dist/bot/menu.d.ts.map +1 -1
  31. package/dist/bot/menu.js +8 -2
  32. package/dist/bot/menu.js.map +1 -1
  33. package/dist/bot/payments/callbacks.d.ts +1 -1
  34. package/dist/bot/payments/callbacks.d.ts.map +1 -1
  35. package/dist/bot/payments/callbacks.js +9 -11
  36. package/dist/bot/payments/callbacks.js.map +1 -1
  37. package/dist/bot/payments/commands.d.ts +1 -1
  38. package/dist/bot/payments/commands.d.ts.map +1 -1
  39. package/dist/bot/payments/commands.js +2 -2
  40. package/dist/bot/payments/commands.js.map +1 -1
  41. package/dist/bot/payments/derive.d.ts.map +1 -1
  42. package/dist/bot/payments/derive.js +7 -6
  43. package/dist/bot/payments/derive.js.map +1 -1
  44. package/dist/bot/payments/handlers.d.ts +1 -5
  45. package/dist/bot/payments/handlers.d.ts.map +1 -1
  46. package/dist/bot/payments/handlers.js +59 -20
  47. package/dist/bot/payments/handlers.js.map +1 -1
  48. package/dist/bot/payments/invoice.d.ts.map +1 -1
  49. package/dist/bot/payments/invoice.js +27 -10
  50. package/dist/bot/payments/invoice.js.map +1 -1
  51. package/dist/bot/payments/menu-item.d.ts.map +1 -1
  52. package/dist/bot/payments/menu-item.js +2 -2
  53. package/dist/bot/payments/menu-item.js.map +1 -1
  54. package/dist/bot/payments/payouts.d.ts +3 -3
  55. package/dist/bot/payments/payouts.js +3 -3
  56. package/dist/bot/payments/plugin.d.ts +87 -29
  57. package/dist/bot/payments/plugin.d.ts.map +1 -1
  58. package/dist/bot/payments/refund.d.ts +1 -5
  59. package/dist/bot/payments/refund.d.ts.map +1 -1
  60. package/dist/bot/payments/refund.js +24 -42
  61. package/dist/bot/payments/refund.js.map +1 -1
  62. package/dist/bot/payments/types.d.ts +2 -9
  63. package/dist/bot/payments/types.d.ts.map +1 -1
  64. package/dist/bot/payments/types.js +0 -7
  65. package/dist/bot/payments/types.js.map +1 -1
  66. package/dist/bot/payments/waiver.d.ts +1 -17
  67. package/dist/bot/payments/waiver.d.ts.map +1 -1
  68. package/dist/bot/payments/waiver.js +5 -33
  69. package/dist/bot/payments/waiver.js.map +1 -1
  70. package/dist/bot/profile.d.ts.map +1 -1
  71. package/dist/bot/profile.js +18 -11
  72. package/dist/bot/profile.js.map +1 -1
  73. package/dist/bot/reactions.d.ts +0 -2
  74. package/dist/bot/reactions.d.ts.map +1 -1
  75. package/dist/bot/reactions.js +0 -16
  76. package/dist/bot/reactions.js.map +1 -1
  77. package/dist/bot/session.d.ts +1 -21
  78. package/dist/bot/session.d.ts.map +1 -1
  79. package/dist/bot/session.js +1 -29
  80. package/dist/bot/session.js.map +1 -1
  81. package/dist/bot/storage-d1.d.ts.map +1 -1
  82. package/dist/bot/storage-d1.js +4 -4
  83. package/dist/bot/storage-d1.js.map +1 -1
  84. package/dist/bot/storage.d.ts +1 -1
  85. package/dist/bot/storage.js +6 -6
  86. package/dist/bot/storage.js.map +1 -1
  87. package/dist/bot/worker.d.ts.map +1 -1
  88. package/dist/bot/worker.js +24 -15
  89. package/dist/bot/worker.js.map +1 -1
  90. package/dist/cli/text.d.ts.map +1 -1
  91. package/dist/cli/text.js +2 -3
  92. package/dist/cli/text.js.map +1 -1
  93. package/dist/llm/index.js +5 -5
  94. package/dist/llm/index.js.map +1 -1
  95. package/dist/offensive.d.ts.map +1 -1
  96. package/dist/offensive.js +3 -4
  97. package/dist/offensive.js.map +1 -1
  98. package/dist/runtime.d.ts +6 -4
  99. package/dist/runtime.d.ts.map +1 -1
  100. package/dist/runtime.js +12 -17
  101. package/dist/runtime.js.map +1 -1
  102. package/dist/tg-html/index.d.ts +11 -4
  103. package/dist/tg-html/index.d.ts.map +1 -1
  104. package/dist/tg-html/index.js +13 -8
  105. package/dist/tg-html/index.js.map +1 -1
  106. package/dist/tg-md/index.d.ts +6 -6
  107. package/dist/tg-md/index.d.ts.map +1 -1
  108. package/dist/tg-md/index.js +9 -19
  109. package/dist/tg-md/index.js.map +1 -1
  110. package/dist/universal/format.d.ts +3 -2
  111. package/dist/universal/format.d.ts.map +1 -1
  112. package/dist/universal/format.js +3 -2
  113. package/dist/universal/format.js.map +1 -1
  114. package/dist/universal/log.d.ts +176 -95
  115. package/dist/universal/log.d.ts.map +1 -1
  116. package/dist/universal/log.js +677 -217
  117. package/dist/universal/log.js.map +1 -1
  118. package/dist/universal/time.d.ts +11 -2
  119. package/dist/universal/time.d.ts.map +1 -1
  120. package/dist/universal/time.js +22 -2
  121. package/dist/universal/time.js.map +1 -1
  122. package/package.json +9 -15
  123. package/dist/bot/index.d.ts +0 -31
  124. package/dist/bot/index.d.ts.map +0 -1
  125. package/dist/bot/index.js +0 -31
  126. package/dist/bot/index.js.map +0 -1
  127. package/dist/universal/currency/download-crypto-list.d.ts +0 -10
  128. package/dist/universal/currency/download-crypto-list.d.ts.map +0 -1
  129. package/dist/universal/currency/download-crypto-list.js +0 -69
  130. package/dist/universal/currency/download-crypto-list.js.map +0 -1
  131. package/dist/universal/url/update-rules.d.ts +0 -15
  132. package/dist/universal/url/update-rules.d.ts.map +0 -1
  133. package/dist/universal/url/update-rules.js +0 -122
  134. package/dist/universal/url/update-rules.js.map +0 -1
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # ts-utils
2
2
 
3
- TypeScript utilities - logger, currency, offensive programming, file operations, environment management, and more. The sibling of [swift-utils](https://github.com/adriangalilea/swift-utils), [py-utils](https://github.com/adriangalilea/py-utils), and [go-utils](https://github.com/adriangalilea/go-utils). The logging doctrine the three share, and the harness that proves they emit the same record line, live in [utils](https://github.com/adriangalilea/utils).
3
+ TypeScript utilities - logger, currency, offensive programming, file operations, environment management, and more. The sibling of [swift-utils](https://github.com/adriangalilea/swift-utils), [py-utils](https://github.com/adriangalilea/py-utils), and [go-utils](https://github.com/adriangalilea/go-utils). The logging contract the three share, and the harness that proves they write the same event stream, live in [utils](https://github.com/adriangalilea/utils).
4
4
 
5
5
  ## Product metrics
6
6
 
@@ -77,19 +77,6 @@ Enable it only with a deletion and retention policy; no identity is inferred.
77
77
  `overlaps: [["a", "b"]]` declares unordered audience overlap, not an ordered funnel.
78
78
  Daily totals support counts and averages, not percentile latency or event ordering.
79
79
 
80
- **7.0 migration:** `defineMetrics` takes `store: metricsStore(driver, project)` instead
81
- of `write: metricsWriter(...)`; a custom store implements `declare` and `write`, and
82
- `Measurement` no longer carries `spec`. Existing tables gain a column and a table:
83
- `ALTER TABLE metric_definition ADD COLUMN per_user INTEGER NOT NULL DEFAULT 0` and the
84
- `metric_project` table from `METRICS_SCHEMA` (one row per project, its overlaps as
85
- JSON; a 6.0 `metric_overlap` table is dropped); the first sample after the deploy
86
- declares the real flags, labels and overlaps. Ingestion credentials need add and
87
- update on every metric table and never delete: the store reconciles nothing. `readAudience` takes the schema
88
- (`readSchema(reader, project)`) instead of an overlap list and returns a row per
89
- declared per-user key. `readReport` replaces hand-rolled window + rows + audience
90
- composition. Metric keys and dimensions are storage identifiers: introduce a new key
91
- when changing meaning rather than relabelling history.
92
-
93
80
  ## Installation
94
81
 
95
82
  ```bash
@@ -100,50 +87,99 @@ pnpm add @adriangalilea/utils
100
87
 
101
88
  ### Logger
102
89
 
103
- Two renderings, one automatic decision: a TTY gets *human* (symbols, color), anything else gets *record* (level words, plain text, greppable). The same program prints `✓ synced` while you develop it and `2026-08-07T12:34:56Z INFO synced` the moment launchd points it at a file. Detection happens on the stream actually written to (stderr in Node; stdout stays clean for data).
90
+ A program's logs and traces are one stream of events. A line is an event; a span is a piece of work written as a start and an end; every line written through a span carries its id. go-utils and py-utils write the same stream byte for byte, so their output can be read, merged and traced together. The contract, its JSON schema and the harness that runs all three live in [utils](https://github.com/adriangalilea/utils).
104
91
 
105
92
  ```typescript
106
- import { wait, step, error, fail, warn, warnOnce, ready, info, success, debug, trace, scope, time, timeEnd } from '@adriangalilea/utils/log'
93
+ import { log } from '@adriangalilea/utils/log'
94
+
95
+ log.info('ready')
96
+ log.warn('retrying', { attempt: 2 })
97
+ log.error('charge failed', { err })
98
+ ```
99
+
100
+ Every verb takes a message, then optional fields. The message says what happened, the fields say with what. Fields are a plain object; anything else (an `Error`, a string, extra arguments) panics.
101
+
102
+ | verb | level | human |
103
+ |---|---|---|
104
+ | `error`, `fail` | error | `⨯` |
105
+ | `warn` | warn | `⚠` |
106
+ | `warnOnce` | warn, once per message and scope (bounded memory) | `⚠` |
107
+ | `info` | info | |
108
+ | `success` | info | `✓` |
109
+ | `wait` | info | `○` |
110
+ | `ready` | info | `▶` |
111
+ | `step` | info | `•`, indented |
112
+ | `debug` | debug | `◦` |
113
+ | `trace` | trace | `»` |
107
114
 
108
- // Verbs express outcome; each renders at its level (fail is error-level, the rest info)
109
- wait('Loading...') // ○
110
- step('resolving config') // • (indented sub-step)
111
- info('Information message')
112
- success('Operation successful') // ✓
113
- ready('Server is ready') // ▶
114
- warn('This is a warning') // ⚠
115
- error('Something went wrong') // ⨯
116
- fail('Deploy failed') // ⨯
117
- debug('Cache miss') // ◦
118
- trace('Trace message') // »
115
+ **Scopes.** `log.scope('api')` returns a child logger whose events carry `["api"]`; `log.scope('api').scope('auth')` carries `["api", "auth"]`.
119
116
 
120
- // Warn once (won't repeat same message)
121
- warnOnce('This warning appears only once')
117
+ **Spans.** A span is a logger: what is written through it belongs to it, and nothing else does (there is no ambient context, a function that logs inside a span takes the span). `span` returns what the callback returns, a Promise when it is async; a throw ends the span failed, with the error in `fields.err`, and is rethrown.
122
118
 
123
- // Timer functionality (reports at trace level)
124
- time('operation')
125
- // ... do something
126
- timeEnd('operation') // operation: 123ms
119
+ ```typescript
120
+ await log.span('sync', { repo }, async (s) => {
121
+ s.info('cloned', { files: 3 })
122
+ await s.span('build', async (c) => { c.step('compiling') })
123
+ s.set({ pushed: true }) // carried on the span's end event
124
+ })
127
125
 
128
- // Scoped logger — lines carry [api], and API_LOG_LEVEL controls it from env
129
- const api = scope('api')
130
- api.info('Request received') // [api] Request received
131
- api.scope('auth').warn('token old') // ⚠ [api auth] token old
126
+ // Work that does not fit a block: open it, end it once (twice panics).
127
+ const up = log.begin('upload', { size })
128
+ stream.on('end', () => up.end())
129
+ stream.on('error', (err) => up.end(err))
132
130
  ```
133
131
 
134
- Levels filter (`silent < error < warn < info < debug < trace`); an unknown `LOG_LEVEL` throws. A scoped logger reads `{SCOPE}_LOG_LEVEL` before `LOG_LEVEL`, live: `GZIP_LOG_LEVEL=silent` shuts one subsystem up without touching code, `GZIP_LOG_LEVEL=trace` opens it wide while the rest stays quiet.
132
+ **Traces cross processes explicitly.** `s.traceparent()` is the W3C value to hand on, in an HTTP `traceparent` header or the `TRACEPARENT` env of a child process. `log.join(req.headers.get('traceparent'))` returns a logger whose spans are children of that remote span; `null` or `undefined` returns the logger itself, and a malformed value is ignored with one warn per distinct value. A process started with `TRACEPARENT` set joins it for every root span, with no code.
135
133
 
136
- Two knobs, both env + runtime setter, both with sane defaults:
134
+ **Levels.** `silent < error < warn < info < debug < trace`, default `info`. A scoped event reads its threshold live from the most specific variable set: `["api", "auth"]` reads `API_AUTH_LOG_LEVEL`, then `API_LOG_LEVEL`, then `LOG_LEVEL`, then `setLogLevel()`, then `info`. `GZIP_LOG_LEVEL=silent` shuts one subsystem up without touching code. Whether a span is shown is decided once, when it begins; a hidden span writes no end, and what it holds (lines, child spans, `traceparent()`) attaches to the nearest shown span, so a viewer never meets a missing parent. A remote span joined through `join` or `TRACEPARENT` counts as shown. With no shown ancestor, a hidden span's lines carry no ids, as lines outside any span.
137
135
 
138
- - `LOG_FORMAT=human|record` / `setLogFormat()` — how lines render. Default: TTY → human, otherwise record. Workers and browsers use level-mapped console methods; Node funnels everything to stderr.
139
- - `LOG_TIME=1|0` / `setLogTime()` — whether lines open with time. Default: on in Node record mode (a daemon's log file must answer "when"), off everywhere else (human eyes don't need stamps; Worker and browser consoles stamp lines themselves). Record time is UTC RFC3339; human time is a dim local `HH:MM:SS`.
136
+ **Renderings.** `json` is one event per line on stderr, keys in the contract's order, fields sorted at every depth, errors as `{type, message, stack, cause, …}`:
140
137
 
141
- The record line format is identical across go-utils and py-utils by design:
138
+ ```json
139
+ {"time":"2026-10-12T12:20:39.123Z","level":"warn","verb":"warn","msg":"retrying","service":"checkout","scope":["payments"],"trace_id":"4bf92f3577b34da6a3ce929d0e0e4736","span_id":"00f067aa0ba902b7","fields":{"attempt":2}}
140
+ ```
142
141
 
142
+ `human` is symbols, colour (TTY, `NO_COLOR`, `FORCE_COLOR`), fields dim as `key=value`, an error's stack under its line, and spans with what they hold indented:
143
+
144
+ ```
145
+ ○ sync repo=ui
146
+ cloned files=3
147
+ ○ build
148
+ • compiling
149
+ ✓ build 51ms
150
+ ✓ sync 51ms pushed=true
143
151
  ```
144
- 2026-08-07T12:34:56Z WARN [scope] message
152
+
153
+ Node, Bun and Deno write every event through `console.error` (stdout stays data, and a `cli` live region keeps lines above it); browsers and Workers use the level's console method.
154
+
155
+ **Configuration.** Each variable has a setter. The setter is the program's default and the variable, read live, overrides it. Values match without regard to case, an empty variable is unset, and an unknown value panics.
156
+
157
+ | variable | setter | values | default |
158
+ |---|---|---|---|
159
+ | `LOG_FORMAT` | `setLogFormat` | `human`, `json` | Node: human when stderr is a TTY, else json. Browser: human. Worker: json |
160
+ | `LOG_TIME` | `setLogTime` | `1`, `0` | off. Puts a dim local `HH:MM:SS` before human lines; json always carries `time` |
161
+ | `LOG_LEVEL`, `{SCOPE}_LOG_LEVEL` | `setLogLevel` | the levels | `info` |
162
+ | `LOG_SERVICE` | `setLogService` | a name | the entry script's name without extension. A Worker or browser has none: set it, or the first json event panics |
163
+
164
+ **Reading the stream.** `LogEvent` is the wire shape as a type (a line, a span start, an ok end or a failed end, discriminated by `phase` and `status`), and `parseEvent(line)` reads one line back: an event, `null` for a line that is not one (pass it through), or a `Panic` for a JSON object with a `verb` key that breaks the shape.
165
+
166
+ ```typescript
167
+ import { parseEvent, type LogEvent } from '@adriangalilea/utils/log'
168
+
169
+ const e = parseEvent(line)
170
+ if (e?.phase === 'end') e.dur_ms
145
171
  ```
146
172
 
173
+ #### Migrating from 8.x
174
+
175
+ - Messages are one string plus fields: `log.warn('retry', n, err)` becomes `log.warn('retry', { attempt: n, err })`, and `log.scope(e.source).error(e.operation, e.toJSON())` becomes `log.scope(e.source).error(e.operation, { err: e })`.
176
+ - Import `log` and call its verbs: the bare `info`, `warn`, `scope`, … exports are gone.
177
+ - `time`/`timeEnd` are gone: wrap the work in `log.span(name, fn)`, which writes its duration.
178
+ - `LOG_FORMAT=record` and `setLogFormat('record')` are gone: `json` replaces the record line, and `logs` from go-utils renders a file back into a greppable line.
179
+ - The environment now wins over `setLogFormat` and `setLogTime`, and `LOG_TIME` is off by default.
180
+ - A Worker that writes json must call `setLogService(name)`.
181
+ - `SourcedError.message` is the message given, without the `[source:operation]` prefix.
182
+
147
183
  ### Currency
148
184
 
149
185
  Currency utilities with comprehensive crypto support (500+ symbols):
@@ -492,7 +528,7 @@ catch (e) {
492
528
  return { error: 'card declined' }
493
529
  }
494
530
  if (isSourcedError(e)) {
495
- log.scope(e.source).error(e.operation, e.toJSON()) // structured forensics
531
+ log.scope(e.source).error(e.operation, { err: e }) // source, operation, status, context, cause
496
532
  throw e
497
533
  }
498
534
  throw e // unknown — re-throw
@@ -503,7 +539,7 @@ Every `SourcedError` carries `source`, `operation`, `status`, `context`, and the
503
539
 
504
540
  ## Features
505
541
 
506
- - **Logger**: human/record dual rendering (TTY-detected), scoped env-controlled levels, shared record format with go-utils and py-utils
542
+ - **Logger**: one event stream shared byte for byte with go-utils and py-utils (lines and spans, W3C trace ids), json or human rendering, scoped env-controlled levels
507
543
  - **Currency**:
508
544
  - 13,750+ crypto symbols from CoinGecko (auto-updatable)
509
545
  - Alternative ticker support (XBT→BTC, wrapped tokens, etc.)
@@ -600,7 +636,7 @@ await ctx.say.answer({ en: 'OK', es: 'OK' }) // → ctx.answer (callback o
600
636
 
601
637
  ### Telegram HTML (`tg-html`)
602
638
 
603
- Transform arbitrary (LLM-emitted) HTML into Telegram-compatible `parse_mode=HTML` with opinionated, consistent spacing. Telegram's HTML subset has no headings, lists, or block layout — send it `<h1>`/`<ul>` and the API rejects the whole message. `transform()` accepts the HTML a model naturally writes and renders the structure typographically: `h1` → bold+underline + blank line, `h2…h6` → bold + blank line, `ul/li` → `• ` bullets, blocks get blank lines, blockquotes keep their internal line breaks, allowed tags pass through with attributes filtered, unknown tags drop their markup (content kept), and stray `<`/`>` escape instead of vanishing. Zero dependencies, no DOM, Worker-safe. Successor of the standalone `tghtml` package (jsr, archived).
639
+ Transform arbitrary (LLM-emitted) HTML into Telegram-compatible `parse_mode=HTML` with opinionated, consistent spacing. Telegram's HTML subset has no headings, lists, or block layout — send it `<h1>`/`<ul>` and the API rejects the whole message. `transform()` accepts the HTML a model naturally writes and renders the structure typographically: `h1` → bold+underline + blank line, `h2…h6` → bold + blank line, `ul/li` → `• ` bullets, blocks get blank lines, blockquotes keep their internal line breaks, allowed tags pass through with attributes filtered, unknown tags drop their markup (content kept), and stray `<`/`>` escape instead of vanishing. Zero dependencies, no DOM, Worker-safe.
604
640
 
605
641
  ```typescript
606
642
  import { transform } from '@adriangalilea/utils/tg-html'
@@ -626,7 +662,7 @@ Zero dependencies, Worker-safe. Typography (heading air, bullet glyph, title und
626
662
 
627
663
  ### Telegram bot plugins (GramIO)
628
664
 
629
- Plugins for personal Telegram bots built on [GramIO](https://gramio.dev). Each plugin lives at its own subpath; peer deps (`gramio`, `@gramio/storage`, `@gramio/session`, `@gramio/format`, `marked`) are **all optional** — install only what you import.
665
+ Plugins for personal Telegram bots built on [GramIO](https://gramio.dev). Each plugin lives at its own subpath; peer deps (`gramio`, `@gramio/storage`, `@gramio/session`, `@gramio/format`) are **all optional** — install only what you import.
630
666
 
631
667
  ```bash
632
668
  pnpm add @adriangalilea/utils gramio @gramio/storage @gramio/session
@@ -677,14 +713,15 @@ Cloudflare is one adapter, not the architecture: `bot/worker` and `bot/storage-d
677
713
 
678
714
  Telegram's [Threaded Mode](https://telegram.org/blog/threaded-conversations) for private chats (BotFather → Bot Settings → Threaded Mode) auto-threads out of the box: `@gramio/contexts` ≥0.9 (what gramio ≥0.12 resolves) forwards `message_thread_id` on every `ctx.send` / `ctx.sendDocument` / `ctx.reply` — including from callback handlers, which carry `threadId` too. No fork, no override.
679
715
 
680
- **Runs on Cloudflare Workers / bun / anywhere.** Every bot subpath below is import-safe off Node — no filesystem access, no `node:*` modules, no import-time side effects anywhere in its graph — **except `bot/kit`**, which deliberately owns the Node-only pieces (process signal handling in `gracefulStart`, kev-backed env reading in `adminContext`). `pnpm test:worker-safe` walks every graph and screams on regression.
716
+ **Runs on Cloudflare Workers / bun / anywhere.** Every bot subpath below is import-safe off Node — no filesystem access, no `node:*` modules, no import-time side effects anywhere in its graph — **except `bot/kit`**, whose `gracefulStart` handles process signals. `pnpm test:worker-safe` walks every graph and screams on regression.
681
717
 
682
718
  | Subpath | What it does |
683
719
  |---|---|
684
720
  | `@adriangalilea/utils/bot/ctx` | Structural ctx types (`BotMessageCtx`, `BotCallbackCtx`, …) and the `narrow<T>(ctx)` cast helper. Pure typing — no runtime state. |
685
721
  | `@adriangalilea/utils/bot/keys` | The bot-id key namespace, a **persisted contract**: `botId(ctx)`, `botStorageKey(ctx, userId)` → `bot-<id>:<userId>`, `botSubKey(ctx, sub)` → `bot-<id>:<sub>`. Every storage key the library writes derives from here, and those keys live in YOUR Redis/D1 rows — the shape never changes. Pure functions of `ctx.bot.info.id` (no env/fs), which is what keeps the whole bot surface Worker-safe. |
686
- | `@adriangalilea/utils/bot/kit` | The Node-only corner. `gracefulStart(bot, opts?)` — SIGINT/SIGTERM → `bot.stop()` → exit; force-kills if shutdown hangs. DMs the admin `@<bot> started.` / `@<bot> shutting down.` by default when `KEV.TELEGRAM_ADMIN_ID` is set (graceful only — crashes don't trigger `onStop`); pass `notifyAdmin: false` to disable or `notifyAdmin: 12345` for an explicit chat id.<br>`adminContext({ adminId? })` — reads `TELEGRAM_ADMIN_ID` from `kev` (with optional hardcoded fallback), decorates `ctx.adminId` + `ctx.isAdmin`.<br>Also re-exports `botSession` / `prefixStorage` from `bot/session` so Node consumers keep one import. |
687
- | `@adriangalilea/utils/bot/session` | `botSession(opts)` — **drop-in replacement for `@gramio/session`'s `session()`** that auto-namespaces every key as `bot-<id>:<senderId>` using `ctx.bot.info.id` (populated by `getMe()` at startup). Use this instead of `session()` — full stop. Multiple bots sharing one Redis/D1 stay isolated by construction; every plugin in this package derives the same prefix internally via `botStorageKey(ctx, userId)` / `botSubKey(ctx, sub)` (from `bot/keys`). No regex, no manual prefix argument, no way to forget.<br>`prefixStorage(storage, prefix)` — escape hatch for adding a top-level prefix on top of the bot-id namespace; almost never needed. Worker-safe. |
722
+ | `@adriangalilea/utils/bot/kit` | The Node-only corner, one function: `gracefulStart(bot, opts?)` — SIGINT/SIGTERM → `bot.stop()` → exit; force-kills if shutdown hangs; calls `bot.syncCommands()` before `bot.start()`. DMs the admin `@<bot> started.` / `@<bot> shutting down.` by default when `KEV.TELEGRAM_ADMIN_ID` is set (graceful only — crashes don't trigger `onStop`); pass `notifyAdmin: false` to disable or `notifyAdmin: 12345` for an explicit chat id. |
723
+ | `@adriangalilea/utils/bot/admin` | `adminContext(admins)` — decorates `ctx.adminId` (the primary admin: the approve/deny and notification target) and `ctx.isAdmin` (any admin) on every event. Ids arrive POSITIONALLY as a number, an array, or a FUNCTION consulted per update, so a db-backed resolver changes who is admin with no restart. Reads no env and touches no OS, which is what keeps it Worker-safe; a Node bot composes env in at the call site (`adminContext(kev.int('TELEGRAM_ADMIN_ID', 123))`). `parseIdList("123, 456 789")` turns an env string into clean ids. `accessControl` and the payments refund flow declare a gramio dependency on this plugin, so gramio throws at `bot.start()` if you forget it. |
724
+ | `@adriangalilea/utils/bot/session` | `botSession(opts)` — **drop-in replacement for `@gramio/session`'s `session()`** that auto-namespaces every key as `bot-<id>:<senderId>` using `ctx.bot.info.id` (populated by `getMe()` at startup). Use this instead of `session()` — full stop. Multiple bots sharing one Redis/D1 stay isolated by construction; every plugin in this package derives the same prefix internally via `botStorageKey(ctx, userId)` / `botSubKey(ctx, sub)` (from `bot/keys`). No regex, no manual prefix argument, no way to forget. Worker-safe. |
688
725
  | `@adriangalilea/utils/bot/notify` | Best-effort admin DMs, worker-safe (you pass the ids; no env, no process). `notifyAdmins(bot, adminIds, text, extra?)` — DM each admin, failures logged and swallowed (a notification must never take the bot down). `alertAdminError(bot, adminIds, label, error, throttle?)` — truncated `🚨 label\nName: message`, rate-limited through a caller-owned `alertThrottle(ms?)` so a failure storm sends one alert per window. `gracefulStart`'s start/stop DMs are built on this. |
689
726
  | `@adriangalilea/utils/bot/profile` | `syncBotProfile(bot, { name?, description?, about?, photo?, commands?, expects?, adminIds? })` — the bot's Telegram-facing identity as CODE, never BotFather: localized name / description / About / public command list, reconciled idempotently on every boot (get → compare → set per field per language; unchanged values cost one read, so cold-start firing is rate-limit-free). `expects: { inline: true }` declares the BotFather-only capabilities the code assumes — a mismatch (inline mode off on an inline-dependent bot) DMs the admins, since the API can only detect it, not fix it. Never throws; failures log and the bot keeps running. |
690
727
  | `@adriangalilea/utils/bot/access-control` | Personal-bot ACL — gates non-admin/non-default users; admin gets DM with `[✅ Aprobar][❌ Denegar]` on first attempt; `/access` opens a persistent menu (revoke / reapprove / list pending). Backed by `@gramio/session` per-user + a small index. **Native alternative**: BotFather → Bot Settings → Access → "Restrict bot usage" — flat allow-list at Telegram. Use this plugin when you want in-bot approval flow instead of a BotFather round-trip; both can coexist. |
@@ -699,11 +736,11 @@ Telegram's [Threaded Mode](https://telegram.org/blog/threaded-conversations) for
699
736
  | `@adriangalilea/utils/bot/coalesce` | Joins client-split inbound messages back into one. When a user pastes >4096 chars, Telegram clients fragment it into separate `message` updates with no marker. Middleware detects the burst and emits one combined event. |
700
737
  | `@adriangalilea/utils/llm` | Multi-provider LLM caller on the Vercel AI SDK — the policy layer the SDK doesn't ship. `createLlm({ providers, health? })` → `.stream(req)` (typed events: `delta` / `reasoning` / `reset` / `tool-call` / `end`) and `.complete(req)`. **Failover:** priority-ordered across OpenAI-compatible, Anthropic-compatible, and OpenRouter endpoints; multiple keys per provider (health tracked per key fingerprint); per-key circuit breaker with exponential cool-down persisted in any KV-shaped `HealthStore`; an attempt that dies after emitting yields `reset` (discard, next candidate regenerates); empty completions count as failures. **Accounting:** tokens summed across billed attempts; `costUsd` is the ACTUAL charge (OpenRouter usage accounting via `providerMetadata`, or a `cost` field on the raw usage frame) — never a price-table estimate. **Knobs:** per-model temperature, maxTokens caps, `disableThinking` mapped to each dialect. **Tools:** AI SDK `tool()` + `toolChoice` pass through (re-exported). Worker-safe; peers: `ai`, `@ai-sdk/openai-compatible`, `@ai-sdk/anthropic`, `@openrouter/ai-sdk-provider`. |
701
738
  | `@adriangalilea/utils/bot/llm` | The Telegram side of an LLM chatbot; the model side is `@adriangalilea/utils/llm`. **Output:** `streamChatReply(ctx, events, opts?)` consumes an `AsyncIterable<LlmStreamEvent>` and paints it with Telegram's native message-draft streaming (`sendMessageDraft` full-frame repaints, throttled ~1/s, keepalive under the ~30s draft TTL), then persists the finished markdown via `ctx.send`, entity-split across 4096 by `@gramio/split`. Reasoning models get a thinking phase — streams into the ephemeral draft and evaporates (`reasoning: 'preview'`, default), persists as an expandable blockquote (`'message'`), or never renders at all (`'hidden'`). Upstream `reset` (provider failover) repaints the draft from scratch. Drafts are private-chat-only (+ BotFather forum-topic mode); elsewhere the preview phase is skipped and only the final send happens. Returns `{ content, reasoning, toolCalls, usage, messages }`. The painter underneath is `@adriangalilea/utils/bot/draft` (`createDraftPreview(ctx, { render, throttleMs? })`): dependency-light (no markdown machinery), for producers that push deltas or own their per-frame rendering (plain or rich-message frames) and persist path, with `streamForensics` (render-regression + reset warnings) built in and exported standalone. **History:** `llmHistory({...})` returns `.plugin` (decorates `ctx.llm` with `.add() / .get() / .clear() / .all() / .clearAll()`, per-(user, thread) OpenAI `ChatMessage` shape, persisted in the shared session record so the menu's 🗑 Forget wipes it automatically) AND `.menuItem` (drop-in "🗑 Delete this thread" for `botMenu` — wipes the LLM history AND calls `deleteForumTopic` so the Telegram thread + all its messages disappear from the chat; falls back to history-only clear when no `threadId` is present). |
702
- | `@adriangalilea/utils/metrics` | Declared counters and timings, bounded dimensions, optional actor IDs, and a schema for generic reporting. Inject a writer; measurement failures are reported without rejecting calls. See Product metrics below. |
703
- | `@adriangalilea/utils/metrics/sqlite` | Atomic SQLite/libSQL writer, explicit provisioning schema, and inclusive UTC daily reports. Connections and credentials belong to the application. |
739
+ | `@adriangalilea/utils/metrics` | Declared counters and timings, bounded dimensions, optional actor IDs, and a schema for generic reporting. Inject a store (`store: metricsStore(driver, project)`); measurement failures are reported without rejecting calls. See Product metrics below. |
740
+ | `@adriangalilea/utils/metrics/sqlite` | Atomic SQLite driver, explicit provisioning schema, and inclusive UTC daily reports; libSQL is its own module (`metrics/libsql`) over the same driver shape. Connections and credentials belong to the application. |
704
741
  | `@adriangalilea/utils/bot/menu` | `botMenu({ command, description, items, privacy?, personalData?, adminContact })` — `/settings` command + InlineKeyboard router. Root view always renders a `🛡️ Privacy & data` submenu button that wraps the privacy policy link plus (if `personalData: { storage }`) 🗑 Forget + 📥 Export buttons. Items take `keepRow` (render on the same row as the next item — e.g. a two-per-row language picker) and `rootExtra` (render at the bottom of the root menu, below Privacy & data). `label` / `header` / `style` / `visible` resolvers may be **async** (read your db at render time — never cache render strings in the session); `parseMode: 'HTML'` renders the header formatted (you own escaping); `personalData.onForget(ctx, userId)` wipes YOUR tables inside the same try as the session delete, so Forget either forgets everything or reports failure. `toggleMenuItem({ id, read, write, label: { off, on }, toast? })` — convenience factory for boolean-toggle items with dynamic label + optional toast, storage-agnostic via `read`/`write` closures. |
705
- | `@adriangalilea/utils/bot/payments` | `botPayments({ session, storage, paysupport, paysupportHint?, legal, waiver, vip?, credits?, perks? })` — Telegram Stars monetization in one drop-in plugin. **Three axes, all optional:** `vip` (positional tier ladder — single rung in v1 is just `vip: [{...}]`, ladder is `vip: [{...}, {...}]`; ids are `vip.1`, `vip.2`, …), `credits` (consumable balance + top-up packs `credits.1`, `credits.2`, …), `perks` (orthogonal one-shot unlocks `perks.<key>`). **Surface:** `ctx.payments.atLeast('vip')` / `atLeast('vip.2')` (typed rank check), `ctx.payments.tier()` / `.tier.level()` / `.tier.label()`, `ctx.payments.credits.{balance, consume, tryConsume}` (throws `InsufficientCredits`), `ctx.payments.has(perkId)`, `await ctx.payments.require('vip', { feature? })` (gate that sends a localized upgrade prompt deep-linked to `/settings → 💎 VIP`), `await ctx.payments.invoice(productKey)` (threads Art. 103(m) TRLGDCU consent inline before `sendInvoice`). **Owns:** waiver consent flow (versioned text → forces re-consent on bump, snapshotted on every charge for audit), `/paysupport` slash command (Telegram ToS §6.5; `paysupportHint` overrides the where-to-manage-charges line when your menu isn't `/settings`), idempotent `successful_payment` fulfillment via `pay:idempotency:{chargeId}` sentinel, lazy subscription expiry (no cron needed), tier upgrade auto-cancel of the lower rung's renewal, and admin-DM refund approval (mirror of `accessControl`'s [✅ Aprobar][❌ Denegar] pattern). **Returns:** `{ plugin, menuItem, payouts, onFulfilled }` — `menuItem` is the drop-in `💎 VIP` entry for `botMenu`; `payouts.{record, list, export, exportForUsers}` is the Fragment payout ledger (you receive TON, log the EUR conversion, export time-windowed CSV/JSON for your gestor); `onFulfilled(productKey \| '*', handler)` / `onRefunded(...)` register fire-and-forget hooks (purchase applied / admin-approved refund — a revenue ledger writes on one, reverses on the other). **Stars-only by design** — Telegram ToS §6.2 forbids third-party payment providers for digital goods. Crypto Pay deferred (MiCA risk); Stripe-outside-Telegram is a future v2 channel. Full compliance memo (Spanish-autónomo seller-of-record analysis, Verifactu vs Crea y Crece, MiCA, Art. 103(m) waiver text, GDPR retention) in `src/bot/payments/CLAUDE.md`. |
706
- | `@adriangalilea/utils/bot/create` | `createBot<S>({ token?, storage?, initial?, admins?, language?, menu?, access?, payments?, handlers?, worker? })` — the composer (see "One bot file, ideation → production" above). Returns `{ build, session, poll, isMain, fetch }`: `poll()` long-polls, `export default app` is a complete Worker, `app.session(ctx)` / the `handlers` callback's `session(ctx)` is the TYPED accessor for your `S` fields. Owns storage+session wiring; resolves storage per environment (D1 binding → `bot/storage-d1`; `BOT_PERSIST` path → sqlite, `redis://` → redis, lazily-imported optional peers; else announced-ephemeral memory) — or pass `storage: (env) => Storage` when the choice is env-dependent (e.g. a D1 binding not named `DB`). Boot NARRATES the composition (`session: memory …`, `features: language(en,es) · menu(/settings)`) so every implicit decision is visible where you're looking. Runnable demo: `pnpm demo:bot`. |
742
+ | `@adriangalilea/utils/bot/payments` | `botPayments({ session, storage, paysupport, paysupportHint?, legal, waiver, vip?, credits?, perks? })` — Telegram Stars monetization in one drop-in plugin. **Three axes, all optional:** `vip` (positional tier ladder — single rung in v1 is just `vip: [{...}]`, ladder is `vip: [{...}, {...}]`; ids are `vip.1`, `vip.2`, …), `credits` (consumable balance + top-up packs `credits.1`, `credits.2`, …), `perks` (orthogonal one-shot unlocks `perks.<key>`). **Surface:** `ctx.payments.atLeast('vip')` / `atLeast('vip.2')` (typed rank check), `ctx.payments.tier()` / `.tier.level()` / `.tier.label()`, `ctx.payments.credits.{balance, consume, tryConsume}` (throws `InsufficientCredits`), `ctx.payments.has(perkId)`, `await ctx.payments.require('vip', { feature? })` (gate that sends a localized upgrade prompt deep-linked to `/settings → 💎 VIP`), `await ctx.payments.invoice(productKey)` (threads Art. 103(m) TRLGDCU consent inline before `sendInvoice`). **Owns:** waiver consent flow (versioned text → forces re-consent on bump, snapshotted on every charge for audit), `/paysupport` slash command (Telegram ToS §6.5; `paysupportHint` overrides the where-to-manage-charges line when your menu isn't `/settings`), idempotent `successful_payment` fulfillment via `pay:idempotency:{chargeId}` sentinel, lazy subscription expiry (no cron needed), tier upgrade auto-cancel of the lower rung's renewal, and admin-DM refund approval (mirror of `accessControl`'s [✅ Aprobar][❌ Denegar] pattern). **Returns:** `{ plugin, menuItem, payouts, admin, onFulfilled, onRefunded }` — `menuItem` is the drop-in `💎 VIP` entry for `botMenu`; `admin.{listCharges, getCharge}` backs custom admin commands; `payouts.{record, list, export, exportForUsers}` is the Fragment payout ledger (you receive TON, log the EUR conversion, export time-windowed CSV/JSON for your gestor); `onFulfilled(productKey \| '*', handler)` / `onRefunded(...)` register fire-and-forget hooks (purchase applied / admin-approved refund — a revenue ledger writes on one, reverses on the other). **Stars-only by design** — Telegram ToS §6.2 forbids third-party payment providers for digital goods. Crypto Pay deferred (MiCA risk); Stripe-outside-Telegram is a future v2 channel. Full compliance memo (Spanish-autónomo seller-of-record analysis, Verifactu vs Crea y Crece, MiCA, Art. 103(m) waiver text, GDPR retention) in `src/bot/payments/CLAUDE.md`. |
743
+ | `@adriangalilea/utils/bot/create` | `createBot<S>({ token?, storage?, initial?, admins?, language?, menu?, access?, payments?, handlers?, worker? })` — the composer (see "One bot file, ideation → production" above). Returns `{ build, session, poll, isMain, fetch }`: `poll()` long-polls, `export default app` is a complete Worker, `app.session(ctx)` / the `handlers` callback's `session(ctx)` is the TYPED accessor for your `S` fields. Owns storage+session wiring; resolves storage per environment (D1 binding → `bot/storage-d1`; `BOT_PERSIST` path → sqlite, `redis://` → redis, lazily-imported optional peers; else announced-ephemeral memory) — or pass `storage: (env) => Storage` when the choice is env-dependent (e.g. a D1 binding not named `DB`). Boot NARRATES the composition (`session storage kind="memory (ephemeral …)"`, `features features=["language(en,es)","menu(/settings)"]`) so every implicit decision is visible where you're looking. Runnable demo: `pnpm demo:bot`. |
707
744
  | `@adriangalilea/utils/bot/worker` | `botWorkerFetch(resolve)` — the Cloudflare Worker cap: secret-checked webhook (ack fast, work + storage flush ride `ctx.waitUntil`, errors DM admins throttled), `POST /setup` (webhook registration with handler-derived `allowed_updates` + a commit-narrating 🚀 deploy DM from the request body), `POST /deploy-started` (the 🛳 "what is shipping" DM — curl it from your deploy script on the still-live version, body `{sha, author, message, etaSeconds?}`), operator-authed `/pause` `/resume` `/webhook-status` (+ `statusExtra`), and a `routes` escape hatch tried before the built-ins. Structural bot type — no workers-types dependency. |
708
745
  | `@adriangalilea/utils/bot/flags` | `defineFlags(spec, { read, write? })` — feature flags declared ONCE in code, resolved LIVE from the bot's operator-config record (D1 json row, Redis hash — storage-agnostic, Worker-safe, zero deps). Each flag: `{ kind: 'bool'\|'number'\|'string', label, help?, default }`, where `default` is a scalar or a **tier map** `{ free, vip?, 'vip.N'? }` resolved off `ctx.payments.tier()` — "free users get this limit, premium users get that model" is one declaration, zero branching at the call site (ladder walks down: exact rung → lower rungs → `vip` → `free`). Read sites: `await flags.<key>(ctx)` — stored override wins over the code default, per-ctx reads coalesce into one config fetch. `flags.describe()` is the JSON schema admin panels render generically, so a new flag appears in every panel with zero panel edits; `flags.set(ctx, key, value)` writes a kind-checked live override (`null` clears it — RFC 7386 null-delete, matching SQLite `json_patch`); `flags.overrides(ctx)` lists what's overridden. Corrupt stored values scream (`SourcedError`); bad specs panic at construction. |
709
746
  | `@adriangalilea/utils/bot/storage-d1` | `d1Storage({ db, table? })` — `@gramio/storage` adapter over a D1 `session` table (schema in the module doc). `flush()` matters: the session plugin writes un-awaited and workerd freezes the isolate the instant `fetch()` returns — hand `flush` to `bot/worker` (automatic via `createBot`) so writes survive. |
@@ -713,11 +750,13 @@ Standard wiring:
713
750
  ```typescript
714
751
  import { Bot } from 'gramio'
715
752
  import { redisStorage } from '@gramio/storage-redis'
716
- import { adminContext, gracefulStart } from '@adriangalilea/utils/bot/kit'
753
+ import { adminContext } from '@adriangalilea/utils/bot/admin'
754
+ import { gracefulStart } from '@adriangalilea/utils/bot/kit'
717
755
  import { botSession } from '@adriangalilea/utils/bot/session'
718
756
  import { accessControl } from '@adriangalilea/utils/bot/access-control'
719
757
  import { createLlm } from '@adriangalilea/utils/llm'
720
758
  import { streamChatReply, llmHistory, toModelMessages } from '@adriangalilea/utils/bot/llm'
759
+ import { kev } from '@adriangalilea/utils'
721
760
 
722
761
  // Raw redis is fine — bot-id namespacing happens inside botSession +
723
762
  // every plugin via ctx.bot.info.id. Multiple bots sharing this Redis
@@ -733,7 +772,7 @@ const llm = createLlm({
733
772
  })
734
773
 
735
774
  const bot = new Bot(process.env.BOT_TOKEN!)
736
- .extend(adminContext({ adminId: 190202471 })) // KEV.TELEGRAM_ADMIN_ID overrides
775
+ .extend(adminContext(kev.int('TELEGRAM_ADMIN_ID', 190202471))) // or a live resolver
737
776
  .extend(userSession)
738
777
  .extend(accessControl({ session: userSession, storage, defaults: [] }))
739
778
  .extend(chat.plugin)
@@ -138,6 +138,7 @@
138
138
  import type { session } from "@gramio/session";
139
139
  import type { Storage } from "@gramio/storage";
140
140
  import { type AnyBot, type DeriveDefinitions, Plugin } from "gramio";
141
+ import { type LangSession } from "./lang.js";
141
142
  export type AccessStatus = "unknown" | "pending" | "approved" | "denied";
142
143
  export type AccessUser = {
143
144
  id: number;
@@ -230,8 +231,7 @@ export type AccessInfo = {
230
231
  */
231
232
  type SessionLike = {
232
233
  access?: AccessRecord;
233
- language?: string;
234
- };
234
+ } & LangSession;
235
235
  /** @internal — kept unexported so it doesn't clash with peers' refs. */
236
236
  type AcSessionPluginRef = ReturnType<typeof session<SessionLike, "session">>;
237
237
  export type AccessControlOptions = {
@@ -312,147 +312,205 @@ export declare const accessControl: (opts: AccessControlOptions) => Plugin<Recor
312
312
  global: AdminDerives & AccessDerives & SessionDerives;
313
313
  } & {
314
314
  message: {
315
- session: SessionLike & {
315
+ session: {
316
+ access?: AccessRecord;
317
+ } & LangSession & {
316
318
  $clear: () => Promise<void>;
317
319
  };
318
320
  };
319
321
  channel_post: {
320
- session: SessionLike & {
322
+ session: {
323
+ access?: AccessRecord;
324
+ } & LangSession & {
321
325
  $clear: () => Promise<void>;
322
326
  };
323
327
  };
324
328
  inline_query: {
325
- session: SessionLike & {
329
+ session: {
330
+ access?: AccessRecord;
331
+ } & LangSession & {
326
332
  $clear: () => Promise<void>;
327
333
  };
328
334
  };
329
335
  chosen_inline_result: {
330
- session: SessionLike & {
336
+ session: {
337
+ access?: AccessRecord;
338
+ } & LangSession & {
331
339
  $clear: () => Promise<void>;
332
340
  };
333
341
  };
334
342
  callback_query: {
335
- session: SessionLike & {
343
+ session: {
344
+ access?: AccessRecord;
345
+ } & LangSession & {
336
346
  $clear: () => Promise<void>;
337
347
  };
338
348
  };
339
349
  shipping_query: {
340
- session: SessionLike & {
350
+ session: {
351
+ access?: AccessRecord;
352
+ } & LangSession & {
341
353
  $clear: () => Promise<void>;
342
354
  };
343
355
  };
344
356
  pre_checkout_query: {
345
- session: SessionLike & {
357
+ session: {
358
+ access?: AccessRecord;
359
+ } & LangSession & {
346
360
  $clear: () => Promise<void>;
347
361
  };
348
362
  };
349
363
  poll_answer: {
350
- session: SessionLike & {
364
+ session: {
365
+ access?: AccessRecord;
366
+ } & LangSession & {
351
367
  $clear: () => Promise<void>;
352
368
  };
353
369
  };
354
370
  chat_join_request: {
355
- session: SessionLike & {
371
+ session: {
372
+ access?: AccessRecord;
373
+ } & LangSession & {
356
374
  $clear: () => Promise<void>;
357
375
  };
358
376
  };
359
377
  new_chat_members: {
360
- session: SessionLike & {
378
+ session: {
379
+ access?: AccessRecord;
380
+ } & LangSession & {
361
381
  $clear: () => Promise<void>;
362
382
  };
363
383
  };
364
384
  new_chat_title: {
365
- session: SessionLike & {
385
+ session: {
386
+ access?: AccessRecord;
387
+ } & LangSession & {
366
388
  $clear: () => Promise<void>;
367
389
  };
368
390
  };
369
391
  new_chat_photo: {
370
- session: SessionLike & {
392
+ session: {
393
+ access?: AccessRecord;
394
+ } & LangSession & {
371
395
  $clear: () => Promise<void>;
372
396
  };
373
397
  };
374
398
  delete_chat_photo: {
375
- session: SessionLike & {
399
+ session: {
400
+ access?: AccessRecord;
401
+ } & LangSession & {
376
402
  $clear: () => Promise<void>;
377
403
  };
378
404
  };
379
405
  group_chat_created: {
380
- session: SessionLike & {
406
+ session: {
407
+ access?: AccessRecord;
408
+ } & LangSession & {
381
409
  $clear: () => Promise<void>;
382
410
  };
383
411
  };
384
412
  message_auto_delete_timer_changed: {
385
- session: SessionLike & {
413
+ session: {
414
+ access?: AccessRecord;
415
+ } & LangSession & {
386
416
  $clear: () => Promise<void>;
387
417
  };
388
418
  };
389
419
  migrate_to_chat_id: {
390
- session: SessionLike & {
420
+ session: {
421
+ access?: AccessRecord;
422
+ } & LangSession & {
391
423
  $clear: () => Promise<void>;
392
424
  };
393
425
  };
394
426
  migrate_from_chat_id: {
395
- session: SessionLike & {
427
+ session: {
428
+ access?: AccessRecord;
429
+ } & LangSession & {
396
430
  $clear: () => Promise<void>;
397
431
  };
398
432
  };
399
433
  pinned_message: {
400
- session: SessionLike & {
434
+ session: {
435
+ access?: AccessRecord;
436
+ } & LangSession & {
401
437
  $clear: () => Promise<void>;
402
438
  };
403
439
  };
404
440
  invoice: {
405
- session: SessionLike & {
441
+ session: {
442
+ access?: AccessRecord;
443
+ } & LangSession & {
406
444
  $clear: () => Promise<void>;
407
445
  };
408
446
  };
409
447
  successful_payment: {
410
- session: SessionLike & {
448
+ session: {
449
+ access?: AccessRecord;
450
+ } & LangSession & {
411
451
  $clear: () => Promise<void>;
412
452
  };
413
453
  };
414
454
  chat_shared: {
415
- session: SessionLike & {
455
+ session: {
456
+ access?: AccessRecord;
457
+ } & LangSession & {
416
458
  $clear: () => Promise<void>;
417
459
  };
418
460
  };
419
461
  proximity_alert_triggered: {
420
- session: SessionLike & {
462
+ session: {
463
+ access?: AccessRecord;
464
+ } & LangSession & {
421
465
  $clear: () => Promise<void>;
422
466
  };
423
467
  };
424
468
  video_chat_scheduled: {
425
- session: SessionLike & {
469
+ session: {
470
+ access?: AccessRecord;
471
+ } & LangSession & {
426
472
  $clear: () => Promise<void>;
427
473
  };
428
474
  };
429
475
  video_chat_started: {
430
- session: SessionLike & {
476
+ session: {
477
+ access?: AccessRecord;
478
+ } & LangSession & {
431
479
  $clear: () => Promise<void>;
432
480
  };
433
481
  };
434
482
  video_chat_ended: {
435
- session: SessionLike & {
483
+ session: {
484
+ access?: AccessRecord;
485
+ } & LangSession & {
436
486
  $clear: () => Promise<void>;
437
487
  };
438
488
  };
439
489
  video_chat_participants_invited: {
440
- session: SessionLike & {
490
+ session: {
491
+ access?: AccessRecord;
492
+ } & LangSession & {
441
493
  $clear: () => Promise<void>;
442
494
  };
443
495
  };
444
496
  web_app_data: {
445
- session: SessionLike & {
497
+ session: {
498
+ access?: AccessRecord;
499
+ } & LangSession & {
446
500
  $clear: () => Promise<void>;
447
501
  };
448
502
  };
449
503
  location: {
450
- session: SessionLike & {
504
+ session: {
505
+ access?: AccessRecord;
506
+ } & LangSession & {
451
507
  $clear: () => Promise<void>;
452
508
  };
453
509
  };
454
510
  passport_data: {
455
- session: SessionLike & {
511
+ session: {
512
+ access?: AccessRecord;
513
+ } & LangSession & {
456
514
  $clear: () => Promise<void>;
457
515
  };
458
516
  };
@@ -1 +1 @@
1
- {"version":3,"file":"access-control.d.ts","sourceRoot":"","sources":["../../src/bot/access-control.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"access-control.d.ts","sourceRoot":"","sources":["../../src/bot/access-control.ts"],"names":[],"mappings":"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"}