12 Commits

Author SHA1 Message Date
25147deb7f 1.4.6
Some checks failed
Default (tags) / security (push) Failing after 21s
Default (tags) / test (push) Failing after 9s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-04-08 15:31:29 +00:00
4030bef7a8 fix(tests & jsonl): Improve test structure and refine JSONL parsing for incomplete data 2025-04-08 15:31:29 +00:00
c6964f0310 1.4.5
Some checks failed
Default (tags) / security (push) Failing after 8s
Default (tags) / test (push) Failing after 8s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-04-05 09:05:08 +00:00
9a9f203af2 fix(metadata): Update repository, bugs, and homepage URLs to code.foss.global 2025-04-05 09:05:08 +00:00
174086defc 1.4.4
Some checks failed
Default (tags) / security (push) Failing after 19s
Default (tags) / test (push) Failing after 8s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-04-05 09:02:32 +00:00
43c9d3b3b6 fix(dependencies & tests): Update dependency versions and refine test search query 2025-04-05 09:02:32 +00:00
39724b61d6 1.4.3
Some checks failed
Default (tags) / security (push) Failing after 15s
Default (tags) / test (push) Failing after 14s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-01-07 05:13:13 +01:00
d9588f8f65 fix(test): Corrected index value in test for fetching specific company data 2025-01-07 05:13:13 +01:00
6ce6153ccf 1.4.2
Some checks failed
Default (tags) / security (push) Failing after 17s
Default (tags) / test (push) Failing after 11s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-01-07 05:06:17 +01:00
ec2d4f9fbc fix(core): Fix concurrency and download handling in HandelsRegister class and adjust test cases 2025-01-07 05:06:16 +01:00
a19be31381 1.4.1
Some checks failed
Default (tags) / security (push) Failing after 15s
Default (tags) / test (push) Failing after 12s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-01-04 13:40:50 +01:00
9c3f012da7 fix(core): Fix issues with JSONL data processing and improve error handling in business record validation 2025-01-04 13:40:50 +01:00
13 changed files with 1750 additions and 1437 deletions

View File

@ -1,5 +1,46 @@
# Changelog
## 2025-04-08 - 1.4.6 - fix(tests & jsonl)
Improve test structure and refine JSONL parsing for incomplete data
- Refactored test files to remove redundant get-specific-company tests in test.ts and added missing tests in test.handelsregister.ts
- Updated JSONL data processor to conditionally parse remaining data when available
## 2025-04-05 - 1.4.5 - fix(metadata)
Update repository, bugs, and homepage URLs to code.foss.global
- Repository URL updated from gitlab.com to code.foss.global
- Bugs URL updated from gitlab.com to code.foss.global
- Homepage URL updated to code.foss.global
## 2025-04-05 - 1.4.4 - fix(dependencies & tests)
Update dependency versions and refine test search query
- Bumped versions for several dependencies in package.json, including @git.zone/tsbuild, @git.zone/tsbundle, @git.zone/tstest, @push.rocks/tapbundle, @push.rocks/smartdata, @push.rocks/smartfile, @push.rocks/smartpromise, @push.rocks/smartrequest, and @tsclass/tsclass
- Updated test file to replace the search query 'Volkswagen' with 'LADR'
- Re-enabled the build initial data test by removing tap.skip
## 2025-01-07 - 1.4.3 - fix(test)
Corrected index value in test for fetching specific company data
- Updated the index from 8 to 7 for the germanParsedRegistration fetch in test
## 2025-01-07 - 1.4.2 - fix(core)
Fix concurrency and download handling in HandelsRegister class and adjust test cases
- Improved the clickFindButton function to include an argument for results limit.
- Enhanced the downloadFile function to rename and ensure files are correctly handled.
- Updated searchCompany method to allow specifying a limit on the number of search results.
- Adjusted test cases to select specific test data indices and output test files to a dedicated directory.
## 2025-01-04 - 1.4.1 - fix(core)
Fix issues with JSONL data processing and improve error handling in business record validation
- Fixed JSONL data processing by adding concurrent processing for each JSON line to enhance performance.
- Added validation logic in BusinessRecord class to ensure that the mandatory fields are checked.
- Adjusted environment variable loading in OpenData class to ensure correct database initialization.
- Included missing dependencies and exports in the project files to ensure proper functionality.
## 2025-01-04 - 1.4.0 - feat(HandelsRegister)
Add file download functionality to HandelsRegister

