@mountainpass/addressr 2.4.1 → 2.4.3

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.
@@ -31,8 +31,7 @@ var _elasticsearch = require("../client/elasticsearch");
31
31
  var _streamDown = _interopRequireDefault(require("../utils/stream-down"));
32
32
  var _setLinkOptions = require("./set-link-options");
33
33
  var _rangeExpansion = require("./range-expansion");
34
- var _keyv = require("keyv");
35
- var _keyvFile = require("keyv-file");
34
+ var _gnafPackageFetch = require("./gnaf-package-fetch");
36
35
  var _nodeCrypto = _interopRequireDefault(require("node:crypto"));
37
36
  var _glob = require("glob");
38
37
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
@@ -44,21 +43,17 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
44
43
  const fsp = _nodeFs.default.promises;
45
44
  var logger = (0, _debug.default)('api');
46
45
  var error = (0, _debug.default)('error');
47
- const cache = new _keyv.Keyv({
48
- store: new _keyvFile.KeyvFile({
49
- filename: 'target/keyv-file.msgpack'
50
- })
51
- });
46
+
47
+ // `cache`, `ONE_DAY_MS`, and `THIRTY_DAYS_MS` previously lived here for
48
+ // fetchPackageData. Moved to ./gnaf-package-fetch.js along with that
49
+ // function — see the comment above its `import` and P033.
50
+
52
51
  const PAGE_SIZE = process.env.PAGE_SIZE || 8;
53
52
  function getCoveredStates() {
54
53
  const covered = process.env.COVERED_STATES || '';
55
54
  return covered == '' ? [] : covered.split(',');
56
55
  }
57
56
  const COVERED_STATES = getCoveredStates();
58
- const ONE_DAY_S = 60 /*sec*/ * 60 /*min*/ * 24; /*hours*/
59
-
60
- const ONE_DAY_MS = 1000 * ONE_DAY_S;
61
- const THIRTY_DAYS_MS = ONE_DAY_MS * 30;
62
57
  const ES_INDEX_NAME = process.env.ES_INDEX_NAME || 'addressr';
