@alaarab/ogrid-mcp 2.14.1 → 2.15.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.
@@ -18,7 +18,7 @@ A collapsible sidebar panel that provides quick access to column visibility cont
18
18
  ```
19
19
 
20
20
  :::tip
21
- The `SideBar` component is framework-agnostic and uses inline styles. All React UI packages (Radix, Fluent, Material) use the same `SideBar` from `@alaarab/ogrid-react`.
21
+ The `SideBar` component is framework-agnostic and uses inline styles. All React UI packages (Radix, Fluent) use the same `SideBar` from `@alaarab/ogrid-react`.
22
22
  :::
23
23
 
24
24
  </TabItem>
@@ -10,7 +10,7 @@ OGrid 2.9 ships seven framework-native hooks that expose grid logic without
10
10
  imposing any chrome. Pair `useHeadlessGrid` with the spreadsheet hooks to
11
11
  add inline edit, range selection, fill handle, clipboard, undo/redo, and
12
12
  keyboard navigation to your own table markup (shadcn `<Table>`, plain HTML,
13
- Material `<DataGrid>`, anything).
13
+ Fluent `<DataGrid>`, anything).
14
14
 
15
15
  All seven hooks ship as React hooks:
16
16
 
@@ -18,7 +18,7 @@ OGrid's premium inputs are optional cell editor components distributed in a sepa
18
18
  npm install @alaarab/ogrid-react-inputs
19
19
  ```
20
20
 
21
- Works with any OGrid React UI package (Radix, Fluent, Material).
21
+ Works with any OGrid React UI package (Radix, Fluent).
22
22
 
23
23
  </TabItem>
24
24
  </Tabs>
@@ -213,7 +213,7 @@ OGrid includes built-in cell editors out of the box. The premium input packages
213
213
 
214
214
  | | Default (built-in) | Premium (add-on packages) |
215
215
  |---|---|---|
216
- | **Package** | Included in all OGrid packages | `@alaarab/ogrid-{react,angular,vue,js}-inputs` |
216
+ | **Package** | Included in all OGrid packages | `@alaarab/ogrid-react-inputs` |
217
217
  | **Editors** | Text, Select, Rich Select, Checkbox, Date (inline) | DatePicker, Rating, ColorPicker, Slider, Tags |
218
218
  | **Bundle cost** | Zero (built-in) | Only added when you `npm install` the package |
219
219
 
