@ham2k/lib-operation-data 0.9.0 → 0.10.1

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/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.1",
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
+
@@ -1,3 +1,40 @@
1
+ export const EXTENDED_BANDS = [ // Includes 11m
2
+ '2190m',
3
+ '630m',
4
+ '560m',
5
+ '160m',
6
+ '80m',
7
+ '60m',
8
+ '40m',
9
+ '30m',
10
+ '20m',
11
+ '17m',
12
+ '15m',
13
+ '12m',
14
+ '11m',
15
+ '10m',
16
+ '6m',
17
+ '5m',
18
+ '4m',
19
+ '2m',
20
+ '1.25m',
21
+ '70cm',
22
+ '33cm',
23
+ '23cm',
24
+ '13cm',
25
+ '9cm',
26
+ '6cm',
27
+ '3cm',
28
+ '1.25cm',
29
+ '6mm',
30
+ '4mm',
31
+ '2.5mm',
32
+ '2mm',
33
+ '1mm',
34
+ 'submm',
35
+ 'other'
36
+ ] as const
37
+
1
38
  export const BANDS = [
2
39
  '2190m',
3
40
  '630m',
@@ -32,22 +69,24 @@ export const BANDS = [
32
69
  '1mm',
33
70
  'submm',
34
71
  'other'
35
- ]
72
+ ] as const
73
+
74
+ export type Band = typeof EXTENDED_BANDS[number]
36
75
 
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']
76
+ export const HF_BANDS = ['160m', '80m', '60m', '40m', '30m', '20m', '17m', '15m', '12m', '10m'] as const
77
+ export const VHF_BANDS = ['6m', '5m', '4m', '2m', '1.25m'] as const
78
+ export const UHF_BANDS = ['70cm', '33cm', '23cm', '13cm'] as const
79
+ export const SHF_BANDS = ['9cm', '6cm', '3cm', '1.25cm'] as const
80
+ export const EHF_BANDS = ['6mm', '4mm', '2mm', '2.5mm', '1mm', 'submm'] as const
42
81
 
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']
82
+ export const POPULAR_BANDS = ['160m', '80m', '60m', '40m', '30m', '20m', '17m', '15m', '12m', '10m', '6m', '2m', '70cm'] as const
83
+ export const CONTEST_BANDS = ['160m', '80m', '40m', '20m', '15m', '10m'] as const
45
84
 
