n8n-nodes-sudomock 0.8.3 → 0.9.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/README.md CHANGED
@@ -21,10 +21,10 @@ n8n community node for the SudoMock API. Integrate mockup rendering into your n8
21
21
  ### 2D Mockups
22
22
 
23
23
  - **2D: Create Mockup**: Create a reusable 2D mockup from an image
24
- - **2D: Get Mockup**: Check preparation status and get ready print areas
24
+ - **2D: Get Mockup**: Check preparation status and get the ready surfaces and print areas
25
25
  - **2D: List Mockups**: List your 2D mockups
26
26
  - **2D: Set Print Areas**: Define print areas with four-point quads
27
- - **2D: Render Mockup**: Render artwork into ready print areas for 5 credits
27
+ - **2D: Render Mockup**: Render artwork onto a ready surface or print area for 5 credits
28
28
  - **2D: Delete Mockup**: Delete a 2D mockup
29
29
 
30
30
  ### Rendering
@@ -120,18 +120,36 @@ Retrieve your account information, subscription details, and credit usage.
120
120
  "name": "John Doe"
121
121
  },
122
122
  "subscription": {
123
- "plan": { "name": "Pro", "tier": "pro" },
123
+ "plan": "pro-25k",
124
+ "tier": "pro",
124
125
  "status": "active"
125
126
  },
126
127
  "usage": {
127
- "credits_used_this_month": 250,
128
- "credits_limit": 1000,
129
- "credits_remaining": 750
128
+ "credits_used_this_month": 6250,
129
+ "credits_limit": 25000,
130
+ "credits_remaining": 18750,
131
+ "prepaid_balance": 0,
132
+ "prepaid_balance_currency": "USD"
130
133
  }
131
134
  }
132
135
  }
133
136
  ```
134
137
 
138
+ An account is funded either by a subscription allowance or by a prepaid balance,
139
+ and the two are independent. The three `credits_*` fields describe the allowance
140
+ only. An account paying as it goes has no allowance, so it reports all three as `0`
141
+ while holding a positive `prepaid_balance` and being perfectly able to pay:
142
+
143
+ ```json
144
+ "usage": {
145
+ "credits_used_this_month": 0,
146
+ "credits_limit": 0,
147
+ "credits_remaining": 0,
148
+ "prepaid_balance": 12.5,
149
+ "prepaid_balance_currency": "USD"
150
+ }
151
+ ```
152
+
135
153
  ---
136
154
 
137
155
  ### Upload PSD
@@ -342,12 +360,28 @@ Deletion confirmation
342
360
  Create, prepare, render, and manage reusable mockups from product images without a PSD.
343
361
 
344
362
  1. **2D: Create Mockup** accepts a public `source_url` or `source_base64`, plus an optional name. By default it waits and returns the ready mockup detail unchanged. Enable **Run Asynchronously** to receive a 202 job with `job_id` and `status_url`, then use **Get Job** when needed.
345
- 2. **2D: Get Mockup** returns `draft`, `ready`, or `failed` status. Poll this operation until the mockup is `ready`. The ready response keeps saved areas in `data.quads` (addressed by `print_area_id`) and full surfaces in `data.surfaces` (addressed by `surface_uuid`).
363
+ 2. **2D: Get Mockup** returns `draft`, `ready`, or `failed` status. Poll this operation until the mockup is `ready`. The ready response lists every printable product in `data.surfaces` (addressed by `surface_uuid`), and every print area someone has drawn in `data.quads` (addressed by `print_area_id`).
346
364
  3. **2D: List Mockups** returns your 2D mockups with limit, offset, and shopper-customizable filtering.
347
365
  4. **2D: Set Print Areas** replaces saved areas with quads made from four `[x, y]` coordinate pairs. It can also send an empty list; the API remains authoritative and accepts that only when the mockup can remain renderable without saved areas.
348
- 5. **2D: Render Mockup** accepts the mockup UUID and one or more ready render targets. Each target sends exactly one identifier: `uuid` for a saved area or `surface_uuid` for a full surface. Each target also includes either an artwork URL or Base64 artwork, with optional background removal (adds 25 credits per artwork), color, public adjustments, and placement. Export options support PNG, JPG, or WebP from 100 to 10000 pixels, plus quality and optional DPI. Each render costs 5 credits and returns `print_files`, where `print_files[0]` is the CDN URL.
366
+ 5. **2D: Render Mockup** accepts the mockup UUID and one or more ready render targets. Each target names exactly one address: `uuid` for a saved print area, or `surface_uuid` for a surface. Each target also includes either an artwork URL or Base64 artwork, with optional background removal (adds 25 credits per artwork), color, public adjustments, and placement. Export options support PNG, JPG, or WebP from 100 to 10000 pixels, plus quality and optional DPI. Each render costs 5 credits and returns `print_files`, where `print_files[0]` is the CDN URL.
349
367
  6. **2D: Delete Mockup** deletes the selected 2D mockup.
350
368
 
369
+ A surface and a print area are separate render targets. Saving a print area on a
370
+ product does not close off the surface under it, so an all-over print and a chest
371
+ logo can both be addressed on the same product, in the same call, as two targets.
372
+
373
+ Placement is where the two kinds differ:
374
+
375
+ - A **surface** target takes **Coverage**, a percentage from 10 to 100. Leave it out
376
+ and the artwork covers the whole surface.
377
+ - A **print area** target takes either **Fit** (`contain`, `fill`, or `cover`), which
378
+ always sizes the artwork to the whole area, or an explicit **Width** and **Height**
379
+ pair when you want to choose the box yourself. Send one or the other, never both,
380
+ and send Width and Height together. Leaving Fit out is the same as `contain`.
381
+
382
+ Both kinds take position, offset, and rotation. Anything you do not set is not sent,
383
+ so the API decides it.
384
+
351
385
  Create must reach `ready` through **2D: Get Mockup** before **2D: Render Mockup**.
352
386
 
353
387
  ---
@@ -539,12 +573,33 @@ See [`examples/README.md`](./examples/README.md) for detailed instructions, cust
539
573
  → Check credit usage
540
574
 
541
575
  3. [IF Node]
542
- → If credits < 100
576
+ → If credits_limit > 0
577
+ AND credits_remaining < 5% of credits_limit
578
+ OR credits_limit == 0
579
+ AND prepaid_balance < 5
543
580
 
544
581
  4. [Send Email/Slack]
545
- → Alert about low credits
582
+ → Alert about low funds
546
583
  ```
