@doswiftly/storefront-operations 4.4.0 → 5.4.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/fragments.graphql CHANGED
@@ -20,6 +20,7 @@ fragment UserError on UserError {
20
20
  field
21
21
  }
22
22
 
23
+ # Base Image — no transform (for logos, favicons, branding assets that are already small)
23
24
  fragment Image on Image {
24
25
  id
25
26
  url
@@ -28,6 +29,37 @@ fragment Image on Image {
28
29
  height
29
30
  }
30
31
 
32
+ # Thumbnail — cart items, variant swatches, author avatars (rendered at ~96px, 300px covers 3x DPI)
33
+ # No preferredContentType — imgproxy auto-negotiates AVIF/WEBP from Accept header
34
+ fragment ImageThumbnail on Image {
35
+ id
36
+ url(transform: { maxWidth: 300 })
37
+ altText
38
+ width
39
+ height
40
+ thumbhash
41
+ }
42
+
43
+ # Card — product cards, collection/category cards, blog posts (rendered at ~400px, 800px covers 2x DPI)
44
+ fragment ImageCard on Image {
45
+ id
46
+ url(transform: { maxWidth: 800 })
47
+ altText
48
+ width
49
+ height
50
+ thumbhash
51
+ }
52
+
53
+ # Full — product detail gallery, hero images (rendered at ~800px, 1600px covers 2x DPI)
54
+ fragment ImageFull on Image {
55
+ id
56
+ url(transform: { maxWidth: 1600 })
57
+ altText
58
+ width
59
+ height
60
+ thumbhash
61
+ }
62
+
31
63
  # Simple money (for originalPrice fields and non-converted prices)
32
64
  fragment Money on Money {
33
65
  amount
@@ -79,7 +111,7 @@ fragment ProductVariant on ProductVariant {
79
111
  available
80
112
  quantityAvailable
81
113
  image {
82
- ...Image
114
+ ...ImageThumbnail
83
115
  }
84
116
  selectedOptions {
85
117
  ...SelectedOption
@@ -102,7 +134,7 @@ fragment ProductCard on Product {
102
134
  reviewCount
103
135
  tags
104
136
  featuredImage {
105
- ...Image
137
+ ...ImageCard
106
138
  }
107
139
  priceRange {
108
140
  minVariantPrice {
@@ -146,6 +178,9 @@ fragment ProductBase on Product {
146
178
  totalInventory
147
179
  type
148
180
  collectRecipientInfo
181
+ # Faza 1.5 (Decision D-A v3 + D-B)
182
+ visibility
183
+ attributeSetId
149
184
  createdAt
150
185
  updatedAt
151
186
  }
@@ -153,7 +188,7 @@ fragment ProductBase on Product {
153
188
  fragment ProductFull on Product {
154
189
  ...ProductBase
155
190
  images(first: 20) {
156
- ...Image
191
+ ...ImageFull
157
192
  }
158
193
  variants(first: 100) {
159
194
  ...ProductVariant
@@ -175,7 +210,7 @@ fragment Collection on Collection {
175
210
  description
176
211
  descriptionHtml
177
212
  image {
178
- ...Image
213
+ ...ImageCard
179
214
  }
180
215
  seo {
181
216
  title
@@ -195,7 +230,7 @@ fragment Category on Category {
195
230
  slug
196
231
  description
197
232
  image {
198
- ...Image
233
+ ...ImageCard
199
234
  }
200
235
  level
201
236
  path
@@ -311,6 +346,24 @@ fragment CartLineCost on CartLineCost {
311
346
  }
312
347
  }
313
348
 
349
+ # Typed customer-filled attribute snapshot (Faza 1 R5 + Decision D-A v3 + D-I).
350
+ # Distinct from CartLine.attributes (Shopify raw KV line item properties).
351
+ fragment AttributeSelection on AttributeSelection {
352
+ attributeDefinitionId
353
+ attributeName
354
+ type
355
+ fillingMode
356
+ billingMode
357
+ optionId
358
+ optionLabel
359
+ optionIds
360
+ textValue
361
+ surchargeAmount
362
+ surchargeType
363
+ taxClassId
364
+ linkedVariantId
365
+ }
366
+
314
367
  fragment CartLine on CartLine {
315
368
  id
316
369
  quantity
@@ -324,6 +377,9 @@ fragment CartLine on CartLine {
324
377
  key
325
378
  value
326
379
  }
380
+ attributeSelections {
381
+ ...AttributeSelection
382
+ }
327
383
  productId
328
384
  productTitle
329
385
  productHandle
@@ -587,7 +643,7 @@ fragment CheckoutLineItem on CheckoutLineItem {
587
643
  productId
588
644
  sku
589
645
  image {
590
- ...Image
646
+ ...ImageThumbnail
591
647
  }
592
648
  }
593
649
 
@@ -1092,7 +1148,7 @@ fragment LoyaltyReward on LoyaltyReward {
1092
1148
  }
1093
1149
  description
1094
1150
  image {
1095
- ...Image
1151
+ ...ImageCard
1096
1152
  }
1097
1153
  available
1098
1154
  tierRequired {
@@ -1242,14 +1298,14 @@ fragment BlogPost on BlogPost {
1242
1298
  content
1243
1299
  contentType
1244
1300
  featuredImage {
1245
- ...Image
1301
+ ...ImageCard
1246
1302
  }
1247
1303
  author {
1248
1304
  id
1249
1305
  name
1250
1306
  bio
1251
1307
  avatar {
1252
- ...Image
1308
+ ...ImageThumbnail
1253
1309
  }
1254
1310
  }
1255
1311
  category {
@@ -1306,24 +1362,210 @@ fragment CartRecommendation on CartRecommendations {
1306
1362
  }
1307
1363
 
1308
1364
  # ============================================
1309
- # Inventory Level Fragments (Multi-Warehouse)
1365
+ # Store Availability Fragments (Shopify parity — BOPIS / multi-location)
1310
1366
  # ============================================
1367
+ # Field access:
1368
+ # - `available: Boolean!` — always public.
1369
+ # - `pickUpTime: String` — public, localized merchant-configured string.
1370
+ # - `quantityAvailable: Int` — TOKEN-GATED (null for anonymous, Int for authenticated).
1371
+ # - `location: Location!` — public, includes coords + businessHours when configured.
1372
+
1373
+ fragment LocationAddress on LocationAddress {
1374
+ address1
1375
+ address2
1376
+ city
1377
+ country
1378
+ countryCode
1379
+ province
1380
+ provinceCode
1381
+ zip
1382
+ phone
1383
+ latitude
1384
+ longitude
1385
+ formatted
1386
+ }
1387
+
1388
+ fragment BusinessHoursWindow on BusinessHoursWindow {
1389
+ openHour
1390
+ closeHour
1391
+ }
1392
+
1393
+ fragment BusinessHours on BusinessHours {
1394
+ monday { ...BusinessHoursWindow }
1395
+ tuesday { ...BusinessHoursWindow }
1396
+ wednesday { ...BusinessHoursWindow }
1397
+ thursday { ...BusinessHoursWindow }
1398
+ friday { ...BusinessHoursWindow }
1399
+ saturday { ...BusinessHoursWindow }
1400
+ sunday { ...BusinessHoursWindow }
1401
+ }
1402
+
1403
+ fragment Location on Location {
1404
+ id
1405
+ name
1406
+ type
1407
+ pickupEnabled
1408
+ timezone
1409
+ pickupInstructions
1410
+ address {
1411
+ ...LocationAddress
1412
+ }
1413
+ businessHours {
1414
+ ...BusinessHours
1415
+ }
1416
+ }
1311
1417
 
1312
- fragment InventoryLevel on InventoryLevel {
1313
- locationId
1314
- locationName
1315
- locationType
1418
+ fragment StoreAvailability on StoreAvailability {
1316
1419
  available
1317
- onHand
1420
+ quantityAvailable
1421
+ pickUpTime
1422
+ location {
1423
+ ...Location
1424
+ }
1318
1425
  }
1319
1426
 
1320
- fragment VariantInventoryLevels on ProductVariant {
1427
+ fragment StoreAvailabilityConnection on StoreAvailabilityConnection {
1428
+ totalCount
1429
+ pageInfo {
1430
+ ...PageInfo
1431
+ }
1432
+ edges {
1433
+ cursor
1434
+ node {
1435
+ ...StoreAvailability
1436
+ }
1437
+ }
1438
+ }
1439
+
1440
+ fragment VariantStoreAvailability on ProductVariant {
1321
1441
  id
1322
1442
  title
1323
1443
  sku
1324
1444
  available
1325
1445
  quantityAvailable
1326
- inventoryLevels {
1327
- ...InventoryLevel
1446
+ storeAvailability(first: 10) {
1447
+ ...StoreAvailabilityConnection
1448
+ }
1449
+ }
1450
+
1451
+ # ============================================
1452
+ # Content: Pages
1453
+ # ============================================
1454
+
1455
+ fragment ShopPage on ShopPage {
1456
+ id
1457
+ handle
1458
+ title
1459
+ body
1460
+ bodySummary
1461
+ seo {
1462
+ title
1463
+ description
1464
+ }
1465
+ publishedAt
1466
+ createdAt
1467
+ updatedAt
1468
+ }
1469
+
1470
+ # ============================================
1471
+ # Content: Navigation Menus
1472
+ # ============================================
1473
+
1474
+ fragment MenuItem on MenuItem {
1475
+ id
1476
+ title
1477
+ url
1478
+ type
1479
+ resourceId
1480
+ image {
1481
+ ...Image
1482
+ }
1483
+ items {
1484
+ id
1485
+ title
1486
+ url
1487
+ type
1488
+ resourceId
1489
+ items {
1490
+ id
1491
+ title
1492
+ url
1493
+ type
1494
+ resourceId
1495
+ }
1496
+ }
1497
+ }
1498
+
1499
+ fragment Menu on Menu {
1500
+ id
1501
+ handle
1502
+ title
1503
+ itemsCount
1504
+ items {
1505
+ ...MenuItem
1506
+ }
1507
+ }
1508
+
1509
+ # ============================================
1510
+ # Content: URL Redirects
1511
+ # ============================================
1512
+
1513
+ fragment UrlRedirect on UrlRedirect {
1514
+ path
1515
+ target
1516
+ }
1517
+
1518
+ # ============================================
1519
+ # Unified Product Configurator (Faza 1 + Faza 1.5 — Decision D-A v3 + D-I)
1520
+ # ============================================
1521
+ # Hybrid Magento binding:
1522
+ # - Product.attributes returns UNION of set definitions (via Product.attributeSetId)
1523
+ # and per-product scoped definitions (AttributeDefinition.scopeProductId = product.id)
1524
+ # - CUSTOMER fillingMode → rendered in storefront configurator UX
1525
+ # - MERCHANT fillingMode → product metadata (filtering, display)
1526
+ # - BOTH fillingMode → admin default + customer override in cart
1527
+
1528
+ fragment LinkedVariantSummary on LinkedVariantSummary {
1529
+ id
1530
+ productId
1531
+ title
1532
+ sku
1533
+ quantityAvailable
1534
+ isAvailable
1535
+ trackQuantity
1536
+ }
1537
+
1538
+ fragment ProductAttributeOption on ProductAttributeOption {
1539
+ id
1540
+ value
1541
+ label
1542
+ sortOrder
1543
+ colorHex
1544
+ surchargeAmount
1545
+ surchargeType
1546
+ isDefault
1547
+ linkedVariantId
1548
+ linkedVariant {
1549
+ ...LinkedVariantSummary
1550
+ }
1551
+ }
1552
+
1553
+ fragment ProductAttributeDefinition on ProductAttributeDefinition {
1554
+ id
1555
+ name
1556
+ slug
1557
+ description
1558
+ type
1559
+ fillingMode
1560
+ billingMode
1561
+ taxClassId
1562
+ scopeProductId
1563
+ isRequired
1564
+ isVisible
1565
+ displayOrder
1566
+ minValue
1567
+ maxValue
1568
+ options {
1569
+ ...ProductAttributeOption
1328
1570
  }
1329
1571
  }
package/mutations.graphql CHANGED
@@ -497,3 +497,18 @@ mutation WishlistDelete($wishlistId: ID!) {
497
497
  userErrors
498
498
  }
499
499
  }
500
+
501
+ # ============================================
502
+ # Cart Attributes
503
+ # ============================================
504
+
505
+ mutation CartAttributesUpdate($cartId: ID!, $attributes: [CartAttributeInput!]!) {
506
+ cartAttributesUpdate(cartId: $cartId, attributes: $attributes) {
507
+ cart {
508
+ ...Cart
509
+ }
510
+ userErrors {
511
+ ...UserError
512
+ }
513
+ }
514
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@doswiftly/storefront-operations",
3
- "version": "4.4.0",
3
+ "version": "5.4.0",
4
4
  "description": "GraphQL operations for DoSwiftly Storefront - SSOT from backend",
5
5
  "homepage": "https://doswiftly.pl",
6
6
  "publishConfig": {
@@ -14,10 +14,6 @@
14
14
  "./fragments.graphql": "./fragments.graphql",
15
15
  "./*.graphql": "./*.graphql"
16
16
  },
17
- "scripts": {
18
- "sync": "node scripts/sync-operations.js",
19
- "build": "npm run sync"
20
- },
21
17
  "keywords": [
22
18
  "graphql",
23
19
  "operations",
@@ -33,5 +29,9 @@
33
29
  "mutations.graphql",
34
30
  "fragments.graphql",
35
31
  "README.md"
36
- ]
37
- }
32
+ ],
33
+ "scripts": {
34
+ "sync": "node scripts/sync-operations.js",
35
+ "build": "npm run sync"
36
+ }
37
+ }
package/queries.graphql CHANGED
@@ -23,6 +23,19 @@ query Product($id: ID, $handle: String) {
23
23
  }
24
24
  }
25
25
 
26
+ # Single-query configurator fetch (Faza 1 + Faza 1.5 — Decision D-A v3).
27
+ # Returns product details + union of set/scoped AttributeDefinitions filtered by CUSTOMER fillingMode.
28
+ # Showcase: hurtowniakopiarek.pl Konica Bizhub C258 — 4 customer text fields (from shared set)
29
+ # + Finiszer/Podstawa/Podajnik ADF (per-product scoped z linkedVariantId).
30
+ query ProductConfigurator($handle: String!, $fillingMode: String = "CUSTOMER") {
31
+ product(handle: $handle) {
32
+ ...ProductFull
33
+ attributes(filter: { fillingMode: $fillingMode }) {
34
+ ...ProductAttributeDefinition
35
+ }
36
+ }
37
+ }
38
+
26
39
  query Products(
27
40
  $first: Int = 20
28
41
  $after: String
@@ -460,7 +473,7 @@ query ProductRecommendations($productId: ID!, $limit: Int = 8, $intent: Recommen
460
473
  query SimilarProducts($productId: String!, $first: Int = 6) {
461
474
  similarProducts(productId: $productId, first: $first) {
462
475
  items {
463
- ...ProductCard
476
+ ...ProductRecommendation
464
477
  }
465
478
  totalCount
466
479
  }
@@ -468,11 +481,110 @@ query SimilarProducts($productId: String!, $first: Int = 6) {
468
481
 
469
482
  query CartRecommendations($cartId: String!, $first: Int = 4) {
470
483
  cartRecommendations(cartId: $cartId, first: $first) {
471
- frequentlyBoughtTogether {
472
- ...ProductCard
484
+ ...CartRecommendation
485
+ }
486
+ }
487
+
488
+ # ============================================
489
+ # Content: Pages
490
+ # ============================================
491
+
492
+ query Page($handle: String, $id: ID) {
493
+ page(handle: $handle, id: $id) {
494
+ ...ShopPage
495
+ }
496
+ }
497
+
498
+ query Pages($first: Int = 20, $after: String, $sortKey: PageSortKeys = TITLE, $reverse: Boolean = false, $query: String) {
499
+ pages(first: $first, after: $after, sortKey: $sortKey, reverse: $reverse, query: $query) {
500
+ edges {
501
+ node {
502
+ ...ShopPage
503
+ }
504
+ cursor
473
505
  }
474
- youMayAlsoLike {
475
- ...ProductCard
506
+ pageInfo {
507
+ ...PageInfo
476
508
  }
509
+ totalCount
510
+ }
511
+ }
512
+
513
+ # ============================================
514
+ # Content: Navigation Menus
515
+ # ============================================
516
+
517
+ query Menu($handle: String!) {
518
+ menu(handle: $handle) {
519
+ ...Menu
520
+ }
521
+ }
522
+
523
+ # ============================================
524
+ # Content: URL Redirects
525
+ # ============================================
526
+
527
+ query UrlRedirects($first: Int = 250, $after: String) {
528
+ urlRedirects(first: $first, after: $after) {
529
+ nodes {
530
+ ...UrlRedirect
531
+ }
532
+ pageInfo {
533
+ ...PageInfo
534
+ }
535
+ }
536
+ }
537
+
538
+ # ============================================
539
+ # Store Availability: per-location stock (Shopify parity — BOPIS / multi-location)
540
+ # ============================================
541
+ # Field returns null for single-location shops (storefront can skip the store picker).
542
+ # `near`, `locationType`, and `@inContext(preferredLocationId)` shape the ordering.
543
+
544
+ query ProductStoreAvailability($handle: String, $id: ID) {
545
+ product(handle: $handle, id: $id) {
546
+ id
547
+ handle
548
+ title
549
+ variants {
550
+ ...VariantStoreAvailability
551
+ }
552
+ }
553
+ }
554
+
555
+ # ============================================
556
+ # Locations (store picker UI, Shopify parity)
557
+ # ============================================
558
+
559
+ query Locations(
560
+ $first: Int = 20
561
+ $after: String
562
+ $near: GeoCoordinateInput
563
+ $hasPickupEnabled: Boolean
564
+ $locationType: LocationType
565
+ ) {
566
+ locations(
567
+ first: $first
568
+ after: $after
569
+ near: $near
570
+ hasPickupEnabled: $hasPickupEnabled
571
+ locationType: $locationType
572
+ ) {
573
+ totalCount
574
+ pageInfo {
575
+ ...PageInfo
576
+ }
577
+ edges {
578
+ cursor
579
+ node {
580
+ ...Location
581
+ }
582
+ }
583
+ }
584
+ }
585
+
586
+ query Location($id: ID!) {
587
+ location(id: $id) {
588
+ ...Location
477
589
  }
478
590
  }