63
58
  async function dropIndex() {
64
59
  await (0, _elasticsearch.dropIndex)(globalThis.esClient);
@@ -98,56 +93,17 @@ async function setAddresses(addr) {
98
93
  //logger(await searchForAddress('657 The Entrance Road')); //'2/25 TOTTERDE'; // 'UNT 2, BELCONNEN';);
99
94
  }
100
95
 
101
- // need to try proxying this to modify the headers if we want to use got's cache implementation
96
+ // fetchPackageData and the GNAF_PACKAGE_URL constant moved to
97
+ // ./gnaf-package-fetch.js so the data.gov.au CKAN fetch is testable in
98
+ // raw Node ESM (this file uses babel-only bare imports). See P033 for the
99
+ // source-inspection anti-pattern that motivated the extraction. The new
100
+ // module also adds a Mozilla-prefixed compatible-mode User-Agent header
101
+ // required by data.gov.au's CloudFront WAF (without it, fetch returns
102
+ // HTTP 403 — surfaced by run 25032179791).
102
103
 
103
- // SEE https://data.gov.au/data/dataset/19432f89-dc3a-4ef3-b943-5326ef1dbecc
104
- const GNAF_PACKAGE_URL = process.env.GNAF_PACKAGE_URL || 'https://data.gov.au/data/api/3/action/package_show?id=19432f89-dc3a-4ef3-b943-5326ef1dbecc';
105
- async function fetchPackageData() {
106
- const packageUrl = GNAF_PACKAGE_URL;
107
- // See if we have the value in cache
108
- const cachedResponse = await cache.get(packageUrl);
109
- logger('cached gnaf package data', cachedResponse);
110
- let age = 0;
111
- if (cachedResponse !== undefined) {
112
- cachedResponse.headers['x-cache'] = 'HIT';
113
- const created = new Date(cachedResponse.headers.date);
114
- logger('created', created);
115
- age = Date.now() - created;
116
- if (age <= ONE_DAY_MS) {
117
- return cachedResponse;
118
- }
119
- }
120
- // cached value was older than one day, so go fetch
121
- try {
122
- const fetchResponse = await fetch(packageUrl);
123
- const body = await fetchResponse.text();
124
- const headers = Object.fromEntries(fetchResponse.headers.entries());
125
- logger('fresh gnaf package data', {
126
- body,
127
- headers
128
- });
129
- await cache.set(packageUrl, {
130
- body,
131
- headers
132
- });
133
- headers['x-cache'] = 'MISS';
134
- return {
135
- body,
136
- headers
137
- };
138
- } catch (error_) {
139
- // we were unable to fetch. if we have cached value that isn't stale, return in
140
- if (cachedResponse !== undefined && age < THIRTY_DAYS_MS) {
141
- cachedResponse.headers['warning'] = '110 custom/1.0 "Response is Stale"';
142
- return cachedResponse;
143
- }
144
- // otherwise, throw the original network error
145
- throw error_;
146
- }
147
- }
148
104
  const GNAF_DIR = process.env.GNAF_DIR || `target/gnaf`;
149
105
  async function fetchGnafFile() {
150
- const response = await fetchPackageData();
106
+ const response = await (0, _gnafPackageFetch.fetchPackageData)();
151
107
  const pack = JSON.parse(response.body);
152
108
  // id as of 16/07 for zip is 4b084096-65e4-4c8e-abbe-5e54ff85f42f
153
109
  const dataResource = pack.result.resources.find(r => r.state === 'active' && r.mimetype === 'application/zip');
@@ -0,0 +1,115 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.LOADER_USER_AGENT = exports.GNAF_PACKAGE_URL = void 0;
7
+ exports.fetchPackageData = fetchPackageData;
8
+ var _keyv = require("keyv");
9
+ var _keyvFile = require("keyv-file");
10
+ var _debug = _interopRequireDefault(require("debug"));
11
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
12
+ // @jtbd JTBD-400 (Ship Releases Reliably From Trunk)
13
+ //
14
+ // Extracted from service/address-service.js so the loader's data.gov.au CKAN
15
+ // fetch is testable behaviourally. service/address-service.js uses babel-only
16
+ // bare imports that raw Node ESM cannot resolve; this module uses clean ESM
17
+ // with `.js` extensions throughout, so behavioural tests can import it
18
+ // directly. See P033 for the source-inspection anti-pattern this avoids.
19
+ //
20
+ // fetchPackageData accepts `fetch` and `cache` via dependency injection, with
21
+ // defaults to globalThis.fetch and a Keyv-file cache backed by
22
+ // target/keyv-file.msgpack (cwd-relative, NOT module-relative — must match
23
+ // the address-service.js call site's process.cwd()).
24
+
25
+ const logger = (0, _debug.default)('api');
26
+ const ONE_DAY_MS = 24 * 60 * 60 * 1000;
27
+ const THIRTY_DAYS_MS = 30 * ONE_DAY_MS;
28
+
29
+ // Dataset registration on data.gov.au:
30
+ // https://data.gov.au/data/dataset/19432f89-dc3a-4ef3-b943-5326ef1dbecc
31
+ const GNAF_PACKAGE_URL = exports.GNAF_PACKAGE_URL = process.env.GNAF_PACKAGE_URL || 'https://data.gov.au/data/api/3/action/package_show?id=19432f89-dc3a-4ef3-b943-5326ef1dbecc';
32
+
33
+ // data.gov.au's CloudFront WAF returns HTTP 403 on Node-default User-Agent
34
+ // (the CKAN package_show response is HTML "<!DOCTYPE…" instead of JSON).
35
+ // A Mozilla-prefixed compatible-mode UA that identifies the tool gets
36
+ // through. Verified 2026-04-28 via curl: same URL with no UA → 403; with
37
+ // this UA → 200. Surfaced by the failed ADR 029 Phase 1 step 5 v2 populate
38
+ // (run 25032179791).
39
+ const LOADER_USER_AGENT = exports.LOADER_USER_AGENT = 'Mozilla/5.0 (compatible; addressr-loader; +https://github.com/mountain-pass/addressr)';
40
+
41
+ // Default cache instance — same Keyv-file backend service/address-service.js
42
+ // originally created. Path is cwd-relative by design (resolves against
43
+ // process.cwd() at runtime, matching the addressr-loader CLI's working dir).
44
+ const defaultCache = new _keyv.Keyv({
45
+ store: new _keyvFile.KeyvFile({
46
+ filename: 'target/keyv-file.msgpack'
47
+ })
48
+ });
49
+
50
+ /**
51
+ * Fetch the data.gov.au CKAN package_show response for the G-NAF dataset.
52
+ *
53
+ * On cache hit (entry < 1 day old): returns the cached response with
54
+ * `x-cache: HIT` annotation, no network call.
55
+ *
56
+ * On cache miss: fetches with the LOADER_USER_AGENT header, persists the
57
+ * response to cache, returns with `x-cache: MISS`.
58
+ *
59
+ * On fetch failure: returns the cached response with a stale `warning`
60
+ * header if one exists and is younger than 30 days; otherwise rethrows.
61
+ *
62
+ * @param {object} [deps] dependency injection for testing
63
+ * @param {typeof globalThis.fetch} [deps.fetch] fetch implementation (default: globalThis.fetch)
64
+ * @param {{ get: (k: string) => Promise<any>, set: (k: string, v: any) => Promise<any> }} [deps.cache] Keyv-shaped cache (default: target/keyv-file.msgpack)
65
+ * @returns {Promise<{ body: string, headers: Record<string, string> }>}
66
+ */
67
+ async function fetchPackageData({
68
+ fetch: fetchFunction = globalThis.fetch,
69
+ cache = defaultCache
70
+ } = {}) {
71
+ const packageUrl = GNAF_PACKAGE_URL;
72
+ const cachedResponse = await cache.get(packageUrl);
73
+ logger('cached gnaf package data', cachedResponse);
74
+ let age = 0;
75
+ if (cachedResponse !== undefined) {
76
+ cachedResponse.headers['x-cache'] = 'HIT';
77
+ const created = new Date(cachedResponse.headers.date);
78
+ logger('created', created);
79
+ age = Date.now() - created;
80
+ if (age <= ONE_DAY_MS) {
81
+ return cachedResponse;
82
+ }
83
+ }
84
+ // Cached value was older than one day, so go fetch.
85
+ try {
86
+ const fetchResponse = await fetchFunction(packageUrl, {
87
+ headers: {
88
+ 'User-Agent': LOADER_USER_AGENT
89
+ }
90
+ });
91
+ const body = await fetchResponse.text();
92
+ const headers = Object.fromEntries(fetchResponse.headers.entries());
93
+ logger('fresh gnaf package data', {
94
+ body,
95
+ headers
96
+ });
97
+ await cache.set(packageUrl, {
98
+ body,
99
+ headers
100
+ });
101
+ headers['x-cache'] = 'MISS';
102
+ return {
103
+ body,
104
+ headers
105
+ };
106
+ } catch (error_) {
107
+ // Network unreachable: serve stale-up-to-30-days from cache rather
108
+ // than fail the load entirely.
109
+ if (cachedResponse !== undefined && age < THIRTY_DAYS_MS) {
110
+ cachedResponse.headers['warning'] = '110\tcustom/1.0 "Response is Stale"';
111
+ return cachedResponse;
112
+ }
113
+ throw error_;
114
+ }
115
+ }
package/lib/version.js CHANGED
@@ -5,4 +5,4 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.version = void 0;
7
7
  // Generated by genversion.
8
- const version = exports.version = '2.4.1';
8
+ const version = exports.version = '2.4.3';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mountainpass/addressr",
3
- "version": "2.4.1",
3
+ "version": "2.4.3",
4
4
  "description": "Australian Address Validation, Search and Autocomplete",
5
5
  "author": {
6
6
  "name": "Mountain Pass",