View File

@ -30,5 +30,8 @@
"npmci": {
"npmGlobalTools": [],
"npmAccessLevel": "public"
},
"tsdoc": {
"legal": "\n## License and Legal Information\n\nThis repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository. \n\n**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.\n\n### Trademarks\n\nThis project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.\n\n### Company Information\n\nTask Venture Capital GmbH \nRegistered at District court Bremen HRB 35230 HB, Germany\n\nFor any legal inquiries or if you require further information, please contact us via email at hello@task.vc.\n\nBy using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.\n"
}
}

View File

@ -1,6 +1,6 @@
{
"name": "@fin.cx/opendata",
"version": "1.4.0",
"version": "1.4.6",
"private": false,
"description": "A TypeScript library for accessing, managing, and updating open business data, focused on German companies and integrating with MongoDB.",
"main": "dist_ts/index.js",
@ -14,36 +14,38 @@
"buildDocs": "(tsdoc)"
},
"devDependencies": {
"@git.zone/tsbuild": "^2.2.0",
"@git.zone/tsbundle": "^2.1.0",
"@git.zone/tsbuild": "^2.3.2",
"@git.zone/tsbundle": "^2.2.5",
"@git.zone/tsrun": "^1.3.3",
"@git.zone/tstest": "^1.0.90",
"@push.rocks/tapbundle": "^5.5.4",
"@types/node": "^22.10.4"
"@git.zone/tstest": "^1.0.96",
"@push.rocks/tapbundle": "^5.6.2",
"@types/node": "^22.14.0"
},
"dependencies": {
"@push.rocks/lik": "^6.1.0",
"@push.rocks/qenv": "^6.1.0",
"@push.rocks/smartarchive": "^4.0.39",
"@push.rocks/smartarray": "^1.1.0",
"@push.rocks/smartbrowser": "^2.0.8",
"@push.rocks/smartdata": "^5.2.10",
"@push.rocks/smartdata": "^5.2.12",
"@push.rocks/smartdelay": "^3.0.5",
"@push.rocks/smartfile": "^11.0.23",
"@push.rocks/smartfile": "^11.2.0",
"@push.rocks/smartpath": "^5.0.18",
"@push.rocks/smartpromise": "^4.0.4",
"@push.rocks/smartrequest": "^2.0.23",
"@push.rocks/smartpromise": "^4.2.3",
"@push.rocks/smartrequest": "^2.1.0",
"@push.rocks/smartstream": "^3.2.5",
"@push.rocks/smartunique": "^3.0.9",
"@tsclass/tsclass": "^4.2.0"
"@push.rocks/smartxml": "^1.1.1",
"@tsclass/tsclass": "^8.2.0"
},
"repository": {
"type": "git",
"url": "git+https://gitlab.com/fin.cx/opendata.git"
"url": "https://code.foss.global/fin.cx/opendata.git"
},
"bugs": {
"url": "https://gitlab.com/fin.cx/opendata/issues"
"url": "https://code.foss.global/fin.cx/opendata/issues"
},
"homepage": "https://gitlab.com/fin.cx/opendata#readme",
"homepage": "https://code.foss.global/fin.cx/opendata#readme",
"browserslist": [
"last 1 chrome versions"
],
@ -74,5 +76,6 @@
"data processing",
"data retrieval",
"data update"
]
],
"packageManager": "pnpm@10.7.0+sha512.6b865ad4b62a1d9842b61d674a393903b871d9244954f652b8842c2b553c72176b278f64c463e52d40fff8aba385c235c8c9ecf5cc7de4fd78b8bb6d49633ab6"
}

