@hebcal/geo-sqlite 5.4.0 → 5.5.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 CHANGED
@@ -33,20 +33,43 @@ db.close();
33
33
  </dd>
34
34
  </dl>
35
35
 
36
+ ## Functions
37
+
38
+ <dl>
39
+ <dt><a href="#buildGeonamesSqlite">buildGeonamesSqlite(opts)</a></dt>
40
+ <dd><p>Builds <code>geonames.sqlite3</code> from files downloaded from geonames.org</p>
41
+ </dd>
42
+ <dt><a href="#doSql">doSql(logger, db, ...sqls)</a></dt>
43
+ <dd></dd>
44
+ <dt><a href="#doFile">doFile(logger, db, infile, tableName, expectedFields, callback)</a></dt>
45
+ <dd></dd>
46
+ </dl>
36
47
 
37
48
  <a name="GeoDb"></a>
38
49
 
39
50
  ## GeoDb
40
51
  Wrapper around sqlite databases
41
52
 
42
- **Kind**: global class
53
+ **Kind**: global class
43
54
 
44
55
  * [GeoDb](#GeoDb)
45
56
  * [new GeoDb(logger, zipsFilename, geonamesFilename)](#new_GeoDb_new)
46
- * [.close()](#GeoDb+close)
47
- * [.lookupZip(zip)](#GeoDb+lookupZip) <code>Location</code>
48
- * [.lookupGeoname(geonameid)](#GeoDb+lookupGeoname) <code>Location</code>
49
- * [.lookupLegacyCity(cityName)](#GeoDb+lookupLegacyCity) <code>Location</code>
57
+ * _instance_
58
+ * [.zipCache](#GeoDb+zipCache) : <code>Map.&lt;string, Location&gt;</code>
59
+ * [.geonamesCache](#GeoDb+geonamesCache) : <code>Map.&lt;number, Location&gt;</code>
60
+ * [.legacyCities](#GeoDb+legacyCities) : <code>Map.&lt;string, number&gt;</code>
61
+ * [.countryNames](#GeoDb+countryNames) : <code>Map.&lt;string, string&gt;</code>
62
+ * [.close()](#GeoDb+close)
63
+ * [.lookupZip(zip)](#GeoDb+lookupZip) ⇒ <code>Location</code>
64
+ * [.lookupGeoname(geonameid)](#GeoDb+lookupGeoname) ⇒ <code>Location</code>
65
+ * [.lookupLegacyCity(cityName)](#GeoDb+lookupLegacyCity) ⇒ <code>Location</code>
66
+ * [.autoComplete(qraw, latlong)](#GeoDb+autoComplete) ⇒ <code>Array.&lt;Object&gt;</code>
67
+ * [.cacheZips()](#GeoDb+cacheZips)
68
+ * [.cacheGeonames()](#GeoDb+cacheGeonames)
69
+ * _static_
70
+ * [.transliterate(source, [options])](#GeoDb.transliterate) ⇒ <code>string</code>
71
+ * [.geonameCityDescr(cityName, admin1, countryName)](#GeoDb.geonameCityDescr) ⇒ <code>string</code>
72
+ * [.version()](#GeoDb.version)
50
73
 
51
74
  <a name="new_GeoDb_new"></a>
52
75
 
@@ -54,39 +77,147 @@ Wrapper around sqlite databases
54
77
 
55
78
  | Param | Type |
56
79
  | --- | --- |
57
- | logger | <code>any</code> |
58
- | zipsFilename | <code>string</code> |
59
- | geonamesFilename | <code>string</code> |
80
+ | logger | <code>any</code> |
81
+ | zipsFilename | <code>string</code> |
82
+ | geonamesFilename | <code>string</code> |
83
+
84
+ <a name="GeoDb+zipCache"></a>
85
+
86
+ ### geoDb.zipCache : <code>Map.&lt;string, Location&gt;</code>
87
+ **Kind**: instance property of [<code>GeoDb</code>](#GeoDb)
88
+ <a name="GeoDb+geonamesCache"></a>
60
89
 
90
+ ### geoDb.geonamesCache : <code>Map.&lt;number, Location&gt;</code>
91
+ **Kind**: instance property of [<code>GeoDb</code>](#GeoDb)
92
+ <a name="GeoDb+legacyCities"></a>
93
+
94
+ ### geoDb.legacyCities : <code>Map.&lt;string, number&gt;</code>
95
+ **Kind**: instance property of [<code>GeoDb</code>](#GeoDb)
96
+ <a name="GeoDb+countryNames"></a>
97
+
98
+ ### geoDb.countryNames : <code>Map.&lt;string, string&gt;</code>
99
+ **Kind**: instance property of [<code>GeoDb</code>](#GeoDb)
61
100
  <a name="GeoDb+close"></a>
62
101
 
63
102
  ### geoDb.close()
64
103
  Closes database handles
65
104
 
66
- **Kind**: instance method of [<code>GeoDb</code>](#GeoDb)
105
+ **Kind**: instance method of [<code>GeoDb</code>](#GeoDb)
67
106
  <a name="GeoDb+lookupZip"></a>
68
107
 
69
108
  ### geoDb.lookupZip(zip) ⇒ <code>Location</code>
70
- **Kind**: instance method of [<code>GeoDb</code>](#GeoDb)
109
+ **Kind**: instance method of [<code>GeoDb</code>](#GeoDb)
71
110
 
72
111
  | Param | Type |
73
112
  | --- | --- |
74
- | zip | <code>string</code> |
113
+ | zip | <code>string</code> |
75
114
 
76
115
  <a name="GeoDb+lookupGeoname"></a>
77
116
 
78
117
  ### geoDb.lookupGeoname(geonameid) ⇒ <code>Location</code>
79
- **Kind**: instance method of [<code>GeoDb</code>](#GeoDb)
118
+ **Kind**: instance method of [<code>GeoDb</code>](#GeoDb)
80
119
 
81
120
  | Param | Type |
82
121
  | --- | --- |
83
- | geonameid | <code>number</code> |
122
+ | geonameid | <code>number</code> |
84
123
 
85
124
  <a name="GeoDb+lookupLegacyCity"></a>
86
125
 
87
126
  ### geoDb.lookupLegacyCity(cityName) ⇒ <code>Location</code>
88
- **Kind**: instance method of [<code>GeoDb</code>](#GeoDb)
127
+ **Kind**: instance method of [<code>GeoDb</code>](#GeoDb)
128
+
129
+ | Param | Type |
130
+ | --- | --- |
131
+ | cityName | <code>string</code> |
132
+
133
+ <a name="GeoDb+autoComplete"></a>
134
+
135
+ ### geoDb.autoComplete(qraw, latlong) ⇒ <code>Array.&lt;Object&gt;</code>
136
+ Generates autocomplete results based on a query string
137
+
138
+ **Kind**: instance method of [<code>GeoDb</code>](#GeoDb)
139
+
140
+ | Param | Type | Default |
141
+ | --- | --- | --- |
142
+ | qraw | <code>string</code> | |
143
+ | latlong | <code>boolean</code> | <code>false</code> |
144
+
145
+ <a name="GeoDb+cacheZips"></a>
146
+
147
+ ### geoDb.cacheZips()
148
+ Reads entire ZIP database and caches in-memory
149
+
150
+ **Kind**: instance method of [<code>GeoDb</code>](#GeoDb)
151
+ <a name="GeoDb+cacheGeonames"></a>
152
+
153
+ ### geoDb.cacheGeonames()
154
+ Reads entire geonames database and caches in-memory
155
+
156
+ **Kind**: instance method of [<code>GeoDb</code>](#GeoDb)
157
+ <a name="GeoDb.transliterate"></a>
158
+
159
+ ### GeoDb.transliterate(source, [options]) ⇒ <code>string</code>
160
+ Convenience wrapper of the `transliterate` function from `transliteration` npm package.
161
+ Transliterate the string `source` and return the result.
162
+
163
+ **Kind**: static method of [<code>GeoDb</code>](#GeoDb)
164
+
165
+ | Param | Type |
166
+ | --- | --- |
167
+ | source | <code>string</code> |
168
+ | [options] | <code>any</code> |
169
+
170
+ <a name="GeoDb.geonameCityDescr"></a>
171
+
172
+ ### GeoDb.geonameCityDescr(cityName, admin1, countryName) ⇒ <code>string</code>
173
+ Builds a city description from geonameid string components
174
+
175
+ **Kind**: static method of [<code>GeoDb</code>](#GeoDb)
176
+
177
+ | Param | Type | Description |
178
+ | --- | --- | --- |
179
+ | cityName | <code>string</code> | e.g. 'Tel Aviv' or 'Chicago' |
180
+ | admin1 | <code>string</code> | e.g. 'England' or 'Massachusetts' |
181
+ | countryName | <code>string</code> | full country name, e.g. 'Israel' or 'United States' |
182
+
183
+ <a name="GeoDb.version"></a>
184
+
185
+ ### GeoDb.version()
186
+ Returns the version of the GeoDb package
187
+
188
+ **Kind**: static method of [<code>GeoDb</code>](#GeoDb)
189
+ <a name="buildGeonamesSqlite"></a>
190
+
191
+ ## buildGeonamesSqlite(opts)
192
+ Builds `geonames.sqlite3` from files downloaded from geonames.org
193
+
194
+ **Kind**: global function
195
+
196
+ | Param | Type |
197
+ | --- | --- |
198
+ | opts | <code>any</code> |
199
+
200
+ <a name="doSql"></a>
201
+
202
+ ## doSql(logger, db, ...sqls)
203
+ **Kind**: global function
204
+
205
+ | Param | Type |
206
+ | --- | --- |
207
+ | logger | <code>pino.Logger</code> |
208
+ | db | <code>Database</code> |
209
+ | ...sqls | <code>string</code> |
210
+
211
+ <a name="doFile"></a>
212
+
213
+ ## doFile(logger, db, infile, tableName, expectedFields, callback)
214
+ **Kind**: global function
89
215
 
90
216
  | Param | Type |
91
217
  | --- | --- |
92
- | cityName | <code>string</code> |
218
+ | logger | <code>pino.Logger</code> |
219
+ | db | <code>Database</code> |
220
+ | infile | <code>string</code> |
221
+ | tableName | <code>string</code> |
222
+ | expectedFields | <code>number</code> |
223
+ | callback | <code>function</code> |
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- /*! @hebcal/geo-sqlite v5.4.0 */
1
+ /*! @hebcal/geo-sqlite v5.5.0 */
2
2
  import Database from 'better-sqlite3';
3
3
  import { Location, Locale } from '@hebcal/core';
4
4
  import '@hebcal/cities';
@@ -6,6 +6,336 @@ import events from 'events';
6
6
  import fs from 'fs';
7
7
  import readline from 'readline';
8
8
 
9
+ class QuickLRU extends Map {
10
+ #size = 0;
11
+ #cache = new Map();
12
+ #oldCache = new Map();
13
+ #maxSize;
14
+ #maxAge;
15
+ #onEviction;
16
+
17
+ constructor(options = {}) {
18
+ super();
19
+
20
+ if (!(options.maxSize && options.maxSize > 0)) {
21
+ throw new TypeError('`maxSize` must be a number greater than 0');
22
+ }
23
+
24
+ if (typeof options.maxAge === 'number' && options.maxAge === 0) {
25
+ throw new TypeError('`maxAge` must be a number greater than 0');
26
+ }
27
+
28
+ this.#maxSize = options.maxSize;
29
+ this.#maxAge = options.maxAge || Number.POSITIVE_INFINITY;
30
+ this.#onEviction = options.onEviction;
31
+ }
32
+
33
+ // For tests.
34
+ get __oldCache() {
35
+ return this.#oldCache;
36
+ }
37
+
38
+ #emitEvictions(cache) {
39
+ if (typeof this.#onEviction !== 'function') {
40
+ return;
41
+ }
42
+
43
+ for (const [key, item] of cache) {
44
+ this.#onEviction(key, item.value);
45
+ }
46
+ }
47
+
48
+ #deleteIfExpired(key, item) {
49
+ if (typeof item.expiry === 'number' && item.expiry <= Date.now()) {
50
+ if (typeof this.#onEviction === 'function') {
51
+ this.#onEviction(key, item.value);
52
+ }
53
+
54
+ return this.delete(key);
55
+ }
56
+
57
+ return false;
58
+ }
59
+
60
+ #getOrDeleteIfExpired(key, item) {
61
+ const deleted = this.#deleteIfExpired(key, item);
62
+ if (deleted === false) {
63
+ return item.value;
64
+ }
65
+ }
66
+
67
+ #getItemValue(key, item) {
68
+ return item.expiry ? this.#getOrDeleteIfExpired(key, item) : item.value;
69
+ }
70
+
71
+ #peek(key, cache) {
72
+ const item = cache.get(key);
73
+ return this.#getItemValue(key, item);
74
+ }
75
+
76
+ #set(key, value) {
77
+ this.#cache.set(key, value);
78
+ this.#size++;
79
+
80
+ if (this.#size >= this.#maxSize) {
81
+ this.#size = 0;
82
+ this.#emitEvictions(this.#oldCache);
83
+ this.#oldCache = this.#cache;
84
+ this.#cache = new Map();
85
+ }
86
+ }
87
+
88
+ #moveToRecent(key, item) {
89
+ this.#oldCache.delete(key);
90
+ this.#set(key, item);
91
+ }
92
+
93
+ * #entriesAscending() {
94
+ for (const item of this.#oldCache) {
95
+ const [key, value] = item;
96
+ if (!this.#cache.has(key)) {
97
+ const deleted = this.#deleteIfExpired(key, value);
98
+ if (deleted === false) {
99
+ yield item;
100
+ }
101
+ }
102
+ }
103
+
104
+ for (const item of this.#cache) {
105
+ const [key, value] = item;
106
+ const deleted = this.#deleteIfExpired(key, value);
107
+ if (deleted === false) {
108
+ yield item;
109
+ }
110
+ }
111
+ }
112
+
113
+ get(key) {
114
+ if (this.#cache.has(key)) {
115
+ const item = this.#cache.get(key);
116
+ return this.#getItemValue(key, item);
117
+ }
118
+
119
+ if (this.#oldCache.has(key)) {
120
+ const item = this.#oldCache.get(key);
121
+ if (this.#deleteIfExpired(key, item) === false) {
122
+ this.#moveToRecent(key, item);
123
+ return item.value;
124
+ }
125
+ }
126
+ }
127
+
128
+ set(key, value, {maxAge = this.#maxAge} = {}) {
129
+ const expiry = typeof maxAge === 'number' && maxAge !== Number.POSITIVE_INFINITY
130
+ ? (Date.now() + maxAge)
131
+ : undefined;
132
+
133
+ if (this.#cache.has(key)) {
134
+ this.#cache.set(key, {
135
+ value,
136
+ expiry,
137
+ });
138
+ } else {
139
+ this.#set(key, {value, expiry});
140
+ }
141
+
142
+ return this;
143
+ }
144
+
145
+ has(key) {
146
+ if (this.#cache.has(key)) {
147
+ return !this.#deleteIfExpired(key, this.#cache.get(key));
148
+ }
149
+
150
+ if (this.#oldCache.has(key)) {
151
+ return !this.#deleteIfExpired(key, this.#oldCache.get(key));
152
+ }
153
+
154
+ return false;
155
+ }
156
+
157
+ peek(key) {
158
+ if (this.#cache.has(key)) {
159
+ return this.#peek(key, this.#cache);
160
+ }
161
+
162
+ if (this.#oldCache.has(key)) {
163
+ return this.#peek(key, this.#oldCache);
164
+ }
165
+ }
166
+
167
+ expiresIn(key) {
168
+ const item = this.#cache.get(key) ?? this.#oldCache.get(key);
169
+ if (item) {
170
+ return item.expiry ? item.expiry - Date.now() : Number.POSITIVE_INFINITY;
171
+ }
172
+ }
173
+
174
+ delete(key) {
175
+ const deleted = this.#cache.delete(key);
176
+ if (deleted) {
177
+ this.#size--;
178
+ }
179
+
180
+ return this.#oldCache.delete(key) || deleted;
181
+ }
182
+
183
+ clear() {
184
+ this.#cache.clear();
185
+ this.#oldCache.clear();
186
+ this.#size = 0;
187
+ }
188
+
189
+ resize(newSize) {
190
+ if (!(newSize && newSize > 0)) {
191
+ throw new TypeError('`maxSize` must be a number greater than 0');
192
+ }
193
+
194
+ const items = [...this.#entriesAscending()];
195
+ const removeCount = items.length - newSize;
196
+ if (removeCount < 0) {
197
+ this.#cache = new Map(items);
198
+ this.#oldCache = new Map();
199
+ this.#size = items.length;
200
+ } else {
201
+ if (removeCount > 0) {
202
+ this.#emitEvictions(items.slice(0, removeCount));
203
+ }
204
+
205
+ this.#oldCache = new Map(items.slice(removeCount));
206
+ this.#cache = new Map();
207
+ this.#size = 0;
208
+ }
209
+
210
+ this.#maxSize = newSize;
211
+ }
212
+
213
+ evict(count = 1) {
214
+ const requested = Number(count);
215
+ if (!requested || requested <= 0) {
216
+ return;
217
+ }
218
+
219
+ const items = [...this.#entriesAscending()];
220
+ const evictCount = Math.trunc(Math.min(requested, Math.max(items.length - 1, 0)));
221
+ if (evictCount <= 0) {
222
+ return;
223
+ }
224
+
225
+ this.#emitEvictions(items.slice(0, evictCount));
226
+ this.#oldCache = new Map(items.slice(evictCount));
227
+ this.#cache = new Map();
228
+ this.#size = 0;
229
+ }
230
+
231
+ * keys() {
232
+ for (const [key] of this) {
233
+ yield key;
234
+ }
235
+ }
236
+
237
+ * values() {
238
+ for (const [, value] of this) {
239
+ yield value;
240
+ }
241
+ }
242
+
243
+ * [Symbol.iterator]() {
244
+ for (const item of this.#cache) {
245
+ const [key, value] = item;
246
+ const deleted = this.#deleteIfExpired(key, value);
247
+ if (deleted === false) {
248
+ yield [key, value.value];
249
+ }
250
+ }
251
+
252
+ for (const item of this.#oldCache) {
253
+ const [key, value] = item;
254
+ if (!this.#cache.has(key)) {
255
+ const deleted = this.#deleteIfExpired(key, value);
256
+ if (deleted === false) {
257
+ yield [key, value.value];
258
+ }
259
+ }
260
+ }
261
+ }
262
+
263
+ * entriesDescending() {
264
+ let items = [...this.#cache];
265
+ for (let i = items.length - 1; i >= 0; --i) {
266
+ const item = items[i];
267
+ const [key, value] = item;
268
+ const deleted = this.#deleteIfExpired(key, value);
269
+ if (deleted === false) {
270
+ yield [key, value.value];
271
+ }
272
+ }
273
+
274
+ items = [...this.#oldCache];
275
+ for (let i = items.length - 1; i >= 0; --i) {
276
+ const item = items[i];
277
+ const [key, value] = item;
278
+ if (!this.#cache.has(key)) {
279
+ const deleted = this.#deleteIfExpired(key, value);
280
+ if (deleted === false) {
281
+ yield [key, value.value];
282
+ }
283
+ }
284
+ }
285
+ }
286
+
287
+ * entriesAscending() {
288
+ for (const [key, value] of this.#entriesAscending()) {
289
+ yield [key, value.value];
290
+ }
291
+ }
292
+
293
+ get size() {
294
+ if (!this.#size) {
295
+ return this.#oldCache.size;
296
+ }
297
+
298
+ let oldCacheSize = 0;
299
+ for (const key of this.#oldCache.keys()) {
300
+ if (!this.#cache.has(key)) {
301
+ oldCacheSize++;
302
+ }
303
+ }
304
+
305
+ return Math.min(this.#size + oldCacheSize, this.#maxSize);
306
+ }
307
+
308
+ get maxSize() {
309
+ return this.#maxSize;
310
+ }
311
+
312
+ get maxAge() {
313
+ return this.#maxAge;
314
+ }
315
+
316
+ entries() {
317
+ return this.entriesAscending();
318
+ }
319
+
320
+ forEach(callbackFunction, thisArgument = this) {
321
+ for (const [key, value] of this.entriesAscending()) {
322
+ callbackFunction.call(thisArgument, value, key, this);
323
+ }
324
+ }
325
+
326
+ get [Symbol.toStringTag]() {
327
+ return 'QuickLRU';
328
+ }
329
+
330
+ toString() {
331
+ return `QuickLRU(${this.size}/${this.maxSize})`;
332
+ }
333
+
334
+ [Symbol.for('nodejs.util.inspect.custom')]() {
335
+ return this.toString();
336
+ }
337
+ }
338
+
9
339
  var city2geonameid = {
10
340
  "AD-Andorra La Vella":3041563,
11
341
  "AE-Abu Dhabi":292968,
@@ -511,7 +841,7 @@ function munge(s) {
511
841
  }
512
842
 
513
843
  // DO NOT EDIT THIS AUTO-GENERATED FILE!
514
- const version = '5.4.0';
844
+ const version = '5.5.0';
515
845
 
516
846
  const GEONAME_SQL = `SELECT
517
847
  g.name as name,
@@ -644,10 +974,10 @@ class GeoDb {
644
974
  this.geonamesDb = new Database(geonamesFilename, {fileMustExist: true});
645
975
  this.zipStmt = this.zipsDb.prepare(ZIPCODE_SQL);
646
976
  /** @type {Map<string, Location>} */
647
- this.zipCache = new Map();
977
+ this.zipCache = new QuickLRU({maxSize: 1000});
648
978
  this.geonamesStmt = this.geonamesDb.prepare(GEONAME_SQL);
649
979
  /** @type {Map<number, Location>} */
650
- this.geonamesCache = new Map();
980
+ this.geonamesCache = new QuickLRU({maxSize: 1000});
651
981
  /** @type {Map<string, number>} */
652
982
  this.legacyCities = new Map();
653
983
  for (const [name, id] of Object.entries(city2geonameid)) {
@@ -1015,6 +1345,7 @@ class GeoDb {
1015
1345
  const start = Date.now();
1016
1346
  const stmt = this.zipsDb.prepare(ZIPCODE_ALL_SQL);
1017
1347
  const rows = stmt.all();
1348
+ this.zipCache = new Map(); // replace QuickLRU
1018
1349
  for (const row of rows) {
1019
1350
  const location = this.makeZipLocation(row);
1020
1351
  this.zipCache.set(row.ZipCode, location);
@@ -1028,6 +1359,7 @@ class GeoDb {
1028
1359
  const start = Date.now();
1029
1360
  const stmt = this.geonamesDb.prepare(GEONAME_ALL_SQL);
1030
1361
  const rows = stmt.all();
1362
+ this.geonamesCache = new Map(); // replace QuickLRU
1031
1363
  for (const row of rows) {
1032
1364
  const location = this.makeGeonameLocation(row.geonameid, row);
1033
1365
  this.geonamesCache.set(row.geonameid, location);
@@ -1036,6 +1368,7 @@ class GeoDb {
1036
1368
  if (this.logger) this.logger.info(`GeoDb: cached ${rows.length} geonames in ${end - start}ms`);
1037
1369
  }
1038
1370
 
1371
+ /** Returns the version of the GeoDb package */
1039
1372
  static version() {
1040
1373
  return version;
1041
1374
  }