547
584
 
585
+ Alert on whichever way the account is actually funded, because the two branches
586
+ measure different things and neither one alone is correct.
587
+
588
+ An account with a subscription has an allowance, so watch `credits_remaining`
589
+ against `credits_limit`. Use a percentage rather than a fixed number: the
590
+ allowance ranges from 5,000 to 100,000 depending on the plan, so a threshold
591
+ tuned for one plan is noise on another.
592
+
593
+ An account paying as it goes has no allowance at all. It reports
594
+ `credits_limit: 0` and `credits_remaining: 0` permanently, and those numbers
595
+ never reset, so a bare `credits_remaining < 100` fires forever on an account
596
+ that is fully funded. Watch `prepaid_balance` for that account instead.
597
+
598
+ Do not build a percentage or a progress bar out of `prepaid_balance`. It is an
599
+ amount, not a fraction, and it has no denominator to be a percentage of. For the
600
+ same reason, never report the pair as `0 / 0`: that reads as an empty account
601
+ when it means the allowance is simply not the thing paying.
602
+
548
603
  ## Rate Limits
549
604
 
550
605
  ### Request Rate Limits
@@ -497,7 +497,7 @@ class SudoMock {
497
497
  },
498
498
  default: {},
499
499
  placeholder: 'Add Render Target',
