UNPKG

@loqate/compose

Version:

Loqate Compose SDK — high-level flows that compose base API clients into easy-to-use sessions.

1,106 lines (1,098 loc) 33.1 kB
import { PARSING_STATUS_RANKING, VERIFICATION_STATUS_RANKING, parseAVC, render } from "./parser-DXBc9ZXL.js"; import { Loqate } from "@loqate/core"; import mitt from "mitt"; //#region src/utils/deepStore.ts function createDeepStore(initial, opts = {}, equals = deepEqual) { let value = clone(initial); let baseline = clone(initial); const subs = /* @__PURE__ */ new Set(); const notify = () => subs.forEach((fn) => fn()); const arrayStrategy = opts.arrayStrategy ?? "replace"; return { get: () => value, set: (next) => { value = clone(next); notify(); }, patch: (delta) => { value = deepMerge(value, delta, arrayStrategy); notify(); }, setAt: (path, v) => { value = setAtPath(value, path, v); notify(); }, reset: () => { value = clone(baseline); notify(); }, markClean: () => { baseline = clone(value); }, isDirty: () => !equals(value, baseline), subscribe: (fn) => { subs.add(fn); return () => subs.delete(fn); }, subscribeSel: (sel, eq = Object.is) => { let prev = sel(value); const listener = () => { const next = sel(value); if (!eq(prev, next)) { prev = next; fn(); } }; const fn = () => {}; subs.add(listener); return () => subs.delete(listener); } }; } function isObj(x) { return x !== null && typeof x === "object"; } function isPlainObject(x) { if (!isObj(x)) return false; const proto = Object.getPrototypeOf(x); return proto === Object.prototype || proto === null; } function clone(x) { if (Array.isArray(x)) return x.map(clone); if (x instanceof Date) return new Date(x.getTime()); if (isPlainObject(x)) { const out = {}; for (const k in x) out[k] = clone(x[k]); return out; } return x; } function deepEqual(a, b) { if (Object.is(a, b)) return true; if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime(); if (Array.isArray(a) && Array.isArray(b)) { if (a.length !== b.length) return false; for (let i = 0; i < a.length; i++) if (!deepEqual(a[i], b[i])) return false; return true; } if (isPlainObject(a) && isPlainObject(b)) { const ak = Object.keys(a), bk = Object.keys(b); if (ak.length !== bk.length) return false; for (const k of ak) if (!deepEqual(a[k], b[k])) return false; return true; } return false; } function deepMerge(base, delta, arrayStrategy) { if (!isPlainObject(base) || !isPlainObject(delta)) return clone(delta) ?? clone(base); const out = Array.isArray(base) ? [...base] : { ...base }; for (const k of Object.keys(delta)) { const bv = base[k]; const dv = delta[k]; if (dv === void 0) continue; if (Array.isArray(bv) && Array.isArray(dv)) out[k] = arrayStrategy === "concat" ? [...bv, ...dv] : clone(dv); else if (isPlainObject(bv) && isPlainObject(dv)) out[k] = deepMerge(bv, dv, arrayStrategy); else out[k] = clone(dv); } return out; } function setAtPath(obj, path, value) { if (!path.length) return clone(value); const out = Array.isArray(obj) ? [...obj] : { ...obj }; let cur = out; for (let i = 0; i < path.length - 1; i++) { const key = path[i]; const next = cur[key]; if (Array.isArray(next)) cur[key] = [...next]; else if (isPlainObject(next)) cur[key] = { ...next }; else cur[key] = typeof path[i + 1] === "number" ? [] : {}; cur = cur[key]; } cur[path[path.length - 1]] = clone(value); return out; } //#endregion //#region src/utils/avc/evaluator.ts const DEFAULT_GREEN_TEST = { verificationStatus: { "===": "V" }, matchscore: { ">=": 90 } }; const DEFAULT_ORANGE_TEST = { verificationStatus: { ">=": "P" }, matchscore: { ">=": 80 } }; const testAVC = (avc, rules = DEFAULT_GREEN_TEST) => { if (typeof avc === "string") avc = parseAVC(avc); for (const [field, test] of Object.entries(rules)) { const actualValue = getFieldNumericValue(field, avc); for (const [operator, expectedRaw] of Object.entries(test)) if (!evaluateOperator(actualValue, operator, resolveRank(field, expectedRaw))) return false; } return true; }; const getFieldNumericValue = (field, avc) => { switch (field) { case "matchscore": return avc.matchscore; case "verificationStatus": return resolveRank(field, avc.verificationStatus); case "postMatchLevel": return avc.postMatchLevel; case "preMatchLevel": return avc.preMatchLevel; case "parsingStatus": return resolveRank(field, avc.parsingStatus); case "lexiconIdentificationMatchLevel": return avc.lexiconIdentificationMatchLevel; case "contextIdentificationMatchLevel": return avc.contextIdentificationMatchLevel; case "postcodeStatus": return avc.postcodeStatusNumeric; } }; const resolveRank = (field, value) => { switch (field) { case "verificationStatus": return VERIFICATION_STATUS_RANKING[value] ?? throwInvalid(field, value); case "parsingStatus": return PARSING_STATUS_RANKING[value] ?? throwInvalid(field, value); default: const num = Number(value); if (isNaN(num)) throwInvalid(field, value); return num; } }; const throwInvalid = (f, v) => { throw new Error(`Invalid symbolic value "${v}" for field "${f}"`); }; const evaluateOperator = (a, operator, b) => { switch (operator) { case ">": return a > b; case ">=": return a >= b; case "<": return a < b; case "<=": return a <= b; case "===": return a === b; default: throw new Error(`Unsupported operator: ${operator}`); } }; //#endregion //#region src/utils/eventful.ts const noopLogger = { debug: () => {}, info: () => {}, warn: () => {}, error: () => {} }; /** * Generic, typed event-emitter base with optional store + state snapshot * provided to *WithStore helpers. * * E: event map (key -> payload type) * S: state shape (your snapshot) * TStore: store interface you want to expose (can be read-only) */ var Eventful = class { constructor(logger) { this.emitter = mitt(); this.logger = logger || noopLogger; } on(type, handler) { this.emitter.on(type, handler); return () => this.off(type, handler); } off(type, handler) { this.emitter.off(type, handler); } once(type, handler) { const wrap = (evt) => { this.emitter.off(type, wrap); handler(evt); }; this.emitter.on(type, wrap); return () => this.emitter.off(type, wrap); } onAny(handler) { this.emitter.on("*", handler); return () => this.offAny(handler); } offAny(handler) { this.emitter.off("*", handler); } onceAny(handler) { const wrap = (type, evt) => { this.emitter.off("*", wrap); handler(type, evt); }; this.emitter.on("*", wrap); return () => this.emitter.off("*", wrap); } onWithStore(type, handler) { const wrap = (payload) => handler(payload, { store: this.getStore(), state: this.getState() }); this.emitter.on(type, wrap); return () => this.emitter.off(type, wrap); } onceWithStore(type, handler) { const wrap = (payload) => { this.emitter.off(type, wrap); handler(payload, { store: this.getStore(), state: this.getState() }); }; this.emitter.on(type, wrap); return () => this.emitter.off(type, wrap); } onAnyWithStore(handler) { const wrap = (type, payload) => handler(type, payload, { store: this.getStore(), state: this.getState() }); this.emitter.on("*", wrap); return () => this.emitter.off("*", wrap); } onceAnyWithStore(handler) { const wrap = (type, payload) => { this.emitter.off("*", wrap); handler(type, payload, { store: this.getStore(), state: this.getState() }); }; this.emitter.on("*", wrap); return () => this.emitter.off("*", wrap); } /** Protected emit for subclasses. Keeps external API clean. */ emit(type, payload) { this.emitter.emit(type, payload); } }; //#endregion //#region src/session/address/AddressSession.ts var AddressSession = class extends Eventful { constructor(init) { super(init.logger); this._findTimer = null; this._findPendingResolves = []; this._findPendingRejects = []; this._loqate = new Loqate({ apiKey: init.apiKey }); this._store = createDeepStore({ findCount: 0, apiKey: init.apiKey, retrieveCount: 0, verifyCount: 0, isCleanCapture: false, biasing: { country: init.biasing?.country, location: { latitude: init.biasing?.location?.latitude, longitude: init.biasing?.location?.longitude } }, verify: { request: init.verify?.request, requestHistory: [], outputMappings: init.verify?.outputMappings ?? [], inputMappings: init.verify?.inputMappings ?? [], items: [], evaluation: { pass: init.verify?.evalution?.pass ?? DEFAULT_GREEN_TEST, review: init.verify?.evalution?.review ?? DEFAULT_ORANGE_TEST } }, capture: { retrieve: { request: init.capture?.retrieve?.request, requestHistory: [], mappings: init.capture?.mappings ?? [] }, find: { request: init.capture?.find?.request, searchText: "", items: [], requestHistory: [], currentContainer: void 0, debounceMs: init.capture?.find?.debounceMs ?? 0 } } }); this.emit("state:changed", this._store.get()); } getState() { return this._store.get(); } getStore() { return this._store; } /** Patch store and emit a single 'state:changed'. */ _patch(next) { this._store.patch(next); this.emit("state:changed", this._store.get()); } _biasChanged() { this.emit("bias:changed", this._store.get().biasing); } async biasToIP(ip) { try { const res = await this._loqate.geocoding.ip2Country({ ipAddress: ip }); this._patch({ biasing: { country: res.items?.[0]?.country || void 0 } }); this.logger.info("Biased to IP country:", this._store.get().biasing); this._biasChanged(); } catch (error) { this.logger.error("Failed to bias to IP country:", error); this.emit("error", error); throw error; } } biasToCountry(country) { this._patch({ biasing: { country } }); this.logger.info("Biased to country:", this._store.get().biasing); this._biasChanged(); } biasToLocation(lat, lon) { this._patch({ biasing: { location: { latitude: lat, longitude: lon } } }); this.logger.info("Biased to location:", this._store.get().biasing); this._biasChanged(); } subscribe(sel, onChange, eq = Object.is) { let prev = sel(this._store.get()); this.logger.debug("Initial slice:", prev); return this._store.subscribe(() => { const next = sel(this._store.get()); this.logger.debug("Next slice:", next); if (!eq(prev, next)) { prev = next; onChange(next, this._store); } }); } _generateOutMap(response, source) { const map = {}; const mappings = source == "capture" ? this._store.get().capture.retrieve.mappings : this._store.get().verify.outputMappings; for (const m of mappings) if (m.field && m.field in response) map[m.id] = response[m.field]; else if (m.field) map[m.id] = render(m.field, response, { escapeHtml: false }); else map[m.id] = void 0; return map; } _generateCaptureMappingMap(response) { return this._generateOutMap(response, "capture"); } _generateVerifyMappingMap(response) { return this._generateOutMap(response, "verify"); } async select(item) { this.logger.debug("Selected item:", item); let finalItem; if (typeof item === "number") { const items = this._store.get().capture.find.items ?? []; if (item < 0 || item >= items.length) throw new Error(`Item index ${item} is out of bounds`); item = items[item]; } finalItem = item; if (finalItem.type !== "Address") { await this.expandContainer(finalItem); return; } else await this.retrieve(finalItem); } async expandContainer(container) { this.logger.debug("Expanding container:", container); if (typeof container === "number") { const items = this._store.get().capture.find.items ?? []; if (container < 0 || container >= items.length) throw new Error(`Container index ${container} is out of bounds`); container = items[container]; } if (typeof container === "object" && "type" in container) { if (container.type === "Address") throw new Error(`Cannot expand an address item`); container = container?.id ?? ""; } this.emit("find:expandContainer", container); this._patch({ capture: { find: { currentContainer: container } } }); let lastRequest = this._store.get().capture.find.requestHistory?.slice(-1)[0]; this.logger.debug("Expanding container checking history:", lastRequest, this._store.get().capture.find.requestHistory); if (lastRequest) { this.logger.debug("Expanding container:", lastRequest); return await this.find({ ...lastRequest.request, container }, lastRequest.options); } return []; } updateMapFields(updates) { const map = this._store.get().outMap?.map ?? {}; for (const u of updates) map[u.id] = u.value; this._patch({ outMap: { map, captureClean: false, verifyClean: false } }); this.emit("map:updated", map); return map; } updateMapField(id, value) { return this.updateMapFields([{ id, value }]); } _generateVerifyInputAddress(countryOverride) { const address = {}; const inputMappings = this._store.get().verify.inputMappings; const outMap = this._store.get().outMap?.map ?? {}; this.logger.debug("Generating verify input address:", { inputMappings, outMap }); for (const m of inputMappings) if (m.field) address[m.field] = outMap[m.id]; if (countryOverride) address.country = countryOverride; this.logger.debug("Generated verify input address:", address); return address; } acceptVerifyResponse() { if (this._store.get().verify.response?.length === 0) throw new Error("No verify response to commit from"); let map = this._generateVerifyMappingMap(this._store.get().verify?.response?.[0]?.match ?? {}); this._patch({ outMap: { map, verifyClean: true } }); return map; } async verify(options) { try { const { force, country } = options ?? {}; if (this._store.get().outMap?.captureClean && !force) { this.logger.debug("Skipping verify, capture data clean and map unchanged"); return { raw: [], composed: [] }; } const request = { ...this._store.get().verify.request, addresses: [this._generateVerifyInputAddress(country)] }; this.emit("verify:request", { request }); this.logger.debug("Verifying address with request:", request); const res = await this._loqate.cleansing.batch(request); this.logger.debug("Verifying address with response:", res); let composed = (res?.[0]?.matches ?? []).map((m) => { if (!m.avc || m.avc.trim().length === 0) return { match: m, parsedAVC: null, evaluation: "fail" }; const parsedAVC = parseAVC(m.avc ?? ""); let evaluation = "fail"; if (testAVC(parsedAVC, this._store.get().verify.evaluation.pass)) evaluation = "pass"; else if (testAVC(parsedAVC, this._store.get().verify.evaluation.review)) evaluation = "review"; return { match: m, parsedAVC, evaluation }; }); this._patch({ verifyCount: this._store.get().verifyCount + 1, verify: { response: composed, requestHistory: [...this._store.get().verify.requestHistory ?? [], { request, options: {} }] } }); const raw = res ?? []; this.emit("verify:response", { request, raw, composed }); return { raw, composed }; } catch (error) { this.logger.error("Verify request failed:", error); this.emit("error", error); throw error; } } async retrieve(request, options) { try { if (typeof request === "number") { const items$1 = this._store.get().capture.find.items ?? []; if (request < 0 || request >= items$1.length) throw new Error(`Item index ${request} is out of bounds`); request = items$1[request]; } if (typeof request === "string") request = { id: request }; if (typeof request === "object" && "type" in request) { if (request.type !== "Address") throw new Error(`Cannot retrieve non-address item of type "${request.type}"`); if (!request.id) throw new Error(`Cannot retrieve item with no id`); request = { id: request.id }; } request = { ...this._store.get().capture.retrieve.request, ...request }; this.logger.debug("Retrieving address with request:", request); this.emit("retrieve:request", { request }); let res = await this._loqate.capture.retrieve(request, options); this.logger.debug("Retrieving address with response:", res); let map = this._generateCaptureMappingMap(res?.items?.[0] ?? {}); this._patch({ outMap: { map, captureClean: true }, retrieveCount: this._store.get().retrieveCount + 1, capture: { find: { items: [] }, retrieve: { items: res.items ?? [], requestHistory: [...this._store.get().capture.retrieve.requestHistory ?? [], { request, options }] } } }); const items = res?.items ?? []; this.emit("map:updated", map); this.emit("retrieve:response", { request, items, map }); return { raw: items, map }; } catch (error) { this.logger.error("Retrieve request failed:", error); this.emit("error", error); throw error; } } _generateBiasParams() { const biasing = this._store.get().biasing; const params = {}; if (biasing?.country) { params.Bias = true; params.Origin = biasing.country; } if (biasing?.location?.latitude !== void 0 && biasing?.location?.longitude !== void 0) { params.Bias = true; params.Origin = `${biasing.location.latitude},${biasing.location.longitude}`; } return params; } /** * Single public find method: * - If debounceMs <= 0: runs immediately. * - If debounceMs > 0: debounces and coalesces callers. */ async find(request, options) { const delay = this._store.get().capture.find.debounceMs ?? 0; const reqObj = typeof request === "string" ? { text: request } : { ...request }; this._patch({ capture: { find: { searchText: reqObj.text ?? "" } } }); if (delay <= 0) { if (this._findTimer) { clearTimeout(this._findTimer); this._findTimer = null; while (this._findPendingRejects.length) this._findPendingRejects.shift()(/* @__PURE__ */ new Error("find() debounced call was superseded by immediate run")); this._findPendingResolves = []; } return this._doFind(reqObj, options); } const p = new Promise((resolve, reject) => { this._findPendingResolves.push(resolve); this._findPendingRejects.push(reject); }); if (this._findTimer) clearTimeout(this._findTimer); this._findTimer = setTimeout(async () => { this._findTimer = null; try { const items = await this._doFind(reqObj, options); while (this._findPendingResolves.length) this._findPendingResolves.shift()(items); this._findPendingRejects = []; } catch (err) { while (this._findPendingRejects.length) this._findPendingRejects.shift()(err); this._findPendingResolves = []; } }, delay); return p; } /** * Internal, immediate execution used by find(). */ async _doFind(request, options) { try { let req = { ...this._store.get().capture.find.request, ...this._generateBiasParams(), ...request, container: request.container ?? this._store.get().capture.find.currentContainer }; this.emit("find:request", { request: req }); const res = await this._loqate.capture.find(req, options); const patch = { findCount: this._store.get().findCount + 1, capture: { find: { items: res?.items ?? [], requestHistory: [...this._store.get().capture.find.requestHistory ?? [], { request: req, options }] } } }; this._patch(patch); const items = res?.items ?? []; this.emit("find:response", { request: req, items }); return items; } catch (error) { this.logger.error("Find request failed:", error); this.emit("error", error); throw error; } } }; //#endregion //#region src/session/email/EmailSession.ts var EmailSession = class extends Eventful { constructor(init) { super(init.logger); this._loqate = new Loqate({ apiKey: init.apiKey }); this._store = createDeepStore({ validateCount: 0, batchCount: 0, apiKey: init.apiKey, validate: { request: init.validate?.request, requestHistory: [], items: [] }, batch: { request: init.batch?.request, requestHistory: [], items: [] } }); this.emit("state:changed", this._store.get()); } getState() { return this._store.get(); } getStore() { return this._store; } /** Patch store and emit a single 'state:changed'. */ _patch(next) { this._store.patch(next); this.emit("state:changed", this._store.get()); } subscribe(sel, onChange, eq = Object.is) { let prev = sel(this._store.get()); this.logger.debug("Initial slice:", prev); return this._store.subscribe(() => { const next = sel(this._store.get()); this.logger.debug("Next slice:", next); if (!eq(prev, next)) { prev = next; onChange(next, this._store); } }); } async validate(request, options) { try { if (typeof request === "string") request = { email: request }; this._patch({ validate: { email: request.email } }); request = { ...this._store.get().validate.request, ...request }; this.emit("validate:request", { request }); this.logger.debug("Validating email with request:", request); let res = await this._loqate.emailValidation.validate(request, options); this.logger.debug("Validating email with response:", res); this._patch({ validateCount: this._store.get().validateCount + 1, validate: { response: res?.items ?? [], requestHistory: [...this._store.get().validate.requestHistory ?? [], { request, options }] } }); const response = res?.items ?? []; this.emit("validate:response", { request, response }); return response; } catch (error) { this.logger.error("Validate request failed:", error); this.emit("error", error); throw error; } } async batch(request, options) { try { if (typeof request === "string") request = { emails: request }; else if (Array.isArray(request)) request = { emails: request.join(",") }; this._patch({ batch: { emails: request.emails.split(",") } }); request = { ...this._store.get().batch.request, ...request }; this.emit("batch:request", { request }); this.logger.debug("Validating emails with request:", request); let res = await this._loqate.emailValidation.validateBatch(request, options); this.logger.debug("Validating emails with response:", res); this._patch({ batchCount: this._store.get().batchCount + 1, batch: { response: res?.items ?? [], requestHistory: [...this._store.get().batch.requestHistory ?? [], { request, options }] } }); const response = res?.items ?? []; this.emit("batch:response", { request, response }); return response; } catch (error) { this.logger.error("Batch request failed:", error); this.emit("error", error); throw error; } } }; //#endregion //#region src/session/phone/PhoneSession.ts var PhoneSession = class extends Eventful { constructor(init) { super(init.logger); this._loqate = new Loqate({ apiKey: init.apiKey }); this._store = createDeepStore({ validateCount: 0, apiKey: init.apiKey, validate: { request: init?.validate?.request, requestHistory: [] } }); this.emit("state:changed", this._store.get()); } getState() { return this._store.get(); } getStore() { return this._store; } /** Patch store and emit a single 'state:changed'. */ _patch(next) { this._store.patch(next); this.emit("state:changed", this._store.get()); } subscribe(sel, onChange, eq = Object.is) { let prev = sel(this._store.get()); this.logger.debug("Initial slice:", prev); return this._store.subscribe(() => { const next = sel(this._store.get()); this.logger.debug("Next slice:", next); if (!eq(prev, next)) { prev = next; onChange(next, this._store); } }); } async validate(request, options) { try { if (typeof request === "string") request = { phone: request }; this._patch({ validate: { phoneNumber: request.phone } }); request = { ...this._store.get().validate.request, ...request }; this.emit("validate:request", { request }); this.logger.debug("Validating phone with request:", request); let res = await this._loqate.phoneNumberValidation.validate(request, options); const response = res?.items ?? []; this.logger.debug("Validating phone with response:", res); this._patch({ validateCount: this._store.get().validateCount + 1, validate: { response, requestHistory: [...this._store.get().validate.requestHistory ?? [], { request, options }] } }); this.emit("validate:response", { request, response }); return response; } catch (error) { this.logger.error("Validate request failed:", error); this.emit("error", error); throw error; } } }; //#endregion //#region src/session/store-finder/StoreFinderSession.ts var StoreFinderSession = class extends Eventful { constructor(init) { super(init.logger); this._loqate = new Loqate({ apiKey: init.apiKey }); this._store = createDeepStore({ apiKey: init.apiKey, selectedLocation: init.selectedLocation ?? void 0, nearby: { count: 0, request: init?.nearby?.request, requestHistory: [] }, typeahead: { count: 0, find: { count: 0, request: init?.typeahead?.find?.request, requestHistory: [] }, retrieve: { count: 0, request: init?.typeahead?.retrieve?.request, requestHistory: [] } }, biasing: { country: init.biasing?.country, location: { latitude: init.biasing?.location?.latitude, longitude: init.biasing?.location?.longitude } } }); this.emit("state:changed", this._store.get()); } getState() { return this._store.get(); } getStore() { return this._store; } /** Patch store and emit a single 'state:changed'. */ _patch(next) { this._store.patch(next); this.emit("state:changed", this._store.get()); } _biasChanged() { this.emit("bias:changed", this._store.get().biasing); } _locationChanged() { this.emit("location:changed", this._store.get().selectedLocation); } _locationListChanged() { this.emit("locationList:changed", this._store.get().nearby?.request ?? {}); } async biasToIP(ip) { try { const res = await this._loqate.geocoding.ip2Country({ ipAddress: ip }); this._patch({ biasing: { country: res.items?.[0]?.country || void 0 } }); this.logger.info("Biased to IP country:", this._store.get().biasing); this._biasChanged(); } catch (error) { this.logger.error("Failed to bias to IP country:", error); this.emit("error", error); throw error; } } biasToCountry(country) { this._patch({ biasing: { country } }); this.logger.info("Biased to country:", this._store.get().biasing); this._biasChanged(); } biasToLocation(lat, lon) { this._patch({ biasing: { location: { latitude: lat, longitude: lon } } }); this.logger.info("Biased to location:", this._store.get().biasing); this._biasChanged(); } subscribe(sel, onChange, eq = Object.is) { let prev = sel(this._store.get()); this.logger.debug("Initial slice:", prev); return this._store.subscribe(() => { const next = sel(this._store.get()); this.logger.debug("Next slice:", next); if (!eq(prev, next)) { prev = next; onChange(next, this._store); } }); } _generateBiasParams() { const biasing = this._store.get().biasing; const params = {}; if (biasing?.country) params.Origin = biasing.country; if (biasing?.location?.latitude !== void 0 && biasing?.location?.longitude !== void 0) params.Origin = `${biasing.location.latitude},${biasing.location.longitude}`; return params; } updateLocation(latitude, longitude) { this._patch({ selectedLocation: { latitude, longitude } }); this.logger.info("Updated selected location:", this._store.get().selectedLocation); this._locationChanged(); } updateLocationList(args) { this._patch({ nearby: { request: { ...this._store.get().nearby?.request, ...args } } }); this._locationListChanged(); this.logger.info("Updated nearby location list ID:", this._store.get().nearby?.request?.locationListId); } async nearby() { try { if (!this._store.get().selectedLocation) throw new Error("Cannot perform nearby search without a selected location"); if (!this._store.get().nearby?.request?.locationListId && !this._store.get().nearby?.request?.locations) throw new Error("Cannot perform nearby search without a locationListId or locations in the request"); const request = { ...this._store.get().nearby?.request, originLocation: { id: "SelectedLocation", latitude: this._store.get().selectedLocation.latitude.toString(), longitude: this._store.get().selectedLocation.longitude.toString() } }; this.emit("nearby:request", { request }); this.logger.debug("Checking for nearby stores with request:", request); const res = await this._loqate.storeFinder.findNearbyDistance(request); this.logger.debug("Checking for nearby stores with response:", res); this._patch({ destinationStores: res.destinationLocations, nearby: { count: this._store.get().nearby.count + 1, response: res.destinationLocations, requestHistory: [...this._store.get().nearby?.requestHistory ?? [], { request, options: {} }] } }); this.emit("destinationStores:changed", res.destinationLocations ?? []); this.emit("nearby:response", { request, response: res }); return res; } catch (error) { this.logger.error("Nearby request failed:", error); this.emit("error", error); throw error; } } async retrieve(request, options) { try { if (typeof request === "number") { const items = this._store.get()?.typeahead?.find?.response ?? []; if (request < 0 || request >= items.length) throw new Error(`Item index ${request} is out of bounds`); request = items[request]; } if (typeof request === "string") request = { addressID: request }; if (typeof request === "object" && "highlight" in request) { if (!request.id) throw new Error(`Cannot retrieve item with no id`); request = { id: request.id }; } const finalRequest = { input: "", countries: "", ...this._store.get().typeahead?.retrieve?.request, ...request }; this.logger.debug("Retrieving address with request:", finalRequest); this.emit("typeahead:retrieve:request", { request: finalRequest }); let res = await this._loqate.storeFinder.geocodeGlobalTypeAhead(finalRequest, options); this.logger.debug("Retrieving address with response:", res); this._patch({ selectedLocation: { latitude: res[0].location?.latitude, longitude: res[0].location?.longitude }, typeahead: { count: this._store.get().typeahead?.count + 1, find: { response: [] }, retrieve: { count: this._store.get().typeahead?.retrieve?.count + 1, addressID: finalRequest.addressID, response: res?.[0], requestHistory: [...this._store.get().typeahead?.retrieve?.requestHistory ?? [], { request: finalRequest, options }] } } }); this.emit("typeahead:retrieve:response", { request: finalRequest, response: res, item: res?.[0] }); this._locationChanged(); return res?.[0]; } catch (error) { this.logger.error("Retrieve request failed:", error); this.emit("error", error); throw error; } } async find(request, options) { try { const finalRequest = typeof request === "string" ? { input: request, countries: this._store.get().typeahead?.find?.request?.countries ?? "" } : request; if (finalRequest.addressID) throw new Error("AddressID cannot be used in Store Finder TypeAhead Find requests, call retrieve instead."); this._patch({ typeahead: { find: { searchText: finalRequest.input } } }); request = { ...this._store.get().typeahead?.find?.request, ...this._generateBiasParams(), ...finalRequest }; this.emit("typeahead:find:request", { request: finalRequest }); this.logger.debug("Finding with request:", finalRequest); let response = await this._loqate.storeFinder.geocodeGlobalTypeAhead(request, options); this.logger.debug("Finding with response:", response); this._patch({ typeahead: { count: this._store.get().typeahead?.count + 1, find: { count: this._store.get().typeahead?.find?.count + 1, response, requestHistory: [...this._store.get().typeahead?.find?.requestHistory ?? [], { request: finalRequest, options }] } } }); this.emit("typeahead:find:response", { request: finalRequest, response }); return response; } catch (error) { this.logger.error("Find request failed:", error); this.emit("error", error); throw error; } } }; //#endregion export { AddressSession, EmailSession, PhoneSession, StoreFinderSession }; //# sourceMappingURL=index.js.map