n8n-nodes-pdfkraft 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.
|
@@ -1,5 +1,10 @@
|
|
|
1
|
-
import type { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
|
|
1
|
+
import type { IExecuteFunctions, ILoadOptionsFunctions, INodeExecutionData, INodePropertyOptions, INodeType, INodeTypeDescription } from 'n8n-workflow';
|
|
2
2
|
export declare class PDFKraft implements INodeType {
|
|
3
3
|
description: INodeTypeDescription;
|
|
4
|
+
methods: {
|
|
5
|
+
loadOptions: {
|
|
6
|
+
getTemplates(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
|
|
7
|
+
};
|
|
8
|
+
};
|
|
4
9
|
execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
|
|
5
10
|
}
|
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.PDFKraft = void 0;
|
|
4
4
|
const n8n_workflow_1 = require("n8n-workflow");
|
|
5
|
+
const MIME_TYPES = {
|
|
6
|
+
pdf: 'application/pdf',
|
|
7
|
+
png: 'image/png',
|
|
8
|
+
jpg: 'image/jpeg',
|
|
9
|
+
webp: 'image/webp',
|
|
10
|
+
};
|
|
5
11
|
class PDFKraft {
|
|
6
12
|
constructor() {
|
|
7
13
|
this.description = {
|
|
@@ -23,58 +29,172 @@ class PDFKraft {
|
|
|
23
29
|
type: 'options',
|
|
24
30
|
noDataExpression: true,
|
|
25
31
|
options: [
|
|
26
|
-
{
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
32
|
+
{
|
|
33
|
+
name: 'Generate Document',
|
|
34
|
+
value: 'generateDocument',
|
|
35
|
+
description: 'Queue a document for generation and return immediately with a document ID',
|
|
36
|
+
action: 'Generate a document',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'Generate Document and Wait',
|
|
40
|
+
value: 'generateAndWait',
|
|
41
|
+
description: 'Generate a document and poll until it is complete — returns the final result with download URL',
|
|
42
|
+
action: 'Generate a document and wait for result',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: 'Get Document',
|
|
46
|
+
value: 'getDocument',
|
|
47
|
+
description: 'Get the status and download URL of a generated document',
|
|
48
|
+
action: 'Get a document',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: 'Download Document',
|
|
52
|
+
value: 'downloadDocument',
|
|
53
|
+
description: 'Download a completed document as binary data',
|
|
54
|
+
action: 'Download a document',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: 'List Documents',
|
|
58
|
+
value: 'listDocuments',
|
|
59
|
+
description: 'List recently generated documents for your account',
|
|
60
|
+
action: 'List documents',
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: 'List Templates',
|
|
64
|
+
value: 'listTemplates',
|
|
65
|
+
description: 'List all published templates for your account',
|
|
66
|
+
action: 'List templates',
|
|
67
|
+
},
|
|
31
68
|
],
|
|
32
|
-
default: '
|
|
69
|
+
default: 'generateAndWait',
|
|
33
70
|
},
|
|
34
|
-
//
|
|
71
|
+
// ── Template picker ──────────────────────────────────────────────────
|
|
35
72
|
{
|
|
36
|
-
displayName: 'Template
|
|
73
|
+
displayName: 'Template',
|
|
37
74
|
name: 'templateId',
|
|
38
|
-
type: '
|
|
75
|
+
type: 'options',
|
|
76
|
+
typeOptions: { loadOptionsMethod: 'getTemplates' },
|
|
39
77
|
default: '',
|
|
40
78
|
required: true,
|
|
41
|
-
displayOptions: { show: { operation: ['generateDocument', '
|
|
42
|
-
description: 'The
|
|
79
|
+
displayOptions: { show: { operation: ['generateDocument', 'generateAndWait'] } },
|
|
80
|
+
description: 'The template to generate from. Only published templates appear. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
|
|
43
81
|
},
|
|
82
|
+
// ── Generate fields ──────────────────────────────────────────────────
|
|
44
83
|
{
|
|
45
84
|
displayName: 'Payload',
|
|
46
85
|
name: 'payload',
|
|
47
86
|
type: 'json',
|
|
48
87
|
default: '{}',
|
|
49
|
-
displayOptions: { show: { operation: ['generateDocument', '
|
|
50
|
-
description: 'JSON
|
|
88
|
+
displayOptions: { show: { operation: ['generateDocument', 'generateAndWait'] } },
|
|
89
|
+
description: 'JSON object with variables to merge into the template. Use Liquid syntax in your template: {{ variable_name }}.',
|
|
90
|
+
hint: 'Tip: use n8n expressions to pass data from previous nodes, e.g. <code>{{ $json.customer_name }}</code>',
|
|
51
91
|
},
|
|
52
92
|
{
|
|
53
93
|
displayName: 'Filename',
|
|
54
94
|
name: 'filename',
|
|
55
95
|
type: 'string',
|
|
56
96
|
default: '',
|
|
57
|
-
|
|
58
|
-
|
|
97
|
+
placeholder: 'invoice-{{order_id}}.pdf',
|
|
98
|
+
displayOptions: { show: { operation: ['generateDocument', 'generateAndWait'] } },
|
|
99
|
+
description: 'Output filename. Supports {{variable}} placeholders using values from the payload. Leave blank to use the template default.',
|
|
59
100
|
},
|
|
60
101
|
{
|
|
61
|
-
displayName: '
|
|
62
|
-
name: '
|
|
63
|
-
type: '
|
|
64
|
-
|
|
65
|
-
|
|
102
|
+
displayName: 'Additional Options',
|
|
103
|
+
name: 'additionalOptions',
|
|
104
|
+
type: 'collection',
|
|
105
|
+
placeholder: 'Add option',
|
|
106
|
+
default: {},
|
|
107
|
+
displayOptions: { show: { operation: ['generateDocument', 'generateAndWait'] } },
|
|
108
|
+
options: [
|
|
109
|
+
{
|
|
110
|
+
displayName: 'Meta',
|
|
111
|
+
name: 'meta',
|
|
112
|
+
type: 'json',
|
|
113
|
+
default: '{}',
|
|
114
|
+
description: 'Arbitrary metadata to attach to the document (returned in webhook payloads and API responses)',
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
},
|
|
118
|
+
// ── Generate and Wait specific ───────────────────────────────────────
|
|
119
|
+
{
|
|
120
|
+
displayName: 'Timeout (Seconds)',
|
|
121
|
+
name: 'timeoutSeconds',
|
|
122
|
+
type: 'number',
|
|
123
|
+
default: 60,
|
|
124
|
+
typeOptions: { minValue: 5, maxValue: 300 },
|
|
125
|
+
displayOptions: { show: { operation: ['generateAndWait'] } },
|
|
126
|
+
description: 'How long to wait for the document to complete before failing',
|
|
66
127
|
},
|
|
67
|
-
//
|
|
128
|
+
// ── Document ID fields ───────────────────────────────────────────────
|
|
68
129
|
{
|
|
69
130
|
displayName: 'Document ID',
|
|
70
131
|
name: 'documentId',
|
|
71
132
|
type: 'string',
|
|
72
133
|
default: '',
|
|
73
134
|
required: true,
|
|
135
|
+
placeholder: 'b8f63e36-cad3-4e57-9058-8d7e85824a4d',
|
|
74
136
|
displayOptions: { show: { operation: ['getDocument', 'downloadDocument'] } },
|
|
137
|
+
description: 'The document ID returned by the Generate Document operation',
|
|
138
|
+
},
|
|
139
|
+
// ── Download options ─────────────────────────────────────────────────
|
|
140
|
+
{
|
|
141
|
+
displayName: 'Output Field Name',
|
|
142
|
+
name: 'binaryPropertyName',
|
|
143
|
+
type: 'string',
|
|
144
|
+
default: 'data',
|
|
145
|
+
displayOptions: { show: { operation: ['downloadDocument'] } },
|
|
146
|
+
description: 'Name of the output field to put the binary file data into',
|
|
147
|
+
},
|
|
148
|
+
// ── List documents options ───────────────────────────────────────────
|
|
149
|
+
{
|
|
150
|
+
displayName: 'Status Filter',
|
|
151
|
+
name: 'statusFilter',
|
|
152
|
+
type: 'options',
|
|
153
|
+
options: [
|
|
154
|
+
{ name: 'All', value: '' },
|
|
155
|
+
{ name: 'Success', value: 'success' },
|
|
156
|
+
{ name: 'Failure', value: 'failure' },
|
|
157
|
+
{ name: 'Pending', value: 'pending' },
|
|
158
|
+
],
|
|
159
|
+
default: '',
|
|
160
|
+
displayOptions: { show: { operation: ['listDocuments'] } },
|
|
161
|
+
description: 'Filter documents by status',
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
displayName: 'Limit',
|
|
165
|
+
name: 'limit',
|
|
166
|
+
type: 'number',
|
|
167
|
+
default: 20,
|
|
168
|
+
typeOptions: { minValue: 1, maxValue: 100 },
|
|
169
|
+
displayOptions: { show: { operation: ['listDocuments'] } },
|
|
170
|
+
description: 'Maximum number of documents to return',
|
|
75
171
|
},
|
|
76
172
|
],
|
|
77
173
|
};
|
|
174
|
+
this.methods = {
|
|
175
|
+
loadOptions: {
|
|
176
|
+
async getTemplates() {
|
|
177
|
+
const credentials = await this.getCredentials('pdfKraftApi');
|
|
178
|
+
const baseUrl = credentials.baseUrl.replace(/\/$/, '');
|
|
179
|
+
const apiKey = credentials.apiKey;
|
|
180
|
+
const response = await this.helpers.request({
|
|
181
|
+
method: 'GET',
|
|
182
|
+
url: `${baseUrl}/document_template_cards`,
|
|
183
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
184
|
+
json: true,
|
|
185
|
+
});
|
|
186
|
+
const templates = (response.data ?? []).filter(t => t.published_at);
|
|
187
|
+
if (templates.length === 0) {
|
|
188
|
+
return [{ name: 'No published templates found — publish one in PDFKraft first', value: '' }];
|
|
189
|
+
}
|
|
190
|
+
return templates.map(t => ({
|
|
191
|
+
name: `${t.name} (${t.output_type.toUpperCase()})`,
|
|
192
|
+
value: t.id,
|
|
193
|
+
description: t.identifier,
|
|
194
|
+
}));
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
};
|
|
78
198
|
}
|
|
79
199
|
async execute() {
|
|
80
200
|
const items = this.getInputData();
|
|
@@ -82,32 +202,75 @@ class PDFKraft {
|
|
|
82
202
|
const credentials = await this.getCredentials('pdfKraftApi');
|
|
83
203
|
const baseUrl = credentials.baseUrl.replace(/\/$/, '');
|
|
84
204
|
const apiKey = credentials.apiKey;
|
|
85
|
-
const headers = {
|
|
86
|
-
Authorization: `Bearer ${apiKey}`,
|
|
87
|
-
'Content-Type': 'application/json',
|
|
88
|
-
};
|
|
205
|
+
const headers = { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json' };
|
|
89
206
|
for (let i = 0; i < items.length; i++) {
|
|
90
207
|
const operation = this.getNodeParameter('operation', i);
|
|
91
|
-
|
|
208
|
+
// ── Generate Document (fire and forget) ────────────────────────────
|
|
209
|
+
if (operation === 'generateDocument') {
|
|
92
210
|
const templateId = this.getNodeParameter('templateId', i);
|
|
93
211
|
const payloadRaw = this.getNodeParameter('payload', i);
|
|
94
212
|
const filename = this.getNodeParameter('filename', i);
|
|
95
|
-
const
|
|
213
|
+
const additionalOptions = this.getNodeParameter('additionalOptions', i);
|
|
96
214
|
const payload = typeof payloadRaw === 'string' ? JSON.parse(payloadRaw) : payloadRaw;
|
|
97
|
-
const meta =
|
|
215
|
+
const meta = additionalOptions.meta
|
|
216
|
+
? (typeof additionalOptions.meta === 'string' ? JSON.parse(additionalOptions.meta) : additionalOptions.meta)
|
|
217
|
+
: {};
|
|
98
218
|
const body = { document_template_id: templateId, payload, meta };
|
|
99
219
|
if (filename)
|
|
100
220
|
body.filename = filename;
|
|
101
|
-
const endpoint = operation === 'generateDocumentSync' ? '/documents/sync' : '/documents';
|
|
102
221
|
const response = await this.helpers.request({
|
|
103
222
|
method: 'POST',
|
|
104
|
-
url: `${baseUrl}
|
|
223
|
+
url: `${baseUrl}/documents`,
|
|
105
224
|
headers,
|
|
106
225
|
body: JSON.stringify(body),
|
|
107
226
|
json: true,
|
|
108
227
|
});
|
|
109
228
|
results.push({ json: response });
|
|
110
229
|
}
|
|
230
|
+
// ── Generate and Wait (poll until complete) ────────────────────────
|
|
231
|
+
if (operation === 'generateAndWait') {
|
|
232
|
+
const templateId = this.getNodeParameter('templateId', i);
|
|
233
|
+
const payloadRaw = this.getNodeParameter('payload', i);
|
|
234
|
+
const filename = this.getNodeParameter('filename', i);
|
|
235
|
+
const additionalOptions = this.getNodeParameter('additionalOptions', i);
|
|
236
|
+
const timeoutSeconds = this.getNodeParameter('timeoutSeconds', i);
|
|
237
|
+
const payload = typeof payloadRaw === 'string' ? JSON.parse(payloadRaw) : payloadRaw;
|
|
238
|
+
const meta = additionalOptions.meta
|
|
239
|
+
? (typeof additionalOptions.meta === 'string' ? JSON.parse(additionalOptions.meta) : additionalOptions.meta)
|
|
240
|
+
: {};
|
|
241
|
+
const body = { document_template_id: templateId, payload, meta };
|
|
242
|
+
if (filename)
|
|
243
|
+
body.filename = filename;
|
|
244
|
+
const created = await this.helpers.request({
|
|
245
|
+
method: 'POST',
|
|
246
|
+
url: `${baseUrl}/documents`,
|
|
247
|
+
headers,
|
|
248
|
+
body: JSON.stringify(body),
|
|
249
|
+
json: true,
|
|
250
|
+
});
|
|
251
|
+
const documentId = created.id;
|
|
252
|
+
const deadline = Date.now() + timeoutSeconds * 1000;
|
|
253
|
+
let doc = {};
|
|
254
|
+
while (Date.now() < deadline) {
|
|
255
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
256
|
+
doc = await this.helpers.request({
|
|
257
|
+
method: 'GET',
|
|
258
|
+
url: `${baseUrl}/document_cards/${documentId}`,
|
|
259
|
+
headers,
|
|
260
|
+
json: true,
|
|
261
|
+
});
|
|
262
|
+
if (doc.status === 'success' || doc.status === 'failure')
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
if (doc.status !== 'success' && doc.status !== 'failure') {
|
|
266
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Document ${documentId} did not complete within ${timeoutSeconds}s`, { itemIndex: i });
|
|
267
|
+
}
|
|
268
|
+
if (doc.status === 'failure') {
|
|
269
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Document generation failed: ${doc.failure_cause ?? 'unknown error'}`, { itemIndex: i });
|
|
270
|
+
}
|
|
271
|
+
results.push({ json: doc });
|
|
272
|
+
}
|
|
273
|
+
// ── Get Document ───────────────────────────────────────────────────
|
|
111
274
|
if (operation === 'getDocument') {
|
|
112
275
|
const documentId = this.getNodeParameter('documentId', i);
|
|
113
276
|
const response = await this.helpers.request({
|
|
@@ -118,20 +281,23 @@ class PDFKraft {
|
|
|
118
281
|
});
|
|
119
282
|
results.push({ json: response });
|
|
120
283
|
}
|
|
121
|
-
|
|
122
|
-
|
|
284
|
+
// ── Download Document ──────────────────────────────────────────────
|
|
285
|
+
if (operation === 'downloadDocument') {
|
|
286
|
+
const documentId = this.getNodeParameter('documentId', i);
|
|
287
|
+
const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i);
|
|
288
|
+
// Fetch document metadata to get filename and output type
|
|
289
|
+
const doc = await this.helpers.request({
|
|
123
290
|
method: 'GET',
|
|
124
|
-
url: `${baseUrl}/
|
|
291
|
+
url: `${baseUrl}/document_cards/${documentId}`,
|
|
125
292
|
headers,
|
|
126
293
|
json: true,
|
|
127
294
|
});
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
results.push({ json: t });
|
|
295
|
+
if (doc.status !== 'success') {
|
|
296
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Document is not ready — status is "${doc.status}"`, { itemIndex: i });
|
|
131
297
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const
|
|
298
|
+
const outputType = doc.output_type ?? 'pdf';
|
|
299
|
+
const filename = doc.filename ?? `document.${outputType}`;
|
|
300
|
+
const mimeType = MIME_TYPES[outputType] ?? 'application/octet-stream';
|
|
135
301
|
const buffer = await this.helpers.request({
|
|
136
302
|
method: 'GET',
|
|
137
303
|
url: `${baseUrl}/documents/${documentId}/download`,
|
|
@@ -139,8 +305,36 @@ class PDFKraft {
|
|
|
139
305
|
encoding: null,
|
|
140
306
|
resolveWithFullResponse: false,
|
|
141
307
|
});
|
|
142
|
-
const binaryData = await this.helpers.prepareBinaryData(buffer,
|
|
143
|
-
results.push({ json: { documentId }, binary: {
|
|
308
|
+
const binaryData = await this.helpers.prepareBinaryData(buffer, filename, mimeType);
|
|
309
|
+
results.push({ json: { documentId, filename, output_type: outputType }, binary: { [binaryPropertyName]: binaryData } });
|
|
310
|
+
}
|
|
311
|
+
// ── List Documents ─────────────────────────────────────────────────
|
|
312
|
+
if (operation === 'listDocuments') {
|
|
313
|
+
const statusFilter = this.getNodeParameter('statusFilter', i);
|
|
314
|
+
const limit = this.getNodeParameter('limit', i);
|
|
315
|
+
const params = new URLSearchParams({ page: '1' });
|
|
316
|
+
if (statusFilter)
|
|
317
|
+
params.set('status', statusFilter);
|
|
318
|
+
const response = await this.helpers.request({
|
|
319
|
+
method: 'GET',
|
|
320
|
+
url: `${baseUrl}/document_cards?${params}`,
|
|
321
|
+
headers,
|
|
322
|
+
json: true,
|
|
323
|
+
});
|
|
324
|
+
const docs = (response.data ?? []).slice(0, limit);
|
|
325
|
+
for (const doc of docs)
|
|
326
|
+
results.push({ json: doc });
|
|
327
|
+
}
|
|
328
|
+
// ── List Templates ─────────────────────────────────────────────────
|
|
329
|
+
if (operation === 'listTemplates') {
|
|
330
|
+
const response = await this.helpers.request({
|
|
331
|
+
method: 'GET',
|
|
332
|
+
url: `${baseUrl}/document_template_cards`,
|
|
333
|
+
headers,
|
|
334
|
+
json: true,
|
|
335
|
+
});
|
|
336
|
+
for (const t of response.data ?? [])
|
|
337
|
+
results.push({ json: t });
|
|
144
338
|
}
|
|
145
339
|
}
|
|
146
340
|
return [results];
|
package/package.json
CHANGED