500
- description: 'Artwork and options for each target. Select exactly one saved print area UUID or full-surface UUID.',
500
+ description: 'Artwork and options for each target. Each target names exactly one address: a saved print area UUID, or a surface UUID.',
501
501
  options: [
502
502
  {
503
503
  name: 'items',
@@ -626,46 +626,30 @@ class SudoMock {
626
626
  placeholder: '#FF0000',
627
627
  description: 'Optional hex color fill or overlay',
628
628
  },
629
- {
630
- displayName: 'Full-Surface UUID',
631
- name: 'surfaceUuid',
632
- type: 'string',
633
- required: true,
634
- displayOptions: {
635
- show: {
636
- targetType: ['fullSurface'],
637
- },
638
- },
639
- default: '',
640
- description: 'The surface_uuid returned in data.surfaces by 2D: Get Mockup',
641
- },
642
629
  {
643
630
  displayName: 'Placement',
644
631
  name: 'placement',
645
632
  type: 'collection',
646
633
  placeholder: 'Add Placement Option',
634
+ displayOptions: {
635
+ show: {
636
+ targetType: ['savedPrintArea'],
637
+ },
638
+ },
647
639
  default: {},
648
- description: 'Artwork position and size settings for this print area',
640
+ description: 'Where the artwork sits inside the saved print area, and how it is sized. Options you do not add are not sent.',
649
641
  options: [
650
- {
651
- displayName: 'Coverage',
652
- name: 'coverage',
653
- type: 'number',
654
- typeOptions: { minValue: 10, maxValue: 100 },
655
- default: 70,
656
- description: 'Percentage of the print area covered by the artwork',
657
- },
658
642
  {
659
643
  displayName: 'Fit',
660
644
  name: 'fit',
661
645
  type: 'options',
662
646
  options: [
663
- { name: 'Fill', value: 'fill' },
664
647
  { name: 'Contain', value: 'contain' },
665
648
  { name: 'Cover', value: 'cover' },
649
+ { name: 'Stretch', value: 'fill' },
666
650
  ],
667
651
  default: 'contain',
668
- description: 'How the artwork fits within the print area',
652
+ description: 'How the artwork is sized to the whole print area. Leaving it out is the same as Contain. To place the artwork in a box of your own instead, add Width and Height and leave Fit out: sending both is rejected.',
669
653
  },
670
654
  {
671
655
  displayName: 'Height',
@@ -720,7 +704,7 @@ class SudoMock {
720
704
  type: 'number',
721
705
  typeOptions: { minValue: 1, maxValue: 30000 },
722
706
  default: 1000,
723
- description: 'Artwork width in print-area pixels. Add Height alongside it; the pair overrides Coverage and Fit. Width and Height are independent, so any aspect ratio is allowed.',
707
+ description: 'Artwork width in print-area pixels. Add Height alongside it. The pair sizes the artwork to a box you choose, so it takes the place of Fit rather than joining it. Width and Height are independent, so any aspect ratio is allowed.',
724
708
  },
725
709
  ],
726
710
  },
@@ -744,6 +728,97 @@ class SudoMock {
744
728
  default: '',
745
729
  description: 'The print_area_id returned in data.quads by 2D: Get Mockup',
746
730
  },
731
+ {
732
+ displayName: 'Surface Placement',
733
+ name: 'surfacePlacement',
734
+ type: 'collection',
735
+ placeholder: 'Add Placement Option',
736
+ displayOptions: {
737
+ show: {
738
+ targetType: ['surface', 'fullSurface'],
739
+ },
740
+ },
741
+ default: {},
742
+ description: 'Where the artwork sits on the surface, and how much of the surface it takes up. Options you do not add are not sent.',
743
+ options: [
744
+ {
745
+ displayName: 'Coverage',
746
+ name: 'coverage',
747
+ type: 'number',
748
+ typeOptions: { minValue: 10, maxValue: 100 },
749
+ default: 100,
750
+ description: 'Percentage of the surface the artwork takes up, from 10 to 100. Leaving it out covers the whole surface. Use this or an explicit Width and Height pair, not both.',
751
+ },
752
+ {
753
+ displayName: 'Height',
754
+ name: 'height',
755
+ type: 'number',
756
+ typeOptions: { minValue: 1, maxValue: 30000 },
757
+ default: 0,
758
+ description: 'Artwork height in pixels. Send together with Width. A drawn size cannot be expressed as a percentage when its proportions differ from the surface, which is why the pair is offered here at all.',
759
+ },
760
+ {
761
+ displayName: 'Offset X',
762
+ name: 'offset_x',
763
+ type: 'number',
764
+ default: 0,
765
+ description: 'Horizontal artwork offset in pixels',
766
+ },
767
+ {
768
+ displayName: 'Offset Y',
769
+ name: 'offset_y',
770
+ type: 'number',
771
+ default: 0,
772
+ description: 'Vertical artwork offset in pixels',
773
+ },
774
+ {
775
+ displayName: 'Position',
776
+ name: 'position',
777
+ type: 'options',
778
+ options: [
779
+ { name: 'Bottom Center', value: 'bottom_center' },
780
+ { name: 'Bottom Left', value: 'bottom_left' },
781
+ { name: 'Bottom Right', value: 'bottom_right' },
782
+ { name: 'Center', value: 'center' },
783
+ { name: 'Center Left', value: 'center_left' },
784
+ { name: 'Center Right', value: 'center_right' },
785
+ { name: 'Top Center', value: 'top_center' },
786
+ { name: 'Top Left', value: 'top_left' },
787
+ { name: 'Top Right', value: 'top_right' },
788
+ ],
789
+ default: 'center',
790
+ description: 'Predefined artwork position on the surface',
791
+ },
792
+ {
793
+ displayName: 'Rotation',
794
+ name: 'rotation',
795
+ type: 'number',
796
+ default: 0,
797
+ description: 'Artwork rotation in degrees',
798
+ },
799
+ {
800
+ displayName: 'Width',
801
+ name: 'width',
802
+ type: 'number',
803
+ typeOptions: { minValue: 1, maxValue: 30000 },
804
+ default: 0,
805
+ description: 'Artwork width in pixels. Send together with Height, and without Coverage.',
806
+ },
807
+ ],
808
+ },
809
+ {
810
+ displayName: 'Surface UUID',
811
+ name: 'surfaceUuid',
812
+ type: 'string',
813
+ required: true,
814
+ displayOptions: {
815
+ show: {
816
+ targetType: ['surface', 'fullSurface'],
817
+ },
818
+ },
819
+ default: '',
820
+ description: 'The surface_uuid returned in data.surfaces by 2D: Get Mockup',
821
+ },
747
822
  {
748
823
  displayName: 'Target Type',
749
824
  name: 'targetType',
@@ -755,13 +830,13 @@ class SudoMock {
755
830
  description: 'Target a saved print area returned in data.quads',
756
831
  },
757
832
  {
758
- name: 'Full Surface',
759
- value: 'fullSurface',
760
- description: 'Target a full surface returned in data.surfaces',
833
+ name: 'Surface',
834
+ value: 'surface',
835
+ description: 'Target a printable product returned in data.surfaces',
761
836
  },
762
837
  ],
763
838
  default: 'savedPrintArea',
764
- description: 'Whether this artwork targets a saved area or a full surface',
839
+ description: 'Whether this artwork targets a saved print area or the surface it sits on. A product can carry both, and they are separate targets.',
765
840
  },
766
841
  ],
767
842
  },
