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
- { name: 'Generate Document', value: 'generateDocument', description: 'Generate a PDF or image from a template' },
27
- { name: 'Generate Document (Sync)', value: 'generateDocumentSync', description: 'Generate and wait for the result' },
28
- { name: 'Get Document', value: 'getDocument', description: 'Get document status and download URL' },
29
- { name: 'List Templates', value: 'listTemplates', description: 'List all available templates' },
30
- { name: 'Download Document', value: 'downloadDocument', description: 'Download document as binary data' },
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: 'generateDocument',
69
+ default: 'generateAndWait',
33
70
  },
34
- // generateDocument / generateDocumentSync fields
71
+ // ── Template picker ──────────────────────────────────────────────────
35
72
  {
36
- displayName: 'Template ID',
73
+ displayName: 'Template',
37
74
  name: 'templateId',
38
- type: 'string',
75
+ type: 'options',
76
+ typeOptions: { loadOptionsMethod: 'getTemplates' },
39
77
  default: '',
40
78
  required: true,
41
- displayOptions: { show: { operation: ['generateDocument', 'generateDocumentSync'] } },
42
- description: 'The UUID of the template to use',
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', 'generateDocumentSync'] } },
50
- description: 'JSON data to merge into the template',
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
- displayOptions: { show: { operation: ['generateDocument', 'generateDocumentSync'] } },
58
- description: 'Output filename. Supports {{variable}} from payload.',
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: 'Meta',
62
- name: 'meta',
63
- type: 'json',
64
- default: '{}',
65
- displayOptions: { show: { operation: ['generateDocument', 'generateDocumentSync'] } },
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
- // getDocument / downloadDocument fields
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
- if (operation === 'generateDocument' || operation === 'generateDocumentSync') {
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 metaRaw = this.getNodeParameter('meta', i);
213
+ const additionalOptions = this.getNodeParameter('additionalOptions', i);
96
214
  const payload = typeof payloadRaw === 'string' ? JSON.parse(payloadRaw) : payloadRaw;
97
- const meta = typeof metaRaw === 'string' ? JSON.parse(metaRaw) : metaRaw;
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}${endpoint}`,
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
- if (operation === 'listTemplates') {
122
- const response = await this.helpers.request({
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}/document_template_cards`,
291
+ url: `${baseUrl}/document_cards/${documentId}`,
125
292
  headers,
126
293
  json: true,
127
294
  });
128
- const res = response;
129
- for (const t of res.data ?? []) {
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
- if (operation === 'downloadDocument') {
134
- const documentId = this.getNodeParameter('documentId', i);
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, 'document.pdf', 'application/pdf');
143
- results.push({ json: { documentId }, binary: { data: binaryData } });
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-pdfkraft",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "n8n community node for PDFKraft — generate PDFs and images via the PDFKraft API",
5
5
  "keywords": ["n8n-community-node-package"],
6
6
  "main": "dist/index.js",