thaizip 0.7.3 → 0.7.4
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 +10 -0
- package/dist/data.cjs +7 -2
- package/dist/data.js +7 -2
- package/dist/index.cjs +17 -16
- package/dist/index.d.cts +4 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.js +17 -16
- package/dist/react.cjs +17 -16
- package/dist/react.js +17 -16
- package/package.json +7 -4
package/README.md
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
# thaizip
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/thaizip)
|
|
4
|
+
[](https://www.npmjs.com/package/thaizip)
|
|
5
|
+
[](https://github.com/naay99999/thai-zip/blob/main/LICENSE)
|
|
6
|
+
|
|
3
7
|
Fast fuzzy autocomplete for Thai addresses — subdistrict, district, province, postal code. Thai and English input, zero runtime dependencies, React optional.
|
|
4
8
|
|
|
5
9
|
**Docs:** https://naay99999.github.io/thai-zip/ (ไทย / [English](https://naay99999.github.io/thai-zip/en/))
|
|
6
10
|
|
|
11
|
+
| Thai input | English input (romanization alias) |
|
|
12
|
+
|---|---|
|
|
13
|
+
|  |  |
|
|
14
|
+
|
|
7
15
|
```bash
|
|
8
16
|
npm install thaizip
|
|
9
17
|
```
|
|
@@ -59,6 +67,8 @@ lookupByZipCode(index, '45000') // exact
|
|
|
59
67
|
lookupByZipCode(index, '450') // prefix
|
|
60
68
|
```
|
|
61
69
|
|
|
70
|
+
Direct ZIP lookup rejects raw inputs longer than 1000 characters before scanning them. A finite `zipLimit` also bounds how many matching records are collected.
|
|
71
|
+
|
|
62
72
|
### English input
|
|
63
73
|
|
|
64
74
|
The dataset indexes official RTGS spellings, so `bang rak` and `chatuchak` work directly. 87 common non-RTGS spellings are mapped on first: `lardprao` → `lat phrao`, `ladkrabang` → `lat krabang`, `krungthep` → `bangkok`. It is a curated dictionary, not a transliterator — unlisted spellings still miss. Opt out with `romanizationAliases: false`.
|
package/dist/data.cjs
CHANGED
|
@@ -2,8 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
var __defProp = Object.defineProperty;
|
|
4
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __esm = (fn, res) => function __init() {
|
|
6
|
-
|
|
5
|
+
var __esm = (fn, res, err) => function __init() {
|
|
6
|
+
if (err) throw err[0];
|
|
7
|
+
try {
|
|
8
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
9
|
+
} catch (e) {
|
|
10
|
+
throw err = [e], e;
|
|
11
|
+
}
|
|
7
12
|
};
|
|
8
13
|
var __export = (target, all) => {
|
|
9
14
|
for (var name in all)
|
package/dist/data.js
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
var __defProp = Object.defineProperty;
|
|
2
2
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
-
var __esm = (fn, res) => function __init() {
|
|
4
|
-
|
|
3
|
+
var __esm = (fn, res, err) => function __init() {
|
|
4
|
+
if (err) throw err[0];
|
|
5
|
+
try {
|
|
6
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
7
|
+
} catch (e) {
|
|
8
|
+
throw err = [e], e;
|
|
9
|
+
}
|
|
5
10
|
};
|
|
6
11
|
var __export = (target, all) => {
|
|
7
12
|
for (var name in all)
|
package/dist/index.cjs
CHANGED
|
@@ -308,14 +308,6 @@ function compareZip(a, b) {
|
|
|
308
308
|
if (a > b) return 1;
|
|
309
309
|
return 0;
|
|
310
310
|
}
|
|
311
|
-
function sortZipMatches(matches, exactZip) {
|
|
312
|
-
matches.sort((a, b) => {
|
|
313
|
-
if (a.zipCode === exactZip && b.zipCode !== exactZip) return -1;
|
|
314
|
-
if (b.zipCode === exactZip && a.zipCode !== exactZip) return 1;
|
|
315
|
-
return compareZip(a.zipCode, b.zipCode);
|
|
316
|
-
});
|
|
317
|
-
return matches;
|
|
318
|
-
}
|
|
319
311
|
function normalizeLimit(value) {
|
|
320
312
|
if (Number.isNaN(value) || value === -Infinity) return 0;
|
|
321
313
|
if (value === Infinity) return value;
|
|
@@ -323,9 +315,11 @@ function normalizeLimit(value) {
|
|
|
323
315
|
}
|
|
324
316
|
function lookupByZipCode(index, zip, options) {
|
|
325
317
|
if (!index || !index.zipIndex || !index.records || typeof zip !== "string") return [];
|
|
318
|
+
if (zip.length > 1e3) return [];
|
|
319
|
+
const zipLimit = normalizeLimit(options?.zipLimit ?? Infinity);
|
|
320
|
+
if (zipLimit === 0) return [];
|
|
326
321
|
const normalized = zip.trim();
|
|
327
322
|
if (!ZIP_CODE_RE.test(normalized) || normalized.length < 2) return [];
|
|
328
|
-
const zipLimit = normalizeLimit(options?.zipLimit ?? Infinity);
|
|
329
323
|
const matches = [];
|
|
330
324
|
const keys = index.sortedZipKeys;
|
|
331
325
|
const postings = index.sortedZipPostings;
|
|
@@ -337,17 +331,19 @@ function lookupByZipCode(index, zip, options) {
|
|
|
337
331
|
if (keys[mid] < normalized) lo = mid + 1;
|
|
338
332
|
else hi = mid;
|
|
339
333
|
}
|
|
340
|
-
for (let i = lo; i < keys.length && keys[i].startsWith(normalized); i++) {
|
|
334
|
+
for (let i = lo; i < keys.length && keys[i].startsWith(normalized) && matches.length < zipLimit; i++) {
|
|
341
335
|
const indices = postings[i];
|
|
342
|
-
for (let j = 0; j < indices.length; j++) matches.push(index.records[indices[j]]);
|
|
336
|
+
for (let j = 0; j < indices.length && matches.length < zipLimit; j++) matches.push(index.records[indices[j]]);
|
|
343
337
|
}
|
|
344
338
|
} else {
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
339
|
+
const matchingKeys = [...index.zipIndex.keys()].filter((z) => z.startsWith(normalized)).sort(compareZip);
|
|
340
|
+
for (const z of matchingKeys) {
|
|
341
|
+
if (matches.length >= zipLimit) break;
|
|
342
|
+
for (const idx of index.zipIndex.get(z)) {
|
|
343
|
+
if (matches.length >= zipLimit) break;
|
|
344
|
+
matches.push(index.records[idx]);
|
|
348
345
|
}
|
|
349
346
|
}
|
|
350
|
-
sortZipMatches(matches, normalized);
|
|
351
347
|
}
|
|
352
348
|
return matches.slice(0, zipLimit).map((record) => ({ ...record }));
|
|
353
349
|
}
|
|
@@ -389,6 +385,7 @@ function searchThaiAddress(index, query, options) {
|
|
|
389
385
|
});
|
|
390
386
|
}
|
|
391
387
|
if (normalized.length < 3) return [];
|
|
388
|
+
if (limit === 0) return [];
|
|
392
389
|
const searchText = options?.romanizationAliases !== false && HAS_LATIN_RE.test(normalized) ? applyRomanizationAliases(normalized) : normalized;
|
|
393
390
|
const queryTrigrams = extractTrigramsNormalized(searchText);
|
|
394
391
|
const { counts, touched } = getScratch(index);
|
|
@@ -415,7 +412,11 @@ function searchThaiAddress(index, query, options) {
|
|
|
415
412
|
if (b.score !== a.score) return b.score - a.score;
|
|
416
413
|
return b.matchRank - a.matchRank;
|
|
417
414
|
});
|
|
418
|
-
|
|
415
|
+
let windowSize = Math.min(limit, scored.length);
|
|
416
|
+
const boundary = scored[windowSize - 1];
|
|
417
|
+
while (windowSize < scored.length && scored[windowSize].score === boundary.score && scored[windowSize].matchRank === boundary.matchRank) {
|
|
418
|
+
windowSize++;
|
|
419
|
+
}
|
|
419
420
|
const window = scored.slice(0, windowSize);
|
|
420
421
|
window.sort((a, b) => {
|
|
421
422
|
if (b.score !== a.score) return b.score - a.score;
|
package/dist/index.d.cts
CHANGED
|
@@ -184,7 +184,8 @@ declare function buildThaiAddressIndex(data: RawData, options?: BuildIndexOption
|
|
|
184
184
|
* prefix range — O(log n_zips + matches). Ascending key order places an
|
|
185
185
|
* exact match before any longer prefix match automatically, so no explicit
|
|
186
186
|
* exact-first sort is needed. Indexes without the sorted arrays (older
|
|
187
|
-
* shape, hand-built)
|
|
187
|
+
* shape, hand-built) scan and sort matching keys before collecting records.
|
|
188
|
+
* Both paths stop collecting when `zipLimit` is reached.
|
|
188
189
|
*
|
|
189
190
|
* Returned records are shallow copies: mutating them cannot corrupt the
|
|
190
191
|
* shared index (see `searchThaiAddress`).
|
|
@@ -193,8 +194,8 @@ declare function buildThaiAddressIndex(data: RawData, options?: BuildIndexOption
|
|
|
193
194
|
* only by `options.zipLimit` (defaults to `Infinity`), since a zip code can
|
|
194
195
|
* legitimately map to dozens of tambons (e.g. `45000` → 33 records).
|
|
195
196
|
*
|
|
196
|
-
* Malformed runtime input (non-string `zip`, missing index shape)
|
|
197
|
-
*
|
|
197
|
+
* Malformed runtime input (non-string `zip`, missing index shape) and raw
|
|
198
|
+
* strings longer than 1000 characters return `[]`.
|
|
198
199
|
*/
|
|
199
200
|
declare function lookupByZipCode(index: TrigramIndex, zip: string, options?: SearchOptions): ThaiAddressRecord[];
|
|
200
201
|
declare function searchThaiAddress(index: TrigramIndex, query: string, options?: SearchOptions): ThaiAddressRecord[];
|
package/dist/index.d.ts
CHANGED
|
@@ -184,7 +184,8 @@ declare function buildThaiAddressIndex(data: RawData, options?: BuildIndexOption
|
|
|
184
184
|
* prefix range — O(log n_zips + matches). Ascending key order places an
|
|
185
185
|
* exact match before any longer prefix match automatically, so no explicit
|
|
186
186
|
* exact-first sort is needed. Indexes without the sorted arrays (older
|
|
187
|
-
* shape, hand-built)
|
|
187
|
+
* shape, hand-built) scan and sort matching keys before collecting records.
|
|
188
|
+
* Both paths stop collecting when `zipLimit` is reached.
|
|
188
189
|
*
|
|
189
190
|
* Returned records are shallow copies: mutating them cannot corrupt the
|
|
190
191
|
* shared index (see `searchThaiAddress`).
|
|
@@ -193,8 +194,8 @@ declare function buildThaiAddressIndex(data: RawData, options?: BuildIndexOption
|
|
|
193
194
|
* only by `options.zipLimit` (defaults to `Infinity`), since a zip code can
|
|
194
195
|
* legitimately map to dozens of tambons (e.g. `45000` → 33 records).
|
|
195
196
|
*
|
|
196
|
-
* Malformed runtime input (non-string `zip`, missing index shape)
|
|
197
|
-
*
|
|
197
|
+
* Malformed runtime input (non-string `zip`, missing index shape) and raw
|
|
198
|
+
* strings longer than 1000 characters return `[]`.
|
|
198
199
|
*/
|
|
199
200
|
declare function lookupByZipCode(index: TrigramIndex, zip: string, options?: SearchOptions): ThaiAddressRecord[];
|
|
200
201
|
declare function searchThaiAddress(index: TrigramIndex, query: string, options?: SearchOptions): ThaiAddressRecord[];
|
package/dist/index.js
CHANGED
|
@@ -306,14 +306,6 @@ function compareZip(a, b) {
|
|
|
306
306
|
if (a > b) return 1;
|
|
307
307
|
return 0;
|
|
308
308
|
}
|
|
309
|
-
function sortZipMatches(matches, exactZip) {
|
|
310
|
-
matches.sort((a, b) => {
|
|
311
|
-
if (a.zipCode === exactZip && b.zipCode !== exactZip) return -1;
|
|
312
|
-
if (b.zipCode === exactZip && a.zipCode !== exactZip) return 1;
|
|
313
|
-
return compareZip(a.zipCode, b.zipCode);
|
|
314
|
-
});
|
|
315
|
-
return matches;
|
|
316
|
-
}
|
|
317
309
|
function normalizeLimit(value) {
|
|
318
310
|
if (Number.isNaN(value) || value === -Infinity) return 0;
|
|
319
311
|
if (value === Infinity) return value;
|
|
@@ -321,9 +313,11 @@ function normalizeLimit(value) {
|
|
|
321
313
|
}
|
|
322
314
|
function lookupByZipCode(index, zip, options) {
|
|
323
315
|
if (!index || !index.zipIndex || !index.records || typeof zip !== "string") return [];
|
|
316
|
+
if (zip.length > 1e3) return [];
|
|
317
|
+
const zipLimit = normalizeLimit(options?.zipLimit ?? Infinity);
|
|
318
|
+
if (zipLimit === 0) return [];
|
|
324
319
|
const normalized = zip.trim();
|
|
325
320
|
if (!ZIP_CODE_RE.test(normalized) || normalized.length < 2) return [];
|
|
326
|
-
const zipLimit = normalizeLimit(options?.zipLimit ?? Infinity);
|
|
327
321
|
const matches = [];
|
|
328
322
|
const keys = index.sortedZipKeys;
|
|
329
323
|
const postings = index.sortedZipPostings;
|
|
@@ -335,17 +329,19 @@ function lookupByZipCode(index, zip, options) {
|
|
|
335
329
|
if (keys[mid] < normalized) lo = mid + 1;
|
|
336
330
|
else hi = mid;
|
|
337
331
|
}
|
|
338
|
-
for (let i = lo; i < keys.length && keys[i].startsWith(normalized); i++) {
|
|
332
|
+
for (let i = lo; i < keys.length && keys[i].startsWith(normalized) && matches.length < zipLimit; i++) {
|
|
339
333
|
const indices = postings[i];
|
|
340
|
-
for (let j = 0; j < indices.length; j++) matches.push(index.records[indices[j]]);
|
|
334
|
+
for (let j = 0; j < indices.length && matches.length < zipLimit; j++) matches.push(index.records[indices[j]]);
|
|
341
335
|
}
|
|
342
336
|
} else {
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
337
|
+
const matchingKeys = [...index.zipIndex.keys()].filter((z) => z.startsWith(normalized)).sort(compareZip);
|
|
338
|
+
for (const z of matchingKeys) {
|
|
339
|
+
if (matches.length >= zipLimit) break;
|
|
340
|
+
for (const idx of index.zipIndex.get(z)) {
|
|
341
|
+
if (matches.length >= zipLimit) break;
|
|
342
|
+
matches.push(index.records[idx]);
|
|
346
343
|
}
|
|
347
344
|
}
|
|
348
|
-
sortZipMatches(matches, normalized);
|
|
349
345
|
}
|
|
350
346
|
return matches.slice(0, zipLimit).map((record) => ({ ...record }));
|
|
351
347
|
}
|
|
@@ -387,6 +383,7 @@ function searchThaiAddress(index, query, options) {
|
|
|
387
383
|
});
|
|
388
384
|
}
|
|
389
385
|
if (normalized.length < 3) return [];
|
|
386
|
+
if (limit === 0) return [];
|
|
390
387
|
const searchText = options?.romanizationAliases !== false && HAS_LATIN_RE.test(normalized) ? applyRomanizationAliases(normalized) : normalized;
|
|
391
388
|
const queryTrigrams = extractTrigramsNormalized(searchText);
|
|
392
389
|
const { counts, touched } = getScratch(index);
|
|
@@ -413,7 +410,11 @@ function searchThaiAddress(index, query, options) {
|
|
|
413
410
|
if (b.score !== a.score) return b.score - a.score;
|
|
414
411
|
return b.matchRank - a.matchRank;
|
|
415
412
|
});
|
|
416
|
-
|
|
413
|
+
let windowSize = Math.min(limit, scored.length);
|
|
414
|
+
const boundary = scored[windowSize - 1];
|
|
415
|
+
while (windowSize < scored.length && scored[windowSize].score === boundary.score && scored[windowSize].matchRank === boundary.matchRank) {
|
|
416
|
+
windowSize++;
|
|
417
|
+
}
|
|
417
418
|
const window = scored.slice(0, windowSize);
|
|
418
419
|
window.sort((a, b) => {
|
|
419
420
|
if (b.score !== a.score) return b.score - a.score;
|
package/dist/react.cjs
CHANGED
|
@@ -152,14 +152,6 @@ function compareZip(a, b) {
|
|
|
152
152
|
if (a > b) return 1;
|
|
153
153
|
return 0;
|
|
154
154
|
}
|
|
155
|
-
function sortZipMatches(matches, exactZip) {
|
|
156
|
-
matches.sort((a, b) => {
|
|
157
|
-
if (a.zipCode === exactZip && b.zipCode !== exactZip) return -1;
|
|
158
|
-
if (b.zipCode === exactZip && a.zipCode !== exactZip) return 1;
|
|
159
|
-
return compareZip(a.zipCode, b.zipCode);
|
|
160
|
-
});
|
|
161
|
-
return matches;
|
|
162
|
-
}
|
|
163
155
|
function normalizeLimit(value) {
|
|
164
156
|
if (Number.isNaN(value) || value === -Infinity) return 0;
|
|
165
157
|
if (value === Infinity) return value;
|
|
@@ -167,9 +159,11 @@ function normalizeLimit(value) {
|
|
|
167
159
|
}
|
|
168
160
|
function lookupByZipCode(index, zip, options) {
|
|
169
161
|
if (!index || !index.zipIndex || !index.records || typeof zip !== "string") return [];
|
|
162
|
+
if (zip.length > 1e3) return [];
|
|
163
|
+
const zipLimit = normalizeLimit(options?.zipLimit ?? Infinity);
|
|
164
|
+
if (zipLimit === 0) return [];
|
|
170
165
|
const normalized = zip.trim();
|
|
171
166
|
if (!ZIP_CODE_RE.test(normalized) || normalized.length < 2) return [];
|
|
172
|
-
const zipLimit = normalizeLimit(options?.zipLimit ?? Infinity);
|
|
173
167
|
const matches = [];
|
|
174
168
|
const keys = index.sortedZipKeys;
|
|
175
169
|
const postings = index.sortedZipPostings;
|
|
@@ -181,17 +175,19 @@ function lookupByZipCode(index, zip, options) {
|
|
|
181
175
|
if (keys[mid] < normalized) lo = mid + 1;
|
|
182
176
|
else hi = mid;
|
|
183
177
|
}
|
|
184
|
-
for (let i = lo; i < keys.length && keys[i].startsWith(normalized); i++) {
|
|
178
|
+
for (let i = lo; i < keys.length && keys[i].startsWith(normalized) && matches.length < zipLimit; i++) {
|
|
185
179
|
const indices = postings[i];
|
|
186
|
-
for (let j = 0; j < indices.length; j++) matches.push(index.records[indices[j]]);
|
|
180
|
+
for (let j = 0; j < indices.length && matches.length < zipLimit; j++) matches.push(index.records[indices[j]]);
|
|
187
181
|
}
|
|
188
182
|
} else {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
183
|
+
const matchingKeys = [...index.zipIndex.keys()].filter((z) => z.startsWith(normalized)).sort(compareZip);
|
|
184
|
+
for (const z of matchingKeys) {
|
|
185
|
+
if (matches.length >= zipLimit) break;
|
|
186
|
+
for (const idx of index.zipIndex.get(z)) {
|
|
187
|
+
if (matches.length >= zipLimit) break;
|
|
188
|
+
matches.push(index.records[idx]);
|
|
192
189
|
}
|
|
193
190
|
}
|
|
194
|
-
sortZipMatches(matches, normalized);
|
|
195
191
|
}
|
|
196
192
|
return matches.slice(0, zipLimit).map((record) => ({ ...record }));
|
|
197
193
|
}
|
|
@@ -233,6 +229,7 @@ function searchThaiAddress(index, query, options) {
|
|
|
233
229
|
});
|
|
234
230
|
}
|
|
235
231
|
if (normalized.length < 3) return [];
|
|
232
|
+
if (limit === 0) return [];
|
|
236
233
|
const searchText = options?.romanizationAliases !== false && HAS_LATIN_RE.test(normalized) ? applyRomanizationAliases(normalized) : normalized;
|
|
237
234
|
const queryTrigrams = extractTrigramsNormalized(searchText);
|
|
238
235
|
const { counts, touched } = getScratch(index);
|
|
@@ -259,7 +256,11 @@ function searchThaiAddress(index, query, options) {
|
|
|
259
256
|
if (b.score !== a.score) return b.score - a.score;
|
|
260
257
|
return b.matchRank - a.matchRank;
|
|
261
258
|
});
|
|
262
|
-
|
|
259
|
+
let windowSize = Math.min(limit, scored.length);
|
|
260
|
+
const boundary = scored[windowSize - 1];
|
|
261
|
+
while (windowSize < scored.length && scored[windowSize].score === boundary.score && scored[windowSize].matchRank === boundary.matchRank) {
|
|
262
|
+
windowSize++;
|
|
263
|
+
}
|
|
263
264
|
const window = scored.slice(0, windowSize);
|
|
264
265
|
window.sort((a, b) => {
|
|
265
266
|
if (b.score !== a.score) return b.score - a.score;
|
package/dist/react.js
CHANGED
|
@@ -150,14 +150,6 @@ function compareZip(a, b) {
|
|
|
150
150
|
if (a > b) return 1;
|
|
151
151
|
return 0;
|
|
152
152
|
}
|
|
153
|
-
function sortZipMatches(matches, exactZip) {
|
|
154
|
-
matches.sort((a, b) => {
|
|
155
|
-
if (a.zipCode === exactZip && b.zipCode !== exactZip) return -1;
|
|
156
|
-
if (b.zipCode === exactZip && a.zipCode !== exactZip) return 1;
|
|
157
|
-
return compareZip(a.zipCode, b.zipCode);
|
|
158
|
-
});
|
|
159
|
-
return matches;
|
|
160
|
-
}
|
|
161
153
|
function normalizeLimit(value) {
|
|
162
154
|
if (Number.isNaN(value) || value === -Infinity) return 0;
|
|
163
155
|
if (value === Infinity) return value;
|
|
@@ -165,9 +157,11 @@ function normalizeLimit(value) {
|
|
|
165
157
|
}
|
|
166
158
|
function lookupByZipCode(index, zip, options) {
|
|
167
159
|
if (!index || !index.zipIndex || !index.records || typeof zip !== "string") return [];
|
|
160
|
+
if (zip.length > 1e3) return [];
|
|
161
|
+
const zipLimit = normalizeLimit(options?.zipLimit ?? Infinity);
|
|
162
|
+
if (zipLimit === 0) return [];
|
|
168
163
|
const normalized = zip.trim();
|
|
169
164
|
if (!ZIP_CODE_RE.test(normalized) || normalized.length < 2) return [];
|
|
170
|
-
const zipLimit = normalizeLimit(options?.zipLimit ?? Infinity);
|
|
171
165
|
const matches = [];
|
|
172
166
|
const keys = index.sortedZipKeys;
|
|
173
167
|
const postings = index.sortedZipPostings;
|
|
@@ -179,17 +173,19 @@ function lookupByZipCode(index, zip, options) {
|
|
|
179
173
|
if (keys[mid] < normalized) lo = mid + 1;
|
|
180
174
|
else hi = mid;
|
|
181
175
|
}
|
|
182
|
-
for (let i = lo; i < keys.length && keys[i].startsWith(normalized); i++) {
|
|
176
|
+
for (let i = lo; i < keys.length && keys[i].startsWith(normalized) && matches.length < zipLimit; i++) {
|
|
183
177
|
const indices = postings[i];
|
|
184
|
-
for (let j = 0; j < indices.length; j++) matches.push(index.records[indices[j]]);
|
|
178
|
+
for (let j = 0; j < indices.length && matches.length < zipLimit; j++) matches.push(index.records[indices[j]]);
|
|
185
179
|
}
|
|
186
180
|
} else {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
181
|
+
const matchingKeys = [...index.zipIndex.keys()].filter((z) => z.startsWith(normalized)).sort(compareZip);
|
|
182
|
+
for (const z of matchingKeys) {
|
|
183
|
+
if (matches.length >= zipLimit) break;
|
|
184
|
+
for (const idx of index.zipIndex.get(z)) {
|
|
185
|
+
if (matches.length >= zipLimit) break;
|
|
186
|
+
matches.push(index.records[idx]);
|
|
190
187
|
}
|
|
191
188
|
}
|
|
192
|
-
sortZipMatches(matches, normalized);
|
|
193
189
|
}
|
|
194
190
|
return matches.slice(0, zipLimit).map((record) => ({ ...record }));
|
|
195
191
|
}
|
|
@@ -231,6 +227,7 @@ function searchThaiAddress(index, query, options) {
|
|
|
231
227
|
});
|
|
232
228
|
}
|
|
233
229
|
if (normalized.length < 3) return [];
|
|
230
|
+
if (limit === 0) return [];
|
|
234
231
|
const searchText = options?.romanizationAliases !== false && HAS_LATIN_RE.test(normalized) ? applyRomanizationAliases(normalized) : normalized;
|
|
235
232
|
const queryTrigrams = extractTrigramsNormalized(searchText);
|
|
236
233
|
const { counts, touched } = getScratch(index);
|
|
@@ -257,7 +254,11 @@ function searchThaiAddress(index, query, options) {
|
|
|
257
254
|
if (b.score !== a.score) return b.score - a.score;
|
|
258
255
|
return b.matchRank - a.matchRank;
|
|
259
256
|
});
|
|
260
|
-
|
|
257
|
+
let windowSize = Math.min(limit, scored.length);
|
|
258
|
+
const boundary = scored[windowSize - 1];
|
|
259
|
+
while (windowSize < scored.length && scored[windowSize].score === boundary.score && scored[windowSize].matchRank === boundary.matchRank) {
|
|
260
|
+
windowSize++;
|
|
261
|
+
}
|
|
261
262
|
const window = scored.slice(0, windowSize);
|
|
262
263
|
window.sort((a, b) => {
|
|
263
264
|
if (b.score !== a.score) return b.score - a.score;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thaizip",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.4",
|
|
4
4
|
"description": "Fast, fuzzy Thai address autocomplete library (headless + React)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"thai",
|
|
@@ -58,14 +58,17 @@
|
|
|
58
58
|
"@testing-library/react": "^16.3.2",
|
|
59
59
|
"@types/node": "^25.5.2",
|
|
60
60
|
"@types/react": "^19.2.14",
|
|
61
|
-
"@vitest/coverage-v8": "^4.1.
|
|
61
|
+
"@vitest/coverage-v8": "^4.1.11",
|
|
62
62
|
"jsdom": "^29.0.1",
|
|
63
63
|
"react": "^19.2.4",
|
|
64
64
|
"react-dom": "^19.2.4",
|
|
65
65
|
"tsup": "^8.5.1",
|
|
66
|
-
"tsx": "^4.
|
|
66
|
+
"tsx": "^4.23.13",
|
|
67
67
|
"typescript": "^6.0.2",
|
|
68
|
-
"vitest": "^4.1.
|
|
68
|
+
"vitest": "^4.1.11"
|
|
69
|
+
},
|
|
70
|
+
"overrides": {
|
|
71
|
+
"esbuild": "^0.28.1"
|
|
69
72
|
},
|
|
70
73
|
"peerDependencies": {
|
|
71
74
|
"react": ">=18.0.0"
|