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.
Files changed (2) hide show
  1. package/README.md +312 -0
  2. 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
+ [![npm version](https://img.shields.io/npm/v/spotr.svg)](https://www.npmjs.com/package/spotr) [![bundle size](https://img.shields.io/badge/bundle%20size-3.4%20kB%20gzipped-brightgreen)](https://bundlephobia.com/package/spotr) [![CI/CD](https://github.com/andymerskin/spotr/actions/workflows/ci.yml/badge.svg)](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.1",
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",