spotr 1.0.0-alpha.1 → 1.0.0-alpha.3

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