data-path 1.0.3 → 2.0.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.
package/LICENSE CHANGED
@@ -1,8 +1,22 @@
1
- ISC License (ISC)
1
+ MIT License
2
2
 
3
3
  Copyright (c) 2026 Sergei Shmakov
4
4
  https://github.com/sergeyshmakov
5
5
 
6
- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
7
12
 
8
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
package/README.md CHANGED
@@ -1,446 +1,97 @@
1
1
  # data-path
2
2
 
3
- A simple, modern, zero-dependency, and fully type-safe library for building, comparing, and manipulating object property paths using TypeScript lambda expressions.
3
+ Type-safe object property paths in TypeScript build, compare, and manipulate with lambda expressions. Zero dependencies.
4
4
 
5
- [![npm version](https://badge.fury.io/js/data-path.svg)](https://badge.fury.io/js/data-path)
6
- [![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](https://opensource.org/licenses/ISC)
5
+ [![npm version](https://img.shields.io/npm/v/data-path.svg)](https://www.npmjs.com/package/data-path)
6
+ [![CI](https://github.com/sergeyshmakov/data-path/actions/workflows/pr.yml/badge.svg)](https://github.com/sergeyshmakov/data-path/actions/workflows/pr.yml)
7
+ [![Bundle size](https://img.shields.io/bundlephobia/minzip/data-path)](https://bundlephobia.com/package/data-path)
8
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-3178c6.svg?logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
9
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
10
 
8
- ## ⚠️ The Problem
11
+ **Documentation:** https://sergeyshmakov.github.io/data-path/
9
12
 
10
- When working with deep object structures, forms, or nested state, we often rely on string-based paths (e.g., `"users.0.name"` or `"company.departments[1].budget"`). This approach is fundamentally flawed in modern TypeScript development:
13
+ ---
11
14
 
12
- - **No Type Safety:** String paths are opaque to the compiler. A typo goes unnoticed until runtime.
13
- - **Refactoring Nightmares:** Renaming a property doesn't update string literals scattered across your codebase.
14
- - **No Autocomplete:** Your IDE cannot guide you through the object structure.
15
- - **No Mathematics:** It is difficult to programmatically determine if path `A` is a child of path `B`, or if they overlap.
15
+ ## Before / after
16
16
 
17
- ## 🚀 The Solution
18
-
19
- `data-path` solves this by using **Proxy-based lambda expressions** to capture paths. It gives you 100% type safety, IDE autocomplete, and a rich API for interacting with data and other paths.
20
-
21
- ```typescript
22
- import { path } from "data-path";
23
-
24
- type User = {
25
- id: string;
26
- profile: {
27
- firstName: string;
28
- lastName: string;
29
- };
30
- tags: string[];
31
- };
32
-
33
- // 1. Create a path with full IDE autocomplete
34
- const firstNamePath = path<User>(p => p.profile.firstName);
35
-
36
- // 2. Output as a string (e.g., for form libraries)
37
- console.log(firstNamePath.$); // "profile.firstName"
38
-
39
- // 3. Read data safely (no "Cannot read properties of undefined" errors)
40
- const user = {
41
- id: "1",
42
- profile: { firstName: "Alice", lastName: "Smith" },
43
- tags: [],
44
- };
45
- console.log(firstNamePath.get(user)); // "Alice"
46
-
47
- // 4. Update data immutably (returns a new structural clone)
48
- const updatedUser = firstNamePath.set(user, "Bob");
49
- console.log(updatedUser.profile.firstName); // "Bob"
17
+ ```ts
18
+ // Before — string literals, invisible to the compiler
19
+ register("users.0.profile.firstName");
20
+ table.getColumn("contact.email");
21
+ set(state => ({ ...state, settings: { ...state.settings, theme } }));
50
22
  ```
51
23
 
52
- ## 📦 Installation
53
-
54
- ```bash
55
- npm install data-path
24
+ ```ts
25
+ // After — typed, IDE-autocompleted, refactor-safe
26
+ register(path((u: FormData) => u.users[0].profile.firstName).$);
27
+ table.getColumn(emailPath.$);
28
+ set(state => themePath.set(state, theme));
56
29
  ```
57
30
 
58
- _(or use `yarn add data-path`, `pnpm add data-path`, `bun add data-path`)_
59
-
60
- ## 🤖 AI Ready
31
+ ## What it is
61
32
 
62
- This package is available in [Context7](https://context7.com/) MCP, so AI assistants can load it directly into context when working with your object property paths.
33
+ A zero-dependency TypeScript library that captures object property paths via proxy-based lambdas. Build a path once use it as a string, read and write data through it, compose paths together, or match one against another.
63
34
 
64
- A [Cubic wiki](https://www.cubic.dev/wikis/sergeyshmakov/data-path) provides AI-ready documentation for this project.
35
+ - Typed root to leaf renaming a property breaks the path at compile time
36
+ - Safe `get`, immutable `set`, read-modify-write `update`
37
+ - Template paths (`each`, `deep`) for bulk operations across collections and trees
38
+ - Path algebra: `merge`, `subtract`, `slice`, `to`
39
+ - Runtime indices and closure variables work natively inside lambdas
65
40
 
66
- It also ships an [Agent Skills](https://agentskills.io/) – compatible skill. Install it so your AI assistant loads data-path guidance:
41
+ ## Install
67
42
 
68
43
  ```bash
69
- npx ctx7 skills install /sergeyshmakov/data-path data-path
70
- ```
71
-
72
- The skill lives in [skills/data-path/SKILL.md](skills/data-path/SKILL.md).
73
-
74
- ## 💡 Philosophy
75
-
76
- - **Stack Agnostic:** Pure data manipulation. Works perfectly with React, Vue, Node.js, or vanilla JavaScript.
77
- - **Zero Dependencies:** A tiny, efficient footprint that doesn't bloat your bundle.
78
- - **Fully Type-Safe:** Built strictly for TypeScript. If the structure changes, the compiler will instantly catch broken paths.
79
- - **Immutable:** All `.set()` operations return structurally cloned objects, making it the perfect companion for modern state managers (Redux, Zustand) and reactive frameworks.
80
-
81
- ## 📚 Core API
82
-
83
- ### 🏷️ API Cheatsheet
84
-
85
- | API | Description |
86
- |-----|-------------|
87
- | **Creation** | |
88
- | `path<T>()` | Create root path |
89
- | `path<T>(p => p.a.b)` | Create path from lambda, generic type |
90
- | `path((p: T) => p.a.b)` | Create path from lambda, infer type |
91
- | `path(base, p => p.c)` | Extend existing path |
92
- | `unsafePath<T>("a.b")` | Create path from raw string |
93
- | **Properties** | |
94
- | `path.$` | String representation (e.g. `"users.0.name"`) |
95
- | `path.segments` | Array of segments |
96
- | `path.length` | Number of segments |
97
- | `path.fn` | Accessor function for `.map()`, `.filter()` |
98
- | **Data Access** | |
99
- | `path.get(data)` | Read value at path (returns `undefined` if missing) |
100
- | `path.set(data, value)` | Immutable write, returns new object |
101
- | **Traversal** | |
102
- | `path.to(p => p.x)` | Extend path from current value |
103
- | `path.each(p => p.x)` | Template: match all items in collection |
104
- | `path.each().to(p => p.x)` | Same as above |
105
- | `path.deep(node => node.id)` | Template: match property at any depth |
106
- | **Manipulation** | |
107
- | `path.merge(other)` | Append path (deduplicates overlap) |
108
- | `path.subtract(other)` | Remove prefix/suffix, or `null` |
109
- | `path.slice(start?, end?)` | Slice segments (like `Array.prototype.slice`) |
110
- | **Relational** | |
111
- | `path.startsWith(other)` | True if path is prefix |
112
- | `path.includes(other)` | True if path contains other |
113
- | `path.equals(other)` | True if paths are identical |
114
- | `path.match(other)` | Returns `{ relation, params }` or `null` |
115
- | **Template-only** | |
116
- | `templatePath.expand(data)` | Resolve template to concrete paths |
117
-
118
- ### Path Creation
119
-
120
- You can create paths starting from the root of a type, using a lambda expression, or unsafely from a raw string.
121
-
122
- ```typescript
123
- // From root
124
- const root = path<User>();
125
- const profile = root.to(p => p.profile);
126
-
127
- // Direct lambda
128
- const tagsPath = path<User>(p => p.tags[0]);
129
-
130
- // Infer type from argument (often preferred)
131
- const lastNamePath = path((user: User) => user.profile.lastName);
132
-
133
- // From raw string (dynamic contexts)
134
- import { unsafePath } from "data-path";
135
- const dynamicPath = unsafePath<User>("profile.firstName");
136
- ```
137
-
138
- ### Data Access
139
-
140
- ```typescript
141
- const namePath = path<User>(p => p.profile.firstName);
142
-
143
- // Read
144
- namePath.get(user); // "Alice"
145
-
146
- // Accessor shorthand (perfect for arrays)
147
- const users = [user1, user2];
148
- const names = users.map(namePath.fn);
149
-
150
- // Write (Immutable)
151
- const updated = namePath.set(user, "Alice 2.0");
152
- ```
153
-
154
- ### Manipulation
155
-
156
- You can programmatically compose, subtract, and slice paths. This is ideal when working with reusable components that don't know their absolute location in a global store.
157
-
158
- ```typescript
159
- // 1. We have a specific record's path
160
- const employeePath = path<Company>(p => p.departments[0].employees[5]);
161
-
162
- // 2. We have a generic sub-path
163
- const nameSubPath = path<Employee>(p => p.profile.firstName);
164
-
165
- // Merge them! (Smart deduplication if they overlap)
166
- const absoluteNamePath = employeePath.merge(nameSubPath);
167
- console.log(absoluteNamePath.$); // "departments.0.employees.5.profile.firstName"
168
-
169
- // Subtract a base path to find the relative path
170
- const relative = absoluteNamePath.subtract(employeePath);
171
- console.log(relative?.$); // "profile.firstName"
172
-
173
- // Slice segments (just like Array.prototype.slice)
174
- const sliced = absoluteNamePath.slice(0, 3);
175
- console.log(sliced.$); // "departments.0.employees"
176
- ```
177
-
178
- ### Templates & Wildcards
179
-
180
- Templates allow you to target multiple elements at once (e.g., all items in an array, or all properties matching a name deep in a tree). This unlocks powerful bulk-operations.
181
-
182
- ```typescript
183
- type AppData = { users: Array<{ id: string; name: string }> };
184
- const data: AppData = {
185
- users: [
186
- { id: "1", name: "Alice" },
187
- { id: "2", name: "Bob" },
188
- ],
189
- };
190
-
191
- // 1. Create a template targeting ALL users
192
- const allUsersPath = path<AppData>(p => p.users).each();
193
- console.log(allUsersPath.$); // "users.*"
194
-
195
- // 2. Create a template targeting ALL user names
196
- const allNamesPath = path<AppData>(p => p.users).each(u => u.name);
197
- console.log(allNamesPath.$); // "users.*.name"
198
-
199
- // 3. Bulk Read: extract an array of all matched values
200
- const names = allNamesPath.get(data);
201
- console.log(names); // ["Alice", "Bob"]
202
-
203
- // 4. Bulk Write: immutably update all matches in one go!
204
- const anonymizedData = allNamesPath.set(data, "Hidden");
205
- console.log(anonymizedData.users[0].name); // "Hidden"
206
- console.log(anonymizedData.users[1].name); // "Hidden"
207
-
208
- // 5. Expand: resolve the template into concrete paths based on actual data
209
- const concretePaths = allNamesPath.expand(data);
210
- console.log(concretePaths.map(p => p.$));
211
- // ["users.0.name", "users.1.name"]
212
-
213
- // 6. Deep Scan: target a property at any depth
214
- const deepIds = path<AppData>().deep(node => node.id);
215
- console.log(deepIds.$); // "**.id"
216
- ```
217
-
218
- ### Runtime Variables & Indices
219
-
220
- Because `data-path` executes the lambda once during creation to build the path, you can seamlessly use runtime variables, indexers, and local scope variables directly inside the path definition.
221
-
222
- ```typescript
223
- function getUserPropertyPath(userIdx: number, property: "name" | "email") {
224
- // Capture function arguments directly in the path!
225
- return path<AppData>(p => p.users[userIdx][property]);
226
- }
227
-
228
- const namePath = getUserPropertyPath(2, "name");
229
- console.log(namePath.$); // "users.2.name"
230
-
231
- const emailPath = getUserPropertyPath(5, "email");
232
- console.log(emailPath.$); // "users.5.email"
233
- ```
234
-
235
- ### Relational Algebra
236
-
237
- Path algebra is extremely useful for permissions, validation, and complex UI logic where you need to know how two paths relate to each other.
238
-
239
- ```typescript
240
- const userPath = path<User>();
241
- const profilePath = userPath.to(p => p.profile);
242
- const namePath = userPath.to(p => p.profile.firstName);
243
-
244
- // Check relationships
245
- namePath.startsWith(profilePath); // true
246
- profilePath.includes(namePath); // true
247
- namePath.includes(profilePath); // false
248
- namePath.equals(namePath); // true
249
-
250
- // .match() provides detailed relational context:
251
- // returns 'includes', 'included-by', 'equals', 'parent', 'child', or null
252
- namePath.match(profilePath); // { relation: 'child', params: {} }
253
- profilePath.match(namePath); // { relation: 'parent', params: {} }
254
- ```
255
-
256
- ### Utility Types
257
-
258
- The library exports several TypeScript types that are useful when writing helper functions or React components that accept paths as props:
259
-
260
- ```typescript
261
- import type { Path, TemplatePath, ResolvedType } from "data-path";
262
-
263
- // 1. Accept a specific path structure
264
- function NameInput({ fieldPath }: { fieldPath: Path<User, string> }) {
265
- return <input name={fieldPath.$} />;
266
- }
267
-
268
- // 2. Extract the resolved value type from an existing path
269
- const agePath = path<User>(p => p.profile.age);
270
- type Age = ResolvedType<typeof agePath>; // number
271
- ```
272
-
273
- ## 💼 Real-World Examples
274
-
275
- ### 1. React Hook Form
276
-
277
- Bind deeply nested fields with 100% type safety. Use runtime indices (like `i` from `useFieldArray`'s map) directly inside the path lambda. React Hook Form's `register` accepts dot-notation names (e.g. `users.0.firstName`).
278
-
279
- ```tsx
280
- import { useForm, useFieldArray } from "react-hook-form";
281
- import { path } from "data-path";
282
-
283
- type FormValues = { users: Array<{ id: string; firstName: string }> };
284
-
285
- function UsersForm() {
286
- const { register, control } = useForm<FormValues>({
287
- defaultValues: { users: [{ id: "1", firstName: "" }] },
288
- });
289
- const { fields } = useFieldArray({ control, name: "users" });
290
-
291
- return (
292
- <form>
293
- {fields.map((field, i) => {
294
- const namePath = path<FormValues>(p => p.users[i].firstName);
295
- return (
296
- <input
297
- key={field.id}
298
- {...register(namePath.$)}
299
- placeholder="First name"
300
- />
301
- );
302
- })}
303
- </form>
304
- );
305
- }
306
- ```
307
-
308
- ### 2. TanStack Form
309
-
310
- Define exact field accessors for complex interactive forms, even inside deeply nested arrays. TanStack Form's `Field` uses `name` (dot-notation for nested paths) and a render prop with `field.state.value` and `field.handleChange`.
311
-
312
- ```tsx
313
- import { useForm } from "@tanstack/react-form";
314
- import { path } from "data-path";
315
-
316
- type FormValues = { users: Array<{ firstName: string }> };
317
-
318
- function UsersForm() {
319
- const form = useForm<FormValues>({
320
- defaultValues: { users: [{ firstName: "" }] },
321
- onSubmit: ({ value }) => console.log(value),
322
- });
323
-
324
- return (
325
- <form>
326
- {form.state.values.users.map((_, i) => {
327
- const namePath = path<FormValues>(p => p.users[i].firstName);
328
- return (
329
- <form.Field key={i} name={namePath.$}>
330
- {field => (
331
- <input
332
- name={field.name}
333
- value={field.state.value}
334
- onChange={e =>
335
- field.handleChange(e.target.value)
336
- }
337
- />
338
- )}
339
- </form.Field>
340
- );
341
- })}
342
- </form>
343
- );
344
- }
345
- ```
346
-
347
- ### 3. Zustand
348
-
349
- Update deeply nested state easily without needing Immer. Pass a function to `set` that receives the current state and returns the updated state via `path.set()`.
350
-
351
- ```typescript
352
- import { create } from "zustand";
353
- import { path } from "data-path";
354
-
355
- type StoreState = {
356
- settings: { profile: { theme: string } };
357
- setTheme: (theme: string) => void;
358
- };
359
-
360
- const themePath = path<StoreState>(p => p.settings.profile.theme);
361
-
362
- const useStore = create<StoreState>(set => ({
363
- settings: { profile: { theme: "light" } },
364
- setTheme: newTheme => set(state => themePath.set(state, newTheme)),
365
- }));
44
+ npm install data-path
366
45
  ```
367
46
 
368
- ### 4. React `useState`
47
+ Requirements: Node `>=20`, TypeScript `>=5.0`
369
48
 
370
- Update deeply nested state with the functional updater. React requires a new object reference; `path.set()` returns a structural clone so you avoid manual spreading.
49
+ ## Quick start
371
50
 
372
- ```tsx
373
- import { useState } from "react";
51
+ ```ts
374
52
  import { path } from "data-path";
375
53
 
376
- type AppState = { settings: { profile: { theme: string } } };
377
-
378
- const themePath = path<AppState>(p => p.settings.profile.theme);
54
+ type User = { profile: { firstName: string; lastName: string }; tags: string[] };
379
55
 
380
- function ThemeToggle() {
381
- const [state, setState] = useState<AppState>({
382
- settings: { profile: { theme: "light" } },
383
- });
56
+ const firstNamePath = path((u: User) => u.profile.firstName);
384
57
 
385
- const setTheme = (theme: string) => {
386
- setState(prev => themePath.set(prev, theme));
387
- };
388
-
389
- return <button onClick={() => setTheme("dark")}>{state.settings.profile.theme}</button>;
390
- }
58
+ firstNamePath.$ // "profile.firstName"
59
+ firstNamePath.get(user) // "Alice" | undefined
60
+ firstNamePath.set(user, "Bob") // returns a new User — original unchanged
61
+ firstNamePath.fn // stable (u: User) => string | undefined
391
62
  ```
392
63
 
393
- ### 5. Zod / Validation Mapping
394
-
395
- Map Zod validation errors to specific UI fields. Zod's `ZodError` has an `issues` array; each issue has a `path` (e.g. `["user", "age"]`) that you can join to compare with `data-path`.
64
+ ## Works well with
396
65
 
397
- ```typescript
398
- import { z } from "zod";
399
- import { unsafePath, path } from "data-path";
66
+ | Package | How it helps |
67
+ |---------|--------------|
68
+ | [React Hook Form](https://sergeyshmakov.github.io/data-path/integrations/react-hook-form/) | Type-safe field names for `register`, `watch`, `setValue` |
69
+ | [TanStack Form](https://sergeyshmakov.github.io/data-path/integrations/tanstack-form/) | Typed field names with runtime index support |
70
+ | [TanStack Table](https://sergeyshmakov.github.io/data-path/integrations/tanstack-table/) | Typed column accessors — no manual `id` strings |
71
+ | [Zustand](https://sergeyshmakov.github.io/data-path/integrations/zustand/) | Immutable nested state updates without Immer |
72
+ | [Zod](https://sergeyshmakov.github.io/data-path/integrations/zod/) | Map `ZodError.issues` paths to specific form fields |
73
+ | [React `useState`](https://sergeyshmakov.github.io/data-path/integrations/react-usestate/) | Structural clones for deeply nested state |
400
74
 
401
- const schema = z.object({
402
- user: z.object({ age: z.number().min(18) }),
403
- });
404
- type FormData = z.infer<typeof schema>;
405
- const agePath = path<FormData>(p => p.user.age);
75
+ ## Guides
406
76
 
407
- const result = schema.safeParse(data);
408
- if (!result.success) {
409
- for (const issue of result.error.issues) {
410
- const errorPath = unsafePath<FormData>(issue.path.join("."));
411
- if (errorPath.equals(agePath)) {
412
- console.log("Age must be at least 18!");
413
- }
414
- }
415
- }
416
- ```
77
+ - [Data access](https://sergeyshmakov.github.io/data-path/guides/data-access/) — `get`, `set`, `update`, `fn`
78
+ - [Templates](https://sergeyshmakov.github.io/data-path/guides/templates/) — `each`, `deep`, bulk writes across collections
79
+ - [Path algebra](https://sergeyshmakov.github.io/data-path/guides/path-algebra/) — `merge`, `subtract`, `slice`, `to`
80
+ - [Relational](https://sergeyshmakov.github.io/data-path/guides/relational/) — `startsWith`, `covers`, `match`
81
+ - [Runtime variables](https://sergeyshmakov.github.io/data-path/guides/runtime-variables/) — dynamic indices and closures
417
82
 
418
- ### 6. TanStack Table
83
+ ## AI tooling
419
84
 
420
- Define type-safe column accessors. `createColumnHelper.accessor()` accepts an accessor function; use `path.fn` for the extractor and `path.$` for the column `id` (required when using an accessor function).
85
+ This package is available in [Context7](https://context7.com/) and documented in a [Cubic wiki](https://www.cubic.dev/wikis/sergeyshmakov/data-path). An [Agent Skills](https://agentskills.io/)-compatible skill is included:
421
86
 
422
- ```typescript
423
- import { createColumnHelper } from "@tanstack/react-table";
424
- import { path } from "data-path";
425
-
426
- type User = { id: string; contact: { email: string } };
427
-
428
- const columnHelper = createColumnHelper<User>();
429
- const emailPath = path<User>(p => p.contact.email);
430
-
431
- const columns = [
432
- columnHelper.accessor(emailPath.fn, {
433
- id: emailPath.$,
434
- header: "Email",
435
- cell: info => info.getValue(),
436
- }),
437
- ];
87
+ ```bash
88
+ npx ctx7 skills install /sergeyshmakov/data-path data-path
438
89
  ```
439
90
 
440
- ## 🤝 Contributing
91
+ ## Contributing
441
92
 
442
- We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for more details.
93
+ See [CONTRIBUTING.md](CONTRIBUTING.md).
443
94
 
444
- ## 📄 License
95
+ ## License
445
96
 
446
- This project is licensed under the [ISC License](LICENSE).
97
+ [MIT License](LICENSE)