Skip to content

Commit d8a4a25

Browse files
committed
wip: database
1 parent 8367a45 commit d8a4a25

16 files changed

+1203
-114
lines changed

packages/util/package.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@
3232
"access": "public"
3333
},
3434
"dependencies": {
35-
"defu": "^6.1.4"
35+
"@fast-csv/parse": "^5.0.0",
36+
"ky": "^1.7.2",
37+
"yauzl": "^3.1.3"
38+
},
39+
"devDependencies": {
40+
"@types/yauzl": "^2.10.3"
3641
}
3742
}

packages/util/src/browser.ts

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ import { numberToCountryCode } from './functions/numberToCountryCode.js'
44
import { numberToDir } from './functions/numberToDir.js'
55
import { parseIp } from './functions/parseIp.js'
66

7+
/**
8+
* Sets up an IP lookup function based on the specified data type.
9+
* @template T - The type of data to return ('country' or 'geocode')
10+
* @returns A function that takes an IP address and returns location data
11+
*/
712
export function setup<T extends 'country' | 'geocode'>(): (ipInput: string) => Promise<T extends 'country' ? { country: string } | null : { latitude: number, longitude: number, country: string } | null> {
813
const CDN_URL = __CDN_URL__
914
const MAIN_RECORD_SIZE = __DATA_TYPE__ === 'country' ? 2 : 8
@@ -16,12 +21,20 @@ export function setup<T extends 'country' | 'geocode'>(): (ipInput: string) => P
1621
6: CDN_URL,
1722
}
1823

24+
/**
25+
* Loads the index for the specified IP version.
26+
* @param ipVersion - The IP version (4 or 6)
27+
* @returns A promise that resolves to the loaded index
28+
*/
1929
async function loadIndex(ipVersion: 4 | 6) {
20-
//* Determine the base URL for downloading the index
2130
const baseUrl = getBaseUrl()
2231
return downloadIndex(baseUrl, ipVersion)
2332
}
2433