@@ -0,0 +1,124 @@
1
+ ---
2
+ sidebar_position: 16
3
+ title: XLSX Import
4
+ description: Drop an .xlsx file into a fully featured OGrid — with multi-sheet tabs, formulas, and cell references on by default
5
+ ---
6
+
7
+
8
+ # XLSX Import
9
+
10
+ Render any `.xlsx`, CSV, or TSV blob as a fully featured OGrid. Multi-sheet workbooks get tabbed navigation, formulas evaluate live, and Excel-style cell references (A1, B2…) are on by default.
11
+
12
+ Two packages ship this:
13
+
14
+ | Package | When to use |
15
+ |---|---|
16
+ | [`@alaarab/ogrid-react-xlsx`](https://www.npmjs.com/package/@alaarab/ogrid-react-xlsx) | React apps with a bundler (Vite, Next.js, Webpack, etc.). |
17
+ | [`@alaarab/ogrid-react-xlsx-browser`](https://www.npmjs.com/package/@alaarab/ogrid-react-xlsx-browser) | Static HTML pages, SharePoint Framework drops, or any no-bundler context. One self-contained ESM file. |
18
+
19
+ Both expose the same component surface — `XlsxWorkbookGrid`, `XlsxGrid`, and an imperative `mount()` helper.
20
+
21
+ ## React (with a bundler)
22
+
23
+ ```bash
24
+ npm i @alaarab/ogrid-react-xlsx
25
+ ```
26
+
27
+ <Tabs groupId="entry">
28
+ <TabItem value="workbook" label="Full workbook" default>
29
+
30
+ ```tsx
31
+
32
+ function Preview({ file }: { file: Blob }) {
33
+ return <XlsxWorkbookGrid blob={file} height={520} />;
34
+ }
35
+ ```
36
+
37
+ </TabItem>
38
+ <TabItem value="single" label="Single sheet">
39
+
40
+ ```tsx
41
+
42
+ function SingleSheet({ file }: { file: Blob }) {
43
+ const [wb, setWb] = useState<ExcelJS.Workbook | null>(null);
44
+ useEffect(() => { workbookFromBlob(file).then(setWb); }, [file]);
45
+ if (!wb) return null;
46
+ return <XlsxGrid workbook={wb} sheetName="Summary" density="compact" />;
47
+ }
48
+ ```
49
+
50
+ </TabItem>
51
+ </Tabs>
52
+
53
+ ### `XlsxWorkbookGrid` props
54
+
55
+ | Prop | Type | Default | Description |
56
+ |---|---|---|---|
57
+ | `blob` | `Blob` | — | The raw `.xlsx`/CSV/TSV file. Parsed inside the component. Use this OR `workbook`. |
58
+ | `workbook` | `ExcelJS.Workbook` | — | A pre-parsed workbook. Use this OR `blob`. |
59
+ | `height` | `number \| string` | `'100%'` | Container height. |
60
+ | `initialSheet` | `string` | first sheet | Which sheet to show on mount. |
61
+ | `density` | `'compact' \| 'normal' \| 'comfortable'` | `'compact'` | Row height. |
62
+ | `onSheetChange` | `(name: string) => void` | — | Fired when the user clicks a sheet tab. |
63
+ | `headerRow` | `'auto' \| 'header' \| 'none'` | `'auto'` | Whether to promote row 1 to the header. `'auto'` detects strings-only top rows; `'header'` forces it; `'none'` uses A/B/C letters. |
64
+
65
+ ### Format support
66
+
67
+ ExcelJS-backed: `.xlsx`, `.csv`, `.tsv`. The `xlsx` package on npm (SheetJS) is currently stuck at the vulnerable 0.18.5, so support for `.xls`, `.xlsm`, `.xlsb`, and `.ods` was dropped in 2.12.0. See CHANGELOG for the swap rationale.
68
+
69
+ ## Browser bundle (no bundler)
70
+
71
+ For SharePoint Framework, static HTML, or any page that can't (or won't) run a JS bundler, install `@alaarab/ogrid-react-xlsx-browser`. It's a single ESM file — `dist/ogrid-xlsx.js` — with React, ReactDOM, ExcelJS, and every `@alaarab/ogrid-*` dep inlined. ~1.5MB minified, gzipped well by any HTTP server.
72
+
73
+ ```bash
74
+ npm i @alaarab/ogrid-react-xlsx-browser
75
+ ```
76
+
77
+ Copy the two output files into a `vendor/` directory you serve statically:
78
+
79
+ ```
80
+ vendor/
81
+ ogrid-xlsx.js
82
+ ogrid-xlsx.css
83
+ ```
84
+
85
+ Then drop this into any HTML page:
86
+
87
+ ```html
88
+ <link rel="stylesheet" href="/vendor/ogrid-xlsx.css" />
89
+ <div id="grid" style="height: 520px"></div>
90
+
91
+ <script type="module">
92
+ const { mount } = await import('/vendor/ogrid-xlsx.js');
93
+
94
+ const file = await fetch('/data/report.xlsx').then(r => r.blob());
95
+ const unmount = mount(document.getElementById('grid'), { blob: file });
96
+
97
+ // When you're done — React 19 does not auto-unmount on DOM removal:
98
+ // unmount();
99
+ </script>
100
+ ```
101
+
102
+ ### Imperative `mount()`
103
+
104
+ ```ts
105
+ mount(node: Element, opts: MountOptions): () => void
106
+ ```
107
+
108
+ Returns an unmount function. Always call it before removing the host node — React 19 won't clean up event listeners or state if you just `node.remove()`.
109
+
110
+ `MountOptions` accepts the same fields as `XlsxWorkbookGrid` props (`blob` / `workbook`, `initialSheet`, `density`, `height`, `onSheetChange`, `headerRow`).
111
+
112
+ ### When to pick this over the React package
113
+
114
+ | Use the React package when | Use the browser bundle when |
115
+ |---|---|
116
+ | You already have a Vite / Webpack / Next.js build. | You're shipping into a static page, SPFx web part, or CDN drop. |
117
+ | You want tree-shaking and a single dependency graph. | You don't have (and don't want) a JS bundler. |
118
+ | You're integrating into an existing React tree. | You want one `<script type="module">` import. |
119
+
120
+ ## Related
121
+
122
+ - [Formulas](./formulas) — the engine that evaluates `=SUM(A1:A5)` cells lifted out of the workbook
123
+ - [Cell References](./cell-references) — A/B/C column headers and the name box, on by default for xlsx grids
124
+ - [CSV Export](./csv-export) — round-trip data back out of the grid
@@ -15,7 +15,7 @@ fits the page you're building — you can mix them in the same app.
15
15
  | **What you write** | One JSX element | A handful of hook calls + your own `<table>` markup |
