n8n-nodes-fingrid 0.1.0 → 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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mychel Garzon
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # n8n-nodes-fingrid
2
2
 
3
+ [![Quality gate status](https://sonarcloud.io/api/project_badges/measure?project=MychelGarzon_n8n-nodes-fingrid&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=MychelGarzon_n8n-nodes-fingrid)
4
+
3
5
  n8n community node for [Fingrid Open Data](https://data.fingrid.fi/en) — real-time and historical
4
6
  Finnish power grid and electricity market data (frequency, power system state, electricity shortage
5
7
  status, production/consumption, cross-border transmission, balancing and reserve markets).
@@ -8,11 +10,18 @@ status, production/consumption, cross-border transmission, balancing and reserve
8
10
 
9
11
  ## Data license
10
12
 
11
- Fingrid's data is published under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/).
13
+ Fingrid's data is published under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/legalcode)
14
+ (see also the [human-readable summary](https://creativecommons.org/licenses/by/4.0/)).
12
15
  Attribution required when displaying or republishing the data:
13
16
 
14
17
  > Source Fingrid / data.fingrid.fi, license CC 4.0 BY
15
18
 
19
+ This data is provided "as-is" without warranties of any kind, per the license terms.
20
+
21
+ This node is free and open-source. If you plan to build a commercial product on top of
22
+ Fingrid's data, review [Fingrid's Legal Terms](https://data.fingrid.fi/en) directly, as a
23
+ separate general terms page contains language that may not apply to non-commercial use.
24
+
16
25
  ## Prerequisites
17
26
 
18
27
  You need a free Fingrid Open Data API key:
@@ -20,7 +29,7 @@ You need a free Fingrid Open Data API key:
20
29
  1. Go to https://data.fingrid.fi/en and sign up (email only, no company/business account required)
21
30
  2. Approve the license and terms of use
22
31
  3. You'll receive a personal API key by email immediately
23
- 4. Rate limits: 10 requests/minute, 10,000/day
32
+ 4. Rate limits: 1 request every 2 seconds, 10,000/day
24
33
 
25
34
  ## Resources & operations
26
35
 
@@ -32,8 +41,8 @@ You need a free Fingrid Open Data API key:
32
41
  - Get File — a single dataset file
33
42
  - Get File Data — file-based data within a time range
34
43
  - **Data (multiple datasets)**
35
- - Get Multiple — time series data for several datasets in one call
36
- - Get Updated — data for datasets changed within a recent period
44
+ - Get Many — time series data for several datasets in one call
45
+ - Get Recently Updated — data for datasets changed within a recent period
37
46
  - **System**
38
47
  - Get Active Notifications
39
48
  - Get Health Status
@@ -0,0 +1,9 @@
1
+ import type { IAuthenticateGeneric, ICredentialTestRequest, ICredentialType, INodeProperties } from 'n8n-workflow';
2
+ export declare class FingridApi implements ICredentialType {
3
+ name: string;
4
+ displayName: string;
5
+ documentationUrl: string;
6
+ properties: INodeProperties[];
7
+ authenticate: IAuthenticateGeneric;
8
+ test: ICredentialTestRequest;
9
+ }
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FingridApi = void 0;
4
+ class FingridApi {
5
+ constructor() {
6
+ this.name = 'fingridApi';
7
+ this.displayName = 'Fingrid API';
8
+ this.documentationUrl = 'https://data.fingrid.fi/en/instructions';
9
+ this.properties = [
10
+ {
11
+ displayName: 'API Key',
12
+ name: 'apiKey',
13
+ type: 'string',
14
+ typeOptions: { password: true },
15
+ default: '',
16
+ required: true,
17
+ description: 'Your personal Fingrid Open Data API key. Register for free at https://data.fingrid.fi/en to get one instantly by email.',
18
+ },
19
+ ];
20
+ this.authenticate = {
21
+ type: 'generic',
22
+ properties: {
23
+ headers: {
24
+ 'x-api-key': '={{$credentials.apiKey}}',
25
+ },
26
+ },
27
+ };
28
+ this.test = {
29
+ request: {
30
+ baseURL: 'https://data.fingrid.fi/api',
31
+ url: '/health',
32
+ method: 'GET',
33
+ },
34
+ };
35
+ }
36
+ }
37
+ exports.FingridApi = FingridApi;
@@ -0,0 +1,10 @@
1
+ import type { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription, ILoadOptionsFunctions, INodeListSearchResult } from "n8n-workflow";
2
+ export declare class Fingrid implements INodeType {
3
+ description: INodeTypeDescription;
4
+ methods: {
5
+ listSearch: {
6
+ searchDatasets(this: ILoadOptionsFunctions, filter?: string): Promise<INodeListSearchResult>;
7
+ };
8
+ };
9
+ execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
10
+ }
@@ -0,0 +1,432 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Fingrid = void 0;
4
+ const GenericFunctions_1 = require("./GenericFunctions");
5
+ class Fingrid {
6
+ constructor() {
7
+ this.description = {
8
+ displayName: "Fingrid",
9
+ name: "fingrid",
10
+ icon: "file:fingrid.svg",
11
+ group: ["transform"],
12
+ version: 1,
13
+ subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
14
+ description: "Read Finnish power grid and electricity market data from Fingrid Open Data",
15
+ defaults: {
16
+ name: "Fingrid",
17
+ },
18
+ inputs: ["main"],
19
+ outputs: ["main"],
20
+ credentials: [
21
+ {
22
+ name: "fingridApi",
23
+ required: true,
24
+ },
25
+ ],
26
+ properties: [
27
+ {
28
+ displayName: "Resource",
29
+ name: "resource",
30
+ type: "options",
31
+ noDataExpression: true,
32
+ options: [
33
+ { name: "Dataset", value: "dataset" },
34
+ { name: "Data (Multiple Datasets)", value: "data" },
35
+ { name: "System", value: "system" },
36
+ ],
37
+ default: "dataset",
38
+ },
39
+ {
40
+ displayName: "Operation",
41
+ name: "operation",
42
+ type: "options",
43
+ noDataExpression: true,
44
+ displayOptions: { show: { resource: ["dataset"] } },
45
+ options: [
46
+ {
47
+ name: "Get",
48
+ value: "get",
49
+ description: "Get metadata for single dataset",
50
+ action: "Get dataset",
51
+ },
52
+ {
53
+ name: "Get Data",
54
+ value: "getData",
55
+ description: "Get time series data for dataset within a time range",
56
+ action: "Get dataset data",
57
+ },
58
+ {
59
+ name: "Get File",
60
+ value: "getFile",
61
+ description: "Get single dataset file",
62
+ action: "Get dataset file",
63
+ },
64
+ {
65
+ name: "Get File Data",
66
+ value: "getFileData",
67
+ description: "Get file-based data for dataset within a time range",
68
+ action: "Get dataset file data",
69
+ },
70
+ {
71
+ name: "Get Latest Data",
72
+ value: "getLatestData",
73
+ description: "Get the most recent data point for dataset",
74
+ action: "Get latest dataset data",
75
+ },
76
+ {
77
+ name: "Search",
78
+ value: "search",
79
+ description: "Search / list all public datasets",
80
+ action: "Search datasets",
81
+ },
82
+ ],
83
+ default: "get",
84
+ },
85
+ {
86
+ displayName: "Operation",
87
+ name: "operation",
88
+ type: "options",
89
+ noDataExpression: true,
90
+ displayOptions: { show: { resource: ["system"] } },
91
+ options: [
92
+ {
93
+ name: "Get Active Notifications",
94
+ value: "getActiveNotifications",
95
+ description: "Get currently active service notifications",
96
+ action: "Get active system notifications",
97
+ },
98
+ {
99
+ name: "Get Health Status",
100
+ value: "getHealthStatus",
101
+ description: "Get the health status of Fingrid Open Data services",
102
+ action: "Get system health status",
103
+ },
104
+ ],
105
+ default: "getHealthStatus",
106
+ },
107
+ {
108
+ displayName: "Operation",
109
+ name: "operation",
110
+ type: "options",
111
+ noDataExpression: true,
112
+ displayOptions: { show: { resource: ["data"] } },
113
+ options: [
114
+ {
115
+ name: "Get Many",
116
+ value: "getMany",
117
+ description: "Get time series data for several datasets at once",
118
+ action: "Get data for many datasets",
119
+ },
120
+ {
121
+ name: "Get Recently Updated",
122
+ value: "getUpdated",
123
+ description: "Get data for datasets that changed within a recent period",
124
+ action: "Get recently updated data",
125
+ },
126
+ ],
127
+ default: "getMany",
128
+ },
129
+ {
130
+ displayName: "Dataset ID",
131
+ name: "datasetId",
132
+ type: "resourceLocator",
133
+ default: { mode: "list", value: "" },
134
+ required: true,
135
+ modes: [
136
+ {
137
+ displayName: "From List",
138
+ name: "list",
139
+ type: "list",
140
+ placeholder: "Select a Dataset...",
141
+ typeOptions: {
142
+ searchListMethod: "searchDatasets",
143
+ searchable: true,
144
+ },
145
+ },
146
+ {
147
+ displayName: "By ID",
148
+ name: "id",
149
+ type: "string",
150
+ placeholder: "e.g. 74",
151
+ },
152
+ ],
153
+ displayOptions: {
154
+ show: {
155
+ resource: ["dataset"],
156
+ operation: [
157
+ "get",
158
+ "getData",
159
+ "getLatestData",
160
+ "getFile",
161
+ "getFileData",
162
+ ],
163
+ },
164
+ },
165
+ description: "The numeric ID of the Fingrid dataset (find it via the 'Search' operation, or in the dataset URL on data.fingrid.fi)",
166
+ },
167
+ {
168
+ displayName: "Search Text",
169
+ name: "search",
170
+ type: "string",
171
+ placeholder: "e.g. windPower",
172
+ default: "",
173
+ displayOptions: {
174
+ show: { resource: ["dataset"], operation: ["search"] },
175
+ },
176
+ description: "Free-text search across dataset names and descriptions. Leave empty to list all datasets.",
177
+ },
178
+ {
179
+ displayName: "Order By",
180
+ name: "orderBy",
181
+ type: "string",
182
+ default: "id",
183
+ placeholder: "e.g. ID",
184
+ displayOptions: {
185
+ show: { resource: ["dataset"], operation: ["search"] },
186
+ },
187
+ description: "Field to sort results by when no 'Search Text' is given",
188
+ },
189
+ {
190
+ displayName: "File ID",
191
+ name: "fileId",
192
+ type: "string",
193
+ placeholder: "e.g. 12345",
194
+ default: "",
195
+ required: true,
196
+ displayOptions: {
197
+ show: { resource: ["dataset"], operation: ["getFile"] },
198
+ },
199
+ description: "The unique identifier of the file to retrieve",
200
+ },
201
+ {
202
+ displayName: "Start Time",
203
+ name: "startTime",
204
+ type: "dateTime",
205
+ default: "",
206
+ required: true,
207
+ displayOptions: {
208
+ show: {
209
+ operation: ["getData", "getFileData", "getMany"],
210
+ },
211
+ },
212
+ description: "Start of the time range to fetch, in ISO 8601 format",
213
+ },
214
+ {
215
+ displayName: "End Time",
216
+ name: "endTime",
217
+ type: "dateTime",
218
+ default: "",
219
+ required: true,
220
+ displayOptions: {
221
+ show: {
222
+ operation: ["getData", "getFileData", "getMany"],
223
+ },
224
+ },
225
+ description: "End of the time range to fetch, in ISO 8601 format",
226
+ },
227
+ {
228
+ displayName: "Dataset IDs",
229
+ name: "datasets",
230
+ type: "string",
231
+ placeholder: "e.g. 74,75,192",
232
+ default: "",
233
+ required: true,
234
+ displayOptions: {
235
+ show: {
236
+ resource: ["data"],
237
+ operation: ["getMany", "getUpdated"],
238
+ },
239
+ },
240
+ description: "Comma-separated list of dataset IDs, e.g. 74,75,192",
241
+ },
242
+ {
243
+ displayName: "Days",
244
+ name: "days",
245
+ type: "number",
246
+ default: 14,
247
+ displayOptions: {
248
+ show: { resource: ["data"], operation: ["getUpdated"] },
249
+ },
250
+ description: "How many days back to check for updated data",
251
+ },
252
+ {
253
+ displayName: "Return All",
254
+ name: "returnAll",
255
+ type: "boolean",
256
+ default: false,
257
+ displayOptions: {
258
+ show: {
259
+ operation: [
260
+ "search",
261
+ "getData",
262
+ "getFileData",
263
+ "getMany",
264
+ "getUpdated",
265
+ ],
266
+ },
267
+ },
268
+ description: "Whether to return all results or only up to a given limit",
269
+ },
270
+ {
271
+ displayName: "Limit",
272
+ name: "limit",
273
+ type: "number",
274
+ typeOptions: { minValue: 1 },
275
+ default: 50,
276
+ displayOptions: {
277
+ show: {
278
+ operation: [
279
+ "search",
280
+ "getData",
281
+ "getFileData",
282
+ "getMany",
283
+ "getUpdated",
284
+ ],
285
+ returnAll: [false],
286
+ },
287
+ },
288
+ description: "Max number of results to return",
289
+ },
290
+ {
291
+ displayName: "Simplify",
292
+ name: "simplify",
293
+ type: "boolean",
294
+ default: true,
295
+ displayOptions: {
296
+ show: {
297
+ operation: [
298
+ "search",
299
+ "getData",
300
+ "getFileData",
301
+ "getMany",
302
+ "getUpdated",
303
+ ],
304
+ },
305
+ },
306
+ description: "Whether to return a streamlined version of the data without heavy metadata",
307
+ },
308
+ {
309
+ displayName: "Additional Options",
310
+ name: "additionalOptions",
311
+ type: "collection",
312
+ placeholder: "Add Option",
313
+ default: {},
314
+ displayOptions: {
315
+ show: {
316
+ operation: [
317
+ "search",
318
+ "getData",
319
+ "getFileData",
320
+ "getMany",
321
+ "getUpdated",
322
+ ],
323
+ },
324
+ },
325
+ options: [
326
+ {
327
+ displayName: "Page Size",
328
+ name: "pageSize",
329
+ type: "number",
330
+ default: 20000,
331
+ description: "Rows to request per page from the API when 'Return All' is on. Fingrid's own client defaults range from 250 to 20000 depending on endpoint.",
332
+ },
333
+ {
334
+ displayName: "Sort By",
335
+ name: "sortBy",
336
+ type: "string",
337
+ placeholder: "e.g. startTime",
338
+ default: "startTime",
339
+ description: "Field to sort the returned rows by",
340
+ },
341
+ {
342
+ displayName: "Sort Order",
343
+ name: "sortOrder",
344
+ type: "options",
345
+ options: [
346
+ { name: "Ascending", value: "asc" },
347
+ { name: "Descending", value: "desc" },
348
+ ],
349
+ default: "asc",
350
+ description: "Direction to sort the results",
351
+ },
352
+ ],
353
+ },
354
+ ],
355
+ };
356
+ this.methods = {
357
+ listSearch: {
358
+ async searchDatasets(filter) {
359
+ const qs = { pageSize: 50 };
360
+ if (filter) {
361
+ qs.search = filter;
362
+ }
363
+ const responseData = await this.helpers.httpRequestWithAuthentication.call(this, "fingridApi", {
364
+ method: "GET",
365
+ url: "https://data.fingrid.fi/api/datasets",
366
+ qs,
367
+ json: true,
368
+ });
369
+ const datasets = Array.isArray(responseData.data)
370
+ ? responseData.data
371
+ : [];
372
+ return {
373
+ results: datasets.map((dataset) => ({
374
+ name: dataset.nameEn || dataset.nameFi || `Dataset ${dataset.id}`,
375
+ value: dataset.id,
376
+ })),
377
+ };
378
+ },
379
+ },
380
+ };
381
+ }
382
+ async execute() {
383
+ const items = this.getInputData();
384
+ const returnData = [];
385
+ const resource = this.getNodeParameter("resource", 0);
386
+ const operation = this.getNodeParameter("operation", 0);
387
+ for (let i = 0; i < items.length; i++) {
388
+ try {
389
+ const { endpoint, qs } = GenericFunctions_1.buildRequestParams.call(this, resource, operation, i);
390
+ let results = GenericFunctions_1.PAGINATED_OPERATIONS.includes(operation)
391
+ ? await GenericFunctions_1.fetchPaginated.call(this, endpoint, qs, i)
392
+ : await GenericFunctions_1.fetchSingle.call(this, endpoint, qs, i);
393
+ const SIMPLIFIABLE_OPERATIONS = [
394
+ "search",
395
+ "getData",
396
+ "getFileData",
397
+ "getMany",
398
+ "getUpdated",
399
+ ];
400
+ const simplify = SIMPLIFIABLE_OPERATIONS.includes(operation)
401
+ ? this.getNodeParameter("simplify", i, true)
402
+ : false;
403
+ if (simplify) {
404
+ results = results.map((item) => {
405
+ return {
406
+ value: item.value,
407
+ startTime: item.startTime,
408
+ endTime: item.endTime,
409
+ datasetId: item.datasetId,
410
+ };
411
+ });
412
+ }
413
+ returnData.push(...results.map((item) => ({
414
+ json: item,
415
+ pairedItem: { item: i },
416
+ })));
417
+ }
418
+ catch (error) {
419
+ if (this.continueOnFail()) {
420
+ returnData.push({
421
+ json: { error: error.message },
422
+ pairedItem: { item: i },
423
+ });
424
+ continue;
425
+ }
426
+ throw error;
427
+ }
428
+ }
429
+ return [returnData];
430
+ }
431
+ }
432
+ exports.Fingrid = Fingrid;
@@ -0,0 +1,11 @@
1
+ import type { IDataObject, IExecuteFunctions } from "n8n-workflow";
2
+ export declare const BASE_URL = "https://data.fingrid.fi/api";
3
+ export declare const PAGINATED_OPERATIONS: string[];
4
+ export declare function respectRateLimit(): Promise<void>;
5
+ export interface RequestParams {
6
+ endpoint: string;
7
+ qs: IDataObject;
8
+ }
9
+ export declare function buildRequestParams(this: IExecuteFunctions, resource: string, operation: string, i: number): RequestParams;
10
+ export declare function fetchPaginated(this: IExecuteFunctions, endpoint: string, qs: IDataObject, i: number): Promise<IDataObject[]>;
11
+ export declare function fetchSingle(this: IExecuteFunctions, endpoint: string, qs: IDataObject, i: number): Promise<IDataObject[]>;
@@ -0,0 +1,187 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PAGINATED_OPERATIONS = exports.BASE_URL = void 0;
4
+ exports.respectRateLimit = respectRateLimit;
5
+ exports.buildRequestParams = buildRequestParams;
6
+ exports.fetchPaginated = fetchPaginated;
7
+ exports.fetchSingle = fetchSingle;
8
+ const n8n_workflow_1 = require("n8n-workflow");
9
+ exports.BASE_URL = "https://data.fingrid.fi/api";
10
+ exports.PAGINATED_OPERATIONS = [
11
+ "search",
12
+ "getData",
13
+ "getFileData",
14
+ "getMany",
15
+ "getUpdated",
16
+ ];
17
+ const MIN_REQUEST_INTERVAL_MS = 2200;
18
+ let lastRequestTimestamp = 0;
19
+ async function sleep(ms) {
20
+ return new Promise((resolve) => setTimeout(resolve, ms));
21
+ }
22
+ async function respectRateLimit() {
23
+ const elapsed = Date.now() - lastRequestTimestamp;
24
+ if (elapsed < MIN_REQUEST_INTERVAL_MS) {
25
+ await sleep(MIN_REQUEST_INTERVAL_MS - elapsed);
26
+ }
27
+ lastRequestTimestamp = Date.now();
28
+ }
29
+ async function requestWithRateLimitHandling(options, i) {
30
+ var _a, _b;
31
+ try {
32
+ return await this.helpers.httpRequestWithAuthentication.call(this, "fingridApi", options);
33
+ }
34
+ catch (error) {
35
+ const statusCode = (_a = error.statusCode) !== null && _a !== void 0 ? _a : (_b = error.response) === null || _b === void 0 ? void 0 : _b.statusCode;
36
+ if (statusCode === 429) {
37
+ throw new n8n_workflow_1.NodeApiError(this.getNode(), error, {
38
+ message: "Fingrid API rate limit exceeded",
39
+ description: "Fingrid allows 1 request every 2 seconds per API key. Wait a moment and try again, or reduce 'Page Size' if using 'Return All'.",
40
+ itemIndex: i,
41
+ });
42
+ }
43
+ throw error;
44
+ }
45
+ }
46
+ function buildDatasetRequest(operation, i) {
47
+ const qs = {};
48
+ if (operation === "get") {
49
+ const datasetId = this.getNodeParameter("datasetId", i);
50
+ return { endpoint: `/datasets/${datasetId}`, qs };
51
+ }
52
+ if (operation === "search") {
53
+ const search = this.getNodeParameter("search", i);
54
+ const orderBy = this.getNodeParameter("orderBy", i);
55
+ if (search)
56
+ qs.search = search;
57
+ if (orderBy)
58
+ qs.orderBy = orderBy;
59
+ Object.assign(qs, this.getNodeParameter("additionalOptions", i, {}));
60
+ return { endpoint: "/datasets", qs };
61
+ }
62
+ if (operation === "getData") {
63
+ const datasetId = this.getNodeParameter("datasetId", i);
64
+ qs.startTime = this.getNodeParameter("startTime", i);
65
+ qs.endTime = this.getNodeParameter("endTime", i);
66
+ Object.assign(qs, this.getNodeParameter("additionalOptions", i, {}));
67
+ return { endpoint: `/datasets/${datasetId}/data`, qs };
68
+ }
69
+ if (operation === "getLatestData") {
70
+ const datasetId = this.getNodeParameter("datasetId", i);
71
+ return { endpoint: `/datasets/${datasetId}/data/latest`, qs };
72
+ }
73
+ if (operation === "getFile") {
74
+ const datasetId = this.getNodeParameter("datasetId", i);
75
+ const fileId = this.getNodeParameter("fileId", i);
76
+ return { endpoint: `/datasets/${datasetId}/files/${fileId}`, qs };
77
+ }
78
+ if (operation === "getFileData") {
79
+ const datasetId = this.getNodeParameter("datasetId", i);
80
+ qs.startTime = this.getNodeParameter("startTime", i);
81
+ qs.endTime = this.getNodeParameter("endTime", i);
82
+ Object.assign(qs, this.getNodeParameter("additionalOptions", i, {}));
83
+ return { endpoint: `/datasets/${datasetId}/files`, qs };
84
+ }
85
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `The dataset operation "${operation}" is not recognized.`, {
86
+ itemIndex: i,
87
+ message: `The dataset 'Operation' "${operation}" is not recognized.`,
88
+ description: "Select a valid 'Operation' from the dropdown menu to continue.",
89
+ });
90
+ }
91
+ function buildDataRequest(operation, i) {
92
+ const datasetsRaw = this.getNodeParameter("datasets", i);
93
+ const qs = {
94
+ datasets: datasetsRaw
95
+ .split(",")
96
+ .map((d) => d.trim())
97
+ .filter((d) => d.length > 0)
98
+ .join(","),
99
+ };
100
+ if (operation === "getMany") {
101
+ qs.startTime = this.getNodeParameter("startTime", i);
102
+ qs.endTime = this.getNodeParameter("endTime", i);
103
+ Object.assign(qs, this.getNodeParameter("additionalOptions", i, {}));
104
+ return { endpoint: "/data", qs };
105
+ }
106
+ if (operation === "getUpdated") {
107
+ qs.days = this.getNodeParameter("days", i);
108
+ Object.assign(qs, this.getNodeParameter("additionalOptions", i, {}));
109
+ return { endpoint: "/data/updates", qs };
110
+ }
111
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `The data operation "${operation}" is not recognized.`, {
112
+ itemIndex: i,
113
+ message: `The data 'Operation' "${operation}" is not recognized.`,
114
+ description: "Select a valid 'Operation' from the dropdown menu to continue.",
115
+ });
116
+ }
117
+ function buildSystemRequest(operation, i) {
118
+ if (operation === "getActiveNotifications") {
119
+ return { endpoint: "/notifications/active", qs: {} };
120
+ }
121
+ if (operation === "getHealthStatus") {
122
+ return { endpoint: "/health", qs: {} };
123
+ }
124
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `The system operation "${operation}" is not recognized.`, {
125
+ itemIndex: i,
126
+ message: `The system 'Operation' "${operation}" is not recognized.`,
127
+ description: "Select a valid 'Operation' from the dropdown menu to continue.",
128
+ });
129
+ }
130
+ function buildRequestParams(resource, operation, i) {
131
+ if (resource === "dataset")
132
+ return buildDatasetRequest.call(this, operation, i);
133
+ if (resource === "data")
134
+ return buildDataRequest.call(this, operation, i);
135
+ if (resource === "system")
136
+ return buildSystemRequest.call(this, operation, i);
137
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `The 'Resource' "${resource}" is not recognized.`, {
138
+ itemIndex: i,
139
+ description: "Select a valid 'Resource' (Dataset, Data, or System) from the dropdown menu to continue.",
140
+ });
141
+ }
142
+ async function fetchPaginated(endpoint, qs, i) {
143
+ var _a;
144
+ const returnAll = this.getNodeParameter("returnAll", i, false);
145
+ const limit = this.getNodeParameter("limit", i, 50);
146
+ const pageSize = qs.pageSize || 20000;
147
+ const collected = [];
148
+ let page = 1;
149
+ let lastPage = 1;
150
+ do {
151
+ await respectRateLimit();
152
+ const pageQs = { ...qs, page, pageSize };
153
+ const response = await requestWithRateLimitHandling.call(this, {
154
+ method: "GET",
155
+ url: `${exports.BASE_URL}${endpoint}`,
156
+ qs: pageQs,
157
+ json: true,
158
+ }, i);
159
+ const pageData = Array.isArray(response)
160
+ ? response
161
+ : ((_a = response.data) !== null && _a !== void 0 ? _a : []);
162
+ collected.push(...pageData);
163
+ const pagination = response.pagination;
164
+ lastPage = pagination ? pagination.lastPage : 1;
165
+ if (!returnAll && collected.length >= limit)
166
+ break;
167
+ page += 1;
168
+ } while (returnAll && page <= lastPage);
169
+ return returnAll ? collected : collected.slice(0, limit);
170
+ }
171
+ async function fetchSingle(endpoint, qs, i) {
172
+ const responseData = await requestWithRateLimitHandling.call(this, {
173
+ method: "GET",
174
+ url: `${exports.BASE_URL}${endpoint}`,
175
+ qs,
176
+ json: true,
177
+ }, i);
178
+ if (Array.isArray(responseData)) {
179
+ return responseData;
180
+ }
181
+ if (responseData &&
182
+ typeof responseData === "object" &&
183
+ Array.isArray(responseData.data)) {
184
+ return responseData.data;
185
+ }
186
+ return [responseData];
187
+ }
@@ -0,0 +1,5 @@
1
+ <svg width="60" height="60" viewBox="0 0 60 60" xmlns="http://www.w3.org/2000/svg">
2
+ <rect width="60" height="60" rx="8" fill="#E30613"/>
3
+ <path d="M15 42 L15 18 L32 18 L32 24 L21 24 L21 28 L30 28 L30 34 L21 34 L21 42 Z" fill="white"/>
4
+ <path d="M38 18 L44 18 L44 42 L38 42 Z" fill="white"/>
5
+ </svg>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-fingrid",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "n8n community node for Fingrid Open Data — Finnish power grid and electricity market data",
5
5
  "keywords": [
6
6
  "n8n-community-node-package",
@@ -19,17 +19,20 @@
19
19
  "type": "git",
20
20
  "url": "git+https://github.com/MychelGarzon/n8n-nodes-fingrid.git"
21
21
  },
22
- "main": "index.js",
23
22
  "scripts": {
24
23
  "build": "npx rimraf dist && tsc && npx gulp build:icons",
25
24
  "dev": "tsc --watch",
26
25
  "format": "prettier nodes credentials --write",
27
26
  "lint": "eslint nodes credentials package.json",
28
27
  "lintfix": "eslint nodes credentials package.json --fix",
29
- "prepublishOnly": "echo skip"
28
+ "lint:n8n": "n8n-node lint",
29
+ "prepublishOnly": "npm run build && npm run lint -c .eslintrc.prepublish.js nodes credentials package.json",
30
+ "release": "n8n-node release",
31
+ "test": "jest "
30
32
  },
31
33
  "files": [
32
- "dist"
34
+ "dist",
35
+ "!dist/**/test/**"
33
36
  ],
34
37
  "n8n": {
35
38
  "n8nNodesApiVersion": 1,
@@ -41,12 +44,17 @@
41
44
  ]
42
45
  },
43
46
  "devDependencies": {
47
+ "@n8n/node-cli": "^0.44.5",
48
+ "@types/jest": "^30.0.0",
44
49
  "@typescript-eslint/parser": "~8.32.0",
50
+ "eslint": "8.57.0",
45
51
  "eslint-plugin-n8n-nodes-base": "^1.16.3",
46
52
  "gulp": "^5.0.0",
53
+ "jest": "^30.4.2",
47
54
  "n8n-core": "^1.14.1",
48
55
  "n8n-workflow": "^1.82.0",
49
56
  "prettier": "^3.5.3",
57
+ "ts-jest": "^29.4.12",
50
58
  "typescript": "^5.8.2"
51
59
  },
52
60
  "peerDependencies": {