2808
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -215,4 +215,22 @@ When working with business data, ensuring integrity and accuracy is crucial. Eac
The `@fin.cx/opendata` module provides an extensive toolset for accessing and managing business data, particularly for companies based in Germany. Its functionalities include creating, updating, retrieving, and deleting business records, as well as keeping them current with the latest open data releases. This makes it an invaluable asset for developers aiming to integrate open data seamlessly into their systems, ensuring robust data management capabilities within their applications.
Happy exploring and integrating open data into your projects!
undefined
## License and Legal Information
This repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository.
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
### Trademarks
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.
### Company Information
Task Venture Capital GmbH
Registered at District court Bremen HRB 35230 HB, Germany
For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.

View File

@ -0,0 +1,42 @@
import { expect, expectAsync, tap } from '@push.rocks/tapbundle';
import * as opendata from '../ts/index.js'
import { BusinessRecord } from '../ts/classes.businessrecord.js';
let testOpenDataInstance: opendata.OpenData;
tap.test('first test', async () => {
testOpenDataInstance = new opendata.OpenData();
expect(testOpenDataInstance).toBeInstanceOf(opendata.OpenData);
});
tap.test('should start the instance', async () => {
await testOpenDataInstance.start();
});
const resultsSearch = tap.test('should get the data for a company', async () => {
const result = await testOpenDataInstance.handelsregister.searchCompany('LADR', 20);
console.log(result);
return result;
});
tap.test('should get the data for a specific company', async () => {
let testCompany: BusinessRecord['data']['germanParsedRegistration'] = (await resultsSearch.testResultPromise)[0]['germanParsedRegistration'];
console.log(`trying to find specific company with:`);
console.log(testCompany);
const result = await testOpenDataInstance.handelsregister.getSpecificCompany(testCompany);
console.log(result);
result.files.map(async (file) => {
await file.writeToDir('./.nogit/testoutput');
});
});
tap.test('should stop the instance', async () => {
await testOpenDataInstance.stop();
});
tap.start()

View File

