@ham2k/lib-operation-data 0.9.0 → 0.10.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
@@ -1,3 +1,89 @@
1
1
  # lib-operation-data
2
2
 
3
3
  Basic data related to Amateur Radio operation, such as bands, modes, etc
4
+
5
+ ## Building
6
+
7
+ This module is written in TypeScript and must be compiled before use.
8
+
9
+ ### Development
10
+
11
+ To build the module during development:
12
+
13
+ ```bash
14
+ npm run build
15
+ ```
16
+
17
+ This will compile TypeScript source files from `src/` to JavaScript in `dist/`, generating:
18
+ - JavaScript files (`.js`)
19
+ - TypeScript declaration files (`.d.ts`)
20
+ - Source maps (`.js.map`, `.d.ts.map`)
21
+
22
+ ### Testing
23
+
24
+ Run the test suite:
25
+
26
+ ```bash
27
+ npm test
28
+ ```
29
+
30
+ ## Distribution
31
+
32
+ ### Publishing to npm
33
+
34
+ Before publishing, ensure the module is built:
35
+
36
+ ```bash
37
+ npm run build
38
+ ```
39
+
40
+ The `prepublishOnly` script will automatically run the build before publishing, so you can also just:
41
+
42
+ ```bash
43
+ npm publish
44
+ ```
45
+
46
+ ### Package Structure
47
+
48
+ - **Source files**: Located in `src/`
49
+ - **Compiled output**: Generated in `dist/` (excluded from git)
50
+ - **Type definitions**: Automatically generated in `dist/` alongside compiled JavaScript
51
+
52
+ ### Module Exports
53
+
54
+ The package exports are configured in `package.json`:
55
+ - **Main entry**: `dist/index.js`
56
+ - **Type definitions**: `dist/index.d.ts`
57
+ - **ESM support**: Configured for ES2020 modules
58
+
59
+ ## Usage
60
+
61
+ ### Installation
62
+
63
+ ```bash
64
+ npm install @ham2k/lib-operation-data
65
+ ```
66
+
67
+ ### Import
68
+
69
+ ```javascript
70
+ import { BANDS, bandForFrequency, modeForFrequency } from '@ham2k/lib-operation-data'
71
+ ```
72
+
73
+ ### TypeScript
74
+
75
+ Type definitions are included, so TypeScript projects will automatically get type checking and autocomplete:
76
+
77
+ ```typescript
78
+ import { BANDS, Band, bandForFrequency } from '@ham2k/lib-operation-data'
79
+
80
+ const band: Band = bandForFrequency(14255) // '20m'
81
+ ```
82
+
83
+ ## Development Setup
84
+
85
+ 1. Clone the repository
86
+ 2. Install dependencies: `npm install`
87
+ 3. Make changes in `src/`
88
+ 4. Build: `npm run build`
89
+ 5. Test: `npm test`
package/jest.config.js ADDED
@@ -0,0 +1,21 @@
1
+ export default {
2
+ preset: 'ts-jest/presets/default-esm',
3
+ extensionsToTreatAsEsm: ['.ts'],
4
+ testEnvironment: 'node',
5
+ transform: {
6
+ '^.+\\.ts$': ['ts-jest', {
7
+ useESM: true,
8
+ tsconfig: {
9
+ module: 'ES2020',
10
+ moduleResolution: 'node'
11
+ }
12
+ }]
13
+ },
14
+ moduleNameMapper: {
15
+ '^(\\.{1,2}/.*)\\.js$': '$1',
16
+ '^(\\.{1,2}/.*)\\.json$': '$1'
17
+ },
18
+ testMatch: ['**/*.spec.ts', '**/*.test.ts'],
19
+ transformIgnorePatterns: []
20
+ }
21
+
package/package.json CHANGED
@@ -1,19 +1,31 @@
1
1
  {
2
2
  "name": "@ham2k/lib-operation-data",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "type": "module",
5
- "main": "./src/index.js",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
6
7
  "exports": {
7
- ".": "./src/index.js"
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.js"
11
+ }
8
12
  },
9
13
  "devDependencies": {
10
- "jest": "~29",
11
- "standard": "^17.1.2"
14
+ "@types/jest": "^30.0.0",
15
+ "@types/node": "^24.10.1",
16
+ "jest": "~30",
17
+ "standard": "^17.1.2",
18
+ "ts-jest": "^29.1.0",
19
+ "typescript": "^5.0.0"
12
20
  },
13
21
  "scripts": {
14
- "test": "node --experimental-vm-modules ./node_modules/.bin/jest"
22
+ "build": "tsc",
23
+ "test": "node --experimental-vm-modules ./node_modules/.bin/jest",
24
+ "prepublishOnly": "npm run build"
15
25
  },
