search-gold 0.9.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/README.md +111 -0
- package/package.json +39 -0
- package/src/search.ts +66 -0
package/README.md
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# search-gold
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Your page displays a list columnar information, based on the ubiquitous data stucture:
|
|
6
|
+
|
|
7
|
+
`array of objects`
|
|
8
|
+
|
|
9
|
+
For an improved UX, you want to add a search field so the user can filter the displayed records.
|
|
10
|
+
|
|
11
|
+
The `search-gold` package is easy to set up and use!
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
$ npm i search-gold
|
|
17
|
+
```
|
|
18
|
+
OR
|
|
19
|
+
```bash
|
|
20
|
+
$ bun i search-gold
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
The following should provide enough to get things up and running.
|
|
26
|
+
|
|
27
|
+
`my-data-list-component.ts`
|
|
28
|
+
```javascript
|
|
29
|
+
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
30
|
+
// 1. Import the two types and methods.
|
|
31
|
+
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
32
|
+
import {
|
|
33
|
+
ConfigParams,
|
|
34
|
+
ItemRecord,
|
|
35
|
+
searchHandler,
|
|
36
|
+
searchSetConfig,
|
|
37
|
+
} from 'search-gold'
|
|
38
|
+
|
|
39
|
+
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
40
|
+
// 2. Create a method to set search configuration.
|
|
41
|
+
//
|
|
42
|
+
// The configuration will be custom for your data.
|
|
43
|
+
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
44
|
+
const initSearchConfig = () => {
|
|
45
|
+
const config: ConfigParams = {
|
|
46
|
+
// required: items must reference an array of
|
|
47
|
+
// objects.
|
|
48
|
+
//
|
|
49
|
+
// 'search-gold' package only reads this data.
|
|
50
|
+
// Results are returned in a separate array.
|
|
51
|
+
items: MyData,
|
|
52
|
+
|
|
53
|
+
// required: keys are the object properties you
|
|
54
|
+
// want to expose to search queries. List only
|
|
55
|
+
// the fields which make sense to you.
|
|
56
|
+
keys: ['productName', 'model', 'status'],
|
|
57
|
+
|
|
58
|
+
// optional: default is false
|
|
59
|
+
caseSensitive: true,
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
searchSetConfig(config)
|
|
63
|
+
|
|
64
|
+
// Passing an empty string effectively returns
|
|
65
|
+
// the unfiltered set, the same as MyData.
|
|
66
|
+
//
|
|
67
|
+
// How you set up your component to display
|
|
68
|
+
// the filtered data is up to you and how
|
|
69
|
+
// your component is structured. This is just
|
|
70
|
+
// an example.
|
|
71
|
+
myFilteredData = <ItemRecord[]>searchHandler('')
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
75
|
+
// 3. Initialize the search engine.
|
|
76
|
+
//
|
|
77
|
+
// This method (from step 2.) should be called when
|
|
78
|
+
// your module is initialized and your data is ready.
|
|
79
|
+
//
|
|
80
|
+
// Also, it should be called whenever your data
|
|
81
|
+
// changes via record create, edit, or delete.
|
|
82
|
+
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
83
|
+
initSearchConfig()
|
|
84
|
+
|
|
85
|
+
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
86
|
+
// 4. Perform search queries (finally!)
|
|
87
|
+
//
|
|
88
|
+
// Presumably, a method is binded to the UI's
|
|
89
|
+
// search field (in this example it's called
|
|
90
|
+
// 'searchMyData') and is called on every keystroke.
|
|
91
|
+
//
|
|
92
|
+
// You can optionally add a debounce in your component.
|
|
93
|
+
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
94
|
+
const searchMyData = (query: string) => {
|
|
95
|
+
myFilteredData = searchHandler(query)
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
During development, I integrated this `search-gold` package
|
|
100
|
+
in a couple of apps I had built using Vue and its Composition API.
|
|
101
|
+
|
|
102
|
+
I tried to keep the Usage code examples framework
|
|
103
|
+
neutral.
|
|
104
|
+
|
|
105
|
+
# Credits
|
|
106
|
+
|
|
107
|
+
G. Gold
|
|
108
|
+
|
|
109
|
+
October 2025
|
|
110
|
+
|
|
111
|
+
Have fun!
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "search-gold",
|
|
3
|
+
"private": false,
|
|
4
|
+
"version": "0.9.0",
|
|
5
|
+
"description": "speedy search engine targeting an array of objects",
|
|
6
|
+
"author": "gold",
|
|
7
|
+
"main": "./src/search.ts",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"files": [
|
|
10
|
+
"./src/search.ts"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"lint": "npx oxlint ./src/search.ts && npx eslint ."
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@typescript-eslint/eslint-plugin": "^8.34.0",
|
|
17
|
+
"@typescript-eslint/parser": "^8.34.0",
|
|
18
|
+
"eslint": "9.33.0",
|
|
19
|
+
"eslint-plugin-oxlint": "1.12.0",
|
|
20
|
+
"globals": "^16.2.0",
|
|
21
|
+
"oxlint": "1.12.0",
|
|
22
|
+
"prettier": "^3.5.3",
|
|
23
|
+
"tslib": "^2.8.1",
|
|
24
|
+
"typescript-eslint": "8.39.1"
|
|
25
|
+
},
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "https://codeberg.org/gold/search-gold.git"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"data search",
|
|
32
|
+
"search",
|
|
33
|
+
"data filter",
|
|
34
|
+
"simple search",
|
|
35
|
+
"fast search"
|
|
36
|
+
],
|
|
37
|
+
"license": "MIT",
|
|
38
|
+
"homepage": "https://codeberg.org/gold/search-gold"
|
|
39
|
+
}
|
package/src/search.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export type ItemRecord = Record<string, any>
|
|
2
|
+
|
|
3
|
+
export interface ConfigParams {
|
|
4
|
+
items: ItemRecord[]
|
|
5
|
+
keys: string[]
|
|
6
|
+
caseSensitive?: boolean
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface Config {
|
|
10
|
+
Items: ItemRecord[]
|
|
11
|
+
Keys: string[]
|
|
12
|
+
CaseSensitive: boolean
|
|
13
|
+
RegexFlag: 'i' | undefined
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
|
|
17
|
+
//
|
|
18
|
+
// RegExp.escape() is a relatively new addition to JS, thus for now
|
|
19
|
+
// we continue to use the following tried and true functionality.
|
|
20
|
+
const escapeRegExp = (str: string): string => {
|
|
21
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const Conf: Config = {
|
|
25
|
+
Items: [],
|
|
26
|
+
Keys: [],
|
|
27
|
+
CaseSensitive: false,
|
|
28
|
+
RegexFlag: 'i',
|
|
29
|
+
}
|
|
30
|
+
export const searchSetConfig = (params: ConfigParams): void => {
|
|
31
|
+
const { items, keys, caseSensitive = false } = params
|
|
32
|
+
Conf.Items = items
|
|
33
|
+
Conf.Keys = keys
|
|
34
|
+
Conf.CaseSensitive = caseSensitive
|
|
35
|
+
Conf.RegexFlag = caseSensitive ? undefined : 'i'
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Since (if no debounce) every search input keystroke calls this
|
|
39
|
+
// method, performance has been optimized: there is no run-time type
|
|
40
|
+
// guarding and no try-catch blocks; regex escaping is still necessary
|
|
41
|
+
// though and has been encapsulated in this module.
|
|
42
|
+
//
|
|
43
|
+
// For a better run-time UX -- no errors and speedy execution -- we feel
|
|
44
|
+
// that it behooves the caller to this method be responsible for
|
|
45
|
+
// correctly setting the config for this module and to check that the
|
|
46
|
+
// search's query argument is a string.
|
|
47
|
+
export const searchHandler = (query: string): ItemRecord[] => {
|
|
48
|
+
const results: ItemRecord[] = []
|
|
49
|
+
const qTrimmed = escapeRegExp(query.trim())
|
|
50
|
+
|
|
51
|
+
if (qTrimmed === '') {
|
|
52
|
+
return Conf.Items
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const rx = new RegExp(qTrimmed, Conf.RegexFlag)
|
|
56
|
+
|
|
57
|
+
// All the dirty work is done here...
|
|
58
|
+
for (const item of Conf.Items) {
|
|
59
|
+
const searchString = Conf.Keys.map((k) => item[k]).join('^')
|
|
60
|
+
if (rx.test(searchString)) {
|
|
61
|
+
results.push(item)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return results
|
|
66
|
+
}
|