@excitedjs/feishu-transport 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +39 -0
  2. package/dist/contract/access-store.d.ts +23 -0
  3. package/dist/contract/access-store.d.ts.map +1 -0
  4. package/dist/contract/access-store.js +16 -0
  5. package/dist/contract/access-store.js.map +1 -0
  6. package/dist/contract/outbound.d.ts +39 -0
  7. package/dist/contract/outbound.d.ts.map +1 -0
  8. package/dist/contract/outbound.js +16 -0
  9. package/dist/contract/outbound.js.map +1 -0
  10. package/dist/contract/types.d.ts +86 -0
  11. package/dist/contract/types.d.ts.map +1 -0
  12. package/dist/contract/types.js +10 -0
  13. package/dist/contract/types.js.map +1 -0
  14. package/dist/index.d.ts +29 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +31 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/json.d.ts +10 -0
  19. package/dist/json.d.ts.map +1 -0
  20. package/dist/json.js +14 -0
  21. package/dist/json.js.map +1 -0
  22. package/dist/parse/comment.d.ts +41 -0
  23. package/dist/parse/comment.d.ts.map +1 -0
  24. package/dist/parse/comment.js +51 -0
  25. package/dist/parse/comment.js.map +1 -0
  26. package/dist/parse/content.d.ts +42 -0
  27. package/dist/parse/content.d.ts.map +1 -0
  28. package/dist/parse/content.js +208 -0
  29. package/dist/parse/content.js.map +1 -0
  30. package/dist/policy/gate.d.ts +105 -0
  31. package/dist/policy/gate.d.ts.map +1 -0
  32. package/dist/policy/gate.js +276 -0
  33. package/dist/policy/gate.js.map +1 -0
  34. package/dist/policy/pairing.d.ts +12 -0
  35. package/dist/policy/pairing.d.ts.map +1 -0
  36. package/dist/policy/pairing.js +15 -0
  37. package/dist/policy/pairing.js.map +1 -0
  38. package/dist/render/render.d.ts +186 -0
  39. package/dist/render/render.d.ts.map +1 -0
  40. package/dist/render/render.js +630 -0
  41. package/dist/render/render.js.map +1 -0
  42. package/dist/transport/connection.d.ts +25 -0
  43. package/dist/transport/connection.d.ts.map +1 -0
  44. package/dist/transport/connection.js +42 -0
  45. package/dist/transport/connection.js.map +1 -0
  46. package/dist/transport/feishu.d.ts +222 -0
  47. package/dist/transport/feishu.d.ts.map +1 -0
  48. package/dist/transport/feishu.js +431 -0
  49. package/dist/transport/feishu.js.map +1 -0
  50. package/package.json +39 -0