16
26
  "standard": {
17
- "env": [ "jest" ]
27
+ "env": [
28
+ "jest"
29
+ ]
18
30
  }
19
31
  }
@@ -1,2 +1,3 @@
1
1
  export * from './lib/bands'
2
2
  export * from './lib/modes'
3
+
@@ -32,22 +32,24 @@ export const BANDS = [
32
32
  '1mm',
33
33
  'submm',
34
34
  'other'
35
- ]
35
+ ] as const
36
36
 
37
- export const HF_BANDS = ['160m', '80m', '60m', '40m', '30m', '20m', '17m', '15m', '12m', '10m']
38
- export const VHF_BANDS = ['6m', '5m', '4m', '2m', '1.25m']
39
- export const UHF_BANDS = ['70cm', '33cm', '23cm', '13cm']
40
- export const SHF_BANDS = ['9cm', '6cm', '3cm', '1.25cm']
41
- export const EHF_BANDS = ['6mm', '4mm', '2mm', '2.5mm', '1mm', 'submm']
37
+ export type Band = typeof BANDS[number]
42
38
 
43
- export const POPULAR_BANDS = ['160m', '80m', '60m', '40m', '30m', '20m', '17m', '15m', '12m', '10m', '6m', '2m', '70cm']
44
- export const CONTEST_BANDS = ['160m', '80m', '40m', '20m', '15m', '10m']
39
+ export const HF_BANDS = ['160m', '80m', '60m', '40m', '30m', '20m', '17m', '15m', '12m', '10m'] as const
40
+ export const VHF_BANDS = ['6m', '5m', '4m', '2m', '1.25m'] as const
41
+ export const UHF_BANDS = ['70cm', '33cm', '23cm', '13cm'] as const
42
+ export const SHF_BANDS = ['9cm', '6cm', '3cm', '1.25cm'] as const
43
+ export const EHF_BANDS = ['6mm', '4mm', '2mm', '2.5mm', '1mm', 'submm'] as const
45
44
 