16
16
  | **Time to first working grid** | ~5 lines | ~50-100 lines |
17
17
  | **Customization ceiling** | High (theme tokens, slots) | Unlimited (you draw the chrome) |
18
- | **Design-system fit** | Inherits via theme presets (shadcn, Fluent, Material) | Native — uses your chrome primitives directly |
18
+ | **Design-system fit** | Inherits via theme presets (shadcn, Fluent) | Native — uses your chrome primitives directly |
19
19
  | **Spreadsheet features** | Built-in | Composable from hooks |
20
20
  | **Best for** | Standard list pages, admin tables, dashboards | Pages where chrome integration matters more than save-time |
21
21
 
@@ -23,7 +23,7 @@ fits the page you're building — you can mix them in the same app.
23
23
 
24
24
  - You want a complete grid in one element. Sort, filter, paginate, edit,
25
25
  range select, fill handle, copy/paste, undo, keyboard nav — all wired.
26
- - The default chrome (via your chosen UI variant — Radix / Fluent / Material)
26
+ - The default chrome (via your chosen UI variant — Radix / Fluent)
27
27
  is close enough to your design system that a theme preset (`preset-shadcn.css`)
28
28
  can bridge the rest.
29
29
  - You're prototyping or building admin pages where shipping fast matters
@@ -42,8 +42,7 @@ const columns = [
42
42
  ## Use `useHeadlessGrid` + the spreadsheet hooks when…
43
43
 
44
44
  - You want OGrid's logic but rendered with your own table chrome
45
- (shadcn `<Table>`, Fluent `<DataGrid>`, Material `<TableRow>`, plain HTML —
46
- anything).
45
+ (shadcn `<Table>`, Fluent `<DataGrid>`, plain HTML — anything).
47
46
  - The chrome integration is non-negotiable: a specific design system,
48
47
  custom row layouts, embedded widgets in cells, or shared `<TableRow>`
49
48
  components from the rest of the app.
@@ -90,7 +90,7 @@ search_docs query="server-side data" framework="react"
90
90
 
91
91
  Parameters:
92
92
  - `query` (required) — what you're looking for
93
- - `framework` (optional) — `react`, `angular`, `vue`, or `js`
93
+ - `framework` (optional) — `react`
94
94
  - `category` (optional) — `features`, `getting-started`, `guides`, or `api`
95
95
 
96
96
  ### `list_docs`
@@ -114,7 +114,7 @@ get_docs path="features/filtering"
114
114
  Pull code snippets from a doc page, optionally filtered by framework.
115
115
 
116
116
  ```
117
- get_code_example path="features/editing" framework="angular"
117
+ get_code_example path="features/editing" framework="react"
118
118
  ```
119
119
 
120
120
  ### `detect_version`
package/dist/esm/index.js CHANGED
@@ -93,6 +93,21 @@ function parseDocFile(filePath, docsDir2) {
93
93
  codeBlocks
94
94
  };
95
95
  }