@@ -0,0 +1,431 @@
1
+ /**
2
+ * The Feishu platform boundary — the only module in the workspace that imports
3
+ * the Feishu SDK.
4
+ *
5
+ * Everything that talks to Feishu — the inbound long-lived WebSocket and the
6
+ * outbound message API — sits behind the `FeishuTransport` interface. A host
7
+ * depends only on that interface, so its wiring can be exercised against an
8
+ * injected fake with no live connection.
9
+ *
10
+ * The transport is event-type agnostic. `start` is handed a route table mapping
11
+ * each Feishu event_type to a callback and registers every entry with the SDK's
12
+ * event dispatcher; decoding a specific event's payload is the job of that
13
+ * event's handler (see `../parse`), not this module. Adding a new event type to
14
+ * a host therefore never touches this file.
15
+ *
16
+ * Cross-process single-instance election is intentionally **not** here. dreamux
17
+ * gives each dispatcher its own bot identity (election is moot), and claudemux
18
+ * wraps this transport with its own elected-transport layer. Core opens the
19
+ * inbound WebSocket directly; whether exactly one process may do so is the
20
+ * host's concern, layered on top. (claudemux#155 §二 / dreamux#25 §7.2.)
21
+ *
22
+ * Ported from claudemux's `feishu-channel/src/feishu.ts` (the source of truth),
23
+ * with the instance-lock removed, `sendText` upgraded to the structured
24
+ * `send(OutboundTarget, …)` contract, and `botOpenId` renamed to `selfId`.
25
+ */
26
+ import * as lark from '@larksuiteoapi/node-sdk';
27
+ import { cardToContent, renderMarkdownToCards, FEISHU_CARD_REQUEST_LIMIT_BYTES, } from '../render/render.js';
28
+ import { connectionErrorLogLine, reconnectedLogLine, reconnectingLogLine, startupTimeoutLogLine, } from './connection.js';
29
+ /** Cap on a single WebSocket handshake before it is aborted into a retry. */
30
+ const WS_HANDSHAKE_TIMEOUT_MS = 15_000;
31
+ /**
32
+ * How long the initial connection is given to come up before the channel
33
+ * stops it. Long enough to absorb a brief blip and the SDK's own early
34
+ * retries; past it, an unreachable Feishu would otherwise retry in a tight
35
+ * loop, so the transport cuts the attempt off.
36
+ */
37
+ const WS_STARTUP_GRACE_MS = 30_000;
38
+ /**
39
+ * A Lark-SDK logger that writes every line to stderr.
40
+ *
41
+ * Hosts that run over an MCP stdio transport reserve stdout for the JSON-RPC
42
+ * stream; the SDK's default logger writes to stdout, which corrupts it. Routing
43
+ * the SDK's logger to stderr keeps stdout clean while the SDK's diagnostics
44
+ * stay visible in the host's log. (Harmless for dreamux, which does not use
45
+ * stdout for a protocol stream.)
46
+ */
47
+ const sdkLogger = {
48
+ error: (...msg) => console.error('[feishu-sdk]', ...msg),
49
+ warn: (...msg) => console.error('[feishu-sdk]', ...msg),
50
+ info: (...msg) => console.error('[feishu-sdk]', ...msg),
51
+ debug: (...msg) => console.error('[feishu-sdk]', ...msg),
52
+ trace: (...msg) => console.error('[feishu-sdk]', ...msg),
53
+ };
54
+ /**
55
+ * Build the `content` string for a Feishu plain-text message — the legacy
56
+ * `msg_type: 'text'` payload, used by `editText`'s fallback path so an edit
57
+ * on a message that was sent before this channel switched to interactive
58
+ * cards still works.
59
+ */
60
+ export function textMessageContent(text) {
61
+ return JSON.stringify({ text });
62
+ }
63
+ /**
64
+ * Safe ceiling for the serialised card `content` string. Stays a few hundred
65
+ * bytes below the documented 30 KB request-body limit so HTTP headers and the
66
+ * `{ params, data: { receive_id, msg_type, content } }` envelope still fit.
67
+ */
68
+ export const FEISHU_CARD_CONTENT_SAFE_BYTES = 28 * 1024;
69
+ /**
70
+ * Throw a clear, model-actionable error when a single card's content would
71
+ * exceed Feishu's request-body limit, before the SDK round-trips and returns a
72
+ * low-level Feishu code with no fix path. The renderer normally keeps each card
73
+ * under the cap by splitting at element boundaries; this guards the residual
74
+ * case where a card cannot be split smaller (an un-splittable oversized token).
75
+ * Used by both `send` (preserving dreamux's prior per-card guard) and `editText`
76
+ * (which additionally cannot fan out a multi-card body).
77
+ */
78
+ function assertCardContentFits(content) {
79
+ const bytes = Buffer.byteLength(content, 'utf8');
80
+ if (bytes > FEISHU_CARD_CONTENT_SAFE_BYTES) {
81
+ throw new Error(`card content is ${bytes} bytes; Feishu rejects a card-message body over ${FEISHU_CARD_REQUEST_LIMIT_BYTES} bytes. ` +
82
+ 'Shorten the message, or break up an oversized table or code block the renderer could not split smaller.');
83
+ }
84
+ }
85
+ /**
86
+ * Render `text` as a single v2 card, throwing when the body exceeds what
87
+ * one card can hold. Used by `editText` — an edit patches one message_id
88
+ * in place and cannot fan out, so a multi-card body has no destination.
89
+ */
90
+ function renderSingleCard(text) {
91
+ const cards = renderMarkdownToCards(text);
92
+ if (cards.length !== 1) {
93
+ throw new Error(`edit body produced ${cards.length} cards, but an edit can only update one ` +
94
+ 'card in place. Reduce the body length, drop oversized tables, or send a ' +
95
+ 'fresh reply (which the channel splits automatically) instead of editing.');
96
+ }
97
+ // The renderer always returns a non-empty array, but TypeScript can't
98
+ // narrow that — pull the element out with the assertion that we just
99
+ // verified there is exactly one.
100
+ return cards[0];
101
+ }
102
+ /** Document types the drive file-comment API serves; others have no comment API. */
103
+ const COMMENT_FILE_TYPES = ['doc', 'docx', 'sheet', 'file'];
104
+ /** Narrow an event's file_type to one the file-comment API accepts, or `undefined`. */
105
+ function asCommentFileType(fileType) {
106
+ return COMMENT_FILE_TYPES.includes(fileType)
107
+ ? fileType
108
+ : undefined;
109
+ }
110
+ /**
111
+ * Pick the comment with `commentId` out of a `fileComment.batchQuery` response
112
+ * and shape it into a `FeishuDocComment`. Returns `null` when the response
113
+ * carried no such comment. Pure: no I/O, never throws — exported so the decode
114
+ * is unit-tested without a live Feishu connection.
115
+ */
116
+ export function commentFromBatchQuery(items, commentId) {
117
+ const item = items.find((c) => c.comment_id === commentId);
118
+ if (!item)
119
+ return null;
120
+ const replies = (item.reply_list?.replies ?? []).map((reply) => ({
121
+ replyId: reply.reply_id ?? '',
122
+ authorId: reply.user_id ?? '',
123
+ elements: reply.content?.elements ?? [],
124
+ }));
125
+ return { isWhole: item.is_whole ?? true, quote: item.quote ?? '', replies };
126
+ }
127
+ /** Document types the drive metadata API serves. */
128
+ const META_DOC_TYPES = [
129
+ 'doc',
130
+ 'docx',
131
+ 'sheet',
132
+ 'bitable',
133
+ 'mindnote',
134
+ 'file',
135
+ 'wiki',
136
+ 'folder',
137
+ 'synced_block',
138
+ 'slides',
139
+ ];
140
+ /** Narrow an event's file_type to one the metadata API accepts, or `undefined`. */
141
+ function asMetaDocType(fileType) {
142
+ return META_DOC_TYPES.includes(fileType)
143
+ ? fileType
144
+ : undefined;
145
+ }
146
+ /**
147
+ * The real Feishu transport, wrapping the official SDK.
148
+ *
149
+ * Inbound: a `WSClient` opens a long-lived WebSocket and an `EventDispatcher`
150
+ * routes every subscribed event_type to its callback. Outbound: a `Client`
151
+ * calls the `im` message API; it manages the `tenant_access_token` internally.
152
+ * The outbound paths are unit-tested through the `client` seam in
153
+ * `FeishuTransportOptions`; inbound still needs a live Feishu connection.
154
+ */
155
+ export function createFeishuTransport(creds, options = {}) {
156
+ const client = options.client ??
157
+ new lark.Client({
158
+ appId: creds.appId,
159
+ appSecret: creds.appSecret,
160
+ logger: sdkLogger,
161
+ });
162
+ let wsClient;
163
+ let resolvedSelfId;
164
+ /**
165
+ * Open the inbound WebSocket and dispatch events through `routes`. Core opens
166
+ * the connection directly; a host that needs single-instance election wraps
167
+ * this transport rather than threading a lock through here.
168
+ */
169
+ async function openInbound(routes) {
170
+ resolvedSelfId = await resolveBotOpenId(client);
171
+ const dispatcher = new lark.EventDispatcher({ logger: sdkLogger }).register(routes);
172
+ // Resolves the first time the connection reaches `ready`; the startup
173
+ // watchdog below races against it.
174
+ let markReady = () => { };
175
+ const ready = new Promise((resolve) => {
176
+ markReady = resolve;
177
+ });
178
+ const ws = new lark.WSClient({
179
+ appId: creds.appId,
180
+ appSecret: creds.appSecret,
181
+ // Route the SDK's own logging to stderr — see `sdkLogger`.
182
+ logger: sdkLogger,
183
+ // Bound a stuck WebSocket handshake so it fails into a retry rather
184
+ // than holding a stuck DNS / NAT path open indefinitely.
185
+ handshakeTimeoutMs: WS_HANDSHAKE_TIMEOUT_MS,
186
+ // autoReconnect stays on: an established connection that drops should
187
+ // self-heal. The callbacks make every step of that loop visible, so a
188
+ // failing connection is observable instead of a silent retry loop.
189
+ autoReconnect: true,
190
+ onReady: () => {
191
+ logConnection('Feishu WebSocket connection is ready');
192
+ markReady();
193
+ },
194
+ onReconnecting: () => logConnection(reconnectingLogLine()),
195
+ onReconnected: () => logConnection(reconnectedLogLine()),
196
+ onError: (err) => logConnection(connectionErrorLogLine(err)),
197
+ });
198
+ wsClient = ws;
199
+ void ws.start({ eventDispatcher: dispatcher }).catch((err) => {
200
+ logConnection(connectionErrorLogLine(err));
201
+ });
202
+ // The SDK retries pullConnectConfig with no delay until it first
203
+ // succeeds — it has no server-provided reconnect interval yet — so a
204
+ // Feishu that is unreachable at startup spins a tight retry loop.
205
+ // Give the initial connection a grace window; if it is still not up,
206
+ // stop it so the loop does not run unbounded and unobserved.
207
+ const cameUp = await raceConnectionReady(ready);
208
+ if (!cameUp) {
209
+ const gaveUp = ws.getConnectionStatus().state === 'failed';
210
+ logConnection(startupTimeoutLogLine(WS_STARTUP_GRACE_MS, gaveUp));
211
+ ws.close();
212
+ // Fail loud rather than leave a dispatcher whose bot is silently dark:
213
+ // the host (dreamux's server) cleans up and surfaces the failure. A host
214
+ // that prefers to stand by on failure catches this in its own wrapper.
215
+ throw new Error(`Feishu inbound WebSocket for app ${creds.appId} did not connect within ${WS_STARTUP_GRACE_MS}ms`);
216
+ }
217
+ }
218
+ return {
219
+ get appId() {
220
+ return creds.appId;
221
+ },
222
+ get selfId() {
223
+ return resolvedSelfId;
224
+ },
225
+ async start(routes) {
226
+ await openInbound(routes);
227
+ },
228
+ async send(target, text) {
229
+ // Render the markdown source into one or more v2 cards. Routing per
230
+ // block type — headings to `header.title`, tables to `tag: table`,
231
+ // everything else to `tag: markdown` (lark_md) — keeps GFM tables and
232
+ // ATX headings from leaking through as literal `|` and `#`. A body
233
+ // too large for one card produces several cards, each sent as its own
234
+ // message_id so the recipient sees a threaded continuation.
235
+ //
236
+ // PR1: addressed by `chatId` only — behavior-identical to the old
237
+ // `sendText`. `replyToMessageId` / `mentionUserIds` are wired in PR2.
238
+ const cards = renderMarkdownToCards(text);
239
+ const messageIds = [];
240
+ for (const card of cards) {
241
+ const content = cardToContent(card);
242
+ // Fail fast on a card that could not be split under Feishu's hard cap,
243
+ // preserving dreamux's prior per-card send guard (see assertCardContentFits).
244
+ assertCardContentFits(content);
245
+ const res = await client.im.message.create({
246
+ params: { receive_id_type: 'chat_id' },
247
+ data: {
248
+ receive_id: target.chatId,
249
+ msg_type: 'interactive',
250
+ content,
251
+ },
252
+ });
253
+ const id = res.data?.message_id;
254
+ if (id)
255
+ messageIds.push(id);
256
+ }
257
+ return { messageIds };
258
+ },
259
+ async addReaction(messageId, emoji) {
260
+ const res = await client.im.messageReaction.create({
261
+ path: { message_id: messageId },
262
+ data: { reaction_type: { emoji_type: emoji } },
263
+ });
264
+ return res.data?.reaction_id ?? '';
265
+ },
266
+ async removeReaction(messageId, reactionId) {
267
+ await client.im.messageReaction.delete({
268
+ path: { message_id: messageId, reaction_id: reactionId },
269
+ });
270
+ },
271
+ async editText(messageId, text) {
272
+ // An edit patches one message_id in place and cannot fan out, so
273
+ // `renderSingleCard` rejects a body the renderer would otherwise split
274
+ // across several cards. `assertCardContentFits` then catches the
275
+ // residual case of a single-card body that still serialises past the
276
+ // 30 KB request cap — both checks surface as actionable errors before
277
+ // any SDK round-trip.
278
+ const card = renderSingleCard(text);
279
+ const cardContent = cardToContent(card);
280
+ assertCardContentFits(cardContent);
281
+ try {
282
+ // The send path produces an interactive card, so the matching edit
283
+ // is `im.message.patch` (card-content update). The original card was
284
+ // sent with `update_multi: true`, which Feishu requires for a later
285
+ // patch on the same message_id to be accepted.
286
+ await client.im.message.patch({
287
+ path: { message_id: messageId },
288
+ data: { content: cardContent },
289
+ });
290
+ }
291
+ catch (patchErr) {
292
+ // Legacy compatibility: a message_id a host is still holding may
293
+ // belong to a `msg_type: 'text'` message that this channel sent
294
+ // before the upgrade to interactive cards. Feishu rejects `patch`
295
+ // on a non-card target, so fall back to `im.message.update` with
296
+ // the legacy text payload. If the update also fails — auth, rate
297
+ // limit, deleted message — surface the original patch error, which
298
+ // describes the path the channel actually intends to use.
299
+ try {
300
+ await client.im.message.update({
301
+ path: { message_id: messageId },
302
+ data: { msg_type: 'text', content: textMessageContent(text) },
303
+ });
304
+ }
305
+ catch {
306
+ throw patchErr;
307
+ }
308
+ }
309
+ },
310
+ async fetchDocComment(fileToken, fileType, commentId) {
311
+ // The file-comment API only serves a subset of document types; for any
312
+ // other type there is no comment to fetch, so skip the call outright.
313
+ const ct = asCommentFileType(fileType);
314
+ if (!ct)
315
+ return null;
316
+ try {
317
+ // `batchQuery` resolves a comment by id and serves both
318
+ // whole-document and local-selection comments. The single-comment
319
+ // `get` endpoint serves only whole-document comments — it returns
320
+ // "not exist" for a comment anchored to a text selection, which is
321
+ // most document comments.
322
+ const res = await client.drive.fileComment.batchQuery({
323
+ path: { file_token: fileToken },
324
+ // Resolve reply authors to open_id, so they match the open_id the
325
+ // event carries and the sender_id of chat messages.
326
+ params: { file_type: ct, user_id_type: 'open_id' },
327
+ data: { comment_ids: [commentId] },
328
+ });
329
+ return commentFromBatchQuery(res.data?.items ?? [], commentId);
330
+ }
331
+ catch (err) {
332
+ console.error(`[feishu-transport] could not fetch comment ${commentId} on ${fileToken}:`, err);
333
+ return null;
334
+ }
335
+ },
336
+ async fetchDocMeta(fileToken, fileType) {
337
+ const dt = asMetaDocType(fileType);
338
+ if (!dt)
339
+ return null;
340
+ try {
341
+ const res = await client.drive.meta.batchQuery({
342
+ data: { request_docs: [{ doc_token: fileToken, doc_type: dt }], with_url: true },
343
+ });
344
+ const meta = res.data?.metas?.[0];
345
+ if (!meta)
346
+ return null;
347
+ return { title: meta.title ?? '', url: meta.url ?? '' };
348
+ }
349
+ catch (err) {
350
+ console.error(`[feishu-transport] could not fetch metadata for ${fileToken}:`, err);
351
+ return null;
352
+ }
353
+ },
354
+ async close() {
355
+ try {
356
+ wsClient?.close();
357
+ }
358
+ catch (err) {
359
+ // A close on an already-closed socket is expected; anything else
360
+ // (e.g. the SDK's close surface changed) is worth a diagnostic line.
361
+ console.error('[feishu-transport] error while closing the Feishu WebSocket:', err);
362
+ }
363
+ wsClient = undefined;
364
+ },
365
+ };
366
+ }
367
+ /** How many times to try resolving the bot's open_id before giving up. */
368
+ const BOT_INFO_ATTEMPTS = 3;
369
+ /**
370
+ * Resolve the bot's own open_id, needed for group mention-gating. The SDK does
371
+ * not expose a bot-info method, so this calls the raw endpoint through the
372
+ * client (which still attaches the token).
373
+ *
374
+ * Best-effort: a failure leaves the open_id unknown rather than blocking
375
+ * startup — but it is not silent. An unknown open_id makes `isBotMentioned`
376
+ * never match, so every mention-gated group would drop every message; each
377
+ * failure is logged with that consequence spelled out, and a transient error
378
+ * is retried a few times before the transport gives up.
379
+ */
380
+ async function resolveBotOpenId(client) {
381
+ for (let attempt = 1; attempt <= BOT_INFO_ATTEMPTS; attempt++) {
382
+ try {
383
+ const res = await client.request({
384
+ method: 'GET',
385
+ url: '/open-apis/bot/v3/info',
386
+ });
387
+ const openId = res.bot?.open_id;
388
+ if (openId)
389
+ return openId;
390
+ // A well-formed response that simply lacks the field will not improve
391
+ // on retry — stop here rather than spend the remaining attempts.
392
+ console.error('[feishu-transport] bot info response carried no open_id — groups that ' +
393
+ 'require an @-mention will drop every message until the channel restarts');
394
+ return undefined;
395
+ }
396
+ catch (err) {
397
+ if (attempt < BOT_INFO_ATTEMPTS) {
398
+ await delay(attempt * 500);
399
+ continue;
400
+ }
401
+ console.error(`[feishu-transport] could not resolve the bot open_id after ${BOT_INFO_ATTEMPTS} ` +
402
+ 'attempts — groups that require an @-mention will drop every message ' +
403
+ 'until the channel restarts:', err);
404
+ return undefined;
405
+ }
406
+ }
407
+ return undefined;
408
+ }
409
+ /** Resolve after `ms` milliseconds — the backoff between bot-info attempts. */
410
+ function delay(ms) {
411
+ return new Promise((resolve) => setTimeout(resolve, ms));
412
+ }
413
+ /** Write a timestamped connection-lifecycle line to the host's stderr log. */
414
+ function logConnection(line) {
415
+ console.error(`[feishu-transport] ${new Date().toISOString()} ${line}`);
416
+ }
417
+ /**
418
+ * Resolve `true` if `ready` settles within the startup grace window, `false`
419
+ * if the window elapses first. The timer is cleared on the winning path so it
420
+ * does not keep the process alive after the race is decided.
421
+ */
422
+ function raceConnectionReady(ready) {
423
+ return new Promise((resolve) => {
424
+ const timer = setTimeout(() => resolve(false), WS_STARTUP_GRACE_MS);
425
+ void ready.then(() => {
426
+ clearTimeout(timer);
427
+ resolve(true);
428
+ });
429
+ });
430
+ }
431
+ //# sourceMappingURL=feishu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"feishu.js","sourceRoot":"","sources":["../../src/transport/feishu.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@excitedjs/feishu-transport",
3
+ "version": "0.0.1",
4
+ "description": "Shared Feishu platform-I/O core for dreamux and claudemux: connect / receive / send / auth / render / parse + stateless policy (issue excitedjs/dreamux#25).",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "default": "./dist/index.js"
14
+ }
15
+ },
16
+ "engines": {
17
+ "node": ">=22.7"
18
+ },
19
+ "files": [
20
+ "dist",
21
+ "README.md"
22
+ ],
23
+ "dependencies": {
24
+ "@larksuiteoapi/node-sdk": "^1.64.0",
25
+ "marked": "^15.0.12"
26
+ },
27
+ "devDependencies": {
28
+ "@types/node": "^22.10.0",
29
+ "typescript": "^5.7.0",
30
+ "vitest": "^2.1.0"
31
+ },
32
+ "scripts": {
33
+ "build": "tsc -p tsconfig.json",
34
+ "typecheck": "tsc -p tsconfig.json --noEmit",
35
+ "test": "vitest run",
36
+ "test:watch": "vitest",
37
+ "clean": "rm -rf dist"
38
+ }
39
+ }