@adriangalilea/utils 0.18.0 → 0.20.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 (101) hide show
  1. package/README.md +3 -1
  2. package/dist/bot/access-control.d.ts.map +1 -1
  3. package/dist/bot/access-control.js +17 -1
  4. package/dist/bot/access-control.js.map +1 -1
  5. package/dist/bot/allow-list.d.ts +38 -0
  6. package/dist/bot/allow-list.d.ts.map +1 -0
  7. package/dist/bot/allow-list.js +41 -0
  8. package/dist/bot/allow-list.js.map +1 -0
  9. package/dist/bot/callbacks.d.ts +89 -0
  10. package/dist/bot/callbacks.d.ts.map +1 -0
  11. package/dist/bot/callbacks.js +128 -0
  12. package/dist/bot/callbacks.js.map +1 -0
  13. package/dist/bot/ctx.d.ts +219 -0
  14. package/dist/bot/ctx.d.ts.map +1 -0
  15. package/dist/bot/ctx.js +52 -0
  16. package/dist/bot/ctx.js.map +1 -0
  17. package/dist/bot/index.d.ts +4 -0
  18. package/dist/bot/index.d.ts.map +1 -1
  19. package/dist/bot/index.js +4 -0
  20. package/dist/bot/index.js.map +1 -1
  21. package/dist/bot/kit.d.ts +1 -1
  22. package/dist/bot/kit.d.ts.map +1 -1
  23. package/dist/bot/kit.js +4 -2
  24. package/dist/bot/kit.js.map +1 -1
  25. package/dist/bot/menu.d.ts +23 -1
  26. package/dist/bot/menu.d.ts.map +1 -1
  27. package/dist/bot/menu.js +30 -1
  28. package/dist/bot/menu.js.map +1 -1
  29. package/dist/bot/payments/callbacks.d.ts +28 -0
  30. package/dist/bot/payments/callbacks.d.ts.map +1 -0
  31. package/dist/bot/payments/callbacks.js +102 -0
  32. package/dist/bot/payments/callbacks.js.map +1 -0
  33. package/dist/bot/payments/commands.d.ts +26 -0
  34. package/dist/bot/payments/commands.d.ts.map +1 -0
  35. package/dist/bot/payments/commands.js +41 -0
  36. package/dist/bot/payments/commands.js.map +1 -0
  37. package/dist/bot/payments/config.d.ts +30 -0
  38. package/dist/bot/payments/config.d.ts.map +1 -0
  39. package/dist/bot/payments/config.js +166 -0
  40. package/dist/bot/payments/config.js.map +1 -0
  41. package/dist/bot/payments/derive.d.ts +75 -0
  42. package/dist/bot/payments/derive.d.ts.map +1 -0
  43. package/dist/bot/payments/derive.js +151 -0
  44. package/dist/bot/payments/derive.js.map +1 -0
  45. package/dist/bot/payments/handlers.d.ts +113 -0
  46. package/dist/bot/payments/handlers.d.ts.map +1 -0
  47. package/dist/bot/payments/handlers.js +284 -0
  48. package/dist/bot/payments/handlers.js.map +1 -0
  49. package/dist/bot/payments/index.d.ts +17 -0
  50. package/dist/bot/payments/index.d.ts.map +1 -0
  51. package/dist/bot/payments/index.js +14 -0
  52. package/dist/bot/payments/index.js.map +1 -0
  53. package/dist/bot/payments/invoice.d.ts +101 -0
  54. package/dist/bot/payments/invoice.d.ts.map +1 -0
  55. package/dist/bot/payments/invoice.js +213 -0
  56. package/dist/bot/payments/invoice.js.map +1 -0
  57. package/dist/bot/payments/menu-item.d.ts +41 -0
  58. package/dist/bot/payments/menu-item.d.ts.map +1 -0
  59. package/dist/bot/payments/menu-item.js +259 -0
  60. package/dist/bot/payments/menu-item.js.map +1 -0
  61. package/dist/bot/payments/payload.d.ts +42 -0
  62. package/dist/bot/payments/payload.d.ts.map +1 -0
  63. package/dist/bot/payments/payload.js +69 -0
  64. package/dist/bot/payments/payload.js.map +1 -0
  65. package/dist/bot/payments/payouts.d.ts +88 -0
  66. package/dist/bot/payments/payouts.d.ts.map +1 -0
  67. package/dist/bot/payments/payouts.js +133 -0
  68. package/dist/bot/payments/payouts.js.map +1 -0
  69. package/dist/bot/payments/plugin.d.ts +271 -0
  70. package/dist/bot/payments/plugin.d.ts.map +1 -0
  71. package/dist/bot/payments/plugin.js +141 -0
  72. package/dist/bot/payments/plugin.js.map +1 -0
  73. package/dist/bot/payments/refund.d.ts +124 -0
  74. package/dist/bot/payments/refund.d.ts.map +1 -0
  75. package/dist/bot/payments/refund.js +308 -0
  76. package/dist/bot/payments/refund.js.map +1 -0
  77. package/dist/bot/payments/schemas.d.ts +58 -0
  78. package/dist/bot/payments/schemas.d.ts.map +1 -0
  79. package/dist/bot/payments/schemas.js +55 -0
  80. package/dist/bot/payments/schemas.js.map +1 -0
  81. package/dist/bot/payments/state.d.ts +90 -0
  82. package/dist/bot/payments/state.d.ts.map +1 -0
  83. package/dist/bot/payments/state.js +149 -0
  84. package/dist/bot/payments/state.js.map +1 -0
  85. package/dist/bot/payments/stores.d.ts +44 -0
  86. package/dist/bot/payments/stores.d.ts.map +1 -0
  87. package/dist/bot/payments/stores.js +39 -0
  88. package/dist/bot/payments/stores.js.map +1 -0
  89. package/dist/bot/payments/types.d.ts +299 -0
  90. package/dist/bot/payments/types.d.ts.map +1 -0
  91. package/dist/bot/payments/types.js +39 -0
  92. package/dist/bot/payments/types.js.map +1 -0
  93. package/dist/bot/payments/waiver.d.ts +81 -0
  94. package/dist/bot/payments/waiver.d.ts.map +1 -0
  95. package/dist/bot/payments/waiver.js +145 -0
  96. package/dist/bot/payments/waiver.js.map +1 -0
  97. package/dist/bot/storage.d.ts +154 -0
  98. package/dist/bot/storage.d.ts.map +1 -0
  99. package/dist/bot/storage.js +222 -0
  100. package/dist/bot/storage.js.map +1 -0
  101. package/package.json +28 -3
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Typed storage abstraction for `bot/*` plugins.
3
+ *
4
+ * Replaces the per-plugin `botSubKey + storage.get/set` boilerplate
5
+ * with three small primitives that automatically:
6
+ *
7
+ * - **Namespace by bot id.** Every key gets `bot-<id>:` prefixed via
8
+ * `ctx.bot.info.id`, so multiple bots sharing one Redis stay
9
+ * isolated by construction (see `bot/CLAUDE.md` § Multi-bot
10
+ * isolation). No plugin has to remember to wrap.
11
+ *
12
+ * - **Validate on read** (optional). Pass any `{ parse(data:
13
+ * unknown): T }` validator (zod / valibot / arktype / yours) and a
14
+ * mis-shaped record from storage throws a clear `SourcedError`
15
+ * instead of NaN-ing downstream. Omit the validator and you get
16
+ * the same untyped read as before.
17
+ *
18
+ * - **Stay typed end-to-end.** `botRecord<ChargeRecord>(...)` returns
19
+ * a `BotRecord<ChargeRecord>` and `.get()` is `Promise<ChargeRecord
20
+ * | undefined>`. No casts at the call site.
21
+ *
22
+ * ## Three primitives
23
+ *
24
+ * - `botRecord<T>(storage, prefix, validator?)` — get/set/delete a
25
+ * single typed value keyed by an id (`pay:charge:<chargeId>`,
26
+ * `ac:user:<userId>`, …). Most plugin state.
27
+ *
28
+ * - `botIndex(storage, prefix, opts?)` — a capped, prepend-friendly
29
+ * `string[]` stored under a single key. Use for "recent N
30
+ * charges", "pending access requests", etc.
31
+ *
32
+ * - `botSentinel(storage, prefix)` — a presence flag (`"1"` / absent)
33
+ * for idempotency. `claim(ctx, id)` returns `true` only on first
34
+ * call; subsequent calls return `false` without setting. Note:
35
+ * read-then-write is NOT atomic under concurrent load; for
36
+ * production we'd want a storage backend with `setNX` semantics.
37
+ *
38
+ * ## Why not couple to zod?
39
+ *
40
+ * The validator parameter is typed as `{ parse(data: unknown): T }` —
41
+ * a structural shape that zod schemas, valibot, ArkType, and hand-rolled
42
+ * type guards all satisfy. The library has zero zod imports; consumers
43
+ * bring whichever validator they prefer.
44
+ */
45
+ import type { Storage } from "@gramio/storage";
46
+ import type { BotIdCtx } from "./ctx.js";
47
+ /**
48
+ * Anything with a synchronous `parse(unknown): T`. zod schemas satisfy
49
+ * this natively (`.parse` throws on invalid input). Custom guards work
50
+ * the same way: `{ parse: (d) => { if (!ok(d)) throw new Error(...); return d as T } }`.
51
+ */
52
+ export type RecordValidator<T> = {
53
+ parse: (data: unknown) => T;
54
+ };
55
+ export type BotRecord<T> = {
56
+ /** Storage key for `id` under this record's prefix. */
57
+ keyFor: (ctx: BotIdCtx, id: string) => string;
58
+ /** `undefined` on miss; throws `SourcedError` on validator failure. */
59
+ get: (ctx: BotIdCtx, id: string) => Promise<T | undefined>;
60
+ /** Overwrites unconditionally. Use `botSentinel` if you need set-if-absent. */
61
+ set: (ctx: BotIdCtx, id: string, value: T) => Promise<void>;
62
+ /** Hard delete. No-op if absent. */
63
+ delete: (ctx: BotIdCtx, id: string) => Promise<void>;
64
+ /** `true` if a record exists at `id` (no read, no validation). */
65
+ has: (ctx: BotIdCtx, id: string) => Promise<boolean>;
66
+ };
67
+ /**
68
+ * Typed record store, auto-namespaced by bot id.
69
+ *
70
+ * @example per-charge record with zod validation
71
+ *
72
+ * const charges = botRecord<ChargeRecord>(
73
+ * storage,
74
+ * 'pay:charge',
75
+ * ChargeRecordSchema, // any zod.ZodType<ChargeRecord>
76
+ * )
77
+ * await charges.set(ctx, chargeId, charge)
78
+ * const c = await charges.get(ctx, chargeId)
79
+ *
80
+ * @example without a validator (backwards-compatible behaviour)
81
+ *
82
+ * const charges = botRecord<ChargeRecord>(storage, 'pay:charge')
83
+ * const c = await charges.get(ctx, chargeId) // typed but unchecked
84
+ */
85
+ export declare const botRecord: <T>(storage: Storage, prefix: string, validator?: RecordValidator<T>) => BotRecord<T>;
86
+ export type BotIndexOptions = {
87
+ /**
88
+ * Maximum number of ids retained. New `prepend` calls past this
89
+ * limit drop the OLDEST id. Omit for an unbounded index (a
90
+ * footgun for high-volume bots — set a cap).
91
+ */
92
+ capacity?: number;
93
+ };
94
+ export type BotIndex = {
95
+ keyFor: (ctx: BotIdCtx) => string;
96
+ /** Newest-first list of ids, capped to `capacity` if configured. */
97
+ list: (ctx: BotIdCtx) => Promise<string[]>;
98
+ /** Insert `id` at the front. Idempotent: re-prepending an existing
99
+ * id moves it to the front. Returns the new length post-cap. */
100
+ prepend: (ctx: BotIdCtx, id: string) => Promise<number>;
101
+ /** Remove `id`. No-op if absent. Returns whether anything was removed. */
102
+ remove: (ctx: BotIdCtx, id: string) => Promise<boolean>;
103
+ /** Wipe the whole index. */
104
+ clear: (ctx: BotIdCtx) => Promise<void>;
105
+ };
106
+ /**
107
+ * Newest-first capped list of ids stored under a single key. Designed
108
+ * for "user's last N charges", "pending access requests", "recent
109
+ * payouts" — small, ordered, bounded.
110
+ *
111
+ * `prepend(id)` is the only mutator. Re-prepending an existing id moves
112
+ * it to the front (de-duplication). At-cap inserts drop the tail.
113
+ */
114
+ export declare const botIndex: (storage: Storage, prefix: string, opts?: BotIndexOptions) => BotIndex;
115
+ export type BotSentinel = {
116
+ keyFor: (ctx: BotIdCtx, id: string) => string;
117
+ /**
118
+ * Attempt to claim `id`. Returns `true` on first claim (and
119
+ * persists), `false` if already claimed (no write).
120
+ *
121
+ * NOT atomic under concurrent load — the read and write happen as
122
+ * two ops. For at-most-once semantics under contention, swap the
123
+ * storage backend for one with `setNX` (e.g. `@gramio/storage-redis`
124
+ * with a `setIfNotExists` helper) and override this method. For
125
+ * single-process polling bots (the v1 target), the race window is
126
+ * bounded by Telegram's retry delay and acceptable.
127
+ */
128
+ claim: (ctx: BotIdCtx, id: string) => Promise<boolean>;
129
+ /** `true` if `id` has been claimed. No write. */
130
+ check: (ctx: BotIdCtx, id: string) => Promise<boolean>;
131
+ /** Release a claim. Returns whether anything was released. */
132
+ release: (ctx: BotIdCtx, id: string) => Promise<boolean>;
133
+ };
134
+ /**
135
+ * Presence-flag store for idempotency. Each id is either claimed
136
+ * (storage has the key) or not (storage doesn't). The stored value is
137
+ * an opaque sentinel string — readers only care about presence.
138
+ *
139
+ * @example idempotent successful_payment fulfillment
140
+ *
141
+ * const idem = botSentinel(storage, 'pay:idem')
142
+ * if (!(await idem.claim(ctx, chargeId))) return // duplicate, no-op
143
+ * await fulfill(charge)
144
+ */
145
+ export declare const botSentinel: (storage: Storage, prefix: string) => BotSentinel;
146
+ /**
147
+ * Wrap any `BotRecord` with trace-level logging for every read/write.
148
+ * Useful when debugging a specific plugin's storage flow without
149
+ * polluting every other plugin's logs. Defaults off.
150
+ *
151
+ * const charges = withTracing(botRecord<ChargeRecord>(...), 'pay:charge')
152
+ */
153
+ export declare const withTracing: <T>(record: BotRecord<T>, label: string) => BotRecord<T>;
154
+ //# sourceMappingURL=storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../src/bot/storage.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,222 @@
1
+ /**
2
+ * Typed storage abstraction for `bot/*` plugins.
3
+ *
4
+ * Replaces the per-plugin `botSubKey + storage.get/set` boilerplate
5
+ * with three small primitives that automatically:
6
+ *
7
+ * - **Namespace by bot id.** Every key gets `bot-<id>:` prefixed via
8
+ * `ctx.bot.info.id`, so multiple bots sharing one Redis stay
9
+ * isolated by construction (see `bot/CLAUDE.md` § Multi-bot
10
+ * isolation). No plugin has to remember to wrap.
11
+ *
12
+ * - **Validate on read** (optional). Pass any `{ parse(data:
13
+ * unknown): T }` validator (zod / valibot / arktype / yours) and a
14
+ * mis-shaped record from storage throws a clear `SourcedError`
15
+ * instead of NaN-ing downstream. Omit the validator and you get
16
+ * the same untyped read as before.
17
+ *
18
+ * - **Stay typed end-to-end.** `botRecord<ChargeRecord>(...)` returns
19
+ * a `BotRecord<ChargeRecord>` and `.get()` is `Promise<ChargeRecord
20
+ * | undefined>`. No casts at the call site.
21
+ *
22
+ * ## Three primitives
23
+ *
24
+ * - `botRecord<T>(storage, prefix, validator?)` — get/set/delete a
25
+ * single typed value keyed by an id (`pay:charge:<chargeId>`,
26
+ * `ac:user:<userId>`, …). Most plugin state.
27
+ *
28
+ * - `botIndex(storage, prefix, opts?)` — a capped, prepend-friendly
29
+ * `string[]` stored under a single key. Use for "recent N
30
+ * charges", "pending access requests", etc.
31
+ *
32
+ * - `botSentinel(storage, prefix)` — a presence flag (`"1"` / absent)
33
+ * for idempotency. `claim(ctx, id)` returns `true` only on first
34
+ * call; subsequent calls return `false` without setting. Note:
35
+ * read-then-write is NOT atomic under concurrent load; for
36
+ * production we'd want a storage backend with `setNX` semantics.
37
+ *
38
+ * ## Why not couple to zod?
39
+ *
40
+ * The validator parameter is typed as `{ parse(data: unknown): T }` —
41
+ * a structural shape that zod schemas, valibot, ArkType, and hand-rolled
42
+ * type guards all satisfy. The library has zero zod imports; consumers
43
+ * bring whichever validator they prefer.
44
+ */
45
+ import { SourcedError } from "../offensive.js";
46
+ import { createLogger } from "../universal/log.js";
47
+ const log = createLogger("bot/storage");
48
+ const botIdOf = (ctx) => {
49
+ const id = ctx.bot?.info?.id;
50
+ if (typeof id !== "number") {
51
+ throw new SourcedError({
52
+ source: "storage",
53
+ operation: "key",
54
+ message: "ctx.bot.info.id is missing — called before bot.start()/bot.init()?",
55
+ });
56
+ }
57
+ return id;
58
+ };
59
+ /**
60
+ * Build the namespaced key for a record / index / sentinel. Matches
61
+ * the format `botSubKey` / `botStorageKey` in `kit.ts` use, so the
62
+ * abstractions compose with anything that still consumes those.
63
+ */
64
+ const buildKey = (ctx, prefix, id) => {
65
+ const base = `bot-${botIdOf(ctx)}:${prefix}`;
66
+ return id !== undefined ? `${base}:${id}` : base;
67
+ };
68
+ const parseOr = (raw, validator, key) => {
69
+ if (!validator)
70
+ return raw;
71
+ try {
72
+ return validator.parse(raw);
73
+ }
74
+ catch (cause) {
75
+ throw new SourcedError({
76
+ source: "storage",
77
+ operation: "validate",
78
+ message: cause instanceof Error
79
+ ? cause.message
80
+ : "validator threw a non-Error value",
81
+ cause,
82
+ context: { key },
83
+ });
84
+ }
85
+ };
86
+ /**
87
+ * Typed record store, auto-namespaced by bot id.
88
+ *
89
+ * @example per-charge record with zod validation
90
+ *
91
+ * const charges = botRecord<ChargeRecord>(
92
+ * storage,
93
+ * 'pay:charge',
94
+ * ChargeRecordSchema, // any zod.ZodType<ChargeRecord>
95
+ * )
96
+ * await charges.set(ctx, chargeId, charge)
97
+ * const c = await charges.get(ctx, chargeId)
98
+ *
99
+ * @example without a validator (backwards-compatible behaviour)
100
+ *
101
+ * const charges = botRecord<ChargeRecord>(storage, 'pay:charge')
102
+ * const c = await charges.get(ctx, chargeId) // typed but unchecked
103
+ */
104
+ export const botRecord = (storage, prefix, validator) => ({
105
+ keyFor: (ctx, id) => buildKey(ctx, prefix, id),
106
+ get: async (ctx, id) => {
107
+ const key = buildKey(ctx, prefix, id);
108
+ const raw = await storage.get(key);
109
+ if (raw === undefined)
110
+ return undefined;
111
+ return parseOr(raw, validator, key);
112
+ },
113
+ // `@gramio/storage`'s methods return `MaybePromise<T>` (sync- or
114
+ // async-storage agnostic). We normalize to `Promise<T>` via
115
+ // `Promise.resolve` so callers don't have to care which backend
116
+ // they're on.
117
+ set: async (ctx, id, value) => void (await storage.set(buildKey(ctx, prefix, id), value)),
118
+ delete: async (ctx, id) => void (await storage.delete(buildKey(ctx, prefix, id))),
119
+ has: async (ctx, id) => (await storage.has(buildKey(ctx, prefix, id))) === true,
120
+ });
121
+ /**
122
+ * Newest-first capped list of ids stored under a single key. Designed
123
+ * for "user's last N charges", "pending access requests", "recent
124
+ * payouts" — small, ordered, bounded.
125
+ *
126
+ * `prepend(id)` is the only mutator. Re-prepending an existing id moves
127
+ * it to the front (de-duplication). At-cap inserts drop the tail.
128
+ */
129
+ export const botIndex = (storage, prefix, opts = {}) => {
130
+ const cap = opts.capacity;
131
+ return {
132
+ keyFor: (ctx) => buildKey(ctx, prefix),
133
+ list: async (ctx) => {
134
+ const key = buildKey(ctx, prefix);
135
+ return (await storage.get(key)) ?? [];
136
+ },
137
+ prepend: async (ctx, id) => {
138
+ const key = buildKey(ctx, prefix);
139
+ const existing = (await storage.get(key)) ?? [];
140
+ // De-dup: remove if present, then unshift. Cap at the end.
141
+ const filtered = existing.filter((x) => x !== id);
142
+ filtered.unshift(id);
143
+ const trimmed = cap !== undefined && filtered.length > cap
144
+ ? filtered.slice(0, cap)
145
+ : filtered;
146
+ await storage.set(key, trimmed);
147
+ return trimmed.length;
148
+ },
149
+ remove: async (ctx, id) => {
150
+ const key = buildKey(ctx, prefix);
151
+ const existing = (await storage.get(key)) ?? [];
152
+ const filtered = existing.filter((x) => x !== id);
153
+ if (filtered.length === existing.length)
154
+ return false;
155
+ await storage.set(key, filtered);
156
+ return true;
157
+ },
158
+ clear: async (ctx) => void (await storage.delete(buildKey(ctx, prefix))),
159
+ };
160
+ };
161
+ /**
162
+ * Presence-flag store for idempotency. Each id is either claimed
163
+ * (storage has the key) or not (storage doesn't). The stored value is
164
+ * an opaque sentinel string — readers only care about presence.
165
+ *
166
+ * @example idempotent successful_payment fulfillment
167
+ *
168
+ * const idem = botSentinel(storage, 'pay:idem')
169
+ * if (!(await idem.claim(ctx, chargeId))) return // duplicate, no-op
170
+ * await fulfill(charge)
171
+ */
172
+ export const botSentinel = (storage, prefix) => {
173
+ const SENTINEL = "1";
174
+ return {
175
+ keyFor: (ctx, id) => buildKey(ctx, prefix, id),
176
+ claim: async (ctx, id) => {
177
+ const key = buildKey(ctx, prefix, id);
178
+ const existing = await storage.get(key);
179
+ if (existing !== undefined)
180
+ return false;
181
+ await storage.set(key, SENTINEL);
182
+ return true;
183
+ },
184
+ check: async (ctx, id) => {
185
+ const key = buildKey(ctx, prefix, id);
186
+ return (await storage.get(key)) !== undefined;
187
+ },
188
+ release: async (ctx, id) => {
189
+ const key = buildKey(ctx, prefix, id);
190
+ if ((await storage.get(key)) === undefined)
191
+ return false;
192
+ await storage.delete(key);
193
+ return true;
194
+ },
195
+ };
196
+ };
197
+ // ─── observability hook (opt-in) ──────────────────────────────────
198
+ /**
199
+ * Wrap any `BotRecord` with trace-level logging for every read/write.
200
+ * Useful when debugging a specific plugin's storage flow without
201
+ * polluting every other plugin's logs. Defaults off.
202
+ *
203
+ * const charges = withTracing(botRecord<ChargeRecord>(...), 'pay:charge')
204
+ */
205
+ export const withTracing = (record, label) => ({
206
+ keyFor: record.keyFor,
207
+ get: async (ctx, id) => {
208
+ const result = await record.get(ctx, id);
209
+ log.trace(`${label}.get(${id}) → ${result === undefined ? "miss" : "hit"}`);
210
+ return result;
211
+ },
212
+ set: async (ctx, id, value) => {
213
+ await record.set(ctx, id, value);
214
+ log.trace(`${label}.set(${id})`);
215
+ },
216
+ delete: async (ctx, id) => {
217
+ await record.delete(ctx, id);
218
+ log.trace(`${label}.delete(${id})`);
219
+ },
220
+ has: (ctx, id) => record.has(ctx, id),
221
+ });
222
+ //# sourceMappingURL=storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.js","sourceRoot":"","sources":["../../src/bot/storage.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adriangalilea/utils",
3
- "version": "0.18.0",
3
+ "version": "0.20.0",
4
4
  "description": "TypeScript utilities - logger, currency, formatter, GramIO bot plugins, and more",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -40,6 +40,10 @@
