@transai/connector-runner-mkg 0.2.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.
Files changed (44) hide show
  1. package/.eslintrc.json +30 -0
  2. package/CHANGELOG.md +24 -0
  3. package/README.md +11 -0
  4. package/jest.config.ts +10 -0
  5. package/package.json +16 -0
  6. package/project.json +29 -0
  7. package/src/index.ts +1 -0
  8. package/src/lib/actions-handler.spec.ts +177 -0
  9. package/src/lib/actions-handler.ts +143 -0
  10. package/src/lib/connector-runner-mkg.spec.ts +219 -0
  11. package/src/lib/connector-runner-mkg.ts +155 -0
  12. package/src/lib/extractor.service.spec.ts +123 -0
  13. package/src/lib/extractor.service.ts +125 -0
  14. package/src/lib/tables/_all.ts +60 -0
  15. package/src/lib/tables/artg.ts +12 -0
  16. package/src/lib/tables/arti.ts +99 -0
  17. package/src/lib/tables/base/action.ts +70 -0
  18. package/src/lib/tables/base/table.ts +57 -0
  19. package/src/lib/tables/bwrg.ts +13 -0
  20. package/src/lib/tables/bwrk.ts +60 -0
  21. package/src/lib/tables/clch.ts +42 -0
  22. package/src/lib/tables/debi.ts +62 -0
  23. package/src/lib/tables/magl.ts +15 -0
  24. package/src/lib/tables/magz.ts +14 -0
  25. package/src/lib/tables/medw.ts +97 -0
  26. package/src/lib/tables/parl.ts +24 -0
  27. package/src/lib/tables/plnb.ts +46 -0
  28. package/src/lib/tables/prbv.ts +31 -0
  29. package/src/lib/tables/prdh.ts +46 -0
  30. package/src/lib/tables/prdr.ts +25 -0
  31. package/src/lib/tables/prmv.ts +31 -0
  32. package/src/lib/tables/rela.ts +40 -0
  33. package/src/lib/tables/rgrs.ts +68 -0
  34. package/src/lib/tables/rsrc.ts +23 -0
  35. package/src/lib/tables/rsrd.ts +98 -0
  36. package/src/lib/tables/rsrg.ts +6 -0
  37. package/src/lib/tables/stlh.ts +24 -0
  38. package/src/lib/tables/stlm.ts +80 -0
  39. package/src/lib/tables/stlr.ts +60 -0
  40. package/src/lib/tables/vrdg.ts +27 -0
  41. package/src/lib/types.ts +45 -0
  42. package/tsconfig.json +22 -0
  43. package/tsconfig.lib.json +10 -0
  44. package/tsconfig.spec.json +14 -0
