search-gold 1.0.2 → 1.1.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 +46 -22
- package/package.json +10 -10
- package/src/search.ts +23 -9
package/README.md
CHANGED
|
@@ -2,20 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
## Overview
|
|
4
4
|
|
|
5
|
-
Your web page displays a list of columnar information,
|
|
5
|
+
Your web page displays a list of columnar information, most often coming from one of the following two common data structures:
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
```javascript
|
|
8
|
+
// an array of objects
|
|
9
|
+
Record<string, any>[]
|
|
10
|
+
|
|
11
|
+
// a Map in which the unique key's value is an object
|
|
12
|
+
Map<string, Record<string, any>>
|
|
13
|
+
````
|
|
8
14
|
|
|
9
|
-
For an improved UX, you
|
|
15
|
+
For an improved UX in the front end, you can add a search field so the user can filter the displayed records.
|
|
10
16
|
|
|
11
|
-
The `search-gold`
|
|
17
|
+
The `search-gold` search engine is easy to set up and use!
|
|
12
18
|
|
|
13
19
|
## Installation
|
|
14
20
|
|
|
15
21
|
```bash
|
|
16
22
|
$ npm i search-gold
|
|
17
23
|
```
|
|
24
|
+
|
|
18
25
|
OR
|
|
26
|
+
|
|
19
27
|
```bash
|
|
20
28
|
$ bun i search-gold
|
|
21
29
|
```
|
|
@@ -26,11 +34,16 @@ The following should provide enough to get local search up and running and keep
|
|
|
26
34
|
|
|
27
35
|
Provide the following in your component, e.g., `my-data-list-component.ts`.
|
|
28
36
|
|
|
29
|
-
### 1. Import
|
|
37
|
+
### 1. Import applicable types and methods.
|
|
38
|
+
|
|
30
39
|
```javascript
|
|
31
40
|
import {
|
|
41
|
+
// types
|
|
32
42
|
ConfigParams,
|
|
33
43
|
ItemRecord,
|
|
44
|
+
MapRecords,
|
|
45
|
+
|
|
46
|
+
// methods
|
|
34
47
|
searchHandler,
|
|
35
48
|
searchSetConfig,
|
|
36
49
|
} from 'search-gold'
|
|
@@ -40,30 +53,32 @@ import {
|
|
|
40
53
|
|
|
41
54
|
This wrapper method only needs to be called in the following two cases:
|
|
42
55
|
|
|
43
|
-
|
|
44
|
-
|
|
56
|
+
- when component is initialized and when data is ready
|
|
57
|
+
- when data changes (record has been created, edited, deleted)
|
|
45
58
|
|
|
46
59
|
You can name this wrapper method anything you want.
|
|
47
60
|
|
|
48
61
|
```javascript
|
|
49
62
|
const initSearchConfig = () => {
|
|
50
63
|
const config: ConfigParams = {
|
|
51
|
-
//
|
|
52
|
-
// objects
|
|
64
|
+
// Required: items must reference an array of
|
|
65
|
+
// objects or a Map<string, Record<string, any>>.
|
|
66
|
+
//
|
|
67
|
+
// Search results are returned in a separate array or Map.
|
|
53
68
|
//
|
|
54
|
-
// 'search-gold' package only reads
|
|
55
|
-
//
|
|
69
|
+
// 'search-gold' package only reads the original data,
|
|
70
|
+
// leaving it unchanged.
|
|
56
71
|
items: MyData,
|
|
57
72
|
|
|
58
|
-
//
|
|
73
|
+
// Required: keys are the object properties you
|
|
59
74
|
// want to expose to search queries. List only
|
|
60
75
|
// the fields which make sense to you.
|
|
61
76
|
keys: ['productName', 'model', 'status'],
|
|
62
77
|
|
|
63
|
-
//
|
|
78
|
+
// Optional: default is false
|
|
64
79
|
caseSensitive: true,
|
|
65
80
|
|
|
66
|
-
//
|
|
81
|
+
// Optional: default is true
|
|
67
82
|
//
|
|
68
83
|
// You should only ever consider disabling
|
|
69
84
|
// memoization if you observe browser memory
|
|
@@ -72,7 +87,7 @@ const initSearchConfig = () => {
|
|
|
72
87
|
}
|
|
73
88
|
|
|
74
89
|
// Call the imported method with config arg.
|
|
75
|
-
searchSetConfig(config)
|
|
90
|
+
searchSetConfig(config),
|
|
76
91
|
|
|
77
92
|
// After setting the config, you can call searchHandler()
|
|
78
93
|
// as many times as necessary without having to set the
|
|
@@ -86,23 +101,27 @@ const initSearchConfig = () => {
|
|
|
86
101
|
//
|
|
87
102
|
// Passing an empty string effectively returns the
|
|
88
103
|
// the unfiltered set of records, the same as MyData.
|
|
89
|
-
myFilteredData = <ItemRecord[]>searchHandler('')
|
|
104
|
+
myFilteredData = <ItemRecord[]>searchHandler(''),
|
|
105
|
+
|
|
106
|
+
// OR
|
|
107
|
+
|
|
108
|
+
myFilteredData = <MapRecords>searchHandler(''),
|
|
90
109
|
}
|
|
91
|
-
|
|
110
|
+
```
|
|
92
111
|
|
|
93
112
|
### 3. Initialize the search engine.
|
|
94
113
|
|
|
95
|
-
This method (from Step 2.) should be called when your component
|
|
96
|
-
initialized and your data is ready.
|
|
114
|
+
This method (from Step 2.) should be called when your component is being
|
|
115
|
+
initialized and when your data is ready.
|
|
97
116
|
|
|
98
|
-
Also, this `initSearchConfig()` method should be called whenever your
|
|
117
|
+
Also, this `initSearchConfig()` method should be called again whenever your
|
|
99
118
|
data changes after a record has been created, edited, or deleted.
|
|
100
119
|
|
|
101
120
|
```javascript
|
|
102
121
|
// For this example we chose this method name; you
|
|
103
122
|
// can name this wrapper method anything you want.
|
|
104
123
|
initSearchConfig()
|
|
105
|
-
|
|
124
|
+
```
|
|
106
125
|
|
|
107
126
|
### 4. Execute search queries and get results.
|
|
108
127
|
|
|
@@ -110,9 +129,13 @@ Presumably, a method is binded to the UI's search field (in this example it's ca
|
|
|
110
129
|
`searchMyData`) and is called on every keystroke, debounced keystrokes, or an explicit
|
|
111
130
|
and separate submit event. Ultimately, the UX behavior is left up to the front end
|
|
112
131
|
engineer / design team.
|
|
132
|
+
|
|
113
133
|
```javascript
|
|
114
134
|
const searchMyData = (query: string) => {
|
|
115
135
|
// Call the imported method and get results.
|
|
136
|
+
//
|
|
137
|
+
// MyFilteredData is binded to the template for filtered display.
|
|
138
|
+
// Use the variable name and appropriate syntax for your app.
|
|
116
139
|
myFilteredData = searchHandler(query)
|
|
117
140
|
}
|
|
118
141
|
```
|
|
@@ -125,10 +148,11 @@ into several apps I had previously created with Vue using its Composition API.
|
|
|
125
148
|
In this documentation I tried to keep the Usage code snippets framework neutral.
|
|
126
149
|
|
|
127
150
|
`search-gold` itself is framework neutral and should work in any TS codebase.
|
|
151
|
+
|
|
128
152
|
# Credits
|
|
129
153
|
|
|
130
154
|
G. Gold
|
|
131
155
|
|
|
132
|
-
|
|
156
|
+
February 2026
|
|
133
157
|
|
|
134
158
|
Have fun!
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "search-gold",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "1.0
|
|
5
|
-
"description": "
|
|
4
|
+
"version": "1.1.0",
|
|
5
|
+
"description": "fast search engine targeting array of objects or Map",
|
|
6
6
|
"author": "gold",
|
|
7
7
|
"main": "./src/search.ts",
|
|
8
8
|
"type": "module",
|
|
@@ -14,14 +14,14 @@
|
|
|
14
14
|
},
|
|
15
15
|
"devDependencies": {
|
|
16
16
|
"@typescript-eslint/eslint-plugin": "^8.34.0",
|
|
17
|
-
"@typescript-eslint/parser": "
|
|
18
|
-
"eslint": "9.
|
|
19
|
-
"eslint-plugin-oxlint": "1.
|
|
20
|
-
"globals": "
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"tslib": "
|
|
24
|
-
"typescript-eslint": "8.
|
|
17
|
+
"@typescript-eslint/parser": "8.56.0",
|
|
18
|
+
"eslint": "9.39.2",
|
|
19
|
+
"eslint-plugin-oxlint": "1.49.0",
|
|
20
|
+
"globals": "17.3.0",
|
|
21
|
+
"oxfmt": "0.34.0",
|
|
22
|
+
"oxlint": "1.49.0",
|
|
23
|
+
"tslib": "2.8.1",
|
|
24
|
+
"typescript-eslint": "8.56.0"
|
|
25
25
|
},
|
|
26
26
|
"repository": {
|
|
27
27
|
"type": "git",
|
package/src/search.ts
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
export type ItemRecord = Record<string, any>
|
|
2
|
+
export type MapRecords = Map<string, ItemRecord>
|
|
2
3
|
|
|
3
4
|
export interface ConfigParams {
|
|
4
|
-
items: ItemRecord[]
|
|
5
|
+
items: ItemRecord[] | MapRecords
|
|
5
6
|
keys: string[]
|
|
6
7
|
caseSensitive?: boolean
|
|
7
8
|
enableMemoize?: boolean
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
interface Config {
|
|
11
|
-
Items: ItemRecord[]
|
|
12
|
+
Items: ItemRecord[] | MapRecords
|
|
12
13
|
Keys: string[]
|
|
13
14
|
RegexFlag: 'i' | undefined
|
|
14
15
|
EnableMemoize: boolean
|
|
@@ -25,6 +26,8 @@ const escapeRegExp = (str: string): string => {
|
|
|
25
26
|
const cache = new Map()
|
|
26
27
|
|
|
27
28
|
const Conf: Config = {
|
|
29
|
+
// Items could be either an array or a map.
|
|
30
|
+
// run-time will handle detection and applicable syntax.
|
|
28
31
|
Items: [],
|
|
29
32
|
Keys: [],
|
|
30
33
|
RegexFlag: 'i',
|
|
@@ -49,25 +52,36 @@ export const searchSetConfig = (params: ConfigParams): void => {
|
|
|
49
52
|
// that it behooves the caller to this method be responsible for
|
|
50
53
|
// correctly setting the config for this module and to check that the
|
|
51
54
|
// search's query argument is a string.
|
|
52
|
-
export const searchHandler = (query: string): ItemRecord[] => {
|
|
53
|
-
const results: ItemRecord[] = []
|
|
55
|
+
export const searchHandler = (query: string): ItemRecord[] | MapRecords => {
|
|
54
56
|
const qTrimmed = escapeRegExp(query.trim())
|
|
55
57
|
|
|
56
58
|
if (qTrimmed === '') {
|
|
57
59
|
return Conf.Items
|
|
58
60
|
}
|
|
59
61
|
|
|
62
|
+
let results: ItemRecord[] | MapRecords
|
|
63
|
+
|
|
60
64
|
if (Conf.EnableMemoize && cache.has(qTrimmed)) {
|
|
61
65
|
return cache.get(qTrimmed)
|
|
62
66
|
}
|
|
63
67
|
|
|
64
68
|
const rx = new RegExp(qTrimmed, Conf.RegexFlag)
|
|
65
69
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
70
|
+
if (Conf.Items instanceof Map) {
|
|
71
|
+
results = <MapRecords>new Map()
|
|
72
|
+
for (const [key, item] of Conf.Items) {
|
|
73
|
+
const searchString = Conf.Keys.map((k) => item[k]).join('^')
|
|
74
|
+
if (rx.test(searchString)) {
|
|
75
|
+
results.set(key, item)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
} else {
|
|
79
|
+
results = <ItemRecord[]>[]
|
|
80
|
+
for (const item of Conf.Items) {
|
|
81
|
+
const searchString = Conf.Keys.map((k) => item[k]).join('^')
|
|
82
|
+
if (rx.test(searchString)) {
|
|
83
|
+
results.push(item)
|
|
84
|
+
}
|
|
71
85
|
}
|
|
72
86
|
}
|
|
73
87
|
|