40
40
  "types": "./dist/bot/access-control.d.ts",
41
41
  "default": "./dist/bot/access-control.js"
42
42
  },
43
+ "./bot/allow-list": {
44
+ "types": "./dist/bot/allow-list.d.ts",
45
+ "default": "./dist/bot/allow-list.js"
46
+ },
43
47
  "./bot/coalesce": {
44
48
  "types": "./dist/bot/coalesce.d.ts",
45
49
  "default": "./dist/bot/coalesce.js"
@@ -59,6 +63,22 @@
59
63
  "./bot/llm": {
60
64
  "types": "./dist/bot/llm.d.ts",
61
65
  "default": "./dist/bot/llm.js"
66
+ },
67
+ "./bot/payments": {
68
+ "types": "./dist/bot/payments/index.d.ts",
69
+ "default": "./dist/bot/payments/index.js"
70
+ },
71
+ "./bot/storage": {
72
+ "types": "./dist/bot/storage.d.ts",
73
+ "default": "./dist/bot/storage.js"
74
+ },
75
+ "./bot/ctx": {
76
+ "types": "./dist/bot/ctx.d.ts",
77
+ "default": "./dist/bot/ctx.js"
78
+ },
79
+ "./bot/callbacks": {
80
+ "types": "./dist/bot/callbacks.d.ts",
81
+ "default": "./dist/bot/callbacks.js"
62
82
  }
63
83
  },
64
84
  "files": [
@@ -98,7 +118,8 @@
98
118
  "gramio": "^0.10.0",
99
119
  "marked": "^15.0.11",
100
120
  "tsx": "^4.20.4",
101
- "typescript": "^5.9.2"
121
+ "typescript": "^5.9.2",
122
+ "zod": "^4.4.3"
102
123
  },
103
124
  "dependencies": {
104
125
  "chalk": "^5.6.0"
@@ -108,7 +129,8 @@
108
129
  "@gramio/session": ">=0.2.0",
109
130
  "@gramio/storage": ">=2.0.0",
110
131
  "gramio": ">=0.10.0",
111
- "marked": ">=12"
132
+ "marked": ">=12",
133
+ "zod": ">=3.22"
112
134
  },
113
135
  "peerDependenciesMeta": {
114
136
  "@gramio/format": {
@@ -125,6 +147,9 @@
125
147
  },
126
148
  "marked": {
127
149
  "optional": true
150
+ },
151
+ "zod": {
152
+ "optional": true
128
153
  }
129
154
  },
130
155
  "publishConfig": {