96
+ function countOccurrences(haystack, needle, cap) {
97
+ if (!needle) return 0;
98
+ let idx = 0;
99
+ let count = 0;
100
+ while (count < cap) {
101
+ idx = haystack.indexOf(needle, idx);
102
+ if (idx === -1) break;
103
+ count++;
104
+ idx += needle.length;
105
+ }
106
+ return count;
107
+ }
108
+ function tokenize(queryLower) {
109
+ return queryLower.split(/\s+/).filter((t) => t.length > 1);
110
+ }
96
111
  function scoreEntry(entry, queryLower) {
97
112
  let score = 0;
98
113
  const titleLower = entry.title.toLowerCase();
@@ -108,15 +123,18 @@ function scoreEntry(entry, queryLower) {
108
123
  }
109
124
  if (contentLower.includes(queryLower)) {
110
125
  score += 10;
111
- let idx = 0;
112
- let count = 0;
113
- while (count < 10) {
114
- idx = contentLower.indexOf(queryLower, idx);
115
- if (idx === -1) break;
116
- count++;
117
- idx += queryLower.length;
126
+ score += countOccurrences(contentLower, queryLower, 10) * 2;
127
+ }
128
+ const tokens = tokenize(queryLower);
129
+ if (tokens.length > 1) {
130
+ for (const token of tokens) {
131
+ if (titleLower.includes(token)) score += 25;
132
+ if (descLower.includes(token)) score += 12;
133
+ if (contentLower.includes(token)) {
134
+ score += 3;
135
+ score += countOccurrences(contentLower, token, 5);
136
+ }
118
137
  }
119
- score += count * 2;
120
138
  }
121
139
  if (entry.category === "features") score += 3;
122
140
  if (entry.category === "getting-started") score += 2;
@@ -125,9 +143,20 @@ function scoreEntry(entry, queryLower) {
125
143
  function scoreCodeBlock(entry, block, queryLower, framework) {
126
144
  let score = 0;
127
145
  if (framework && block.framework !== framework) return -1;
128
- if (entry.title.toLowerCase().includes(queryLower)) score += 50;
129
- if (entry.description.toLowerCase().includes(queryLower)) score += 25;
130
- if (block.code.toLowerCase().includes(queryLower)) score += 30;
146
+ const titleLower = entry.title.toLowerCase();
147
+ const descLower = entry.description.toLowerCase();
148
+ const codeLower = block.code.toLowerCase();
149
+ if (titleLower.includes(queryLower)) score += 50;
150
+ if (descLower.includes(queryLower)) score += 25;
151
+ if (codeLower.includes(queryLower)) score += 30;
152
+ const tokens = tokenize(queryLower);
153
+ if (tokens.length > 1) {
154
+ for (const token of tokens) {
155
+ if (titleLower.includes(token)) score += 12;
156
+ if (descLower.includes(token)) score += 6;
157
+ if (codeLower.includes(token)) score += 8;
158
+ }
159
+ }
131
160
  return score;
132
161
  }
133
162
  function loadDocsIndex(docsDir2) {
@@ -215,15 +244,19 @@ function fromResourcePath(resourcePath, index2) {
215
244
  return index2.getByPath(resourcePath + ".mdx") ?? index2.getByPath(resourcePath + ".md") ?? index2.getByPath(resourcePath);
216
245
  }
217
246
  function createOGridMcpServer(index2, bridge) {
218
- const server2 = new McpServer({
219
- name: "ogrid-docs",
220
- version: "2.3.0",
221
- instructions: `OGrid documentation server. OGrid is a lightweight, headless data grid for React (Radix and Fluent UI implementations).
247
+ const server2 = new McpServer(
248
+ {
249
+ name: "ogrid-docs",
250
+ version: "2.3.0"
251
+ },
252
+ {
253
+ instructions: `OGrid documentation server. OGrid is a lightweight, headless data grid for React (Radix and Fluent UI implementations).
222
254
 
223
255
  Tools: search_docs (keyword search), list_docs (browse by category), get_docs (full page), get_code_example (code snippets), detect_version (detect OGrid version in your project).
224
256
  Resources: ogrid://quick-reference (key API overview), ogrid://docs/{path} (any doc page by path).
225
257
  Categories: features, getting-started, guides, api.`
226
- });
258
+ }
259
+ );
227
260
  server2.tool(
228
261
  "search_docs",
229
262
  "Search OGrid documentation by keyword. Returns matching docs with title, description, and content excerpt.",
@@ -440,12 +473,14 @@ Tip: use \`get_code_example\` with framework="${result.framework}" or \`search_d
440
473
  {
441
474
  uri: uri.href,
442
475
  mimeType: "text/markdown",
476
+ // Source of truth: docs/api/ogrid-props.mdx + docs/api/column-def.mdx.
477
+ // Keep this in sync when those API references change.
443
478
  text: [
444
479
  "# OGrid Quick Reference",
445
480
  "",
446
481
  "## Install",
447
482
  "```bash",
448
- "# React (choose one)",
483
+ "# React (choose one UI variant)",
449
484
  "npm install @alaarab/ogrid-react-radix",
450
485
  "npm install @alaarab/ogrid-react-fluent",
451
486
  "```",
@@ -453,34 +488,38 @@ Tip: use \`get_code_example\` with framework="${result.framework}" or \`search_d
453
488
  "## Core Props (IOGridProps)",
454
489
  "| Prop | Type | Description |",
455
490
  "|------|------|-------------|",
456
- "| `data` | `T[]` | Client-side row data |",
457
- "| `columns` | `IColumnDef<T>[]` | Column definitions |",
458
- "| `dataSource` | `IDataSource<T>` | Server-side data source |",
459
- "| `pagination` | `boolean \\| number` | Enable pagination (number = page size) |",
460
- '| `rowSelection` | `"single" \\| "multiple"` | Row selection mode |',
491
+ "| `data` | `T[]` | Client-side row data (mutually exclusive with `dataSource`) |",
492
+ "| `dataSource` | `IDataSource<T>` | Server-side data source (mutually exclusive with `data`) |",
493
+ "| `columns` | `(IColumnDef<T> \\| IColumnGroupDef<T>)[]` | Column definitions (and optional groups) |",
494
+ "| `getRowId` | `(item: T) => RowId` | **Required.** Stable unique id for each row |",
495
+ "| `defaultPageSize` | `number` | Initial page size in uncontrolled mode (default 25) |",
496
+ "| `pageSizeOptions` | `number[]` | Options shown in the page-size dropdown |",
497
+ '| `rowSelection` | `"none" \\| "single" \\| "multiple"` | Row selection mode |',
498
+ "| `editable` | `boolean` | Enable inline cell editing globally |",
461
499
  "| `formulas` | `boolean` | Enable formula engine (=SUM, =IF, etc.) |",
462
500
  "| `cellReferences` | `boolean` | Excel-style A1 column headers + name box |",
463
501
  '| `workerSort` | `boolean \\| "auto"` | Web Worker sort/filter |',
464
502
  '| `columnChooser` | `boolean \\| "toolbar" \\| "sidebar"` | Column visibility control |',
465
503
  "| `sideBar` | `boolean \\| ISideBarDef` | Sidebar panel |",
466
504
  "| `toolbar` | `ReactNode` | Custom toolbar content |",
467
- "| `onRowSelectionChanged` | `(rows: T[]) => void` | Row selection callback |",
468
- "| `onCellValueChanged` | `(e: ICellValueChangedEvent) => void` | Cell edit callback |",
505
+ "| `onSelectionChange` | `(e: IRowSelectionChangeEvent<T>) => void` | Row selection callback |",
506
+ "| `onCellValueChanged` | `(e: ICellValueChangedEvent<T>) => void` | Cell edit callback |",
469
507
  "",
470
508
  "## IColumnDef Key Fields",
471
509
  "| Field | Type | Description |",
472
510
  "|-------|------|-------------|",
473
- "| `columnId` | `string` | Unique column ID (maps to data key) |",
474
- "| `headerName` | `string` | Column header label |",
511
+ "| `columnId` | `string` | Unique column id (maps to the data key) |",
512
+ "| `name` | `string` | Column header label |",
475
513
  '| `type` | `"text" \\| "numeric" \\| "date" \\| "boolean"` | Data type |',
476
- '| `filter` | `"none" \\| "text" \\| "multiSelect" \\| "date"` | Filter type |',
477
- "| `editable` | `boolean \\| (row) => boolean` | Enable inline editing |",
478
- "| `width` | `number` | Column width in px |",
479
- '| `pinned` | `"left" \\| "right"` | Pin column |',
480
- "| `sortable` | `boolean` | Enable sorting |",
481
- "| `hidden` | `boolean` | Hide column by default |",
482
- "| `valueGetter` | `(row: T) => unknown` | Custom value extractor |",
483
- "| `renderCell` | `(value, row) => ReactNode` | Custom cell renderer (React) |",
514
+ "| `filterable` | `IColumnFilterDef` | Filter config; omit to make the column not filterable |",
515
+ "| `editable` | `boolean \\| ((item: T) => boolean)` | Enable inline editing |",
516
+ "| `sortable` | `boolean` | Enable sorting (default true) |",
517
+ "| `defaultWidth` | `number` | Default column width in pixels |",
518
+ '| `pinned` | `"left" \\| "right"` | Pin column to an edge |',
519
+ "| `defaultVisible` | `boolean` | Show column by default (false = hidden, toggle via chooser) |",
520
+ '| `cellEditor` | `"text" \\| "select" \\| "checkbox" \\| "richSelect" \\| "date" \\| ComponentType` | Cell editor |',
521
+ "| `valueGetter` | `(item: T) => unknown` | Custom value extractor |",
522
+ "| `renderCell` | `(item: T) => ReactNode` | Custom cell renderer |",
484
523
  "",
485
524
  "## Common Patterns",
486
525
  "",
@@ -488,27 +527,27 @@ Tip: use \`get_code_example\` with framework="${result.framework}" or \`search_d
488
527
  "```tsx",
489
528
  'import { OGrid } from "@alaarab/ogrid-react-radix";',
490
529
  "const columns = [",
491
- ' { columnId: "name", headerName: "Name", type: "text", filter: "text" },',
492
- ' { columnId: "age", headerName: "Age", type: "numeric", sortable: true },',
530
+ ' { columnId: "name", name: "Name", type: "text", filterable: { type: "text" } },',
531
+ ' { columnId: "age", name: "Age", type: "numeric", sortable: true },',
493
532
  "];",
494
- "<OGrid data={rows} columns={columns} pagination={50} />",
533
+ "<OGrid data={rows} columns={columns} getRowId={(r) => r.id} defaultPageSize={50} />",
495
534
  "```",
496
535
  "",
497
536
  "### Server-side data",
498
537
  "```tsx",
499
538
  "const dataSource = {",
500
- " fetchPage: async ({ page, pageSize, sortModel, filterModel }) => {",
501
- " const res = await fetch(`/api/data?page=${page}&size=${pageSize}`);",
539
+ " fetchPage: async ({ page, pageSize, sort, filters, signal }) => {",
540
+ " const res = await fetch(`/api/data?page=${page}&size=${pageSize}`, { signal });",
502
541
  " const json = await res.json();",
503
- " return { rows: json.data, totalCount: json.total };",
504
- " }",
542
+ " return { items: json.data, totalCount: json.total };",
543
+ " },",
505
544
  "};",
506
- "<OGrid dataSource={dataSource} columns={columns} pagination={50} />",
545
+ "<OGrid dataSource={dataSource} columns={columns} getRowId={(r) => r.id} defaultPageSize={50} />",
507
546
  "```",
508
547
  "",
509
548
  "### Formula support",
510
549
  "```tsx",
511
- "<OGrid data={rows} columns={columns} formulas cellReferences />",
550
+ "<OGrid data={rows} columns={columns} getRowId={(r) => r.id} formulas cellReferences />",
512
551
  '// Users can type =SUM(A1:C3), =IF(A1>0,"yes","no"), etc.',
513
552
  "```"
514
553
  ].join("\n")
@@ -742,7 +781,7 @@ _${state.rowCount - limit} more rows not shown. Increase maxRows to see more._`)
742
781
  {
743
782
  gridId: z.string().describe("Grid ID as registered by connectGridToBridge()"),
744
783
  type: z.enum(["update_cell", "set_filter", "clear_filters", "set_sort", "go_to_page"]).describe("Command type"),
745
- payload: z.record(z.unknown()).describe("Command-specific payload (see tool description for fields per type)"),
784
+ payload: z.record(z.string(), z.unknown()).describe("Command-specific payload (see tool description for fields per type)"),
746
785
  timeoutMs: z.number().int().min(100).max(3e4).optional().describe("How long to wait for the app to execute the command (default: 5000ms)")
747
786
  },
748
787
  async ({ gridId, type, payload, timeoutMs }) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alaarab/ogrid-mcp",
3
- "version": "2.14.1",
3
+ "version": "2.15.0",
4
4
  "description": "MCP server for OGrid documentation",
5
5
  "type": "module",
6
6
  "bin": {
@@ -25,6 +25,7 @@
25
25
  "prebuild": "node scripts/bundle-docs.mjs",
26
26
  "build": "tsup",
27
27
  "dev": "tsup --watch",
28
+ "typecheck": "tsc -p tsconfig.json --noEmit",
28
29
  "test": "bun test --preload ../../bun-test.setup.ts"
29
30
  },
30
31
  "dependencies": {