46
- export function bandForFrequency (freq) {
45
+ export const POPULAR_BANDS = ['160m', '80m', '60m', '40m', '30m', '20m', '17m', '15m', '12m', '10m', '6m', '2m', '70cm'] as const
46
+ export const CONTEST_BANDS = ['160m', '80m', '40m', '20m', '15m', '10m'] as const
47
+
48
+ export function bandForFrequency(freq: number): Band {
47
49
  return bandForExactFrequencyInMHz(freq) ?? bandForExactFrequencyInMHz(freq * 1000) ?? 'other'
48
50
  }
49
51
 
50
- function bandForExactFrequencyInMHz (freq) {
52
+ function bandForExactFrequencyInMHz(freq: number): Band | undefined {
51
53
  if (freq >= 130 && freq <= 140) return '2190m'
52
54
  else if (freq >= 450 && freq <= 499) return '630m'
53
55
  else if (freq >= 500 && freq <= 505) return '560m'
@@ -83,7 +85,7 @@ function bandForExactFrequencyInMHz (freq) {
83
85
  else return undefined
84
86
  }
85
87
 
86
- export function frequencyForBand(band, mode) {
88
+ export function frequencyForBand(band: Band, mode?: string): number | undefined {
87
89
  if (band === '2190m') return 135.7
88
90
  else if (band === '630m') return 472
89
91
  else if (band === '560m') return 501
@@ -169,3 +171,4 @@ export function frequencyForBand(band, mode) {
169
171
  else if (band === 'submm') return 300000000
170
172
  else return undefined
171
173
  }
174
+
@@ -31,3 +31,4 @@ describe('modeForFrequency', () => {
31
31
  })
32
32
 
33
33
  })
34
+
@@ -1,10 +1,13 @@
1
1
  import BandPlans from '../data/bandPlans.json'
2
+ import type { BandPlansData, SegmentWithMetadata, ModeForFrequencyOptions } from '../types'
2
3
 
3
4
  export const SUPER_MODES = [
4
5
  'CW',
5
6
  'PHONE',
6
7
  'DATA'
7
- ]
8
+ ] as const
9
+
10
+ export type SuperMode = typeof SUPER_MODES[number]
8
11
 
9
12
  export const MAIN_MODES = [
10
13
  'CW',
@@ -14,7 +17,9 @@ export const MAIN_MODES = [
14
17
  'FT8',
15
18
  'FT4',
16
19
  'RTTY'
17
- ]
20
+ ] as const
21
+
22
+ export type MainMode = typeof MAIN_MODES[number]
18
23
 
19
24
  export const POPULAR_MODES = [
20
25
  'CW',
@@ -42,7 +47,9 @@ export const POPULAR_MODES = [
42
47
  'PSK',
43
48
  'SSTV',
44
49
  'WSPR',
45
- ]
50
+ ] as const
51
+
52
+ export type PopularMode = typeof POPULAR_MODES[number]
46
53
 
47
54
  export const ADIF_MODES = [
48
55
  'AM',
@@ -91,9 +98,11 @@ export const ADIF_MODES = [
91
98
  'VOI',
92
99
  'WINMOR',
93
100
  'WSPR'
94
- ]
101
+ ] as const
102
+
103
+ export type AdifMode = typeof ADIF_MODES[number]
95
104
 
96
- export const ADIF_SUBMODES = {
105
+ export const ADIF_SUBMODES: Record<string, readonly string[]> = {
97
106
  'CHIP': ['CHIP64', 'CHIP128'],
98
107
  'DIGITALVOICE': ['C4FM', 'DMR', 'DSTAR', 'FREEDV', 'M17'],
99
108
  'DOMINO': ['DOM-M', 'DOM4', 'DOM5', 'DOM8', 'DOM11', 'DOM16', 'DOM22', 'DOM44', 'DOM88', 'DOMINOEX', 'DOMINOF'],
@@ -116,18 +125,18 @@ export const ADIF_SUBMODES = {
116
125
  'THOR': ['THOR-M', 'THOR4', 'THOR5', 'THOR8', 'THOR11', 'THOR16', 'THOR22', 'THOR25X4', 'THOR50X1', 'THOR50X2', 'THOR100'],
117
126
  'THRB': ['THRBX', 'THRBX1', 'THRBX2', 'THRBX4', 'THROB1', 'THROB2', 'THROB4'],
118
127
  'TOR': ['AMTORFEC', 'GTOR', 'NAVTEX', 'SITORB']
119
- }
128
+ } as const
120
129
 
121
- export const ADIF_MODE_FOR_SUBMODE = {}
130
+ export const ADIF_MODE_FOR_SUBMODE: Record<string, string> = {}
122
131
  Object.keys(ADIF_SUBMODES).forEach(mode => {
123
132
  ADIF_SUBMODES[mode].forEach(submode => {
124
133
  ADIF_MODE_FOR_SUBMODE[submode] = mode
125
134
  })
126
135
  })
127
136
 
128
- export const ADIF_MODES_AND_SUBMODES = [... new Set(ADIF_MODES.concat(Object.values(ADIF_SUBMODES)).flat())].sort()
137
+ export const ADIF_MODES_AND_SUBMODES: readonly string[] = [...new Set([...ADIF_MODES, ...Object.values(ADIF_SUBMODES).flat()])].sort()
129
138
 
130
- export function adifModeAndSubmodeForMode(mode) {
139
+ export function adifModeAndSubmodeForMode(mode: string): string[] {
131
140
  if (ADIF_MODE_FOR_SUBMODE[mode]) {
132
141
  return [ADIF_MODE_FOR_SUBMODE[mode], mode]
133
142
  } else {
@@ -135,7 +144,7 @@ export function adifModeAndSubmodeForMode(mode) {
135
144
  }
136
145
  }
137
146
 
138
- export function superModeForMode(mode) {
147
+ export function superModeForMode(mode: string): SuperMode {
139
148
  if (ADIF_MODE_FOR_SUBMODE[mode]) mode = ADIF_MODE_FOR_SUBMODE[mode]
140
149
 
141
150
  if (mode === 'CW') {
@@ -147,27 +156,38 @@ export function superModeForMode(mode) {
147
156
  }
148
157
  }
149
158
 
150
- const AllSegments = Object.keys(BandPlans.bands).reduce((segments, band) => {
159
+ const bandPlansData = BandPlans as unknown as BandPlansData
160
+
161
+ const AllSegments: SegmentWithMetadata[] = Object.keys(bandPlansData.bands).reduce((segments, band) => {
151
162
  return segments.concat(
152
- BandPlans.bands[band].segments.map(segment => (
153
- {
154
- ...segment,
163
+ bandPlansData.bands[band].segments.map(segment => {
164
+ const mhz = Array.isArray(segment.mhz) && segment.mhz.length >= 2
165
+ ? [segment.mhz[0], segment.mhz[1]] as [number, number]
166
+ : [0, 0] as [number, number]
167
+ return {
168
+ name: segment.name,
169
+ mode: segment.mode,
170
+ submode: segment.submode,
171
+ priority: segment.priority,
172
+ mhz,
155
173
  band,
156
- width: segment.mhz[1] - segment.mhz[0],
157
- regions: (segment.regions || []).reduce((regions, region) => { regions[region] = true; return regions }, {}),
158
- entities: (segment.entities || []).reduce((entities, entity) => { entities[entity] = true; return entities }, {}),
159
- countries: (segment.countries || []).reduce((countries, country) => { countries[country] = true; return countries }, {}),
160
- }
161
- ))
174
+ width: mhz[1] - mhz[0],
175
+ regions: (segment.regions || []).reduce((regions: Record<string, boolean>, region) => { regions[region.toString()] = true; return regions }, {}),
176
+ entities: (segment.entities || []).reduce((entities: Record<string, boolean>, entity) => { entities[entity] = true; return entities }, {}),
177
+ countries: (segment.countries || []).reduce((countries: Record<string, boolean>, country) => { countries[country] = true; return countries }, {}),
178
+ } as SegmentWithMetadata
179
+ })
162
180
  )
163
- }, [])
181
+ }, [] as SegmentWithMetadata[])
182
+
183
+ export function modeForFrequency(frequency: number, options: ModeForFrequencyOptions = {}): string | null | undefined {
184
+ const { ituRegion, countryCode, entityPrefix } = options
164
185
 
165
- export function modeForFrequency(frequency, { ituRegion, countryCode, entityPrefix } = {}) {
166
186
  const segments = AllSegments.filter(segment => (
167
187
  (segment.mhz[0] <= frequency && segment.mhz[1] >= frequency) &&
168
188
  (!entityPrefix || Object.keys(segment.entities).length === 0 || segment.entities[entityPrefix]) &&
169
189
  (!countryCode || Object.keys(segment.countries).length === 0 || segment.countries[countryCode]) &&
170
- (!ituRegion || Object.keys(segment.regions).length === 0 || segment.regions[ituRegion])
190
+ (!ituRegion || Object.keys(segment.regions).length === 0 || segment.regions[ituRegion.toString()])
171
191
  ))
172
192
 
173
193
  const sortedSegments = segments.sort((a, b) => {
@@ -180,11 +200,12 @@ export function modeForFrequency(frequency, { ituRegion, countryCode, entityPref
180
200
  if (countryCode && !a.countries[countryCode] && b.countries[countryCode]) return 1
181
201
  if (countryCode && a.countries[countryCode] && !b.countries[countryCode]) return -1
182
202
 
183
- if (ituRegion && !a.regions[ituRegion] && b.regions[ituRegion]) return 1
184
- if (ituRegion && a.regions[ituRegion] && !b.regions[ituRegion]) return -1
203
+ if (ituRegion && !a.regions[ituRegion.toString()] && b.regions[ituRegion.toString()]) return 1
204
+ if (ituRegion && a.regions[ituRegion.toString()] && !b.regions[ituRegion.toString()]) return -1
185
205
 
186
206
  return a.width - b.width
187
207
  })
188
208
 
189
209
  return sortedSegments[0]?.mode
190
210
  }
211
+
package/src/types.ts ADDED
@@ -0,0 +1,40 @@
1
+ export interface BandPlanSegment {
2
+ name: string
3
+ mhz: [number, number] | number[]
4
+ mode?: string | null
5
+ submode?: string
6
+ priority?: number
7
+ regions?: number[]
8
+ entities?: string[]
9
+ countries?: string[]
10
+ }
11
+
12
+ export interface BandPlan {
13
+ mhz: [number, number] | number[]
14
+ segments: BandPlanSegment[]
15
+ }
16
+
17
+ export interface BandPlansData {
18
+ sources: Array<{ url: string }>
19
+ bands: Record<string, BandPlan>
20
+ }
21
+
22
+ export interface SegmentWithMetadata {
23
+ name: string
24
+ mhz: [number, number]
25
+ mode?: string | null
26
+ submode?: string
27
+ priority?: number
28
+ band: string
29
+ width: number
30
+ regions: Record<string, boolean>
31
+ entities: Record<string, boolean>
32
+ countries: Record<string, boolean>
33
+ }
34
+
35
+ export interface ModeForFrequencyOptions {
36
+ ituRegion?: number
37
+ countryCode?: string
38
+ entityPrefix?: string
39
+ }
40
+
package/tsconfig.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "ES2020",
5
+ "lib": ["ES2020"],
6
+ "moduleResolution": "node",
7
+ "outDir": "./dist",
8
+ "rootDir": "./src",
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "resolveJsonModule": true,
14
+ "declaration": true,
15
+ "declarationMap": true,
16
+ "sourceMap": true
17
+ },
18
+ "include": ["src/**/*"],
19
+ "exclude": ["node_modules", "dist"]
20
+ }
21
+