34+
/**
35+
* Determines the base URL for downloading the index.
36+
* @returns The base URL as a string
37+
*/
2538
function getBaseUrl(): string {
2639
//* If we are not in the DOM we just use the CDN_URL to download the index
2740
if (typeof document === 'undefined' || !document.currentScript) {
@@ -37,6 +50,12 @@ export function setup<T extends 'country' | 'geocode'>(): (ipInput: string) => P
3750
return document.currentScript.src.split('/').slice(0, -1).join('/')
3851
}
3952

53+
/**
54+
* Downloads the index file for the specified IP version.
55+
* @param baseUrl - The base URL for downloading
56+
* @param version - The IP version (4 or 6)
57+
* @returns A promise that resolves to the downloaded index
58+
*/
4059
async function downloadIndex(baseUrl: string, version: 4 | 6) {
4160
const result = await fetchArrayBuffer(
4261
new URL(`indexes/${version}.idx`, baseUrl),
@@ -50,6 +69,11 @@ export function setup<T extends 'country' | 'geocode'>(): (ipInput: string) => P
5069
return (INDEXES[version] = new BigUint64Array(buffer))
5170
}
5271

72+
/**
73+
* Performs an IP lookup and returns location data.
74+
* @param ipInput - The IP address to look up
75+
* @returns A promise that resolves to location data or null if not found
76+
*/
5377
return async function IpLookup(ipInput: string) {
5478
const { version, ip } = parseIp(ipInput)
5579

@@ -100,6 +124,15 @@ export function setup<T extends 'country' | 'geocode'>(): (ipInput: string) => P
100124
return null
101125
} as (ipInput: string) => Promise<T extends 'country' ? { country: string } | null : { latitude: number, longitude: number, country: string } | null>
102126

127+
/**
128+
* Retrieves the end IP for a given record.
129+
* @param dataBuffer - The buffer containing IP data
130+
* @param ipVersion - The IP version (4 or 6)
131+
* @param recordCount - The total number of records
132+
* @param recordIndex - The index of the current record
133+
* @param ipSize - The size of an IP address in bytes
134+
* @returns The end IP as a number
135+
*/
103136
function getEndIp(dataBuffer: ArrayBuffer, ipVersion: 4 | 6, recordCount: number, recordIndex: number, ipSize: number) {
104137
const endIpBuffer = dataBuffer.slice(
105138
(recordCount + recordIndex) * ipSize,
@@ -110,6 +143,15 @@ export function setup<T extends 'country' | 'geocode'>(): (ipInput: string) => P
110143
: new BigUint64Array(endIpBuffer)[0]!
111144
}
112145

146+
/**
147+
* Parses a record and returns location data.
148+
* @param dataBuffer - The buffer containing record data
149+
* @param recordCount - The total number of records
150+
* @param recordIndex - The index of the current record
151+
* @param ipSize - The size of an IP address in bytes
152+
* @param MAIN_RECORD_SIZE - The size of the main record data
153+
* @returns Location data object
154+
*/
113155
function parseRecord(dataBuffer: ArrayBuffer, recordCount: number, recordIndex: number, ipSize: number, MAIN_RECORD_SIZE: number) {
114156
const recordBuffer = dataBuffer.slice(
115157
recordCount * ipSize * 2 + recordIndex * MAIN_RECORD_SIZE,

packages/util/src/db.ts

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,39 @@
1+
import type { IpLocationApiInputSettings, IpLocationApiSettings } from './functions/getSettings.js'
2+
/* eslint-disable jsdoc/check-param-names */
13
import { existsSync } from 'node:fs'
24
import { mkdir } from 'node:fs/promises'
3-
import { getSettings, type IpLocationApiSettings } from './functions/getSettings.js'
5+
import { createDatabase } from './functions/database/createDatabase.js'
6+
import { downloadAndExtractDatabase } from './functions/database/downloadAndExtractDatabase.js'
7+
import { getSettings } from './functions/getSettings.js'
48

5-
export async function update(settings?: Partial<IpLocationApiSettings>) {
6-
const { dataDir, tmpDataDir } = getSettings(settings)
9+
/**
10+
* Updates the GeoIP database based on the provided settings.
11+
* @param inputSettings - Partial settings to override the default ones.
12+
* @returns An array of extracted file names or false if no update was needed.
13+
*/
14+
export async function update(inputSettings?: Partial<IpLocationApiInputSettings>): Promise<false | string[]> {
15+
const settings = getSettings(inputSettings)
16+
await ensureDirectoriesExist(settings)
17+
const { files, dataType } = await downloadAndExtractDatabase(settings)
18+
if (!files)
19+
return false
720

8-
if (!existsSync(dataDir)) {
9-
// TODO add debug log
10-
await mkdir(dataDir, { recursive: true })
11-
}
21+
// TODO: Add debug log
22+
await createDatabase(files, settings, dataType)
23+
// TODO: Add debug log
1224

13-
if (!existsSync(tmpDataDir)) {
14-
// TODO add debug log
15-
await mkdir(tmpDataDir, { recursive: true })
16-
}
25+
return files
26+
}
1727

18-
// TODO add debug log
19-
// TODO download db and save
28+
/**
29+
* Ensures that the necessary directories exist.
30+
* @param param0 - Object containing fieldDir and tmpDataDir paths.
31+
*/
32+
async function ensureDirectoriesExist({ fieldDir, tmpDataDir }: IpLocationApiSettings): Promise<void> {
33+
for (const dir of [fieldDir, tmpDataDir]) {
34+
if (!existsSync(dir)) {
35+
// TODO: Add debug log to indicate that the directory is being created
36+
await mkdir(dir, { recursive: true })
37+
}
38+
}
2039
}

packages/util/src/functions/binarySearch.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
1+
/**
2+
* Performs a binary search on a sorted array of BigUint64 or Uint32 values.
3+
*
4+
* @param array - The sorted array to search in (BigUint64Array or Uint32Array).
5+
* @param target - The value to search for (number or bigint).
6+
* @returns The index of the target if found, or the index where it would be inserted if not found.
7+
* Returns null if the target is smaller than all elements in the array.
8+
*/
19
export function binarySearch(array: BigUint64Array | Uint32Array, target: number | bigint): number | null {
210
let low = 0
311
let high = array.length - 1
412

513
while (low <= high) {
14+
//* Bitwise right shift by 1 is equivalent to Math.floor((low + high) / 2)
615
const mid = (low + high) >> 1
716
const midValue = array[mid]!
817

@@ -13,9 +22,10 @@ export function binarySearch(array: BigUint64Array | Uint32Array, target: number
1322
low = mid + 1
1423
}
1524
else {
16-
return mid
25+
return mid //* Target found
1726
}
1827
}
1928

29+
//* If target not found, return the insertion point or null if smaller than all elements
2030
return high >= 0 ? high : null
2131
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import type { IpLocationApiSettings } from '../getSettings.js'
2+
import type { LocationData } from './createDatabase.js'
3+
4+
/**
5+
* Creates a block database from the provided file and location data.
6+
* @param file - The file path of the block database CSV
7+
* @param locationData - Array of location data records
8+
* @param locationIdList - List of location IDs to process
9+
* @param settings - IP location API settings
10+
*/
11+
export async function createBlockDatabase(file: string, locationData: Record<number, LocationData | string>[], locationIdList: number[], settings: IpLocationApiSettings): Promise<void> {
12+
const version = file.endsWith('v4.csv') ? 4 : 6
13+
//* TODO: Implement the block database creation logic
14+
console.log(`Creating block database for IP${version}...`)
15+
}

0 commit comments

Comments
 (0)