@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,630 @@
1
+ /**
2
+ * Render a Markdown source into one or more Feishu v2 interactive cards.
3
+ *
4
+ * Feishu's `tag: markdown` body element accepts only the "lark_md" subset —
5
+ * bold, italic, strikethrough, inline code, fenced code, links, and (on
6
+ * Feishu 7.6+) lists. It does NOT support headings or GFM tables: a `#` is
7
+ * shown as a literal `#`, and `| a | b |` is shown as the source pipes.
8
+ * Routing every block through `tag: markdown` therefore leaks raw markup into
9
+ * the rendered card whenever the body has a heading or a table.
10
+ *
11
+ * This module parses the source into block tokens (via `marked.lexer`) and
12
+ * routes each block to the v2 card component that actually renders it:
13
+ *
14
+ * - The first `# h1` becomes the card's `header.title` plain-text slot.
15
+ * A later `# h1`, and every `##`/`###`/... heading, becomes a body
16
+ * element wrapping the flattened heading text in `**...**` so lark_md
17
+ * renders it as visible bold.
18
+ * - A GFM `| ... |` table becomes a dedicated `tag: table` element with
19
+ * the header alignment markers mapped to each column's
20
+ * `horizontal_align`. Cells that contain inline markup flip the
21
+ * column's `data_type` from `text` to `lark_md`. A cell over
22
+ * `CELL_MAX_BYTES` is a render-time error — splitting one row's cell
23
+ * across multiple rows would break alignment, and silently truncating
24
+ * hides the data the caller asked to deliver.
25
+ * - A horizontal rule (`---`) becomes a `tag: hr` element.
26
+ * - Every other block — paragraphs, lists, blockquotes, fenced code —
27
+ * is emitted as a `tag: markdown` element with the block's raw source,
28
+ * since lark_md renders those natively.
29
+ *
30
+ * Elements are then packed greedily into cards by a dual budget: Feishu's
31
+ * ~30 KB request body cap AND the v2 card per-card element-count cap.
32
+ * Only the first card carries the header — splitting the body produces
33
+ * follow-up cards with no header, the way a long thread reads. A markdown
34
+ * element body too large for one card is split with `splitMarkdownByBytes`,
35
+ * which counts UTF-8 bytes (so a CJK-heavy body is not budgeted as if it
36
+ * were ASCII) and respects grapheme cluster boundaries (so a ZWJ emoji
37
+ * does not get cut in half).
38
+ *
39
+ * `renderMarkdownToCards` validates every produced card against both
40
+ * budgets before returning; if any card would still exceed them despite
41
+ * splitting, it throws. Callers therefore know that the array they get
42
+ * back is wire-ready — there is no path where the renderer hands the
43
+ * sender a card Feishu would reject. This is the structural half of the
44
+ * "atomic" semantic the channel offers; the network half (every send
45
+ * succeeds or all sends fail) cannot be guaranteed on Feishu's IM API,
46
+ * which has no message-batch transaction.
47
+ *
48
+ * The result is `RenderedCard[]`. `cardToContent(card)` turns one card into
49
+ * the JSON string the `im.message.create` `content` field expects.
50
+ *
51
+ * The 9-row × 9-column limit in `markdown-ref.md` belongs to a different
52
+ * surface (the docs-create pipeline that turns markdown into Feishu document
53
+ * blocks); v2 card tables accept up to 50 columns, paginate rows in-card via
54
+ * `page_size`, and have no per-table row cap of their own. This renderer
55
+ * therefore column-splits at 50 (with the first column preserved on each
56
+ * split half as the identifier) and never row-splits when row-splitting
57
+ * would not actually reduce a card's size.
58
+ */
59
+ import { marked } from 'marked';
60
+ /**
61
+ * Feishu's documented hard limit for a card request body. Past this the API
62
+ * rejects the call outright; the packer keeps each card's serialised content
63
+ * below `CARD_CONTENT_SAFE_BYTES` so HTTP headers and the request envelope
64
+ * still fit underneath the hard cap.
65
+ */
66
+ export const FEISHU_CARD_REQUEST_LIMIT_BYTES = 30 * 1024;
67
+ const CARD_CONTENT_SAFE_BYTES = 28 * 1024;
68
+ /**
69
+ * v2 card per-card element-count cap. Observed in PR #73 review: a card with
70
+ * 250 short markdown elements is rejected by Feishu, though the JSON itself
71
+ * is well under the byte cap. The exact upper bound is not in the open
72
+ * docs; the reviewer-cited 200 figure is treated as the hard limit and a
73
+ * lower number is used as the safe budget so a card on the boundary does
74
+ * not silently fail under server-side counting differences.
75
+ */
76
+ export const FEISHU_CARD_ELEMENT_HARD_CAP = 200;
77
+ const CARD_ELEMENT_SAFE_CAP = 180;
78
+ /**
79
+ * v2 table column cap from the Feishu card API. A wider table is split into
80
+ * several adjacent table elements; each split half repeats the original
81
+ * first column as an identifier the reader can still align rows against.
82
+ */
83
+ const TABLE_COLUMN_HARD_CAP = 50;
84
+ /** In-card paginator page size for tables. The API caps this at 10. */
85
+ const TABLE_DEFAULT_PAGE_SIZE = 10;
86
+ /**
87
+ * Per-cell byte cap. A cell larger than this is rejected at render time:
88
+ * splitting one row's cell across multiple rows breaks alignment with the
89
+ * other columns, and silently truncating drops the data the caller asked
90
+ * to deliver. The author is expected to move the oversized content into a
91
+ * paragraph or fenced code block, where the byte-aware splitter handles
92
+ * arbitrary sizes.
93
+ *
94
+ * 4 KB is comfortably above any reasonable cell value (a long sentence is
95
+ * ~200 bytes) while small enough that a 50-column table of full-budget
96
+ * cells still leaves room for the card envelope inside the byte cap.
97
+ */
98
+ export const CELL_MAX_BYTES = 4 * 1024;
99
+ /**
100
+ * Convert `<@ou_...>` shorthand into the lark_md `<at id="ou_..."></at>`
101
+ * mention tag, leaving inline code spans untouched. Fenced code block tokens
102
+ * are excluded upstream by the `token.type === 'code'` guard in
103
+ * `tokensToElements`; this function only needs to skip backtick-delimited
104
+ * inline code spans (single or multi-backtick) within the raw text of other
105
+ * block types.
106
+ */
107
+ function replaceAtMentions(text) {
108
+ const parts = [];
109
+ let lastIndex = 0;
110
+ // Match any inline code span — one or more backticks as the delimiter.
111
+ const codeSpan = /`+[\s\S]*?`+/g;
112
+ let match;
113
+ while ((match = codeSpan.exec(text)) !== null) {
114
+ // Non-code segment before this span — apply the substitution.
115
+ parts.push(text.slice(lastIndex, match.index).replace(/<@(ou_[A-Za-z0-9_-]+)>/g, '<at id="$1"></at>'));
116
+ // Inline code span — pass through unchanged.
117
+ parts.push(match[0]);
118
+ lastIndex = match.index + match[0].length;
119
+ }
120
+ // Trailing non-code segment.
121
+ parts.push(text.slice(lastIndex).replace(/<@(ou_[A-Za-z0-9_-]+)>/g, '<at id="$1"></at>'));
122
+ return parts.join('');
123
+ }
124
+ /**
125
+ * Render a Markdown source into one or more v2 cards.
126
+ *
127
+ * The output is always non-empty: an empty source produces one card with a
128
+ * single empty `tag: markdown` element, so the caller always has something
129
+ * to send. Splitting happens automatically when the serialised card would
130
+ * exceed Feishu's 30 KB request cap, when the body would exceed the
131
+ * 200-element cap, or when a table has more than 50 columns.
132
+ *
133
+ * Throws when a single block cannot be made to fit even after splitting —
134
+ * for example a table whose row, after every cell has been validated under
135
+ * `CELL_MAX_BYTES`, still serialises above the per-card byte cap. The
136
+ * caller therefore sees a render-time error before any send is attempted,
137
+ * instead of the channel posting some cards and then failing on a later
138
+ * one (partial visible state).
139
+ */
140
+ export function renderMarkdownToCards(text) {
141
+ const tokens = marked.lexer(text);
142
+ const { header, elements } = tokensToElements(tokens);
143
+ const cards = packIntoCards(elements, header);
144
+ // Final structural check. By construction every card already fits both
145
+ // budgets, but a defensive assertion makes a future packing-loop bug
146
+ // surface here as a clear error rather than a Feishu reject downstream.
147
+ for (let i = 0; i < cards.length; i++) {
148
+ const card = cards[i];
149
+ const bytes = cardContentBytes(card);
150
+ const count = card.body.elements.length;
151
+ if (bytes > CARD_CONTENT_SAFE_BYTES) {
152
+ throw new Error(`rendered card ${i + 1} of ${cards.length} is ${bytes} bytes; ` +
153
+ `Feishu rejects a card body over ${FEISHU_CARD_REQUEST_LIMIT_BYTES} bytes. ` +
154
+ 'Reduce the content (shorter paragraphs, fewer rows in any one table).');
155
+ }
156
+ if (count > FEISHU_CARD_ELEMENT_HARD_CAP) {
157
+ throw new Error(`rendered card ${i + 1} of ${cards.length} has ${count} elements; ` +
158
+ `Feishu rejects a card with more than ${FEISHU_CARD_ELEMENT_HARD_CAP} elements. ` +
159
+ 'Combine adjacent paragraphs or send fewer items per reply.');
160
+ }
161
+ }
162
+ return cards;
163
+ }
164
+ /**
165
+ * Serialise one card into the JSON string Feishu's `im.message.create`
166
+ * `content` field expects. Pure: no side effects, no I/O.
167
+ */
168
+ export function cardToContent(card) {
169
+ return JSON.stringify(card);
170
+ }
171
+ /** Byte length of `card`'s serialised content, in UTF-8. */
172
+ export function cardContentBytes(card) {
173
+ return Buffer.byteLength(cardToContent(card), 'utf8');
174
+ }
175
+ /**
176
+ * Walk every block token, route it to the right v2 component, and emit
177
+ * `{ header, elements }`. The header slot is filled by the first heading
178
+ * with depth 1; any later h1 falls through to the bold-body fallback so its
179
+ * text is not silently dropped.
180
+ */
181
+ function tokensToElements(tokens) {
182
+ let header;
183
+ const elements = [];
184
+ for (const token of tokens) {
185
+ if (token.type === 'space')
186
+ continue;
187
+ if (token.type === 'heading') {
188
+ const heading = token;
189
+ const flat = flattenInline(heading.tokens);
190
+ if (heading.depth === 1 && header === undefined) {
191
+ header = { title: { tag: 'plain_text', content: flat } };
192
+ continue;
193
+ }
194
+ // h2+, or a later h1 — lark_md has no heading syntax, so the rendered
195
+ // text would otherwise show the literal `##`. Wrap the flattened text
196
+ // in `**...**` so it renders as visible bold.
197
+ elements.push({ tag: 'markdown', content: `**${flat}**` });
198
+ continue;
199
+ }
200
+ if (token.type === 'hr') {
201
+ elements.push({ tag: 'hr' });
202
+ continue;
203
+ }
204
+ if (token.type === 'table') {
205
+ for (const t of tableTokenToElements(token)) {
206
+ elements.push(t);
207
+ }
208
+ continue;
209
+ }
210
+ // Everything else — paragraph, list, blockquote, code, html block — has
211
+ // a `raw` field that preserves the original markdown source. lark_md
212
+ // renders the supported subset; unsupported markup (e.g. blockquote `>`)
213
+ // passes through and may show its leading characters literally.
214
+ const raw = token.raw ?? '';
215
+ const trimmed = raw.replace(/\n+$/, '');
216
+ if (trimmed.length === 0)
217
+ continue;
218
+ // Fenced and indented code blocks must not have <@> patterns substituted —
219
+ // a code example like `<@ou_abc>` must not ping anyone. All other blocks
220
+ // (paragraphs, lists, blockquotes, …) run through replaceAtMentions, which
221
+ // skips inline code spans within them.
222
+ const content = token.type === 'code' ? trimmed : replaceAtMentions(trimmed);
223
+ elements.push({ tag: 'markdown', content });
224
+ }
225
+ if (elements.length === 0 && header === undefined) {
226
+ // Always emit something so the send loop has at least one card to post.
227
+ elements.push({ tag: 'markdown', content: '' });
228
+ }
229
+ return { header, elements };
230
+ }
231
+ /**
232
+ * Walk a `marked` inline-token tree and produce the plain-text equivalent —
233
+ * `**bold**` becomes `bold`, `[link](url)` becomes `link`, etc. Used for the
234
+ * card's plain-text header slot, which renders markup characters literally.
235
+ */
236
+ function flattenInline(tokens) {
237
+ if (!tokens)
238
+ return '';
239
+ let out = '';
240
+ for (const t of tokens) {
241
+ if ('tokens' in t && Array.isArray(t.tokens)) {
242
+ out += flattenInline(t.tokens);
243
+ continue;
244
+ }
245
+ out += t.text ?? '';
246
+ }
247
+ return out;
248
+ }
249
+ /**
250
+ * Convert one GFM table token into one or more `tag: table` body elements.
251
+ * A table wider than the API's 50-column cap is split into adjacent halves;
252
+ * each half repeats the original first column so the reader can still align
253
+ * a row across the split. Returns at least one element.
254
+ *
255
+ * Throws when any cell exceeds `CELL_MAX_BYTES` — a giant cell does not
256
+ * render usefully and would either force silent truncation or cell-level
257
+ * row splitting that breaks alignment with the other columns. The author
258
+ * should move the oversized content into a paragraph or fenced code block.
259
+ */
260
+ function tableTokenToElements(table) {
261
+ validateCellSizes(table);
262
+ const headerCells = table.header;
263
+ const rows = table.rows;
264
+ const totalCols = headerCells.length;
265
+ if (totalCols <= TABLE_COLUMN_HARD_CAP) {
266
+ return [buildTableElement(headerCells, rows, 0, totalCols, false)];
267
+ }
268
+ const out = [];
269
+ // First slice: columns [0, 50). Later slices repeat column 0 plus the next
270
+ // 49 columns, so every emitted table stays within the 50-column cap.
271
+ out.push(buildTableElement(headerCells, rows, 0, TABLE_COLUMN_HARD_CAP, false));
272
+ const chunkSize = TABLE_COLUMN_HARD_CAP - 1;
273
+ for (let start = TABLE_COLUMN_HARD_CAP; start < totalCols; start += chunkSize) {
274
+ const end = Math.min(totalCols, start + chunkSize);
275
+ out.push(buildTableElement(headerCells, rows, start, end, true));
276
+ }
277
+ return out;
278
+ }
279
+ /**
280
+ * Walk every cell of a parsed table and throw on the first one larger than
281
+ * `CELL_MAX_BYTES`. Run at render time so the error names the row and
282
+ * column position the author can locate in the source.
283
+ */
284
+ function validateCellSizes(table) {
285
+ for (let rowIdx = 0; rowIdx < table.rows.length; rowIdx++) {
286
+ const row = table.rows[rowIdx];
287
+ if (!row)
288
+ continue;
289
+ for (let colIdx = 0; colIdx < row.length; colIdx++) {
290
+ const cell = row[colIdx];
291
+ const text = cell?.text ?? '';
292
+ const bytes = Buffer.byteLength(text, 'utf8');
293
+ if (bytes > CELL_MAX_BYTES) {
294
+ const header = table.header[colIdx]?.text ?? `column ${colIdx + 1}`;
295
+ throw new Error(`table cell at row ${rowIdx + 1}, column "${header}" is ${bytes} bytes; ` +
296
+ `cells over ${CELL_MAX_BYTES} bytes do not render usefully in a card table. ` +
297
+ 'Move the content out of the table — a paragraph or fenced code block has no such cap.');
298
+ }
299
+ }
300
+ }
301
+ }
302
+ /**
303
+ * Build one `tag: table` element from the rows of an already-parsed GFM
304
+ * table. `start..end` selects which columns this element covers; when
305
+ * `includeIdentifier` is true, the original first column is prepended to
306
+ * the selection so a split table keeps an identifier column on every half.
307
+ *
308
+ * Column `data_type` is `text` unless any cell in that column carries
309
+ * inline markup, in which case it flips to `lark_md` so the markup renders.
310
+ */
311
+ function buildTableElement(headerCells, rows, start, end, includeIdentifier) {
312
+ // Indices into the source table that this element covers. The identifier
313
+ // column is duplicated by index so each cell still resolves correctly.
314
+ const indices = [];
315
+ if (includeIdentifier && start > 0)
316
+ indices.push(0);
317
+ for (let i = start; i < end; i++)
318
+ indices.push(i);
319
+ // Stable, in-row-unique column names so a row object can be keyed by name
320
+ // without collisions even when display names repeat. `col_0`, `col_1`, ...
321
+ // are internal keys; the `display_name` is the visible label.
322
+ const columns = indices.map((srcIdx, outIdx) => {
323
+ const cell = headerCells[srcIdx];
324
+ const hasInline = rows.some((row) => cellHasInlineMarkup(row[srcIdx]));
325
+ return {
326
+ name: `col_${outIdx}`,
327
+ display_name: cell?.text ?? '',
328
+ data_type: hasInline ? 'lark_md' : 'text',
329
+ ...alignToHorizontal(cell?.align),
330
+ };
331
+ });
332
+ const builtRows = rows.map((row) => {
333
+ const record = {};
334
+ indices.forEach((srcIdx, outIdx) => {
335
+ const cell = row[srcIdx];
336
+ const colName = `col_${outIdx}`;
337
+ record[colName] = cell?.text ?? '';
338
+ });
339
+ return record;
340
+ });
341
+ return {
342
+ tag: 'table',
343
+ page_size: TABLE_DEFAULT_PAGE_SIZE,
344
+ row_height: 'low',
345
+ header_style: { bold: true, background_style: 'grey' },
346
+ columns,
347
+ rows: builtRows,
348
+ };
349
+ }
350
+ /**
351
+ * Translate a GFM alignment marker into the column field. `marked` reports
352
+ * `align` as `'left' | 'center' | 'right' | null`; an unaligned column gets
353
+ * no `horizontal_align`, so the API picks its default rather than this
354
+ * renderer forcing one.
355
+ */
356
+ function alignToHorizontal(align) {
357
+ if (align === 'left' || align === 'center' || align === 'right') {
358
+ return { horizontal_align: align };
359
+ }
360
+ return {};
361
+ }
362
+ /**
363
+ * True when a table cell's parsed tokens include any non-text token —
364
+ * bold, italic, link, inline code, etc. The column whose cells trigger this
365
+ * needs `data_type: lark_md` so the markup renders instead of leaking
366
+ * through as literal characters.
367
+ */
368
+ function cellHasInlineMarkup(cell) {
369
+ if (!cell || !cell.tokens)
370
+ return false;
371
+ for (const t of cell.tokens) {
372
+ if (t.type !== 'text')
373
+ return true;
374
+ }
375
+ return false;
376
+ }
377
+ /**
378
+ * Pack elements into cards by a dual budget: Feishu's 30 KB request cap AND
379
+ * the per-card element count cap. Both must be satisfied for an element to
380
+ * join the current card; either tripping flushes the card. The first card
381
+ * carries the header; later cards do not, so a long reply reads as a
382
+ * threaded continuation rather than repeated banners.
383
+ *
384
+ * A single element that itself would exceed either budget is split inline:
385
+ * a `tag: markdown` element by `splitMarkdownByBytes`, a `tag: table`
386
+ * element by reducing the rows it carries until it fits.
387
+ */
388
+ function packIntoCards(elements, header) {
389
+ const cards = [];
390
+ let current = newCard(header);
391
+ let isFirstCard = true;
392
+ const flush = () => {
393
+ if (current.body.elements.length === 0 && current.header === undefined)
394
+ return;
395
+ cards.push(current);
396
+ isFirstCard = false;
397
+ current = newCard(undefined);
398
+ };
399
+ const tryAdd = (element) => {
400
+ if (current.body.elements.length + 1 > CARD_ELEMENT_SAFE_CAP)
401
+ return false;
402
+ const trial = {
403
+ ...current,
404
+ body: { elements: [...current.body.elements, element] },
405
+ };
406
+ return cardContentBytes(trial) <= CARD_CONTENT_SAFE_BYTES;
407
+ };
408
+ const addPiece = (piece) => {
409
+ if (!tryAdd(piece)) {
410
+ if (current.body.elements.length > 0)
411
+ flush();
412
+ }
413
+ current.body.elements.push(piece);
414
+ };
415
+ for (const element of elements) {
416
+ if (tryAdd(element)) {
417
+ current.body.elements.push(element);
418
+ continue;
419
+ }
420
+ // The element does not fit alongside what is already in the current
421
+ // card. Flush so the current card seals at its safe size, then retry.
422
+ if (current.body.elements.length > 0)
423
+ flush();
424
+ if (tryAdd(element)) {
425
+ current.body.elements.push(element);
426
+ continue;
427
+ }
428
+ // The element alone exceeds at least one budget on a fresh card. Split
429
+ // it into pieces that each fit on their own; each piece is then added
430
+ // through the normal packing path so adjacent small pieces can share a
431
+ // card.
432
+ const pieces = splitOversizedElement(element, isFirstCard ? header : undefined);
433
+ for (const piece of pieces)
434
+ addPiece(piece);
435
+ }
436
+ flush();
437
+ if (cards.length === 0)
438
+ cards.push(newCard(header));
439
+ return cards;
440
+ }
441
+ /** Build an empty card, optionally pre-populated with a header slot. */
442
+ function newCard(header) {
443
+ const card = {
444
+ schema: '2.0',
445
+ config: { update_multi: true },
446
+ body: { elements: [] },
447
+ };
448
+ if (header)
449
+ card.header = header;
450
+ return card;
451
+ }
452
+ /**
453
+ * Split one too-big element into pieces that each individually fit under
454
+ * the per-card budgets. A markdown element is split by `splitMarkdownByBytes`;
455
+ * a table element is split by reducing its row count.
456
+ *
457
+ * `headerOnFirstCard` lets the splitter budget the first piece against the
458
+ * card with the header attached, so a piece that just fits next to the
459
+ * header doesn't get oversized for the very first card.
460
+ */
461
+ function splitOversizedElement(element, headerOnFirstCard) {
462
+ if (element.tag === 'markdown') {
463
+ return splitMarkdownElement(element, headerOnFirstCard);
464
+ }
465
+ if (element.tag === 'table') {
466
+ return splitTableByRows(element, headerOnFirstCard);
467
+ }
468
+ return [element];
469
+ }
470
+ /**
471
+ * Split a too-big `tag: markdown` element into smaller ones, each whose
472
+ * serialised content fits the per-card byte budget. The body is split with
473
+ * `splitMarkdownByBytes`, which counts UTF-8 bytes — so a 12 K-character
474
+ * CJK paragraph (~36 KB) is correctly split, where a char-based budget
475
+ * would believe it fits and ship one oversized card.
476
+ */
477
+ function splitMarkdownElement(element, headerOnFirstCard) {
478
+ // Reserve room for the JSON envelope of one card carrying one element:
479
+ // `{"schema":"2.0","config":{"update_multi":true},"body":{"elements":[{"tag":"markdown","content":"..."}]}}`
480
+ // plus the optional header. The envelope is measured with an empty
481
+ // content string; the 256-byte cushion covers JSON-escape expansion of
482
+ // body characters that need escaping (quotes, backslashes, control
483
+ // characters) versus their raw byte count.
484
+ const envelope = cardContentBytes(newCardWithEmpty(headerOnFirstCard));
485
+ const innerBudget = Math.max(256, CARD_CONTENT_SAFE_BYTES - envelope - 256);
486
+ const pieces = splitMarkdownByBytes(element.content, innerBudget);
487
+ return pieces.map((content) => ({ tag: 'markdown', content }));
488
+ }
489
+ /**
490
+ * Build an empty card carrying one empty markdown element, used to size the
491
+ * fixed envelope overhead when splitting an oversized markdown element. The
492
+ * cushion in `splitMarkdownElement` covers the difference between an empty
493
+ * content string and the brackets/quotes around real content plus any
494
+ * JSON escapes the content contributes on its own.
495
+ */
496
+ function newCardWithEmpty(header) {
497
+ const card = newCard(header);
498
+ card.body.elements.push({ tag: 'markdown', content: '' });
499
+ return card;
500
+ }
501
+ /**
502
+ * Split a Markdown block's source into pieces whose UTF-8 byte length each
503
+ * stays at or under `byteBudget`. The input is one block's `raw` field from
504
+ * `marked.lexer`, so the block kind drives the split strategy:
505
+ *
506
+ * - A fenced code block (opens with ``` or ~~~ and closes with the same
507
+ * run) is split by line and the open / close lines are repeated on
508
+ * every piece, so each piece is itself a well-formed fenced block.
509
+ * - Any other block is split at line boundaries; a single line longer
510
+ * than the budget is split by grapheme cluster, so a ZWJ-bound emoji
511
+ * cluster or a Hangul syllable is not cut in half.
512
+ *
513
+ * Every returned piece is non-empty and fits the budget — the caller can
514
+ * use them directly as `tag: markdown` element bodies.
515
+ */
516
+ export function splitMarkdownByBytes(text, byteBudget) {
517
+ if (Buffer.byteLength(text, 'utf8') <= byteBudget)
518
+ return [text];
519
+ const fenceLineMatch = /^([`~]{3,}[^\n]*)\n([\s\S]*?)\n([`~]{3,})\s*$/.exec(text);
520
+ if (fenceLineMatch) {
521
+ const open = fenceLineMatch[1];
522
+ const body = fenceLineMatch[2];
523
+ const close = fenceLineMatch[3];
524
+ const overhead = Buffer.byteLength(open, 'utf8') + Buffer.byteLength(close, 'utf8') + 2; // two \n separators
525
+ const inner = Math.max(64, byteBudget - overhead);
526
+ const bodyPieces = splitLinesByBytes(body.split('\n'), inner);
527
+ return bodyPieces.map((piece) => [open, piece, close].join('\n'));
528
+ }
529
+ return splitLinesByBytes(text.split('\n'), byteBudget);
530
+ }
531
+ /**
532
+ * Pack consecutive lines into pieces that each fit `byteBudget`. A line
533
+ * larger than the budget on its own is split by grapheme cluster — never
534
+ * by code unit, since that risks cutting a UTF-8 sequence or a ZWJ-bound
535
+ * emoji cluster in half.
536
+ */
537
+ function splitLinesByBytes(lines, byteBudget) {
538
+ const pieces = [];
539
+ let current = [];
540
+ let currentBytes = 0;
541
+ const flush = () => {
542
+ if (current.length === 0)
543
+ return;
544
+ pieces.push(current.join('\n'));
545
+ current = [];
546
+ currentBytes = 0;
547
+ };
548
+ for (const line of lines) {
549
+ const lineBytes = Buffer.byteLength(line, 'utf8');
550
+ const sepBytes = current.length === 0 ? 0 : 1; // joining newline
551
+ if (lineBytes + sepBytes + currentBytes <= byteBudget) {
552
+ current.push(line);
553
+ currentBytes += lineBytes + sepBytes;
554
+ continue;
555
+ }
556
+ // Adding this line would overflow the current piece.
557
+ flush();
558
+ if (lineBytes <= byteBudget) {
559
+ current.push(line);
560
+ currentBytes = lineBytes;
561
+ continue;
562
+ }
563
+ // The single line is itself larger than the budget — split it inside.
564
+ for (const sub of splitByGraphemeBytes(line, byteBudget)) {
565
+ pieces.push(sub);
566
+ }
567
+ }
568
+ flush();
569
+ return pieces;
570
+ }
571
+ /**
572
+ * Split one string into UTF-8 byte chunks at grapheme cluster boundaries.
573
+ * Walks `Intl.Segmenter`'s grapheme segments, accumulating bytes until the
574
+ * next grapheme would exceed `byteBudget`. A line of CJK characters or
575
+ * ZWJ-bound emoji clusters therefore splits without ever cutting a
576
+ * code-point or an emoji-cluster in half.
577
+ */
578
+ function splitByGraphemeBytes(text, byteBudget) {
579
+ // `und` (undetermined locale) gives the Unicode default grapheme rules,
580
+ // which is what we want — clusters formed by combining marks, ZWJ, etc.
581
+ const segmenter = new Intl.Segmenter('und', { granularity: 'grapheme' });
582
+ const pieces = [];
583
+ let current = '';
584
+ let currentBytes = 0;
585
+ for (const { segment } of segmenter.segment(text)) {
586
+ const segBytes = Buffer.byteLength(segment, 'utf8');
587
+ if (currentBytes + segBytes > byteBudget) {
588
+ if (current.length > 0)
589
+ pieces.push(current);
590
+ current = segment;
591
+ currentBytes = segBytes;
592
+ continue;
593
+ }
594
+ current += segment;
595
+ currentBytes += segBytes;
596
+ }
597
+ if (current.length > 0)
598
+ pieces.push(current);
599
+ return pieces;
600
+ }
601
+ /**
602
+ * Split a too-big `tag: table` element into smaller tables by row. The
603
+ * header columns are repeated on each split half so every emitted table is
604
+ * independently readable; cell-size validation has already rejected a row
605
+ * whose cells alone make it too big to fit, so the row-count window can
606
+ * always shrink to a piece that fits.
607
+ */
608
+ function splitTableByRows(table, headerOnFirstCard) {
609
+ if (table.rows.length <= 1)
610
+ return [table];
611
+ const envelope = cardContentBytes(newCardWithEmpty(headerOnFirstCard));
612
+ const budget = Math.max(2048, CARD_CONTENT_SAFE_BYTES - envelope - 256);
613
+ const out = [];
614
+ let start = 0;
615
+ while (start < table.rows.length) {
616
+ let end = table.rows.length;
617
+ while (end > start + 1) {
618
+ const candidate = { ...table, rows: table.rows.slice(start, end) };
619
+ if (Buffer.byteLength(JSON.stringify(candidate), 'utf8') <= budget)
620
+ break;
621
+ end -= 1;
622
+ }
623
+ out.push({ ...table, rows: table.rows.slice(start, end) });
624
+ if (end === start)
625
+ break;
626
+ start = end;
627
+ }
628
+ return out;
629
+ }
630
+ //# sourceMappingURL=render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../../src/render/render.ts"],"names":[],"mappings":"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"}