2 Commits

Author SHA1 Message Date
28ae2bd737 2.0.0
Some checks failed
Default (tags) / security (push) Failing after 21s
Default (tags) / test (push) Failing after 12s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-10-31 12:12:29 +00:00
c806524e0c BREAKING CHANGE(OpenData): Require explicit directory paths for OpenData (nogit/download/germanBusinessData); remove automatic .nogit creation; update HandelsRegister, JsonlDataProcessor, tests and README. 2025-10-31 12:12:29 +00:00
10 changed files with 172 additions and 37 deletions

View File

@@ -1,5 +1,16 @@
# Changelog # Changelog
## 2025-10-31 - 2.0.0 - BREAKING CHANGE(OpenData)
Require explicit directory paths for OpenData (nogit/download/germanBusinessData); remove automatic .nogit creation; update HandelsRegister, JsonlDataProcessor, tests and README.
- Breaking: OpenData constructor now requires a config object with nogitDir, downloadDir and germanBusinessDataDir. The constructor will throw if these paths are not provided.
- Removed automatic creation/export of .nogit/download/germanBusinessData from ts/paths. OpenData.start now ensures the required directories exist.
- HandelsRegister API changed: constructor now accepts downloadDir and manages its own unique download folder; screenshot and download paths now use the configured downloadDir.
- JsonlDataProcessor now accepts a germanBusinessDataDir parameter and uses it when ensuring/storing data instead of relying on global paths.
- Updated tests to provide explicit path configuration (tests now set testNogitDir, testDownloadDir, testGermanBusinessDataDir and write outputs accordingly) and to use updated constructors and qenv usage.
- Documentation updated (README) to document the breaking change and show examples for required directory configuration when instantiating OpenData.
- Added .claude/settings.local.json for local permissions/config used in development/CI environments.
## 2025-10-11 - 1.7.0 - feat(stocks) ## 2025-10-11 - 1.7.0 - feat(stocks)
Add Marketstack provider (EOD) with tests, exports and documentation updates Add Marketstack provider (EOD) with tests, exports and documentation updates

View File

