@surfaice/format 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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 surfaiceai
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,55 @@
1
+ # @surfaice/format
2
+
3
+ Parser, serializer, and validator for the `.surfaice.md` format — the machine-readable UI description used by [Surfaice](https://github.com/surfaiceai/surfaice).
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @surfaice/format
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```typescript
14
+ import { parse, serialize, validate } from '@surfaice/format'
15
+
16
+ // Parse a .surfaice.md file
17
+ const page = parse(markdownString) // → SurfaicePage AST
18
+
19
+ // Validate structure
20
+ const errors = validate(page) // → ValidationError[]
21
+
22
+ // Serialize back to markdown
23
+ const md = serialize(page) // → string (roundtrip stable)
24
+ ```
25
+
26
+ ## What Is `.surfaice.md`?
27
+
28
+ A markdown-based format that describes your UI elements for AI agents and CI tools:
29
+
30
+ ```markdown
31
+ ---
32
+ surfaice: v1
33
+ route: /settings
34
+ states: [auth-required]
35
+ ---
36
+
37
+ # /settings
38
+
39
+ ## Profile
40
+ - [name] textbox "Display Name" → current: {user.name} → accepts: string
41
+ - [save] button "Save Changes" (destructive) → PUT /api/profile → toast 'Saved!'
42
+ ```
43
+
44
+ ## API
45
+
46
+ ### `parse(markdown: string): SurfaicePage`
47
+ ### `serialize(page: SurfaicePage): string`
48
+ ### `validate(page: SurfaicePage): ValidationError[]`
49
+
50
+ ## Part of Surfaice
51
+
52
+ - [`@surfaice/react`](../react) — React annotations
53
+ - [`@surfaice/differ`](../differ) — Structural diff engine
54
+ - [`@surfaice/cli`](../cli) — CLI tools
55
+ - [`@surfaice/next`](../next) — Next.js middleware
@@ -0,0 +1,4 @@
1
+ export type { ElementType, Element, Section, SurfaicePage, Capability, PageState, StateChange, ValidationError, } from './types.js';
2
+ export { parse } from './parser.js';
3
+ export { serialize } from './serializer.js';
4
+ export { validate } from './validator.js';
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { parse } from './parser.js';
2
+ export { serialize } from './serializer.js';
3
+ export { validate } from './validator.js';
@@ -0,0 +1,5 @@
1
+ import type { SurfaicePage } from './types.js';
2
+ /**
3
+ * Parse a .surfaice.md string into a SurfaicePage AST.
4
+ */
5
+ export declare function parse(input: string): SurfaicePage;
package/dist/parser.js ADDED
@@ -0,0 +1,200 @@
1
+ import { unified } from 'unified';
2
+ import remarkParse from 'remark-parse';
3
+ import remarkFrontmatter from 'remark-frontmatter';
4
+ import { parse as parseYaml } from 'yaml';
5
+ // Regex to match element lines: - [id] type "Label" (attrs) → ...
6
+ const ELEMENT_LINE_RE = /^-\s+\[(\w[\w-]*)\]\s+(\w[\w-]*)\s+"([^"]+)"(?:\s+\(([^)]+)\))?(.*)?$/;
7
+ /**
8
+ * Parse a .surfaice.md string into a SurfaicePage AST.
9
+ */
10
+ export function parse(input) {
11
+ const processor = unified()
12
+ .use(remarkParse)
13
+ .use(remarkFrontmatter, ['yaml']);
14
+ const mdast = processor.parse(input);
15
+ // Extract frontmatter
16
+ const frontmatterNode = mdast.children.find(n => n.type === 'yaml');
17
+ const frontmatter = frontmatterNode ? parseYaml(frontmatterNode.value) : {};
18
+ const version = String(frontmatter['surfaice'] ?? 'v1');
19
+ const route = String(frontmatter['route'] ?? '/');
20
+ const name = frontmatter['name'] ? String(frontmatter['name']) : undefined;
21
+ const states = Array.isArray(frontmatter['states']) ? frontmatter['states'].map(String) : undefined;
22
+ const capabilities = parseCapabilities(frontmatter['capabilities']);
23
+ // Walk the AST and collect sections + page states
24
+ const sections = [];
25
+ let pageStates;
26
+ let currentSection = null;
27
+ let inStatesBlock = false;
28
+ for (const node of mdast.children) {
29
+ if (node.type === 'yaml')
30
+ continue;
31
+ if (node.type === 'heading') {
32
+ const heading = node;
33
+ const headingText = extractText(heading);
34
+ if (heading.depth === 1) {
35
+ // Page heading — skip
36
+ currentSection = null;
37
+ inStatesBlock = false;
38
+ continue;
39
+ }
40
+ if (heading.depth === 2) {
41
+ if (headingText === 'States') {
42
+ inStatesBlock = true;
43
+ currentSection = null;
44
+ }
45
+ else {
46
+ inStatesBlock = false;
47
+ currentSection = { name: headingText, elements: [] };
48
+ sections.push(currentSection);
49
+ }
50
+ continue;
51
+ }
52
+ }
53
+ if (node.type === 'list') {
54
+ const list = node;
55
+ if (inStatesBlock) {
56
+ pageStates = parseStatesBlock(list);
57
+ }
58
+ else if (currentSection) {
59
+ const elements = parseElementList(list);
60
+ currentSection.elements.push(...elements);
61
+ }
62
+ }
63
+ }
64
+ return {
65
+ version,
66
+ route,
67
+ ...(name !== undefined && { name }),
68
+ ...(states !== undefined && { states }),
69
+ ...(capabilities !== undefined && { capabilities }),
70
+ sections,
71
+ ...(pageStates !== undefined && { pageStates }),
72
+ };
73
+ }
74
+ function parseCapabilities(raw) {
75
+ if (!Array.isArray(raw))
76
+ return undefined;
77
+ return raw.map((cap) => {
78
+ const c = cap;
79
+ return {
80
+ id: String(c['id'] ?? ''),
81
+ description: String(c['description'] ?? ''),
82
+ elements: Array.isArray(c['elements']) ? c['elements'].map(String) : [],
83
+ };
84
+ });
85
+ }
86
+ function extractText(node) {
87
+ return node.children
88
+ .filter((c) => c.type === 'text')
89
+ .map(c => c.value)
90
+ .join('');
91
+ }
92
+ function extractListItemText(item) {
93
+ const para = item.children.find((c) => c.type === 'paragraph');
94
+ if (!para)
95
+ return '';
96
+ return para.children
97
+ .filter((c) => c.type === 'text')
98
+ .map(c => c.value)
99
+ .join('');
100
+ }
101
+ function parseElementList(list) {
102
+ const elements = [];
103
+ for (const item of list.children) {
104
+ const listItem = item;
105
+ const text = extractListItemText(listItem);
106
+ const el = parseElementLine(text);
107
+ if (!el)
108
+ continue;
109
+ // Check for nested list (reveals)
110
+ const nestedList = listItem.children.find((c) => c.type === 'list');
111
+ if (nestedList) {
112
+ el.reveals = parseElementList(nestedList);
113
+ }
114
+ elements.push(el);
115
+ }
116
+ return elements;
117
+ }
118
+ function parseElementLine(line) {
119
+ const trimmed = line.trim().startsWith('- ') ? line.trim() : `- ${line.trim()}`;
120
+ const match = trimmed.match(ELEMENT_LINE_RE);
121
+ if (!match)
122
+ return null;
123
+ const [, id, type, label, attrs, rest] = match;
124
+ const element = {
125
+ id,
126
+ type: type,
127
+ label,
128
+ };
129
+ if (attrs) {
130
+ element.attributes = attrs.split(',').map(a => a.trim()).filter(Boolean);
131
+ }
132
+ if (rest && rest.trim()) {
133
+ parseActions(rest.trim(), element);
134
+ }
135
+ return element;
136
+ }
137
+ function parseActions(text, element) {
138
+ // Split on → (unicode arrow) or ASCII →
139
+ const parts = text.split(/\s*→\s*/).map(p => p.trim()).filter(Boolean);
140
+ for (const part of parts) {
141
+ if (part.startsWith('navigates:')) {
142
+ element.navigates = part.slice('navigates:'.length).trim();
143
+ }
144
+ else if (part.startsWith('current:')) {
145
+ element.current = part.slice('current:'.length).trim();
146
+ }
147
+ else if (part.startsWith('accepts:')) {
148
+ element.accepts = part.slice('accepts:'.length).trim();
149
+ }
150
+ else if (part.startsWith('shows:')) {
151
+ element.shows = part.slice('shows:'.length).trim();
152
+ }
153
+ else if (part.startsWith('options:')) {
154
+ element.options = part.slice('options:'.length).trim().split(',').map(o => o.trim()).filter(Boolean);
155
+ }
156
+ else if (part.startsWith('reveals:')) {
157
+ // reveals children are parsed from nested list, not inline
158
+ }
159
+ else if (/^(GET|POST|PUT|PATCH|DELETE)\s/.test(part)) {
160
+ element.action = part;
161
+ }
162
+ else if (part.startsWith('toast') || part.startsWith('confirms:') || part.startsWith('inline-error')) {
163
+ element.result = part;
164
+ }
165
+ else {
166
+ // Fallback: first unknown part → action, second → result
167
+ if (!element.action) {
168
+ element.action = part;
169
+ }
170
+ else if (!element.result) {
171
+ element.result = part;
172
+ }
173
+ }
174
+ }
175
+ }
176
+ function parseStatesBlock(list) {
177
+ const states = [];
178
+ for (const item of list.children) {
179
+ const text = extractListItemText(item).trim();
180
+ // Format: [state-name]: modifier elementId description
181
+ const stateMatch = text.match(/^\[([^\]]+)\]:\s*([+\-~])\s+(\w[\w-]*)\s+(.*)$/);
182
+ if (!stateMatch)
183
+ continue;
184
+ const [, stateName, modifier, elementId, description] = stateMatch;
185
+ const change = {
186
+ elementId,
187
+ modifier: modifier,
188
+ description: description.trim(),
189
+ };
190
+ // Check if we already have this state
191
+ const existing = states.find(s => s.name === stateName);
192
+ if (existing) {
193
+ existing.changes.push(change);
194
+ }
195
+ else {
196
+ states.push({ name: stateName, changes: [change] });
197
+ }
198
+ }
199
+ return states.length > 0 ? states : [];
200
+ }
@@ -0,0 +1,6 @@
1
+ import type { SurfaicePage } from './types.js';
2
+ /**
3
+ * Serialize a SurfaicePage AST back to a .surfaice.md string.
4
+ * Guarantees roundtrip stability: parse(serialize(page)) deepEquals page.
5
+ */
6
+ export declare function serialize(page: SurfaicePage): string;
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Serialize a SurfaicePage AST back to a .surfaice.md string.
3
+ * Guarantees roundtrip stability: parse(serialize(page)) deepEquals page.
4
+ */
5
+ export function serialize(page) {
6
+ const lines = [];
7
+ // Frontmatter
8
+ lines.push('---');
9
+ lines.push(`surfaice: ${page.version}`);
10
+ lines.push(`route: ${page.route}`);
11
+ if (page.name !== undefined) {
12
+ lines.push(`name: ${page.name}`);
13
+ }
14
+ if (page.states?.length) {
15
+ lines.push(`states: [${page.states.join(', ')}]`);
16
+ }
17
+ if (page.capabilities?.length) {
18
+ lines.push('capabilities:');
19
+ for (const cap of page.capabilities) {
20
+ lines.push(` - id: ${cap.id}`);
21
+ lines.push(` description: "${cap.description}"`);
22
+ lines.push(` elements: [${cap.elements.join(', ')}]`);
23
+ }
24
+ }
25
+ lines.push('---');
26
+ lines.push('');
27
+ // Page heading
28
+ lines.push(`# ${page.route}`);
29
+ // Sections
30
+ for (const section of page.sections) {
31
+ lines.push('');
32
+ lines.push(`## ${section.name}`);
33
+ for (const el of section.elements) {
34
+ lines.push(serializeElement(el, 0));
35
+ }
36
+ }
37
+ // Page states
38
+ if (page.pageStates?.length) {
39
+ lines.push('');
40
+ lines.push('## States');
41
+ for (const state of page.pageStates) {
42
+ for (const change of state.changes) {
43
+ lines.push(`- [${state.name}]: ${change.modifier} ${change.elementId} ${change.description}`);
44
+ }
45
+ }
46
+ }
47
+ return lines.join('\n') + '\n';
48
+ }
49
+ function serializeElement(el, indent) {
50
+ const prefix = ' '.repeat(indent) + '- ';
51
+ let line = `${prefix}[${el.id}] ${el.type} "${el.label}"`;
52
+ if (el.attributes?.length) {
53
+ line += ` (${el.attributes.join(', ')})`;
54
+ }
55
+ const parts = [];
56
+ // Order matters for roundtrip stability — must match parser's parseActions order
57
+ if (el.current !== undefined)
58
+ parts.push(`current: ${el.current}`);
59
+ if (el.accepts !== undefined)
60
+ parts.push(`accepts: ${el.accepts}`);
61
+ if (el.options?.length)
62
+ parts.push(`options: ${el.options.join(', ')}`);
63
+ if (el.shows !== undefined)
64
+ parts.push(`shows: ${el.shows}`);
65
+ if (el.action !== undefined)
66
+ parts.push(el.action);
67
+ if (el.result !== undefined)
68
+ parts.push(el.result);
69
+ if (el.navigates !== undefined)
70
+ parts.push(`navigates: ${el.navigates}`);
71
+ if (el.reveals?.length) {
72
+ parts.push('reveals:');
73
+ line += parts.length > 0 ? ' → ' + parts.join(' → ') : '';
74
+ for (const child of el.reveals) {
75
+ line += '\n' + serializeElement(child, indent + 1);
76
+ }
77
+ return line;
78
+ }
79
+ if (parts.length > 0) {
80
+ line += ' → ' + parts.join(' → ');
81
+ }
82
+ return line;
83
+ }
@@ -0,0 +1,79 @@
1
+ export type ElementType = 'button' | 'textbox' | 'textarea' | 'link' | 'select' | 'checkbox' | 'radio' | 'toggle' | 'slider' | 'image' | 'image-upload' | 'badge' | 'heading' | 'text' | 'list';
2
+ export interface Capability {
3
+ /** Unique identifier for this capability, e.g. "update-profile" */
4
+ id: string;
5
+ /** Human-readable description, e.g. "User updates their display name" */
6
+ description: string;
7
+ /** Element IDs involved in this capability */
8
+ elements: string[];
9
+ }
10
+ export interface StateChange {
11
+ /** The element ID this change applies to */
12
+ elementId: string;
13
+ /** '+' = add/show, '-' = remove/hide, '~' = modify */
14
+ modifier: '+' | '-' | '~';
15
+ /** Description of what changes, e.g. "disabled, shows spinner" */
16
+ description: string;
17
+ }
18
+ export interface PageState {
19
+ /** State name, e.g. "loading", "error", "success" */
20
+ name: string;
21
+ changes: StateChange[];
22
+ }
23
+ export interface Element {
24
+ /** Unique identifier within this page, e.g. "save" */
25
+ id: string;
26
+ /** Element type */
27
+ type: ElementType;
28
+ /** Human-readable label, e.g. "Save Changes" */
29
+ label: string;
30
+ /** Optional modifiers: "readonly", "required", "destructive" */
31
+ attributes?: string[];
32
+ /** HTTP action, e.g. "PUT /api/profile" */
33
+ action?: string;
34
+ /** Side-effect after action, e.g. "toast 'Saved!'" */
35
+ result?: string;
36
+ /** Route this element navigates to */
37
+ navigates?: string;
38
+ /** Elements revealed after interaction with this element */
39
+ reveals?: Element[];
40
+ /** Runtime live value (injected at render time), e.g. "Haosu Wu" */
41
+ value?: string;
42
+ /** Template binding for static export, e.g. "{user.name}" */
43
+ current?: string;
44
+ /** Input type hint for textboxes, e.g. "email", "string" */
45
+ accepts?: string;
46
+ /** Options for select elements */
47
+ options?: string[];
48
+ /** Value displayed by display elements (e.g. badge count), e.g. "{notifications.count}" */
49
+ shows?: string;
50
+ }
51
+ export interface Section {
52
+ /** Section name, e.g. "Profile Section" */
53
+ name: string;
54
+ elements: Element[];
55
+ }
56
+ export interface SurfaicePage {
57
+ /** Format version, always "v1" for now */
58
+ version: string;
59
+ /** Route this page corresponds to, e.g. "/settings" */
60
+ route: string;
61
+ /** Optional human-readable page name */
62
+ name?: string;
63
+ /** Page-level states that gate access, e.g. ["auth-required"] */
64
+ states?: string[];
65
+ /** Named capabilities grouping elements into user flows */
66
+ capabilities?: Capability[];
67
+ /** Ordered list of UI sections */
68
+ sections: Section[];
69
+ /** Optional state machine describing UI state transitions */
70
+ pageStates?: PageState[];
71
+ }
72
+ export interface ValidationError {
73
+ /** Machine-readable error code */
74
+ code: string;
75
+ /** Human-readable message */
76
+ message: string;
77
+ /** Optional path to the offending node, e.g. "sections[0].elements[2].id" */
78
+ path?: string;
79
+ }
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ // @surfaice/format — Core type definitions
2
+ // These types form the AST that everything else produces and consumes.
3
+ export {};
@@ -0,0 +1,6 @@
1
+ import type { SurfaicePage, ValidationError } from './types.js';
2
+ /**
3
+ * Validate a SurfaicePage AST for structural correctness.
4
+ * Returns an array of ValidationErrors — empty array means valid.
5
+ */
6
+ export declare function validate(page: SurfaicePage): ValidationError[];
@@ -0,0 +1,102 @@
1
+ const VALID_ELEMENT_TYPES = new Set([
2
+ 'button', 'textbox', 'textarea', 'link', 'select', 'checkbox',
3
+ 'radio', 'toggle', 'slider', 'image', 'image-upload',
4
+ 'badge', 'heading', 'text', 'list',
5
+ ]);
6
+ /**
7
+ * Validate a SurfaicePage AST for structural correctness.
8
+ * Returns an array of ValidationErrors — empty array means valid.
9
+ */
10
+ export function validate(page) {
11
+ const errors = [];
12
+ // Collect all element IDs for duplicate and reference checking
13
+ const allIds = new Set();
14
+ const seenIds = new Set();
15
+ // First pass: collect all element IDs (including in reveals)
16
+ for (const section of page.sections) {
17
+ collectIds(section.elements, allIds);
18
+ }
19
+ // Second pass: validate each element and check for duplicates
20
+ for (let si = 0; si < page.sections.length; si++) {
21
+ const section = page.sections[si];
22
+ validateElements(section.elements, seenIds, errors, `sections[${si}]`);
23
+ }
24
+ // Validate capabilities reference existing element IDs
25
+ if (page.capabilities) {
26
+ for (let ci = 0; ci < page.capabilities.length; ci++) {
27
+ const cap = page.capabilities[ci];
28
+ for (const elementId of cap.elements) {
29
+ if (!allIds.has(elementId)) {
30
+ errors.push({
31
+ code: 'UNKNOWN_ELEMENT_REF',
32
+ message: `Capability "${cap.id}" references unknown element id "${elementId}"`,
33
+ path: `capabilities[${ci}].elements`,
34
+ });
35
+ }
36
+ }
37
+ }
38
+ }
39
+ // Validate page state changes reference existing element IDs
40
+ if (page.pageStates) {
41
+ for (let psi = 0; psi < page.pageStates.length; psi++) {
42
+ const state = page.pageStates[psi];
43
+ for (let chi = 0; chi < state.changes.length; chi++) {
44
+ const change = state.changes[chi];
45
+ if (!allIds.has(change.elementId)) {
46
+ errors.push({
47
+ code: 'UNKNOWN_ELEMENT_REF',
48
+ message: `State "${state.name}" references unknown element id "${change.elementId}"`,
49
+ path: `pageStates[${psi}].changes[${chi}].elementId`,
50
+ });
51
+ }
52
+ }
53
+ }
54
+ }
55
+ return errors;
56
+ }
57
+ function collectIds(elements, ids) {
58
+ for (const el of elements) {
59
+ ids.add(el.id);
60
+ if (el.reveals) {
61
+ collectIds(el.reveals, ids);
62
+ }
63
+ }
64
+ }
65
+ function validateElements(elements, seenIds, errors, path) {
66
+ for (let i = 0; i < elements.length; i++) {
67
+ const el = elements[i];
68
+ const elPath = `${path}.elements[${i}]`;
69
+ // Required fields
70
+ if (!el.id) {
71
+ errors.push({ code: 'MISSING_REQUIRED', message: 'Element is missing required field "id"', path: `${elPath}.id` });
72
+ }
73
+ if (!el.label) {
74
+ errors.push({ code: 'MISSING_REQUIRED', message: 'Element is missing required field "label"', path: `${elPath}.label` });
75
+ }
76
+ // Valid type
77
+ if (!VALID_ELEMENT_TYPES.has(el.type)) {
78
+ errors.push({
79
+ code: 'INVALID_TYPE',
80
+ message: `Element "${el.id}" has invalid type "${el.type}". Valid types: ${[...VALID_ELEMENT_TYPES].join(', ')}`,
81
+ path: `${elPath}.type`,
82
+ });
83
+ }
84
+ // Unique ID
85
+ if (el.id) {
86
+ if (seenIds.has(el.id)) {
87
+ errors.push({
88
+ code: 'DUPLICATE_ID',
89
+ message: `Duplicate element id "${el.id}" found at ${elPath}`,
90
+ path: `${elPath}.id`,
91
+ });
92
+ }
93
+ else {
94
+ seenIds.add(el.id);
95
+ }
96
+ }
97
+ // Recurse into reveals
98
+ if (el.reveals) {
99
+ validateElements(el.reveals, seenIds, errors, `${elPath}.reveals`);
100
+ }
101
+ }
102
+ }
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@surfaice/format",
3
+ "version": "0.0.1",
4
+ "description": "Surfaice format package",
5
+ "main": "./dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "license": "MIT",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/surfaiceai/surfaice",
11
+ "directory": "packages/format"
12
+ },
13
+ "devDependencies": {
14
+ "@types/mdast": "^4.0.4",
15
+ "typescript": "^5.5.0",
16
+ "vitest": "^2.0.0"
17
+ },
18
+ "dependencies": {
19
+ "remark-frontmatter": "^5.0.0",
20
+ "remark-parse": "^11.0.0",
21
+ "unified": "^11.0.0",
22
+ "yaml": "^2.4.0"
23
+ },
24
+ "type": "module",
25
+ "exports": {
26
+ ".": {
27
+ "import": "./dist/index.js",
28
+ "types": "./dist/index.d.ts"
29
+ }
30
+ },
31
+ "files": [
32
+ "dist",
33
+ "README.md"
34
+ ],
35
+ "publishConfig": {
36
+ "access": "public",
37
+ "registry": "https://registry.npmjs.org"
38
+ },
39
+ "scripts": {
40
+ "build": "tsc",
41
+ "test": "vitest run",
42
+ "dev": "tsc --watch"
43
+ }
44
+ }