Repository navigation
v5.0.0
Major Changes
-
#428
28773deThanks @korsvanloon! - Scope the/meandas-associateendpoints to the caller, and fail closed.This is a breaking change. Both scopes used to answer from the whole
collection:/me/orders/{id}returned any order, and an associate-scoped
request accepted anyassociateIdand ignored the business unit in the path.
A test asserting that a shopper cannot read someone else's order, or that an
associate cannot see a colleague's cart, passed whether or not the code under
test actually scoped its request./menow answers for the customer or anonymous session the bearer token was
issued for, and returns403 insufficient_scopewithout one. A resource
belonging to someone else is404, not a leak. Resources created through/me
are stamped with the caller.as-associateresolves the associate named in the path against the business
unit named in the path, collects the permissions of their AssociateRoles, and
enforces all 47 of them.Mymeans the resource's customer is the acting
associate;Othersmeans a different customer in the same business unit. A list
request with only theMypermission is narrowed rather than refused; anything
else missing returns403 AssociateMissingPermissioncarrying the permissions
that would have sufficed. A resource of another business unit is404.Migrating. Tests that call these endpoints now need to say who is calling.
A new@labdigital/commercetools-mock/testingentrypoint exports
customerSession,loginCustomerandanonymousSessionfor/me, and
createAssociateScopefor the associate scope, which seeds the customer,
associate role and business unit in one call.Identity resolution no longer depends on
enableAuthentication: the mock always
reads the identity from a token it issued, so scoping works without turning
authentication on.Also in this change, because the scopes are unusable without them:
POST /oauth/{projectKey}/anonymous/tokenhonours a suppliedanonymous_id
instead of always generating one.PaymentDraft.customerandanonymousIdare stored.- An order and a quote request created from a cart carry the cart's
businessUnit. GET /me/active-cartanswers for the caller instead of returning the first
active cart in the project.
Minor Changes
-
#438
88f3808Thanks @korsvanloon! - Support the seller's side of the quote flow:- Staged quotes and quotes are now stored when created, so they can be read and updated afterwards.
- A staged quote is created from a submitted quote request only, checks
quoteRequestVersion, prices the offer in a copy of the requested cart, and honoursquoteRequestStateToAccepted. - A quote is created in the
Pendingstate (it wasAccepted) from anInProgressstaged quote only, checksstagedQuoteVersion, carries the staged quote'svalidTo,sellerComment, business unit and store, and honoursstagedQuoteStateToSent. - New staged quote update actions:
changeStagedQuoteState,setSellerCommentandsetValidTo. - New quote update actions:
changeQuoteStateandrequestQuoteRenegotiation.DeclinedForRenegotiationcan only be reached through a renegotiation request.
-
#442
74ac7b5Thanks @mvantellingen! - Support thesetCarrierupdate action on shipping methods -
#435
7a33c6bThanks @robertmoelker! - Support custom fields on addresses.BaseAddressis polymorphic between read and write, so address drafts can carry acustomfield holding aCustomFieldsDraft. Drafts and update actions carrying such an address (customer, business unit, cart, order and channel) now resolve it toCustomFieldsagainst the referenced type, returning a 400ReferencedResourceNotFoundwhen the type does not exist. In strict mode the generated draft schemas accept and validatecustomon an address.Customer and business unit creation, and the
addAddresscustomer action, now go through the sharedcreateAddresshelper, which means the addresscountryfield is enforced there as well. -
#430
2e8ae39Thanks @mvantellingen! - PersistexpansionPaths,dependenciesandadditionalContextwhen creating
an API Extension.The Extension repository only copied
key,timeoutInMs,destinationand
triggersout of the draft, so the three fields added by the 2026-03-12 API
release were silently dropped on create.POST /{projectKey}/extensionswith
expansionPathsreturned an extension without them, which made it look like
the field was rejected. ThesetExpansionPaths,setDependenciesand
setAdditionalContextupdate actions were already implemented, so only the
create path was affected.dependenciesare now resolved through the storage layer like every other
resource identifier, so they can be given bykeyas well as byid(on both
create andsetDependencies, which previously assumedidwas set) and an
unknown dependency returns aReferencedResourceNotFounderror instead of a
reference withid: undefined.
Patch Changes
-
#442
f45820eThanks @mvantellingen! - Update dependencies -
#433
74adf7cThanks @robertmoelker! - Update astro (docs) to v7.* for security reasons -
#432
756eb56Thanks @robertmoelker! - Enforce BusinessUnit key uniqueness within a project. Creating a business unit with a key that is already taken now returns a 400DuplicateFielderror on thekeyfield instead of silently storing a second unit under the same key. -
#432
756eb56Thanks @robertmoelker! -clear()now also resets the auth store. Tokens issued before aclear()used to stay valid and keep resolving to customers and anonymous sessions that no longer existed, leaking identity between tests. Tests that issue a token (for example throughcustomerSession) must do so after eachclear(). -
#436
8a8a282Thanks @jsm1t! - Fixcontains any/contains allthrowing on resources where the field is not
set.The handler rejected any non-array value, so a predicate such as
custom(fields(orderNumbers contains any ("R-123")))raised
The field 'orderNumbers' does not support this expression.as soon as one
resource in the collection lacked the field — failing the entire query rather
than filtering that resource out. Real commercetools treats an unset set as
having no members, so it simply does not match.An unset (
undefinedornull) field now evaluates tofalse. A field that is
present but is not a set still raises aPredicateError, since that is a
genuine type mismatch. -
#434
cb56f14Thanks @robertmoelker! - Scope customer email uniqueness to the stores a customer is assigned to. The same email can now be used in different stores, matching commercetools behaviour. Customers created through an in-store endpoint are assigned to that store, and the in-store password flow only matches customers of that store.Implement the
addStore,removeStoreandsetStorescustomer update actions, which re-validate email uniqueness for any store scope the customer newly enters (including becoming a global customer again).Store resource identifiers are now validated by
keyas well as byid, so referencing a non-existent store in a draft returns a 400ReferencedResourceNotFounderror instead of silently passing through. This also fixesgetStoreKeyReference, which previously always failed forid-based references.In-store endpoints (
/{projectKey}/in-store/key={storeKey}/...) now return a 404ResourceNotFoundwhen the store in the path does not exist, matching commercetools. -
#439
b120400Thanks @jsm1t! - Support the infixnot inoperator in query predicates.A predicate such as
custom(fields(externalOrderType not in :hiddenOrderTypes))
failed withUnexpected token: not, becausenotwas only understood as a
prefix (not (...)). Real commercetools documentsage not in (42, 43, 44)as
a membership check, sonot innow matches every resource thatinwould not.The prefix
not (...)form also forwards query variables to the negated
expression now, sonot (field in :values)no longer ignores:values.