kokkai
Version:
A TypeScript client for the Kokkai API, providing access to Japanese Diet data.
133 lines (132 loc) • 5.39 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.KokkaiClient = void 0;
const axios_1 = require("axios");
const errors_1 = require("./errors");
const DEFAULT_BASE_URL = 'https://kokkai.ndl.go.jp/api';
const DEFAULT_TIMEOUT = 10000; // 10 seconds
const DEFAULT_REQUEST_INTERVAL = 3000; // 3 seconds
class KokkaiClient {
constructor(options = {}) {
this.lastRequestTime = 0;
const { baseURL = DEFAULT_BASE_URL, timeout = DEFAULT_TIMEOUT, requestInterval = DEFAULT_REQUEST_INTERVAL, userAgent, headers = {}, } = options;
this.requestInterval = requestInterval;
const axiosConfig = {
baseURL,
timeout,
headers: {
'Accept': 'application/json',
...(userAgent && { 'User-Agent': userAgent }),
...headers,
},
};
this.httpClient = axios_1.default.create(axiosConfig);
}
/**
* 会議単位簡易出力 API (/meeting_list)
* @param params 検索パラメータ
* @returns 会議録情報のリスト
*/
async getMeetingList(params) {
const apiParams = this.applyDefaultParams(params, 30, 100);
this.validateCommonParams(apiParams);
return this.request('/meeting_list', apiParams);
}
/**
* 会議単位出力 API (/meeting)
* @param params 検索パラメータ
* @returns 会議録情報と全発言のリスト
*/
async getMeeting(params) {
const apiParams = this.applyDefaultParams(params, 3, 10);
this.validateCommonParams(apiParams);
return this.request('/meeting', apiParams);
}
/**
* 発言単位出力 API (/speech)
* @param params 検索パラメータ
* @returns 発言情報のリスト
*/
async getSpeech(params) {
const apiParams = this.applyDefaultParams(params, 30, 100);
this.validateCommonParams(apiParams);
return this.request('/speech', apiParams);
}
async request(endpoint, params) {
await this.enforceRateLimit();
const queryParams = this.serializeParams(params);
try {
const response = await this.httpClient.get(endpoint, { params: queryParams });
const responseData = response.data;
if (this.isErrorResponse(responseData)) {
throw new errors_1.KokkaiError(responseData.message, undefined, responseData.details);
}
return responseData;
}
catch (error) {
throw (0, errors_1.handleApiError)(error);
}
}
async enforceRateLimit() {
const now = Date.now();
const timeSinceLastRequest = now - this.lastRequestTime;
const waitTime = this.requestInterval - timeSinceLastRequest;
if (waitTime > 0) {
await new Promise(resolve => setTimeout(resolve, waitTime));
}
this.lastRequestTime = Date.now();
}
validateCommonParams(params) {
if (params.startRecord !== undefined && params.startRecord < 1) {
throw new errors_1.KokkaiError('startRecord must be 1 or greater.', 'ValidationError');
}
if (params.from)
this.validateDateFormat(params.from, 'from');
if (params.until)
this.validateDateFormat(params.until, 'until');
this.validateDateRange(params.from, params.until);
const hasSearchCondition = [
params.nameOfHouse, params.nameOfMeeting, params.any, params.speaker,
params.from, params.until, params.speechNumber, params.speakerPosition,
params.speakerGroup, params.speakerRole, params.speechID, params.issueID,
params.sessionFrom, params.sessionTo, params.issueFrom, params.issueTo
].some(p => p !== undefined && p !== null && p !== '');
if (!hasSearchCondition) {
throw new errors_1.KokkaiError('At least one search condition parameter is required.', 'ValidationError', ['19007']);
}
}
validateDateFormat(date, fieldName) {
const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
if (!dateRegex.test(date)) {
throw new errors_1.KokkaiError(`"${fieldName}" date must be in YYYY-MM-DD format.`, 'ValidationError');
}
}
validateDateRange(from, until) {
if (from && until && from > until) {
throw new errors_1.KokkaiError('Date range is invalid: "from" date cannot be after "until" date.', 'ValidationError');
}
}
applyDefaultParams(params, defaultMax, maxLimit) {
return {
...params,
maximumRecords: Math.min(params.maximumRecords ?? defaultMax, maxLimit),
};
}
serializeParams(params) {
const queryParams = {};
for (const key in params) {
if (Object.prototype.hasOwnProperty.call(params, key)) {
const value = params[key];
if (value !== undefined && value !== null && value !== '') {
queryParams[key] = value;
}
}
}
queryParams['recordPacking'] = 'json'; // Always enforce JSON response
return queryParams;
}
isErrorResponse(data) {
return typeof data === 'object' && data !== null && 'message' in data && !('numberOfRecords' in data);
}
}
exports.KokkaiClient = KokkaiClient;