@ -1,6 +1,8 @@
import { expect, expectAsync, tap } from '@push.rocks/tapbundle';
import * as opendata from '../ts/index.js'
import { BusinessRecord } from '../ts/classes.businessrecord.js';
let testOpenDataInstance: opendata.OpenData;
tap.test('first test', async () => {
@ -12,24 +14,10 @@ tap.test('should start the instance', async () => {
await testOpenDataInstance.start();
})
tap.skip.test('should build initial data', async () => {
tap.test('should build initial data', async () => {
await testOpenDataInstance.buildInitialDb();
});
const resultsSearch = tap.test('should get the data for a company', async () => {
const result = await testOpenDataInstance.handelsregister.searchCompany('Volkswagen');
console.log(result);
return result;
});
tap.test('should get the data for a specific company', async () => {
const testCompany = (await resultsSearch.testResultPromise)[21]['germanParsedRegistration'];
console.log(`trying to find specific company with:`);
console.log(testCompany);
const result = await testOpenDataInstance.handelsregister.getSpecificCompany(testCompany);
console.log(result);
});
tap.test('should stop the instance', async () => {
await testOpenDataInstance.stop();
});

View File

@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@fin.cx/opendata',
version: '1.4.0',
version: '1.4.6',
description: 'A TypeScript library for accessing, managing, and updating open business data, focused on German companies and integrating with MongoDB.'
}

View File

@ -5,12 +5,27 @@ export class BusinessRecord extends plugins.smartdata.SmartDataDbDoc<
BusinessRecord,
BusinessRecord
> {
// STATIC
public static getByGermanParsedRegistration = async (parsedGermanRegistrationArg: BusinessRecord['data']['germanParsedRegistration']) => {
const businessRecords = await BusinessRecord.getInstance({
data: {
germanParsedRegistration: parsedGermanRegistrationArg,
}
});
return businessRecords;
};
// INSTANCE
@plugins.smartdata.unI()
id: string;
@plugins.smartdata.svDb()
data: {
name?: string;
startDate?: string;
endDate?: string;
status?: 'active' | 'liquidating' | 'closed';
address?: string;
postalCode?: string;
city?: string;
@ -42,4 +57,11 @@ export class BusinessRecord extends plugins.smartdata.SmartDataDbDoc<
purpose?: string;
lastUpdate?: string;
} = {};
/**
* validates the record against the Handelregister.
*/
public async validate() {
if (!this.data.name) throw new Error('Name is required.');
}
}

View File

@ -113,13 +113,13 @@ export class HandelsRegister {
return businessRecords;
};
private clickFindButton = async (pageArg: plugins.smartbrowser.smartpuppeteer.puppeteer.Page) => {
private clickFindButton = async (pageArg: plugins.smartbrowser.smartpuppeteer.puppeteer.Page, resultsLimitArg: number = 100) => {
try {
// Wait for the button with the text "Find" to appear
await pageArg.waitForSelector('span.ui-button-text.ui-c', { timeout: 5000 });
// adjust to 100 results per page
await pageArg.select('#form\\:ergebnisseProSeite_input', '100');
await pageArg.select('#form\\:ergebnisseProSeite_input', `${resultsLimitArg}`);
// Locate and click the button using its text
await pageArg.evaluate(() => {
@ -183,14 +183,24 @@ export class HandelsRegister {
}
}, typeArg);
// Wait a bit for the download to complete (you might want to implement
// a more robust file-exists check or a wait-for-download library)
await pageArg.waitForTimeout(10000);
await plugins.smartfile.fs.waitForFileToBeReady(this.uniqueDowloadFolder);
const files = await plugins.smartfile.fs.fileTreeToObject(this.uniqueDowloadFolder, '**/*');
await plugins.smartfile.fs.ensureEmptyDir(this.uniqueDowloadFolder);
const file = files[0];
return files [0];
// lets clear the folder for the next download
await plugins.smartfile.fs.ensureEmptyDir(this.uniqueDowloadFolder);
switch (typeArg) {
case 'AD':
await file.rename(`ad.pdf`);
break;
case 'SI':
await file.rename(`si.xml`);
break;
break;
}
return file;
}
/**
@ -216,7 +226,7 @@ export class HandelsRegister {
/**
* Search for a company by name and return basic info
*/
public async searchCompany(companyNameArg: string) {
public async searchCompany(companyNameArg: string, resultsLimitArg: number = 100) {
return this.asyncExecutionStack.getExclusiveExecutionSlot(async () => {
const page = await this.getNewPage();
await this.navigateToPage(page, 'Normal search');
@ -261,7 +271,7 @@ export class HandelsRegister {
console.error('Failed to find or click the radio button:', error);
}
await this.clickFindButton(page);
await this.clickFindButton(page, resultsLimitArg);
const businessRecords = await this.waitForResults(page);
@ -336,4 +346,13 @@ export class HandelsRegister {
};
}, 60000);
}
/**
* get specific company by full name
*/
public async getSpecificCompanyByName(companyNameArg: string) {
const businessRecords = await this.searchCompany(companyNameArg, 1);
const result = this.getSpecificCompany(businessRecords[0].germanParsedRegistration);
return result;
}
}

View File

@ -2,16 +2,55 @@ import * as plugins from './plugins.js';
import * as paths from './paths.js';
import type { OpenData } from './classes.main.opendata.js';
export class JsonlDataProcessor {
public openDataRef: OpenData;
constructor(openDataRefArg: OpenData) {
this.openDataRef = openDataRefArg;
export type SeedEntryType = {
all_attributes: {
_registerArt: string;
_registerNummer: string;
additional_data: {
AD: boolean;
CD: boolean;
DK: boolean;
HD: boolean;
SI: boolean;
UT: boolean;
: boolean;
};
federal_state: string;
native_company_number: string;
registered_office: string;
registrar: string;
};
company_number: string;
current_status: string;
jurisdiction_code: string;
name: string;
officers: {
name: string;
other_attributes: {
city: string;
firstname: string;
flag: string;
lastname: string;
};
position: string;
start_date: string; // ISO 8601 date string
type: string;
}[];
registered_address: string;
retrieved_at: string; // ISO 8601 date string
};
export class JsonlDataProcessor<T> {
public forEachFunction: (entryArg: T) => Promise<void>;
constructor(forEachFunctionArg: typeof this.forEachFunction) {
this.forEachFunction = forEachFunctionArg;
}
// TODO: define a mapper as argument instead of hard-coding it
public async processDataFromUrl(dataUrlArg = 'https://daten.offeneregister.de/de_companies_ocdata.jsonl.bz2') {
public async processDataFromUrl(
dataUrlArg = 'https://daten.offeneregister.de/de_companies_ocdata.jsonl.bz2'
) {
const done = plugins.smartpromise.defer();
const promiseArray: Promise<any>[] = [];
const dataExists = await plugins.smartfile.fs.isDirectory(paths.germanBusinessDataDir);
if (!dataExists) {
await plugins.smartfile.fs.ensureDir(paths.germanBusinessDataDir);
@ -19,10 +58,6 @@ export class JsonlDataProcessor {
}
const smartarchive = await plugins.smartarchive.SmartArchive.fromArchiveUrl(dataUrlArg);
promiseArray
.push
// smartarchive.exportToFs(paths.germanBusinessDataDir, 'de_companies_ocdata.jsonl')
();
const jsonlDataStream = await smartarchive.exportToStreamOfStreamFiles();
let totalRecordsCounter = 0;
let nextRest: string = '';
@ -39,44 +74,38 @@ export class JsonlDataProcessor {
const lines = currentString.split('\n');
nextRest = lines.pop();
console.log(`Got another ${lines.length} records.`);
for (const line of lines) {
let entry: any;
if (!line) continue;
try {
entry = JSON.parse(line);
console.log(JSON.stringify(entry, null, 2));
process.exit(0);
} catch (err) {
console.log(line);
await plugins.smartdelay.delayFor(10000);
}
if (!entry) continue;
totalRecordsCounter++;
if (totalRecordsCounter % 10000 === 0) console.log(`${totalRecordsCounter} total records.`);
const businessRecord = new this.openDataRef.CBusinessRecord();
businessRecord.id = await this.openDataRef.CBusinessRecord.getNewId();
businessRecord.data.name = entry.name;
await businessRecord.save();
}
const concurrentProcessor = new plugins.smartarray.ConcurrentProcessor<string>(
async (line) => {
let entry: T;
if (!line) return;
try {
entry = JSON.parse(line);
} catch (err) {
console.log(line);
await plugins.smartdelay.delayFor(10000);
}
if (!entry) return;
totalRecordsCounter++;
if (totalRecordsCounter % 10000 === 0)
console.log(`${totalRecordsCounter} total records.`);
await this.forEachFunction(entry);
},
1000
);
await concurrentProcessor.process(lines);
},
finalFunction: async (streamToolsArg) => {
console.log(`finished processing ${totalRecordsCounter} records.`);
if (!nextRest) return;
JSON.parse(nextRest);
}
if (nextRest) {
JSON.parse(nextRest);
};
done.resolve();
},
})
);
},
})
);
}
public async getBusinessRecordByName(nameArg: string) {
const businessRecord = await this.openDataRef.CBusinessRecord.getInstance({
data: {
name: { $regex: `${nameArg}`, $options: "i" } as any,
}
});
return businessRecord;
await done.promise;
}
}

View File

@ -1,27 +1,37 @@
import { BusinessRecord } from './classes.businessrecord.js';
import { HandelsRegister } from './classes.handelsregister.js';
import { JsonlDataProcessor } from './classes.jsonldata.js';
import { HandelsRegister } from './classes.handelsregister.js';
import { JsonlDataProcessor, type SeedEntryType } from './classes.jsonldata.js';
import * as paths from './paths.js';
import * as plugins from './plugins.js';
export class OpenData {
public db: plugins.smartdata.SmartdataDb;
private serviceQenv = new plugins.qenv.Qenv(paths.packageDir, paths.nogitDir);
public jsonLDataProcessor: JsonlDataProcessor;
public jsonLDataProcessor: JsonlDataProcessor<SeedEntryType>;
public handelsregister: HandelsRegister;
public CBusinessRecord = plugins.smartdata.setDefaultManagerForDoc(this, BusinessRecord);
public async start() {
this.db = new plugins.smartdata.SmartdataDb({
mongoDbUrl: await this.serviceQenv.getEnvVarOnDemand('MONGODB_URL'),
mongoDbName: await this.serviceQenv.getEnvVarOnDemand('MONGODB_NAME'),
mongoDbUser: await this.serviceQenv.getEnvVarOnDemand('MONGODB_USER'),
mongoDbPass: await this.serviceQenv.getEnvVarOnDemand('MONGODB_PASS'),
mongoDbUrl: await this.serviceQenv.getEnvVarOnDemand('MONGODB_URL'),
mongoDbName: await this.serviceQenv.getEnvVarOnDemand('MONGODB_NAME'),
mongoDbUser: await this.serviceQenv.getEnvVarOnDemand('MONGODB_USER'),
mongoDbPass: await this.serviceQenv.getEnvVarOnDemand('MONGODB_PASS'),
});
await this.db.init();
this.jsonLDataProcessor = new JsonlDataProcessor(this);
this.jsonLDataProcessor = new JsonlDataProcessor(async (entryArg) => {
const businessRecord = new this.CBusinessRecord();
businessRecord.id = await this.CBusinessRecord.getNewId();
businessRecord.data.name = entryArg.name;
businessRecord.data.germanParsedRegistration = {
court: entryArg.all_attributes.registered_office,
number: entryArg.all_attributes._registerNummer,
type: entryArg.all_attributes._registerArt as 'HRA' | 'HRB',
};
await businessRecord.save();
});
this.handelsregister = new HandelsRegister(this);
await this.handelsregister.start();
}
@ -30,8 +40,22 @@ export class OpenData {
await this.jsonLDataProcessor.processDataFromUrl();
}
public async slowValidateDb() {
}
public async validateSearchByName() {
}
public async searchDbByBusinessNameAndPostalCode(businessNameArg: string, postalCodeArg: string) {
}
public async stop() {
await this.db.close();
await this.handelsregister.stop();
}
}
}

View File

@ -9,6 +9,7 @@ export {
import * as lik from '@push.rocks/lik';
import * as qenv from '@push.rocks/qenv';
import * as smartarchive from '@push.rocks/smartarchive';
import * as smartarray from '@push.rocks/smartarray';
import * as smartbrowser from '@push.rocks/smartbrowser';
import * as smartdata from '@push.rocks/smartdata';
import * as smartdelay from '@push.rocks/smartdelay';
@ -18,11 +19,13 @@ import * as smartpromise from '@push.rocks/smartpromise';
import * as smartrequest from '@push.rocks/smartrequest';
import * as smartstream from '@push.rocks/smartstream';
import * as smartunique from '@push.rocks/smartunique';
import * as smartxml from '@push.rocks/smartxml';
export {
lik,
qenv,
smartarchive,
smartarray,
smartbrowser,
smartdata,
smartdelay,
@ -32,6 +35,7 @@ export {
smartrequest,
smartstream,
smartunique,
smartxml,
}
// @tsclass scope