thaizip 0.5.1 → 0.7.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.
package/dist/data.js CHANGED
@@ -25,8 +25,8 @@ var init_defaultData = __esm({
25
25
  });
26
26
 
27
27
  // src/core/normalizer.ts
28
- var TONE_MARKS = /[\u0E48-\u0E4B\u0E47\u0E4C]/g;
29
- var ADDRESS_PREFIXES = /^(จังหวัด|อำเภอ|ตำบล|แขวง|เขต)/;
28
+ var TONE_MARKS = /[่-๋็์]/g;
29
+ var ADDRESS_PREFIXES = /^(จังหวัด|อำเภอ|ตำบล|แขวง|เขต|จ\.|อ\.|ต\.|ข\.)/;
30
30
  function normalizeThaiAddressText(input) {
31
31
  if (!input) return "";
32
32
  let text = input.trim();
@@ -54,7 +54,56 @@ function extractTrigramsNormalized(text) {
54
54
  }
55
55
 
56
56
  // src/core/indexer.ts
57
- function buildThaiAddressIndex(data) {
57
+ function typeErr(table, id, field, expected, value) {
58
+ return new TypeError(`[thaizip] ${table} ${id}: expected ${expected} for ${field}, got ${typeof value}`);
59
+ }
60
+ function validateRawData(data) {
61
+ const { provinces, amphures, tambons } = data;
62
+ for (let i = 0; i < provinces.length; i++) {
63
+ const p2 = provinces[i];
64
+ if (typeof p2.id !== "number") throw typeErr("province", p2.id, "id", "number", p2.id);
65
+ if (typeof p2.name_th !== "string") throw typeErr("province", p2.id, "name_th", "string", p2.name_th);
66
+ if (typeof p2.name_en !== "string") throw typeErr("province", p2.id, "name_en", "string", p2.name_en);
67
+ }
68
+ for (let i = 0; i < amphures.length; i++) {
69
+ const a2 = amphures[i];
70
+ if (typeof a2.id !== "number") throw typeErr("amphure", a2.id, "id", "number", a2.id);
71
+ if (typeof a2.name_th !== "string") throw typeErr("amphure", a2.id, "name_th", "string", a2.name_th);
72
+ if (typeof a2.name_en !== "string") throw typeErr("amphure", a2.id, "name_en", "string", a2.name_en);
73
+ if (typeof a2.province_id !== "number") throw typeErr("amphure", a2.id, "province_id", "number", a2.province_id);
74
+ }
75
+ for (let i = 0; i < tambons.length; i++) {
76
+ const t2 = tambons[i];
77
+ if (typeof t2.id !== "number") throw typeErr("tambon", t2.id, "id", "number", t2.id);
78
+ if (typeof t2.name_th !== "string") throw typeErr("tambon", t2.id, "name_th", "string", t2.name_th);
79
+ if (typeof t2.name_en !== "string") throw typeErr("tambon", t2.id, "name_en", "string", t2.name_en);
80
+ const zipType = typeof t2.zip_code;
81
+ if (zipType !== "string" && zipType !== "number") {
82
+ throw typeErr("tambon", t2.id, "zip_code", "string or number", t2.zip_code);
83
+ }
84
+ if (typeof t2.amphure_id !== "number") throw typeErr("tambon", t2.id, "amphure_id", "number", t2.amphure_id);
85
+ }
86
+ }
87
+ function addTrigrams(map, trigrams, idx) {
88
+ for (const trigram of trigrams) {
89
+ let set = map.get(trigram);
90
+ if (!set) {
91
+ set = /* @__PURE__ */ new Set();
92
+ map.set(trigram, set);
93
+ }
94
+ set.add(idx);
95
+ }
96
+ }
97
+ function combinedTrigrams(nameTh, nameEn) {
98
+ const s = /* @__PURE__ */ new Set();
99
+ for (const t2 of extractTrigrams(nameTh)) s.add(t2);
100
+ for (const t2 of extractTrigrams(nameEn)) s.add(t2);
101
+ return s;
102
+ }
103
+ function buildThaiAddressIndex(data, options) {
104
+ if (options?.validate !== false) {
105
+ validateRawData(data);
106
+ }
58
107
  const { provinces, amphures, tambons } = data;
59
108
  const provMap = new Map(
60
109
  provinces.filter((p2) => !p2.deleted_at).map((p2) => [p2.id, p2])
@@ -62,20 +111,33 @@ function buildThaiAddressIndex(data) {
62
111
  const ampMap = new Map(
63
112
  amphures.filter((a2) => !a2.deleted_at).map((a2) => [a2.id, a2])
64
113
  );
114
+ const provTrigrams = /* @__PURE__ */ new Map();
115
+ for (const [id, prov] of provMap) {
116
+ provTrigrams.set(id, combinedTrigrams(prov.name_th, prov.name_en));
117
+ }
118
+ const ampTrigrams = /* @__PURE__ */ new Map();
119
+ for (const [id, amp] of ampMap) {
120
+ ampTrigrams.set(id, combinedTrigrams(amp.name_th, amp.name_en));
121
+ }
65
122
  const records = [];
66
123
  const map = /* @__PURE__ */ new Map();
67
124
  const zipIndex = /* @__PURE__ */ new Map();
125
+ const normTambon = [];
126
+ const normTambonEn = [];
127
+ const byProvince = /* @__PURE__ */ new Map();
128
+ const byAmphure = /* @__PURE__ */ new Map();
68
129
  for (const tambon of tambons) {
69
130
  if (tambon.deleted_at) continue;
70
131
  const amphure = ampMap.get(tambon.amphure_id);
71
132
  if (!amphure) {
72
- console.warn(
73
- `[thaizip] skipping tambon id=${tambon.id} (${tambon.name_th}): amphure_id=${tambon.amphure_id} not found or deleted`
74
- );
133
+ if (options?.onSkip) options.onSkip(tambon);
75
134
  continue;
76
135
  }
77
136
  const province = provMap.get(amphure.province_id);
78
- if (!province) continue;
137
+ if (!province) {
138
+ if (options?.onSkip) options.onSkip(tambon);
139
+ continue;
140
+ }
79
141
  const record = {
80
142
  provinceId: province.id,
81
143
  provinceNameTh: province.name_th,
@@ -93,43 +155,55 @@ function buildThaiAddressIndex(data) {
93
155
  const existing = zipIndex.get(record.zipCode);
94
156
  if (existing) existing.push(idx);
95
157
  else zipIndex.set(record.zipCode, [idx]);
96
- const fields = [
97
- record.tambonNameTh,
98
- record.tambonNameEn,
99
- record.amphureNameTh,
100
- record.amphureNameEn,
101
- record.provinceNameTh,
102
- record.provinceNameEn,
103
- record.zipCode
104
- ];
105
- for (const field of fields) {
106
- for (const trigram of extractTrigrams(field)) {
107
- let set = map.get(trigram);
108
- if (!set) {
109
- set = /* @__PURE__ */ new Set();
110
- map.set(trigram, set);
111
- }
112
- set.add(idx);
113
- }
114
- }
158
+ const provList = byProvince.get(province.id);
159
+ if (provList) provList.push(idx);
160
+ else byProvince.set(province.id, [idx]);
161
+ const ampList = byAmphure.get(amphure.id);
162
+ if (ampList) ampList.push(idx);
163
+ else byAmphure.set(amphure.id, [idx]);
164
+ const normTh = normalizeThaiAddressText(record.tambonNameTh);
165
+ normTambon.push(normTh);
166
+ addTrigrams(map, extractTrigramsNormalized(normTh), idx);
167
+ const normEn = normalizeThaiAddressText(record.tambonNameEn);
168
+ normTambonEn.push(normEn);
169
+ addTrigrams(map, extractTrigramsNormalized(normEn), idx);
170
+ addTrigrams(map, extractTrigrams(record.zipCode), idx);
171
+ addTrigrams(map, provTrigrams.get(province.id), idx);
172
+ addTrigrams(map, ampTrigrams.get(amphure.id), idx);
115
173
  }
116
- return { map, records, zipIndex };
174
+ return { map, records, zipIndex, normTambon, normTambonEn, byProvince, byAmphure };
117
175
  }
118
176
 
119
177
  // src/data/loader.ts
120
178
  var cached = null;
179
+ var inflightPromise = null;
180
+ var loadGeneration = 0;
121
181
  function clearDefaultIndex() {
122
182
  cached = null;
183
+ inflightPromise = null;
184
+ loadGeneration++;
123
185
  }
124
186
  async function loadDefaultIndex() {
125
187
  if (cached) return cached;
126
- const { p: p2, a: a2, t: t2 } = await Promise.resolve().then(() => (init_defaultData(), defaultData_exports));
127
- cached = buildThaiAddressIndex({
128
- provinces: p2.map(([id, name_th, name_en]) => ({ id, name_th, name_en, geography_id: 0, deleted_at: null })),
129
- amphures: a2.map(([id, name_th, name_en, province_id]) => ({ id, name_th, name_en, province_id, deleted_at: null })),
130
- tambons: t2.map(([id, name_th, name_en, amphure_id, zip_code]) => ({ id, name_th, name_en, amphure_id, zip_code: Number(zip_code), deleted_at: null }))
131
- });
132
- return cached;
188
+ if (!inflightPromise) {
189
+ const gen = loadGeneration;
190
+ inflightPromise = Promise.resolve().then(() => (init_defaultData(), defaultData_exports)).then(({ p: p2, a: a2, t: t2 }) => {
191
+ const index = buildThaiAddressIndex({
192
+ provinces: p2.map(([id, name_th, name_en]) => ({ id, name_th, name_en, geography_id: 0, deleted_at: null })),
193
+ amphures: a2.map(([id, name_th, name_en, province_id]) => ({ id, name_th, name_en, province_id, deleted_at: null })),
194
+ tambons: t2.map(([id, name_th, name_en, amphure_id, zip_code]) => ({ id, name_th, name_en, amphure_id, zip_code, deleted_at: null }))
195
+ }, { validate: false });
196
+ if (loadGeneration === gen) {
197
+ cached = index;
198
+ inflightPromise = null;
199
+ }
200
+ return index;
201
+ }).catch((err) => {
202
+ if (loadGeneration === gen) inflightPromise = null;
203
+ throw err;
204
+ });
205
+ }
206
+ return inflightPromise;
133
207
  }
134
208
 
135
209
  export { clearDefaultIndex, loadDefaultIndex };