@@ -931,9 +1006,9 @@ class SudoMock {
931
1006
  type: 'options',
932
1007
  options: [
933
1008
  {
934
- name: 'Fill',
1009
+ name: 'Stretch',
935
1010
  value: 'fill',
936
- description: 'Stretch to fill entire area (default)',
1011
+ description: 'Scales the design to the bounds without keeping its proportions (default)',
937
1012
  },
938
1013
  {
939
1014
  name: 'Contain',
@@ -943,7 +1018,7 @@ class SudoMock {
943
1018
  {
944
1019
  name: 'Cover',
945
1020
  value: 'cover',
946
- description: 'Fill area, may crop edges',
1021
+ description: 'Covers the area and crops the overflow, proportions kept',
947
1022
  },
948
1023
  ],
949
1024
  default: 'fill',
@@ -1325,9 +1400,9 @@ class SudoMock {
1325
1400
  type: 'options',
1326
1401
  options: [
1327
1402
  {
1328
- name: 'Fill',
1403
+ name: 'Stretch',
1329
1404
  value: 'fill',
1330
- description: 'Stretch to fill entire area',
1405
+ description: 'Scales the design to the bounds without keeping its proportions',
1331
1406
  },
1332
1407
  {
1333
1408
  name: 'Contain',
@@ -1337,7 +1412,7 @@ class SudoMock {
1337
1412
  {
1338
1413
  name: 'Cover',
1339
1414
  value: 'cover',
1340
- description: 'Fill area, may crop edges (recommended)',
1415
+ description: 'Covers the area and crops the overflow, proportions kept (recommended)',
1341
1416
  },
1342
1417
  ],
1343
1418
  default: 'cover',
@@ -2170,14 +2245,15 @@ class SudoMock {
2170
2245
  const mockupUuid = this.getNodeParameter('twoDMockupUuid', i);
2171
2246
  const printAreasData = this.getNodeParameter('twoDRenderPrintAreas.items', i, []);
2172
2247
  const printAreas = printAreasData.map((area) => {
2173
- const targetType = area.targetType ?? 'savedPrintArea';
2174
- const targetValue = targetType === 'fullSurface' ? area.surfaceUuid : area.uuid;
2248
+ // 'fullSurface' is what workflows saved before the rename carry.
2249
+ const targetType = area.targetType === 'fullSurface' ? 'surface' : area.targetType ?? 'savedPrintArea';
2250
+ const targetValue = targetType === 'surface' ? area.surfaceUuid : area.uuid;
2175
2251
  if (!targetValue) {
2176
- throw new n8n_workflow_1.NodeOperationError(this.getNode(), targetType === 'fullSurface'
2177
- ? 'Full-Surface UUID is required for a full-surface render target'
2252
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), targetType === 'surface'
2253
+ ? 'Surface UUID is required for a surface render target'
2178
2254
  : 'Saved Print Area UUID is required for a saved print-area render target');
2179
2255
  }
2180
- const printArea = targetType === 'fullSurface' ? { surface_uuid: targetValue } : { uuid: targetValue };
2256
+ const printArea = targetType === 'surface' ? { surface_uuid: targetValue } : { uuid: targetValue };
2181
2257
  if (area.artworkSource === 'base64') {
2182
2258
  printArea.base64 = area.base64;
2183
2259
  }
@@ -2202,8 +2278,55 @@ class SudoMock {
2202
2278
  blend_mode,
2203
2279
  }).filter(([, value]) => value !== undefined));
2204
2280
  }
2205
- if (area.placement && Object.keys(area.placement).length > 0) {
2206
- printArea.placement = area.placement;
2281
+ const placement = (targetType === 'surface' ? area.surfacePlacement : area.placement);
2282
+ if (placement && Object.keys(placement).length > 0) {
2283
+ // Which options the caller WROTE, not which of them hold a value. An
2284
+ // explicitly written null is the caller naming the option, and the API
2285
+ // answers it by name; a value check waves that through and teaches the
2286
+ // author nothing.
2287
+ const named = (key) => Object.prototype.hasOwnProperty.call(placement, key);
2288
+ const hasWidth = named('width');
2289
+ const hasHeight = named('height');
2290
+ if (hasWidth !== hasHeight) {
2291
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Width and Height travel together. Add the missing one, or drop both.');
2292
+ }
2293
+ // `scale` was a placement control until 0.8.0, where one multiplier
2294
+ // was found unable to express two free axes. It was retired with no
2295
+ // alias and no mapping, so workflows saved before that still carry
2296
+ // it on disk with no control left to remove it through. Naming it
2297
+ // here keeps the retirement visible, which was the point, without
2298
+ // putting a field the API refuses on the wire.
2299
+ if (named('scale')) {
2300
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Scale was retired: one multiplier cannot size two independent axes. Size the artwork with a Width and Height pair instead, and remove Scale.');
2301
+ }
2302
+ if (targetType === 'savedPrintArea') {
2303
+ if (named('fit') && (hasWidth || hasHeight)) {
2304
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'A print area takes either Fit or an explicit Width and Height pair, not both. Remove one of them.');
2305
+ }
2306
+ // A percentage on a print area is no longer a thing the API will
2307
+ // answer. Workflows saved before 2026-08-19 still carry
2308
+ // `coverage: 70` on disk and cannot edit it away through a
2309
+ // control that no longer exists, so it is dropped here rather
2310
+ // than forwarded into a 422 whose cause the author cannot see.
2311
+ delete placement.coverage;
2312
+ }
2313
+ else {
2314
+ // A surface is the whole product, so there are no bounds to fit
2315
+ // against and Fit belongs to a print area alone. The surface
2316
+ // collection never offers it, so arriving here means the target
2317
+ // was built by an expression rather than the panel. Refusing it
2318
+ // by name beats forwarding a 422 the author has no field to
2319
+ // trace back to.
2320
+ if (named('fit')) {
2321
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'A surface covers the whole product, so it has no bounds to fit against. Fit belongs to a print area. Use Coverage, or a Width and Height pair.');
2322
+ }
2323
+ if (named('coverage') && (hasWidth || hasHeight)) {
2324
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'A surface takes either Coverage or an explicit Width and Height pair, not both. Remove one of them.');
2325
+ }
2326
+ }
2327
+ if (Object.keys(placement).length > 0) {
2328
+ printArea.placement = placement;
2329
+ }
2207
2330
  }
2208
2331
  return printArea;
2209
2332
  });
@@ -95,7 +95,9 @@ class SudoMockTrigger {
95
95
  });
96
96
  }
97
97
  catch (error) {
98
- throw new n8n_workflow_1.NodeApiError(this.getNode(), error);
98
+ if (Number(error.statusCode) !== 404) {
99
+ throw new n8n_workflow_1.NodeApiError(this.getNode(), error);
100
+ }
99
101
  }
100
102
  delete data.webhookId;
101
103
  delete data.webhookSecret;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-sudomock",
3
- "version": "0.8.3",
3
+ "version": "0.9.0",
4
4
  "description": "n8n community node for SudoMock mockup rendering API - POD automation",
5
5
  "license": "MIT",
6
6
  "homepage": "https://sudomock.com",