@@ -0,0 +1,155 @@
1
+ /* eslint-disable camelcase,@typescript-eslint/naming-convention */
2
+ import {
3
+ ConnectorRuntimeSDK,
4
+ ConnectorSDKInterface,
5
+ HttpClientSDKInterface,
6
+ HttpRequestOptions,
7
+ } from '@transai/connector-runtime-sdk';
8
+ import { ConnectorInterface } from '@xip-online-data/types';
9
+
10
+ import { ActionsHandler } from './actions-handler';
11
+ import { ExtractorService } from './extractor.service';
12
+ import { MKG_TABLES } from './tables/_all';
13
+ import {
14
+ ConnectorConfig,
15
+ SESSION_COOKIE_NAME,
16
+ SESSION_EXPIRATION_SECONDS,
17
+ } from './types';
18
+
19
+ export class ConnectorRunnerMkg extends ConnectorRuntimeSDK<ConnectorConfig> {
20
+ readonly #actionsHandler: ActionsHandler;
21
+
22
+ readonly #mkgHttpClient: HttpClientSDKInterface;
23
+
24
+ readonly #sessionTokenHttpClient: HttpClientSDKInterface;
25
+
26
+ #sessionToken?: {
27
+ token: string;
28
+ expiresAt: number;
29
+ };
30
+
31
+ constructor(
32
+ connector: ConnectorInterface,
33
+ connectorSDK: ConnectorSDKInterface,
34
+ ) {
35
+ super(connector, connectorSDK);
36
+
37
+ const { config } = this.connectorSDK;
38
+
39
+ this.#sessionTokenHttpClient = this.connectorSDK.httpClient({
40
+ baseUrl: `https://${config.server}:${config.port ?? 443}/${(config.path ?? '/mkg').replace(/^\/+/, '')}`,
41
+ });
42
+ this.#mkgHttpClient = this.connectorSDK
43
+ .httpClient({
44
+ baseUrl: `https://${config.server}:${config.port ?? 443}/${(config.path ?? '/mkg').replace(/^\/+/, '')}`,
45
+ })
46
+ .setRequestOptionsFormatter(this.#requestOptionsFormatter);
47
+
48
+ this.#actionsHandler = new ActionsHandler(
49
+ this.connectorSDK,
50
+ this.#mkgHttpClient,
51
+ );
52
+
53
+ this.callbackFunction = this.#actionsHandler.callbackFunctionChain;
54
+ }
55
+
56
+ override init = async (): Promise<void> => {
57
+ const { config } = this.connectorSDK;
58
+
59
+ Object.entries(config.tables ?? {}).forEach(
60
+ ([tableIdentifier, configOrTrue]) => {
61
+ // eslint-disable-next-line security/detect-object-injection
62
+ const table = MKG_TABLES[tableIdentifier];
63
+ if (!table) {
64
+ this.connectorSDK.logger.warn(
65
+ `[MKG] Unknown table identifier "${tableIdentifier}", skipping configuration.`,
66
+ );
67
+ return;
68
+ }
69
+
70
+ if (!table.interval || table.interval <= 0) {
71
+ this.connectorSDK.logger.verbose(
72
+ `[MKG] Table "${tableIdentifier}" does not have a valid interval configured, skipping registration.`,
73
+ );
74
+ return;
75
+ }
76
+
77
+ this.connectorSDK.processing.registerInterval(
78
+ table.interval,
79
+ new ExtractorService(
80
+ this.connectorSDK,
81
+ this.#mkgHttpClient,
82
+ tableIdentifier,
83
+ table.cloneFromTableConfig(configOrTrue),
84
+ ),
85
+ { immediate: table.immediate },
86
+ );
87
+ },
88
+ );
89
+ };
90
+
91
+ #requestOptionsFormatter = async <D = string | object>(
92
+ options: HttpRequestOptions<D>,
93
+ ): Promise<HttpRequestOptions<D>> => {
94
+ const { config } = this.connectorSDK;
95
+
96
+ return {
97
+ ...options,
98
+ headers: {
99
+ ...(options?.headers ?? {}),
100
+ Cookie: `${SESSION_COOKIE_NAME}=${await this.#getSessionCookie(config.username, config.password)}`,
101
+ 'X-CustomerID': config.apiToken,
102
+ },
103
+ };
104
+ };
105
+
106
+ async #getSessionCookie(
107
+ j_username: string,
108
+ j_password: string,
109
+ ): Promise<string> {
110
+ const token = this.#sessionToken;
111
+ if (token && token.expiresAt > Date.now()) {
112
+ return token.token;
113
+ }
114
+
115
+ const response = await this.#sessionTokenHttpClient.post(
116
+ '/static/auth/j_spring_security_check',
117
+ {
118
+ j_username,
119
+ j_password,
120
+ },
121
+ {
122
+ headers: {
123
+ Accept: 'application/json',
124
+ 'Content-Type': 'application/x-www-form-urlencoded',
125
+ },
126
+ },
127
+ );
128
+
129
+ if (!response) {
130
+ throw new Error('Failed to authenticate and retrieve session cookie');
131
+ }
132
+
133
+ const cookies = (response.headers?.['set-cookie'] ??
134
+ response.headers?.['Set-Cookie'] ??
135
+ []) as Array<string>;
136
+ const sessionCookie = cookies.find((cookie: string) =>
137
+ cookie.startsWith(`${SESSION_COOKIE_NAME}=`),
138
+ );
139
+
140
+ if (!sessionCookie) {
141
+ throw new Error('Session cookie not found in authentication response');
142
+ }
143
+
144
+ const sessionToken = sessionCookie
145
+ .split(';')[0]
146
+ ?.substring(`${SESSION_COOKIE_NAME}=`.length);
147
+
148
+ this.#sessionToken = {
149
+ token: sessionToken,
150
+ expiresAt: Date.now() + SESSION_EXPIRATION_SECONDS - 1000,
151
+ };
152
+
153
+ return sessionToken;
154
+ }
155
+ }
@@ -0,0 +1,123 @@
1
+ import {
2
+ ConnectorSDKInterface,
3
+ HttpClientSDKInterface,
4
+ } from '@transai/connector-runtime-sdk';
5
+
6
+ import { ExtractorService } from './extractor.service';
7
+ import { MkgTable } from './tables/base/table';
8
+ import { ConnectorConfig } from './types';
9
+
10
+ describe('ExtractorService', () => {
11
+ let service: ExtractorService;
12
+
13
+ const sdkMock = {
14
+ logger: {
15
+ info: jest.fn(),
16
+ debug: jest.fn(),
17
+ error: jest.fn(),
18
+ verbose: jest.fn(),
19
+ },
20
+ offsetStore: {
21
+ getOffset: jest.fn().mockResolvedValue({
22
+ id: 0,
23
+ timestamp: new Date().getTime(),
24
+ rawTimestamp: new Date().toISOString(),
25
+ isoDate: new Date().toISOString(),
26
+ }),
27
+ setOffset: jest.fn().mockResolvedValue(undefined),
28
+ },
29
+ sender: {
30
+ documents: jest.fn().mockResolvedValue(undefined),
31
+ },
32
+ config: {},
33
+ } as unknown as ConnectorSDKInterface<ConnectorConfig>;
34
+
35
+ const httpClientMock = {} as HttpClientSDKInterface;
36
+
37
+ beforeEach(() => {
38
+ service = new ExtractorService(
39
+ sdkMock,
40
+ httpClientMock,
41
+ 'test-table',
42
+ new MkgTable({
43
+ identifier: 'test-table',
44
+ fields: ['field1', 'field2'],
45
+ }).cloneFromTableConfig({
46
+ fields: ['field1', 'field11', 'field2'],
47
+ }),
48
+ );
49
+ });
50
+
51
+ afterEach(() => {
52
+ jest.clearAllMocks();
53
+ });
54
+
55
+ it('should initialize and register interval', () => {
56
+ expect(service).toBeDefined();
57
+ });
58
+
59
+ describe('onRun', () => {
60
+ it('should run without errors', async () => {
61
+ const data = [
62
+ {
63
+ sys_dat_wijzig: '2025-11-02',
64
+ RowKey: '1',
65
+ field1: 'value1',
66
+ field2: 'value2',
67
+ },
68
+ {
69
+ sys_dat_wijzig: '2025-11-03',
70
+ RowKey: '2',
71
+ field1: 'value3',
72
+ field2: 'value4',
73
+ },
74
+ ];
75
+ httpClientMock.get = jest.fn().mockResolvedValue({
76
+ success: true,
77
+ data: {
78
+ response: {
79
+ ResultData: [
80
+ {
81
+ 'test-table': data,
82
+ },
83
+ ],
84
+ },
85
+ },
86
+ });
87
+
88
+ await expect(service.onRun()).resolves.toBeUndefined();
89
+
90
+ expect(sdkMock.sender.documents).toHaveBeenCalledWith(data, {
91
+ keyField: 'RowKey',
92
+ collection: 'mkg_test-table',
93
+ });
94
+ expect(sdkMock.offsetStore.setOffset).toHaveBeenCalledWith(
95
+ {
96
+ id: '2',
97
+ timestamp: expect.any(Number),
98
+ rawTimestamp: expect.any(String),
99
+ },
100
+ 'offset_test-table',
101
+ );
102
+ });
103
+
104
+ it('should now send documents on error response', async () => {
105
+ httpClientMock.get = jest.fn().mockResolvedValue({
106
+ success: false,
107
+ error: 'some-error',
108
+ });
109
+
110
+ await expect(service.onRun()).resolves.toBeUndefined();
111
+
112
+ expect(sdkMock.sender.documents).not.toHaveBeenCalled();
113
+ expect(sdkMock.offsetStore.setOffset).toHaveBeenCalledWith(
114
+ {
115
+ id: '0',
116
+ timestamp: expect.any(Number),
117
+ rawTimestamp: expect.any(String),
118
+ },
119
+ 'offset_test-table',
120
+ );
121
+ });
122
+ });
123
+ });
@@ -0,0 +1,125 @@
1
+ import {
2
+ ConnectorSDKInterface,
3
+ HttpClientSDKInterface,
4
+ HttpError,
5
+ IntervalHandler,
6
+ StoredOffset,
7
+ } from '@transai/connector-runtime-sdk';
8
+
9
+ import {
10
+ ConnectorConfig,
11
+ DEFAULT_DATASOURCE,
12
+ MkgTableInterface,
13
+ } from './types';
14
+
15
+ type FetchDataResponse<D> = {
16
+ response: {
17
+ ResultData: [
18
+ {
19
+ [key: string]: Array<D>;
20
+ },
21
+ ];
22
+ };
23
+ };
24
+
25
+ export class ExtractorService implements IntervalHandler {
26
+ readonly #sdk: ConnectorSDKInterface<ConnectorConfig>;
27
+
28
+ readonly #httpClient: HttpClientSDKInterface;
29
+
30
+ readonly #tableIdentifier: string;
31
+
32
+ readonly #table: Required<MkgTableInterface>;
33
+
34
+ readonly #fieldsList: string;
35
+
36
+ constructor(
37
+ sdk: ConnectorSDKInterface<ConnectorConfig>,
38
+ httpClient: HttpClientSDKInterface,
39
+ tableIdentifier: string,
40
+ table: Required<MkgTableInterface>,
41
+ ) {
42
+ this.#sdk = sdk;
43
+ this.#httpClient = httpClient;
44
+ this.#tableIdentifier = tableIdentifier;
45
+ this.#table = table;
46
+ this.#fieldsList = table.fields.join(',');
47
+ }
48
+
49
+ get name(): string {
50
+ return `mkg-extractor-${this.#tableIdentifier}`;
51
+ }
52
+
53
+ async onRun(): Promise<void> {
54
+ try {
55
+ const now = new Date();
56
+ const latestOffset = await this.#sdk.offsetStore.getOffset(
57
+ `offset_${this.#tableIdentifier}`,
58
+ );
59
+
60
+ const data = await this.#fetchData(latestOffset);
61
+ if (data.length > 0) {
62
+ await this.#sdk.sender.documents(data, {
63
+ keyField: this.#table.identifierField,
64
+ collection: `${this.#sdk.config.datasourceIdentifier ?? DEFAULT_DATASOURCE}_${this.#tableIdentifier}`,
65
+ });
66
+ }
67
+
68
+ this.#sdk.logger.debug(
69
+ `[MKG] Processed ${data.length} records from ${this.#tableIdentifier}`,
70
+ );
71
+
72
+ this.#sdk.offsetStore.setOffset(
73
+ {
74
+ id: String(
75
+ data[data.length - 1]?.[this.#table.identifierField] ??
76
+ latestOffset.id,
77
+ ),
78
+ timestamp: now.getTime(),
79
+ rawTimestamp: now.toISOString(),
80
+ },
81
+ `offset_${this.#tableIdentifier}`,
82
+ );
83
+ } catch (error) {
84
+ this.#sdk.logger.error(
85
+ `[MKG] Failed to retrieve and process data from OPC UA source service`,
86
+ { error },
87
+ );
88
+ }
89
+ }
90
+
91
+ #fetchData = async (
92
+ latestOffset: StoredOffset,
93
+ ): Promise<
94
+ Array<{
95
+ [key: string]: string | number | boolean;
96
+ }>
97
+ > => {
98
+ const parameters = new URLSearchParams();
99
+ parameters.set('NumRows', String(this.#table.rows));
100
+ parameters.set(
101
+ 'Filter',
102
+ `${this.#table.dateField}>=${latestOffset.rawTimestamp}`,
103
+ );
104
+ parameters.set('FieldList', `${this.#table.dateField},${this.#fieldsList}`);
105
+
106
+ try {
107
+ const response = await this.#httpClient.get<
108
+ FetchDataResponse<{
109
+ [key: string]: string | number | boolean;
110
+ }>
111
+ >(
112
+ `/web/v3/MKG/Documents/${this.#tableIdentifier}?${parameters.toString()}`,
113
+ );
114
+
115
+ return (
116
+ response.data?.response?.ResultData?.[0]?.[this.#tableIdentifier] ?? []
117
+ );
118
+ } catch (error: HttpError | unknown) {
119
+ const logMessage = `[MKG] Failed to fetch data from table "${this.#tableIdentifier}": (${(error as HttpError)?.status}) ${(error as HttpError)?.error ?? (error as Error)?.message}`;
120
+ this.#sdk.logger.error(logMessage);
121
+ }
122
+
123
+ return [];
124
+ };
125
+ }
@@ -0,0 +1,60 @@
1
+ import { MKG_TABLE_ARTG } from './artg';
2
+ import { MKG_TABLE_ARTI } from './arti';
3
+ import { MkgTable } from './base/table';
4
+ import { MKG_TABLE_BWRG } from './bwrg';
5
+ import { MKG_TABLE_BWRK } from './bwrk';
6
+ import { MKG_TABLE_CLCH } from './clch';
7
+ import { MKG_TABLE_DEBI } from './debi';
8
+ import { MKG_TABLE_MAGL } from './magl';
9
+ import { MKG_TABLE_MAGZ } from './magz';
10
+ import { MKG_TABLE_MEDW } from './medw';
11
+ import { MKG_TABLE_PARL } from './parl';
12
+ import { MKG_TABLE_PLNB } from './plnb';
13
+ import { MKG_TABLE_PRBV } from './prbv';
14
+ import { MKG_TABLE_PRDH } from './prdh';
15
+ import { MKG_TABLE_PRDR } from './prdr';
16
+ import { MKG_TABLE_PRMV } from './prmv';
17
+ import { MKG_TABLE_RELA } from './rela';
18
+ import { MKG_TABLE_RGRS } from './rgrs';
19
+ import { MKG_TABLE_RSRC } from './rsrc';
20
+ import { MKG_TABLE_RSRD } from './rsrd';
21
+ import { MKG_TABLE_RSRG } from './rsrg';
22
+ import { MKG_TABLE_STLH } from './stlh';
23
+ import { MKG_TABLE_STLM } from './stlm';
24
+ import { MKG_TABLE_STLR } from './stlr';
25
+ import { MKG_TABLE_VRDG } from './vrdg';
26
+
27
+ const tables = [
28
+ MKG_TABLE_RELA,
29
+ MKG_TABLE_DEBI,
30
+ MKG_TABLE_MEDW,
31
+ MKG_TABLE_ARTI,
32
+ MKG_TABLE_RSRC,
33
+ MKG_TABLE_RSRD,
34
+ MKG_TABLE_RSRG,
35
+ MKG_TABLE_MAGZ,
36
+ MKG_TABLE_MAGL,
37
+ MKG_TABLE_STLH,
38
+ MKG_TABLE_STLR,
39
+ MKG_TABLE_STLM,
40
+ MKG_TABLE_PRDH,
41
+ MKG_TABLE_BWRK,
42
+ MKG_TABLE_BWRG,
43
+ MKG_TABLE_CLCH,
44
+ MKG_TABLE_VRDG,
45
+ MKG_TABLE_RGRS,
46
+ MKG_TABLE_PLNB,
47
+ MKG_TABLE_ARTG,
48
+ MKG_TABLE_PRDR,
49
+ MKG_TABLE_PRMV,
50
+ MKG_TABLE_PRBV,
51
+ MKG_TABLE_PARL,
52
+ ];
53
+
54
+ export const MKG_TABLES: { [identifier: string]: MkgTable } = tables.reduce(
55
+ (acc, table) => {
56
+ acc[table.identifier] = table;
57
+ return acc;
58
+ },
59
+ {} as { [identifier: string]: MkgTable },
60
+ );
@@ -0,0 +1,12 @@
1
+ import { MkgTable } from './base/table';
2
+
3
+ export const MKG_TABLE_ARTG = new MkgTable({
4
+ identifier: 'artg',
5
+ fields: [
6
+ 'artg_num',
7
+ 'artg_rela',
8
+ 'artg_cred',
9
+ 'artg_oms',
10
+ 'artg_inkoopofferte',
11
+ ],
12
+ });
@@ -0,0 +1,99 @@
1
+ import { MkgTable } from './base/table';
2
+
3
+ export const MKG_TABLE_ARTI = new MkgTable({
4
+ identifier: 'arti',
5
+ interval: 300,
6
+ fields: [
7
+ 'arti_parameter_2',
8
+ 'arti_prijs_vast_man',
9
+ 'arti_seriegrootte',
10
+ 'arti_vrij_memo_1',
11
+ 'arti_bestellen',
12
+ 'arti_btw_aanpasbaar',
13
+ 'arti_vrij_datum_1',
14
+ 'arti_eenh_uitgifte',
15
+ 'arti_eenh_prijs_inkoop',
16
+ 'arti_partij_per_1',
17
+ 'arti_partij',
18
+ 'arti_voorraad_waarde_mach',
19
+ 'arti_parameter_4',
20
+ 'arti_eenh_ontvangst',
21
+ 'arti_prijs_vast_mat',
22
+ 'arti_verh_tijd_aantal_grp',
23
+ 'arti_voorraad_aantal',
24
+ 'arti_bestel_freq_eenh',
25
+ 'arti_vrij_datum_4',
26
+ 'arti_eenh_vrk',
27
+ 'arti_trans_eenh',
28
+ 'arti_kosten_artikel',
29
+ 'arti_verh_tijd_aantal_vrd',
30
+ 'arti_verh_volume_aantal_grp',
31
+ 'arti_memo_verkoop_intern',
32
+ 'arti_verh_volume_aantal_vrd',
33
+ 'arti_vrij_memo_2',
34
+ 'arti_zoeknaam',
35
+ 'arti_blok_verkoop',
36
+ 'arti_verh_aantal_aantal_vrd',
37
+ 'arti_actief',
38
+ 'arti_bestelcode',
39
+ 'arti_locatie',
40
+ 'arti_prijs_netto_mat',
41
+ 'arti_prijs_vast',
42
+ 'arti_vrij_veld_1',
43
+ 'arti_parameter_1',
44
+ 'arti_vrij_datum_2',
45
+ 'arti_gewicht_per_m',
46
+ 'arti_memo_inkoop_extern',
47
+ 'arti_inkoopofferte',
48
+ 'arti_prijs_gemiddeld_mat',
49
+ 'arti_dienst_verkoop',
50
+ 'arti_oms_2',
51
+ 'arti_levertijd_inkoop',
52
+ 'arti_verh_tijd_eenh_grp',
53
+ 'arti_prijs_netto_uitb',
54
+ 'arti_oppervlakte',
55
+ 'arti_voorraadsysteem',
56
+ 'arti_kostprijssysteem',
57
+ 'arti_niet_autores',
58
+ 'arti_wvp_obv_gvp',
59
+ 'arti_vrij_memo_5',
60
+ 'arti_prijs_inkoop',
61
+ 'arti_levertijd_productie',
62
+ 'arti_verh_gewicht_aantal_grp',
63
+ 'arti_verh_gewicht_eenh_grp',
64
+ 'arti_vrij_veld_2',
65
+ 'arti_prijs_gemiddeld_mach',
66
+ 'arti_lijst_3',
67
+ 'arti_verh_gewicht_aantal_vrd',
68
+ 'arti_parameter_3',
69
+ 'arti_lijst_1',
70
+ 'arti_prijs_vast_mach',
71
+ 'arti_vrij_veld_5',
72
+ 'arti_ict_fact_verkoop',
73
+ 'arti_verh_lengte_aantal_vrd',
74
+ 'arti_backflush',
75
+ 'arti_vrij_datum_3',
76
+ 'arti_vrd_aanvullen_tot',
77
+ 'arti_inkoop_artikel',
78
+ 'arti_verh_rest_aantal_grp',
79
+ 'arti_eenh_vrd',
80
+ 'arti_prijs_vast_uitb',
81
+ 'arti_trans_gewicht',
82
+ 'arti_memo_inkoop_intern',
83
+ 'arti_vrij_memo_3',
84
+ 'arti_prijs_netto',
85
+ 'arti_mat_lengte',
86
+ 'arti_bestelhoeveelheid',
87
+ 'arti_eenh_bestelling',
88
+ 'arti_verh_oppv_eenh_grp',
89
+ 'arti_voorraad_artikel',
90
+ 'arti_backflush_uitval',
91
+ 'arti_lijst_9',
92
+ 'arti_vrij_datum_5',
93
+ 'arti_verh_oppv_aantal_grp',
94
+ 'arti_prijs_gemiddeld_uitb',
95
+ 'arti_prijs_verkoop_eenm',
96
+ 'arti_oms_3',
97
+ 'arti_oms_1',
98
+ ],
99
+ });
@@ -0,0 +1,70 @@
1
+ import { MkgTableInterface } from '../../types';
2
+
3
+ type MkgActionMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
4
+
5
+ export interface MkgActionInterface {
6
+ identifier: string;
7
+
8
+ method: MkgActionMethod;
9
+
10
+ fields: Array<string>;
11
+
12
+ optionalFields?: Array<string>;
13
+
14
+ path?: (inputData: Record<string, unknown>) => string;
15
+ }
16
+
17
+ export class MkgAction implements MkgActionInterface {
18
+ readonly identifier: string;
19
+
20
+ readonly method: MkgActionMethod;
21
+
22
+ readonly fields: Array<string>;
23
+
24
+ readonly optionalFields: Array<string>;
25
+
26
+ readonly path: (inputData: Record<string, unknown>) => string;
27
+
28
+ #table!: MkgTableInterface;
29
+
30
+ constructor(params: MkgActionInterface) {
31
+ this.identifier = params.identifier;
32
+ this.method = params.method;
33
+ this.fields = params.fields;
34
+ this.optionalFields = params.optionalFields ?? [];
35
+ this.path = params.path ?? ((): string => this.#table.identifier);
36
+ }
37
+
38
+ withParentTable(table: MkgTableInterface): MkgAction {
39
+ const action = new MkgAction(this);
40
+ action.#table = table;
41
+
42
+ return action;
43
+ }
44
+
45
+ fieldSet(inputData: Record<string, unknown>): Record<string, unknown> {
46
+ const fieldData = new Map<string, unknown>();
47
+
48
+ this.fields.forEach((field) => {
49
+ // eslint-disable-next-line security/detect-object-injection
50
+ const inputDataValue = inputData[field];
51
+ if (!inputDataValue) {
52
+ throw new Error(
53
+ `Missing required field '${field}' for action '${this.identifier}'`,
54
+ );
55
+ }
56
+
57
+ fieldData.set(field, inputDataValue);
58
+ });
59
+
60
+ this.optionalFields.forEach((field) => {
61
+ // eslint-disable-next-line security/detect-object-injection
62
+ const inputDataValue = inputData[field];
63
+ if (inputDataValue) {
64
+ fieldData.set(field, inputDataValue);
65
+ }
66
+ });
67
+
68
+ return Object.fromEntries(fieldData.entries());
69
+ }
70
+ }
@@ -0,0 +1,57 @@
1
+ import {
2
+ DEFAULT_DATE_FIELD,
3
+ DEFAULT_INTERVAL,
4
+ DEFAULT_NUM_ROWS,
5
+ DEFAULT_ROW_KEY_FIELD,
6
+ MkgTableInterface,
7
+ TableConfig,
8
+ } from '../../types';
9
+
10
+ import { MkgAction } from './action';
11
+
12
+ export class MkgTable implements MkgTableInterface {
13
+ readonly identifier: string;
14
+
15
+ readonly fields: Array<string>;
16
+
17
+ readonly immediate: boolean;
18
+
19
+ readonly interval: number;
20
+
21
+ readonly identifierField: string;
22
+
23
+ readonly dateField: string;
24
+
25
+ readonly rows: number;
26
+
27
+ readonly actions: Array<MkgAction>;
28
+
29
+ constructor(params: MkgTableInterface, actions: Array<MkgAction> = []) {
30
+ this.identifier = params.identifier;
31
+ this.fields = params.fields;
32
+ this.immediate = params.immediate !== false;
33
+ this.interval =
34
+ params.interval ?? (this.fields.length > 0 ? DEFAULT_INTERVAL : 0);
35
+ this.identifierField = params.identifierField ?? DEFAULT_ROW_KEY_FIELD;
36
+ this.dateField = params.dateField ?? DEFAULT_DATE_FIELD;
37
+ this.rows = params.rows ?? DEFAULT_NUM_ROWS;
38
+ this.actions = actions.map((action) => action.withParentTable(this));
39
+ }
40
+
41
+ cloneFromTableConfig(tableConfig: TableConfig | true): MkgTable {
42
+ if (tableConfig === true || tableConfig.fields === undefined) {
43
+ return this;
44
+ }
45
+
46
+ return new MkgTable({
47
+ ...this,
48
+ fields: this.fields.filter((field) =>
49
+ tableConfig.fields?.includes(field),
50
+ ),
51
+ });
52
+ }
53
+
54
+ action(identifier: string): MkgAction | undefined {
55
+ return this.actions.find((a) => a.identifier === identifier);
56
+ }
57
+ }