@@ -1,6 +1,6 @@
{ {
"name": "@fin.cx/opendata", "name": "@fin.cx/opendata",
"version": "1.7.0", "version": "2.0.0",
"private": false, "private": false,
"description": "A comprehensive TypeScript library for accessing business data and real-time financial information. Features include German company data management with MongoDB integration, JSONL bulk processing, automated Handelsregister interactions, and real-time stock market data from multiple providers.", "description": "A comprehensive TypeScript library for accessing business data and real-time financial information. Features include German company data management with MongoDB integration, JSONL bulk processing, automated Handelsregister interactions, and real-time stock market data from multiple providers.",
"main": "dist_ts/index.js", "main": "dist_ts/index.js",

View File

@@ -4,6 +4,20 @@
Access live stock prices, cryptocurrencies, forex, commodities AND comprehensive German company data - all through a single, unified API. Access live stock prices, cryptocurrencies, forex, commodities AND comprehensive German company data - all through a single, unified API.
## ⚠️ Breaking Change in v2.0
**Directory paths are now MANDATORY when using German business data features.** The package no longer creates `.nogit/` directories automatically. You must explicitly configure all directory paths when instantiating `OpenData`:
```typescript
const openData = new OpenData({
nogitDir: '/path/to/your/data',
downloadDir: '/path/to/your/data/downloads',
germanBusinessDataDir: '/path/to/your/data/germanbusinessdata'
});
```
This change enables the package to work in read-only filesystems (like Deno compiled binaries) and gives you full control over where data is stored.
## Installation ## Installation
```bash ```bash
@@ -54,8 +68,14 @@ Access comprehensive data on German companies:
```typescript ```typescript
import { OpenData } from '@fin.cx/opendata'; import { OpenData } from '@fin.cx/opendata';
import * as path from 'path';
const openData = new OpenData(); // REQUIRED: Configure directory paths
const openData = new OpenData({
nogitDir: path.join(process.cwd(), '.nogit'),
downloadDir: path.join(process.cwd(), '.nogit', 'downloads'),
germanBusinessDataDir: path.join(process.cwd(), '.nogit', 'germanbusinessdata')
});
await openData.start(); await openData.start();
// Create a business record // Create a business record
@@ -159,6 +179,17 @@ console.log('Marketstack Stats:', {
Automate German company data retrieval: Automate German company data retrieval:
```typescript ```typescript
import { OpenData } from '@fin.cx/opendata';
import * as path from 'path';
// Configure paths first
const openData = new OpenData({
nogitDir: path.join(process.cwd(), '.nogit'),
downloadDir: path.join(process.cwd(), '.nogit', 'downloads'),
germanBusinessDataDir: path.join(process.cwd(), '.nogit', 'germanbusinessdata')
});
await openData.start();
// Search for a company // Search for a company
const results = await openData.handelsregister.searchCompany("Siemens AG"); const results = await openData.handelsregister.searchCompany("Siemens AG");
@@ -182,6 +213,21 @@ for (const file of details.files) {
Merge financial and business data: Merge financial and business data:
```typescript ```typescript
import { OpenData, StockPriceService, MarketstackProvider } from '@fin.cx/opendata';
import * as path from 'path';
// Configure OpenData with paths
const openData = new OpenData({
nogitDir: path.join(process.cwd(), '.nogit'),
downloadDir: path.join(process.cwd(), '.nogit', 'downloads'),
germanBusinessDataDir: path.join(process.cwd(), '.nogit', 'germanbusinessdata')
});
await openData.start();
// Setup stock service
const stockService = new StockPriceService({ ttl: 60000, maxEntries: 1000 });
stockService.register(new MarketstackProvider('YOUR_API_KEY'));
// Find all public German companies (AG) // Find all public German companies (AG)
const publicCompanies = await openData.db const publicCompanies = await openData.db
.collection('businessrecords') .collection('businessrecords')
@@ -212,6 +258,48 @@ for (const company of publicCompanies) {
## Configuration ## Configuration
### Directory Configuration (Required for German Business Data)
**All directory paths are mandatory when using `OpenData`.** Here are examples for different environments:
#### Development Environment
```typescript
import { OpenData } from '@fin.cx/opendata';
import * as path from 'path';
const openData = new OpenData({
nogitDir: path.join(process.cwd(), '.nogit'),
downloadDir: path.join(process.cwd(), '.nogit', 'downloads'),
germanBusinessDataDir: path.join(process.cwd(), '.nogit', 'germanbusinessdata')
});
```
#### Production Environment
```typescript
import { OpenData } from '@fin.cx/opendata';
import * as path from 'path';
const openData = new OpenData({
nogitDir: '/var/lib/myapp/data',
downloadDir: '/var/lib/myapp/data/downloads',
germanBusinessDataDir: '/var/lib/myapp/data/germanbusinessdata'
});
```
#### Deno Compiled Binaries (or other read-only filesystems)
```typescript
import { OpenData } from '@fin.cx/opendata';
// Use OS temp directory or user data directory
const dataDir = Deno.env.get('HOME') + '/.myapp/data';
const openData = new OpenData({
nogitDir: dataDir,
downloadDir: dataDir + '/downloads',
germanBusinessDataDir: dataDir + '/germanbusinessdata'
});
```
### Stock Service Options ### Stock Service Options
```typescript ```typescript

View File

@@ -1,12 +1,24 @@
import { expect, tap } from '@git.zone/tstest/tapbundle'; import { expect, tap } from '@git.zone/tstest/tapbundle';
import * as opendata from '../ts/index.js' import * as opendata from '../ts/index.js'
import * as paths from '../ts/paths.js';
import * as plugins from '../ts/plugins.js';
import { BusinessRecord } from '../ts/classes.businessrecord.js'; import { BusinessRecord } from '../ts/classes.businessrecord.js';
// Test configuration - explicit paths required
const testNogitDir = plugins.path.join(paths.packageDir, '.nogit');
const testDownloadDir = plugins.path.join(testNogitDir, 'downloads');
const testGermanBusinessDataDir = plugins.path.join(testNogitDir, 'germanbusinessdata');
const testOutputDir = plugins.path.join(testNogitDir, 'testoutput');
let testOpenDataInstance: opendata.OpenData; let testOpenDataInstance: opendata.OpenData;
tap.test('first test', async () => { tap.test('first test', async () => {
testOpenDataInstance = new opendata.OpenData(); testOpenDataInstance = new opendata.OpenData({
nogitDir: testNogitDir,
downloadDir: testDownloadDir,
germanBusinessDataDir: testGermanBusinessDataDir
});
expect(testOpenDataInstance).toBeInstanceOf(opendata.OpenData); expect(testOpenDataInstance).toBeInstanceOf(opendata.OpenData);
}); });
@@ -28,7 +40,7 @@ tap.test('should get the data for a specific company', async () => {
console.log(result); console.log(result);
await Promise.all(result.files.map(async (file) => { await Promise.all(result.files.map(async (file) => {
await file.writeToDir('./.nogit/testoutput'); await file.writeToDir(testOutputDir);
})); }));

View File

@@ -3,6 +3,9 @@ import * as opendata from '../ts/index.js';
import * as paths from '../ts/paths.js'; import * as paths from '../ts/paths.js';
import * as plugins from '../ts/plugins.js'; import * as plugins from '../ts/plugins.js';
// Test configuration - explicit paths required
const testNogitDir = plugins.path.join(paths.packageDir, '.nogit');
// Test data // Test data
const testTickers = ['AAPL', 'MSFT', 'GOOGL']; const testTickers = ['AAPL', 'MSFT', 'GOOGL'];
const invalidTicker = 'INVALID_TICKER_XYZ'; const invalidTicker = 'INVALID_TICKER_XYZ';
@@ -22,7 +25,7 @@ tap.test('should create StockPriceService instance', async () => {
tap.test('should create MarketstackProvider instance', async () => { tap.test('should create MarketstackProvider instance', async () => {
try { try {
// Create qenv and get API key // Create qenv and get API key
testQenv = new plugins.qenv.Qenv(paths.packageDir, paths.nogitDir); testQenv = new plugins.qenv.Qenv(paths.packageDir, testNogitDir);
const apiKey = await testQenv.getEnvVarOnDemand('MARKETSTACK_COM_TOKEN'); const apiKey = await testQenv.getEnvVarOnDemand('MARKETSTACK_COM_TOKEN');
marketstackProvider = new opendata.MarketstackProvider(apiKey, { marketstackProvider = new opendata.MarketstackProvider(apiKey, {

View File

@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@fin.cx/opendata', name: '@fin.cx/opendata',
version: '1.7.0', version: '2.0.0',
description: 'A comprehensive TypeScript library for accessing business data and real-time financial information. Features include German company data management with MongoDB integration, JSONL bulk processing, automated Handelsregister interactions, and real-time stock market data from multiple providers.' description: 'A comprehensive TypeScript library for accessing business data and real-time financial information. Features include German company data management with MongoDB integration, JSONL bulk processing, automated Handelsregister interactions, and real-time stock market data from multiple providers.'
} }

View File

@@ -1,7 +1,6 @@
import type { BusinessRecord } from './classes.businessrecord.js'; import type { BusinessRecord } from './classes.businessrecord.js';
import type { OpenData } from './classes.main.opendata.js'; import type { OpenData } from './classes.main.opendata.js';
import * as plugins from './plugins.js'; import * as plugins from './plugins.js';
import * as paths from './paths.js';
/** /**
* the HandlesRegister exposed as a class * the HandlesRegister exposed as a class
@@ -9,13 +8,16 @@ import * as paths from './paths.js';
export class HandelsRegister { export class HandelsRegister {
private openDataRef: OpenData; private openDataRef: OpenData;
private asyncExecutionStack = new plugins.lik.AsyncExecutionStack(); private asyncExecutionStack = new plugins.lik.AsyncExecutionStack();
private uniqueDowloadFolder = plugins.path.join(paths.downloadDir, plugins.smartunique.uniSimple()); private downloadDir: string;
private uniqueDowloadFolder: string;
// Puppeteer wrapper instance // Puppeteer wrapper instance
public smartbrowserInstance = new plugins.smartbrowser.SmartBrowser(); public smartbrowserInstance = new plugins.smartbrowser.SmartBrowser();
constructor(openDataRef: OpenData) { constructor(openDataRef: OpenData, downloadDirArg: string) {
this.openDataRef = openDataRef; this.openDataRef = openDataRef;
this.downloadDir = downloadDirArg;
this.uniqueDowloadFolder = plugins.path.join(this.downloadDir, plugins.smartunique.uniSimple());
} }
public async start() { public async start() {
@@ -76,7 +78,7 @@ export class HandelsRegister {
timeout: 30000, timeout: 30000,
}) })
.catch(async (err) => { .catch(async (err) => {
await pageArg.screenshot({ path: paths.downloadDir + '/error.png' }); await pageArg.screenshot({ path: this.downloadDir + '/error.png' });
throw err; throw err;
}); });

View File

@@ -1,5 +1,4 @@
import * as plugins from './plugins.js'; import * as plugins from './plugins.js';
import * as paths from './paths.js';
import type { OpenData } from './classes.main.opendata.js'; import type { OpenData } from './classes.main.opendata.js';
export type SeedEntryType = { export type SeedEntryType = {
@@ -41,8 +40,11 @@ export type SeedEntryType = {
}; };
export class JsonlDataProcessor<T> { export class JsonlDataProcessor<T> {
private germanBusinessDataDir: string;
public forEachFunction: (entryArg: T) => Promise<void>; public forEachFunction: (entryArg: T) => Promise<void>;
constructor(forEachFunctionArg: typeof this.forEachFunction) {
constructor(germanBusinessDataDirArg: string, forEachFunctionArg: typeof this.forEachFunction) {
this.germanBusinessDataDir = germanBusinessDataDirArg;
this.forEachFunction = forEachFunctionArg; this.forEachFunction = forEachFunctionArg;
} }
@@ -51,9 +53,9 @@ export class JsonlDataProcessor<T> {
dataUrlArg = 'https://daten.offeneregister.de/de_companies_ocdata.jsonl.bz2' dataUrlArg = 'https://daten.offeneregister.de/de_companies_ocdata.jsonl.bz2'
) { ) {
const done = plugins.smartpromise.defer(); const done = plugins.smartpromise.defer();
const dataExists = await plugins.smartfile.fs.isDirectory(paths.germanBusinessDataDir); const dataExists = await plugins.smartfile.fs.isDirectory(this.germanBusinessDataDir);
if (!dataExists) { if (!dataExists) {
await plugins.smartfile.fs.ensureDir(paths.germanBusinessDataDir); await plugins.smartfile.fs.ensureDir(this.germanBusinessDataDir);
} else { } else {
} }

View File

@@ -4,16 +4,39 @@ import { JsonlDataProcessor, type SeedEntryType } from './classes.jsonldata.js';
import * as paths from './paths.js'; import * as paths from './paths.js';
import * as plugins from './plugins.js'; import * as plugins from './plugins.js';
export interface IOpenDataConfig {
downloadDir: string;
germanBusinessDataDir: string;
nogitDir: string;
}
export class OpenData { export class OpenData {
public db: plugins.smartdata.SmartdataDb; public db: plugins.smartdata.SmartdataDb;
private serviceQenv = new plugins.qenv.Qenv(paths.packageDir, paths.nogitDir); private serviceQenv: plugins.qenv.Qenv;
private config: IOpenDataConfig;
public jsonLDataProcessor: JsonlDataProcessor<SeedEntryType>; public jsonLDataProcessor: JsonlDataProcessor<SeedEntryType>;
public handelsregister: HandelsRegister; public handelsregister: HandelsRegister;
public CBusinessRecord = plugins.smartdata.setDefaultManagerForDoc(this, BusinessRecord); public CBusinessRecord = plugins.smartdata.setDefaultManagerForDoc(this, BusinessRecord);
constructor(configArg: IOpenDataConfig) {
if (!configArg) {
throw new Error('@fin.cx/opendata: Configuration is required. You must provide downloadDir, germanBusinessDataDir, and nogitDir paths.');
}
if (!configArg.downloadDir || !configArg.germanBusinessDataDir || !configArg.nogitDir) {
throw new Error('@fin.cx/opendata: All directory paths are required (downloadDir, germanBusinessDataDir, nogitDir).');
}
this.config = configArg;
this.serviceQenv = new plugins.qenv.Qenv(paths.packageDir, this.config.nogitDir);
}
public async start() { public async start() {
// Ensure configured directories exist
await plugins.smartfile.fs.ensureDir(this.config.nogitDir);
await plugins.smartfile.fs.ensureDir(this.config.downloadDir);
await plugins.smartfile.fs.ensureDir(this.config.germanBusinessDataDir);
this.db = new plugins.smartdata.SmartdataDb({ this.db = new plugins.smartdata.SmartdataDb({
mongoDbUrl: await this.serviceQenv.getEnvVarOnDemand('MONGODB_URL'), mongoDbUrl: await this.serviceQenv.getEnvVarOnDemand('MONGODB_URL'),
mongoDbName: await this.serviceQenv.getEnvVarOnDemand('MONGODB_NAME'), mongoDbName: await this.serviceQenv.getEnvVarOnDemand('MONGODB_NAME'),
@@ -21,7 +44,9 @@ export class OpenData {
mongoDbPass: await this.serviceQenv.getEnvVarOnDemand('MONGODB_PASS'), mongoDbPass: await this.serviceQenv.getEnvVarOnDemand('MONGODB_PASS'),
}); });
await this.db.init(); await this.db.init();
this.jsonLDataProcessor = new JsonlDataProcessor(async (entryArg) => { this.jsonLDataProcessor = new JsonlDataProcessor(
this.config.germanBusinessDataDir,
async (entryArg) => {
const businessRecord = new this.CBusinessRecord(); const businessRecord = new this.CBusinessRecord();
businessRecord.id = await this.CBusinessRecord.getNewId(); businessRecord.id = await this.CBusinessRecord.getNewId();
businessRecord.data.name = entryArg.name; businessRecord.data.name = entryArg.name;
@@ -31,8 +56,9 @@ export class OpenData {
type: entryArg.all_attributes._registerArt as 'HRA' | 'HRB', type: entryArg.all_attributes._registerArt as 'HRA' | 'HRB',
}; };
await businessRecord.save(); await businessRecord.save();
}); }
this.handelsregister = new HandelsRegister(this); );
this.handelsregister = new HandelsRegister(this, this.config.downloadDir);
await this.handelsregister.start(); await this.handelsregister.start();
} }

View File

@@ -4,12 +4,3 @@ export const packageDir = plugins.path.join(
plugins.smartpath.get.dirnameFromImportMetaUrl(import.meta.url), plugins.smartpath.get.dirnameFromImportMetaUrl(import.meta.url),
'../' '../'
); );
export const nogitDir = plugins.path.join(packageDir, './.nogit/');
plugins.smartfile.fs.ensureDirSync(nogitDir);
export const downloadDir = plugins.path.join(nogitDir, 'downloads');
plugins.smartfile.fs.ensureDirSync(downloadDir);
export const germanBusinessDataDir = plugins.path.join(nogitDir, 'germanbusinessdata');