46
- export function bandForFrequency (freq) {
85
+ export function bandForFrequency(freq: number): Band {
47
86
  return bandForExactFrequencyInMHz(freq) ?? bandForExactFrequencyInMHz(freq * 1000) ?? 'other'
48
87
  }
49
88
 
50
- function bandForExactFrequencyInMHz (freq) {
89
+ function bandForExactFrequencyInMHz(freq: number): Band | undefined {
51
90
  if (freq >= 130 && freq <= 140) return '2190m'
52
91
  else if (freq >= 450 && freq <= 499) return '630m'
53
92
  else if (freq >= 500 && freq <= 505) return '560m'
@@ -60,6 +99,7 @@ function bandForExactFrequencyInMHz (freq) {
60
99
  else if (freq >= 18000 && freq <= 18200) return '17m'
61
100
  else if (freq >= 20900 && freq <= 21600) return '15m'
62
101
  else if (freq >= 24500 && freq <= 25200) return '12m'
102
+ else if (freq >= 26000 && freq <= 27500) return '11m'
63
103
  else if (freq >= 27900 && freq <= 30000) return '10m'
64
104
  else if (freq >= 50000 && freq <= 54000) return '6m'
65
105
  else if (freq >= 54001 && freq <= 69999) return '5m'
@@ -83,7 +123,7 @@ function bandForExactFrequencyInMHz (freq) {
83
123
  else return undefined
84
124
  }
85
125
 
86
- export function frequencyForBand(band, mode) {
126
+ export function frequencyForBand(band: Band, mode?: string): number | undefined {
87
127
  if (band === '2190m') return 135.7
88
128
  else if (band === '630m') return 472
89
129
  else if (band === '560m') return 501
@@ -136,6 +176,9 @@ export function frequencyForBand(band, mode) {
136
176
  else if (mode === 'FT4') return 24919
137
177
  else return 24890
138
178
  }
179
+ else if (band === '11m') {
180
+ return 26965
181
+ }
139
182
  else if (band === '10m') {
140
183
  if (mode === 'SSB' || mode === 'USB') return 28300
141
184
  else if (mode === 'FM' || mode === 'AM') return 28500
@@ -169,3 +212,4 @@ export function frequencyForBand(band, mode) {
169
212
  else if (band === 'submm') return 300000000
170
213
  else return undefined
171
214
  }
215
+
@@ -31,3 +31,4 @@ describe('modeForFrequency', () => {
31
31
  })
32
32
 
33
33
  })
34
+
@@ -1,12 +1,15 @@
1
1
  import BandPlans from '../data/bandPlans.json'
2
+ import type { BandPlansData, SegmentWithMetadata, ModeForFrequencyOptions } from '../types'
2
3
 
3
- export const SUPER_MODES = [
4
+ export const SUPER_MODES: readonly string[] = [
4
5
  'CW',
5
6
  'PHONE',
6
7
  'DATA'
7
- ]
8
+ ] as const
8
9
 
9
- export const MAIN_MODES = [
10
+ export type SuperMode = 'CW' | 'PHONE' | 'DATA'
11
+
12
+ export const MAIN_MODES: readonly string[] = [
10
13
  'CW',
11
14
  'SSB',
12
15
  'AM',
@@ -14,9 +17,9 @@ export const MAIN_MODES = [
14
17
  'FT8',
15
18
  'FT4',
16
19
  'RTTY'
17
- ]
20
+ ] as const
18
21
 
19
- export const POPULAR_MODES = [
22
+ export const POPULAR_MODES: readonly string[] = [
20
23
  'CW',
21
24
  'SSB',
22
25
  'USB',
@@ -42,9 +45,9 @@ export const POPULAR_MODES = [
42
45
  'PSK',
43
46
  'SSTV',
44
47
  'WSPR',
45
- ]
48
+ ] as const
46
49
 
47
- export const ADIF_MODES = [
50
+ export const ADIF_MODES: readonly string[] = [
48
51
  'AM',
49
52
  'ARDOP',
50
53
  'ATV',
@@ -91,9 +94,9 @@ export const ADIF_MODES = [
91
94
  'VOI',
92
95
  'WINMOR',
93
96
  'WSPR'
94
- ]
97
+ ] as const
95
98
 
96
- export const ADIF_SUBMODES = {
99
+ export const ADIF_SUBMODES: Record<string, readonly string[]> = {
97
100
  'CHIP': ['CHIP64', 'CHIP128'],
98
101
  'DIGITALVOICE': ['C4FM', 'DMR', 'DSTAR', 'FREEDV', 'M17'],
99
102
  'DOMINO': ['DOM-M', 'DOM4', 'DOM5', 'DOM8', 'DOM11', 'DOM16', 'DOM22', 'DOM44', 'DOM88', 'DOMINOEX', 'DOMINOF'],
@@ -116,18 +119,18 @@ export const ADIF_SUBMODES = {
116
119
  'THOR': ['THOR-M', 'THOR4', 'THOR5', 'THOR8', 'THOR11', 'THOR16', 'THOR22', 'THOR25X4', 'THOR50X1', 'THOR50X2', 'THOR100'],
117
120
  'THRB': ['THRBX', 'THRBX1', 'THRBX2', 'THRBX4', 'THROB1', 'THROB2', 'THROB4'],
118
121
  'TOR': ['AMTORFEC', 'GTOR', 'NAVTEX', 'SITORB']
119
- }
122
+ } as const
120
123
 
121
- export const ADIF_MODE_FOR_SUBMODE = {}
124
+ export const ADIF_MODE_FOR_SUBMODE: Record<string, string> = {}
122
125
  Object.keys(ADIF_SUBMODES).forEach(mode => {
123
126
  ADIF_SUBMODES[mode].forEach(submode => {
124
127
  ADIF_MODE_FOR_SUBMODE[submode] = mode
125
128
  })
126
129
  })
127
130
 
128
- export const ADIF_MODES_AND_SUBMODES = [... new Set(ADIF_MODES.concat(Object.values(ADIF_SUBMODES)).flat())].sort()
131
+ export const ADIF_MODES_AND_SUBMODES: readonly string[] = [...new Set([...ADIF_MODES, ...Object.values(ADIF_SUBMODES).flat()])].sort()
129
132
 
130
- export function adifModeAndSubmodeForMode(mode) {
133
+ export function adifModeAndSubmodeForMode(mode: string): string[] {
131
134
  if (ADIF_MODE_FOR_SUBMODE[mode]) {
132
135
  return [ADIF_MODE_FOR_SUBMODE[mode], mode]
133
136
  } else {
@@ -135,7 +138,7 @@ export function adifModeAndSubmodeForMode(mode) {
135
138
  }
136
139
  }
137
140
 
138
- export function superModeForMode(mode) {
141
+ export function superModeForMode(mode: string): SuperMode {
139
142
  if (ADIF_MODE_FOR_SUBMODE[mode]) mode = ADIF_MODE_FOR_SUBMODE[mode]
140
143
 
141
144
  if (mode === 'CW') {
@@ -147,27 +150,38 @@ export function superModeForMode(mode) {
147
150
  }
148
151
  }
149
152
 
150
- const AllSegments = Object.keys(BandPlans.bands).reduce((segments, band) => {
153
+ const bandPlansData = BandPlans as unknown as BandPlansData
154
+
155
+ const AllSegments: SegmentWithMetadata[] = Object.keys(bandPlansData.bands).reduce((segments, band) => {
151
156
  return segments.concat(
152
- BandPlans.bands[band].segments.map(segment => (
153
- {
154
- ...segment,
157
+ bandPlansData.bands[band].segments.map(segment => {
158
+ const mhz = Array.isArray(segment.mhz) && segment.mhz.length >= 2
159
+ ? [segment.mhz[0], segment.mhz[1]] as [number, number]
160
+ : [0, 0] as [number, number]
161
+ return {
162
+ name: segment.name,
163
+ mode: segment.mode,
164
+ submode: segment.submode,
165
+ priority: segment.priority,
166
+ mhz,
155
167
  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
- ))
168
+ width: mhz[1] - mhz[0],
169
+ regions: (segment.regions || []).reduce((regions: Record<string, boolean>, region) => { regions[region.toString()] = true; return regions }, {}),
170
+ entities: (segment.entities || []).reduce((entities: Record<string, boolean>, entity) => { entities[entity] = true; return entities }, {}),
171
+ countries: (segment.countries || []).reduce((countries: Record<string, boolean>, country) => { countries[country] = true; return countries }, {}),
172
+ } as SegmentWithMetadata
173
+ })
162
174
  )
163
- }, [])
175
+ }, [] as SegmentWithMetadata[])
176
+
177
+ export function modeForFrequency(frequency: number, options: ModeForFrequencyOptions = {}): string | null | undefined {
178
+ const { ituRegion, countryCode, entityPrefix } = options
164
179
 
165
- export function modeForFrequency(frequency, { ituRegion, countryCode, entityPrefix } = {}) {
166
180
  const segments = AllSegments.filter(segment => (
167
181
  (segment.mhz[0] <= frequency && segment.mhz[1] >= frequency) &&
168
182
  (!entityPrefix || Object.keys(segment.entities).length === 0 || segment.entities[entityPrefix]) &&
169
183
  (!countryCode || Object.keys(segment.countries).length === 0 || segment.countries[countryCode]) &&
170
- (!ituRegion || Object.keys(segment.regions).length === 0 || segment.regions[ituRegion])
184
+ (!ituRegion || Object.keys(segment.regions).length === 0 || segment.regions[ituRegion.toString()])
171
185
  ))
172
186
 
173
187
  const sortedSegments = segments.sort((a, b) => {
@@ -180,11 +194,12 @@ export function modeForFrequency(frequency, { ituRegion, countryCode, entityPref
180
194
  if (countryCode && !a.countries[countryCode] && b.countries[countryCode]) return 1
181
195
  if (countryCode && a.countries[countryCode] && !b.countries[countryCode]) return -1
182
196
 
183
- if (ituRegion && !a.regions[ituRegion] && b.regions[ituRegion]) return 1
184
- if (ituRegion && a.regions[ituRegion] && !b.regions[ituRegion]) return -1
197
+ if (ituRegion && !a.regions[ituRegion.toString()] && b.regions[ituRegion.toString()]) return 1
198
+ if (ituRegion && a.regions[ituRegion.toString()] && !b.regions[ituRegion.toString()]) return -1
185
199
 
186
200
  return a.width - b.width
187
201
  })
188
202
 
189
203
  return sortedSegments[0]?.mode
190
204
  }
205
+
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
+