spotr 1.0.0-alpha.1 → 1.0.0-alpha.2
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/README.md +312 -0
- package/package.json +5 -3
package/README.md
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
<img src="./examples/shared/spotr.svg" alt="Spotr logo" width="160" />
|
|
2
|
+
|
|
3
|
+
# Spotr
|
|
4
|
+
|
|
5
|
+
A powerful and minimal client-side fuzzy search library with zero dependencies.
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/spotr) [](https://bundlephobia.com/package/spotr) [](https://github.com/andymerskin/spotr/actions/workflows/ci.yml)
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Zero Dependencies** - Custom Levenshtein implementation, no external deps
|
|
12
|
+
- **Fuzzy Matching** - Configurable threshold (0-1) for fuzzy string matching
|
|
13
|
+
- **Nested Field Support** - Dot notation for deeply nested object properties
|
|
14
|
+
- **Keywords** - Add custom filtering logic for exact keyword matches
|
|
15
|
+
- **Framework Integrations** - React, Vue, Svelte, Solid, and Preact support
|
|
16
|
+
- **TypeScript** - Full type definitions included
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
npm install spotr
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## CDN
|
|
25
|
+
|
|
26
|
+
You can use Spotr via a CDN:
|
|
27
|
+
|
|
28
|
+
- **unpkg**: `https://unpkg.com/spotr`
|
|
29
|
+
- **jsDelivr**: `https://cdn.jsdelivr.net/npm/spotr`
|
|
30
|
+
- **esm.sh**: `https://esm.sh/spotr`
|
|
31
|
+
|
|
32
|
+
## Basic Usage
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { Spotr } from 'spotr';
|
|
36
|
+
|
|
37
|
+
type Game = {
|
|
38
|
+
title: string;
|
|
39
|
+
genres: string[];
|
|
40
|
+
releaseYear: number;
|
|
41
|
+
completed: boolean;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const games = new Spotr<Game>({
|
|
45
|
+
collection: gamesArray,
|
|
46
|
+
threshold: 0.3,
|
|
47
|
+
fields: [
|
|
48
|
+
{ name: 'title', weight: 1 },
|
|
49
|
+
{ name: 'releaseYear', weight: 0.8 },
|
|
50
|
+
],
|
|
51
|
+
limit: 20,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const { results, matchedKeywords, tokens, warnings } = games.query('witcher');
|
|
55
|
+
// results: Array<{ item: Game, score: number }>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Options
|
|
59
|
+
|
|
60
|
+
### `collection` (required)
|
|
61
|
+
|
|
62
|
+
Array or Set of objects to search.
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
collection: gamesArray; // or new Set(gamesArray)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### `fields` (required)
|
|
69
|
+
|
|
70
|
+
Properties to search against with weight configuration. Supports dot notation for nested objects.
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
fields: [
|
|
74
|
+
'title', // string shorthand, weight: 1
|
|
75
|
+
{ name: 'title', weight: 1 }, // full config
|
|
76
|
+
{ name: 'email', weight: 0.7 }, // lower priority
|
|
77
|
+
{ name: 'address.city', weight: 0.8 }, // nested field
|
|
78
|
+
];
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### `keywords` (optional)
|
|
82
|
+
|
|
83
|
+
Keywords that trigger specific collection filtering before fuzzy matching.
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
keywords: {
|
|
87
|
+
mode: 'and',
|
|
88
|
+
definitions: [
|
|
89
|
+
{
|
|
90
|
+
name: 'completed',
|
|
91
|
+
triggers: ['done', 'complete', 'finished'],
|
|
92
|
+
handler: (collection) => collection.filter(item => item.completed),
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
name: 'platform',
|
|
96
|
+
triggers: ['ps4', 'ps5', 'xbox', 'pc', 'switch'],
|
|
97
|
+
handler: (collection, matchedTerms) =>
|
|
98
|
+
collection.filter(item =>
|
|
99
|
+
matchedTerms.some(term =>
|
|
100
|
+
item.platforms.some(p => p.toLowerCase().includes(term))
|
|
101
|
+
)
|
|
102
|
+
),
|
|
103
|
+
},
|
|
104
|
+
],
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### `threshold` (optional)
|
|
109
|
+
|
|
110
|
+
Global fuzzy matching threshold. Default: `0.3`
|
|
111
|
+
|
|
112
|
+
- `0` = match anything (no filtering)
|
|
113
|
+
- `0.3` = recommended default
|
|
114
|
+
- `1` = exact match required
|
|
115
|
+
|
|
116
|
+
### `limit` (optional)
|
|
117
|
+
|
|
118
|
+
Maximum number of results to return. Default: `Infinity`
|
|
119
|
+
|
|
120
|
+
### `caseSensitive` (optional)
|
|
121
|
+
|
|
122
|
+
Enable case-sensitive matching. Default: `false`
|
|
123
|
+
|
|
124
|
+
### `minMatchCharLength` (optional)
|
|
125
|
+
|
|
126
|
+
Minimum query length to trigger matching. Default: `1`
|
|
127
|
+
|
|
128
|
+
### `maxStringLength` (optional)
|
|
129
|
+
|
|
130
|
+
Maximum string length limit for both search query tokens and collection field values before truncation. Default: `1000`
|
|
131
|
+
|
|
132
|
+
- Search query tokens (search terms) exceeding this limit are truncated
|
|
133
|
+
- Collection field values exceeding this limit are truncated
|
|
134
|
+
- Warnings are added to `result.warnings` when truncation occurs
|
|
135
|
+
- Used for performance optimization to prevent slowdowns with very long strings in fuzzy matching
|
|
136
|
+
|
|
137
|
+
## Result Type
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
interface SpotrResult<T> {
|
|
141
|
+
results: ScoredResult<T>[]; // Array of { item, score }
|
|
142
|
+
matchedKeywords: MatchedKeyword[]; // Keywords that matched
|
|
143
|
+
tokens: string[]; // Non-keyword search terms
|
|
144
|
+
warnings: string[]; // Warnings (e.g., missing nested paths)
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## React Hook
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
import { useSpotr } from 'spotr/react';
|
|
152
|
+
|
|
153
|
+
function GameSearch({ games, searchQuery }) {
|
|
154
|
+
const spotr = useSpotr({
|
|
155
|
+
collection: games,
|
|
156
|
+
fields: [{ name: 'title', weight: 1 }],
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
const { results } = useMemo(
|
|
160
|
+
() => spotr.query(searchQuery),
|
|
161
|
+
[spotr, searchQuery]
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
return <ResultsTable results={results} />;
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Vue Composable
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
import { useSpotr } from 'spotr/vue';
|
|
172
|
+
|
|
173
|
+
const games = ref<Game[]>([]);
|
|
174
|
+
const query = ref('');
|
|
175
|
+
|
|
176
|
+
const spotr = useSpotr(() => ({
|
|
177
|
+
collection: games.value,
|
|
178
|
+
fields: [{ name: 'title', weight: 1 }],
|
|
179
|
+
}));
|
|
180
|
+
|
|
181
|
+
const results = computed(() => spotr.value?.query(query.value));
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Svelte Store
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
import { createSpotr } from 'spotr/svelte';
|
|
188
|
+
|
|
189
|
+
const { spotr, query, results } = createSpotr({
|
|
190
|
+
collection: games,
|
|
191
|
+
fields: [{ name: 'title', weight: 1 }],
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// query is a writable store, results is a derived store
|
|
195
|
+
// Use $query and $results in your Svelte template
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Solid Hook
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
import { createSpotr } from 'spotr/solid';
|
|
202
|
+
|
|
203
|
+
const { query, setQuery, results } = createSpotr({
|
|
204
|
+
collection: games,
|
|
205
|
+
fields: [{ name: 'title', weight: 1 }],
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// query() is a signal getter, setQuery is a signal setter, results() is a memo
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Preact Hook
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
import { useSpotr } from 'spotr/preact';
|
|
215
|
+
|
|
216
|
+
function GameSearch({ games, searchQuery }) {
|
|
217
|
+
const spotr = useSpotr({
|
|
218
|
+
collection: games,
|
|
219
|
+
fields: [{ name: 'title', weight: 1 }],
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
const { results } = useMemo(
|
|
223
|
+
() => spotr.query(searchQuery),
|
|
224
|
+
[spotr, searchQuery]
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
return <ResultsTable results={results} />;
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## API Methods
|
|
232
|
+
|
|
233
|
+
### `query(search: string): SpotrResult<T>`
|
|
234
|
+
|
|
235
|
+
Search the collection with a string query.
|
|
236
|
+
|
|
237
|
+
### `queryAsync(search: string): Promise<SpotrResult<T>>`
|
|
238
|
+
|
|
239
|
+
Async search with optional debouncing.
|
|
240
|
+
|
|
241
|
+
### `setCollection(collection: T[] | Set<T>): void`
|
|
242
|
+
|
|
243
|
+
Update the collection.
|
|
244
|
+
|
|
245
|
+
### `collection: T[]` (getter)
|
|
246
|
+
|
|
247
|
+
Access the current collection.
|
|
248
|
+
|
|
249
|
+
## Development
|
|
250
|
+
|
|
251
|
+
This project uses [Bun](https://bun.sh) as the package manager. After cloning the repository, install dependencies with:
|
|
252
|
+
|
|
253
|
+
```sh
|
|
254
|
+
bun install
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Core Scripts
|
|
258
|
+
|
|
259
|
+
- `bun run build` - Build the library (outputs to `dist/`)
|
|
260
|
+
- `bun run test` - Run all tests once
|
|
261
|
+
- `bun run test:watch` - Run tests in watch mode
|
|
262
|
+
- `bun run test:coverage` - Run tests with coverage report
|
|
263
|
+
- `bun run typecheck` - Type check the library (`tsc --noEmit`)
|
|
264
|
+
- `bun run audit` - Audit the Spotr package for vulnerabilities and apply fixes (`scripts/audit-spotr.ts`)
|
|
265
|
+
- `bun run lint` - Lint the codebase (ESLint)
|
|
266
|
+
- `bun run format` - Format the codebase (Prettier, whole repo from root)
|
|
267
|
+
- `bun run format:check` - Check formatting without modifying
|
|
268
|
+
- `bun run validate` - Full validation (format, lint, typecheck, coverage, examples, build)
|
|
269
|
+
- `bun run size:check` - Verify bundle size under 5KB
|
|
270
|
+
- `bun run clean` - Remove dist and coverage directories
|
|
271
|
+
|
|
272
|
+
### Example Scripts
|
|
273
|
+
|
|
274
|
+
- `bun run examples:typecheck` - Type check all example applications
|
|
275
|
+
- `bun run examples:sync` - Sync shared files from `examples/shared/` to all examples
|
|
276
|
+
- `bun run examples:install` - Install dependencies for all examples
|
|
277
|
+
- `bun run examples:update` - Update dependencies for all examples
|
|
278
|
+
- `bun run examples:audit` - Audit all example packages for vulnerabilities and apply fixes (`scripts/audit-examples.ts`)
|
|
279
|
+
- `bun run examples:dev` - Launch interactive dev servers for a selected framework (starts all 5 examples on ports 5173-5177)
|
|
280
|
+
|
|
281
|
+
### Setup Scripts
|
|
282
|
+
|
|
283
|
+
- `bun run prepare` - Setup Husky git hooks (runs automatically on install)
|
|
284
|
+
|
|
285
|
+
## Releasing
|
|
286
|
+
|
|
287
|
+
To release a new version to npm, use the release script:
|
|
288
|
+
|
|
289
|
+
```sh
|
|
290
|
+
bun run release
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
The script automates the release process: it runs `validate` (format check, lint, typecheck, test coverage, examples typecheck, and build) and a bundle size check (enforcing a 5KB gzipped limit) before proceeding with version bumping, commit amending, and publishing.
|
|
294
|
+
|
|
295
|
+
**Note:** Due to a known npm bug with workspaces, `npm version` may not automatically commit and tag changes when the package.json is in a subdirectory. The release script automatically detects this and creates the commit and tag manually if needed.
|
|
296
|
+
|
|
297
|
+
You can also pass a version bump type directly:
|
|
298
|
+
|
|
299
|
+
```sh
|
|
300
|
+
bun run release patch # for patch releases
|
|
301
|
+
bun run release minor # for minor releases
|
|
302
|
+
bun run release major # for major releases
|
|
303
|
+
bun run release prerelease # increment existing pre-release
|
|
304
|
+
bun run release premajor --preid beta # start major pre-release with beta tag
|
|
305
|
+
bun run release preminor --preid=rc # start minor pre-release with rc tag
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
See [CONTRIBUTING.md](./CONTRIBUTING.md) for the complete release process and manual release steps.
|
|
309
|
+
|
|
310
|
+
## License
|
|
311
|
+
|
|
312
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spotr",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.2",
|
|
4
4
|
"description": "A powerful and minimal client-side fuzzy search library",
|
|
5
5
|
"author": "Andy Merskin <andymerskin@gmail.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
"type": "module",
|
|
18
18
|
"sideEffects": false,
|
|
19
19
|
"files": [
|
|
20
|
-
"dist"
|
|
20
|
+
"dist",
|
|
21
|
+
"README.md"
|
|
21
22
|
],
|
|
22
23
|
"exports": {
|
|
23
24
|
".": {
|
|
@@ -52,7 +53,8 @@
|
|
|
52
53
|
}
|
|
53
54
|
},
|
|
54
55
|
"scripts": {
|
|
55
|
-
"prepack": "bun run build",
|
|
56
|
+
"prepack": "rm -f README.md && cp ../../README.md README.md && bun run build",
|
|
57
|
+
"postpack": "rm -f README.md && ln -s ../../README.md README.md",
|
|
56
58
|
"prepublishOnly": "cd ../.. && bun run format:check && bun run lint && bun run typecheck && bun run test:coverage && bun run examples:typecheck",
|
|
57
59
|
"build": "vite build",
|
|
58
60
|
"test": "vitest run",
|