Changelog
All notable changes to @propeller-commerce/propeller-sdk-v2 are documented here.
[0.11.1] - 2026-06-04
Changed
- Package renamed to
@propeller-commerce/propeller-sdk-v2and published to the public npm registry for the first time. Install withnpm install @propeller-commerce/propeller-sdk-v2. The GitHub repository URL is unchanged. Consumers previously installing via thegithub:URL should switch to"@propeller-commerce/propeller-sdk-v2": "^0.11". No API or runtime behaviour changes.
[0.11.0] - 2026-05-23
Adds a per-operation transport hint slot so host frameworks (Next.js, the
Vue projects' Express SSR proxy, Cloudflare Workers, Nitro) can attach
data-cache options to the underlying fetch() call. Additive, fully
backward-compatible — existing callers see no behaviour or type changes.
Note on the next field name: it looks Next.js-coupled but is in fact the
de-facto extension slot used by every runtime that extends
RequestInit — Next, Cloudflare Workers, Nitro (via undici). The SDK
forwards it unmodified and has no Next.js dependency. Hosts that use a
different mechanism (e.g. an application-level proxy keyed by request
headers, like propeller-vue's /api/graphql LRU) set headers on the
client config instead — fetchOptions is purely for the runtimes that
consume hints directly on fetch().
Added
GraphQLFetchOptionstype (client/GraphQLClient.ts). A deliberately narrow shape —{ cache?: RequestCache; next?: { revalidate?, tags? } }— that callers can attach to a single operation to control transport behaviour. Exposed via the main entry:import type { GraphQLFetchOptions } from 'propeller-sdk-v2'.fetchOptionsfield onGraphQLOperation. Optional. When set, the SDK extractscacheandnextfrom it and forwards them tofetch(). The shape happens to match the Next.jsfetchextension, so a Next-aware host can attach{ next: { revalidate: 300, tags: ['product:42'] } }without the SDK taking a Next.js dependency.- Optional fifth parameter
fetchOptionsonrunOperation()(the helper every service method routes through). Service factories that want to expose cache hints can pass them through with no per-service type change.
Why the shape is narrow
We deliberately did NOT type fetchOptions as RequestInit — callers could
have reached method / body / headers / signal and broken SDK
invariants (the HTTP method, the JSON payload, the auth headers, and the
30 s timeout signal are all SDK-owned). Pick<RequestInit, 'next' | 'cache'>
was rejected because next is not a standard lib.dom field — the Pick
would degenerate. The bespoke GraphQLFetchOptions keeps the surface tiny
and Next-agnostic.
Cache-key safety
fetchOptions is NEVER serialised into the GraphQL request body. The wire
payload remains { query, variables, operationName }. Two callers passing
the same operation with different tags hit the same cache entry — no
cache-key pollution.
Migration
See MIGRATION-0.11.0.md for the Next.js wiring
example and a note on exactOptionalPropertyTypes.
[0.10.2] - 2026-05-18
Documentation-quality release. No runtime behaviour, types, or method
signatures changed — only JSDoc @param names. Safe to upgrade with no
migration.
Fixed
- Corrected JSDoc
@paramnames on service methods so they match the actual parameter. Methods aligned to take a singlevariables: <Op>Variablesobject still documented the old conceptual names (@param input,@param id,@param categoryId, …); these now read@param variables. This removes ~350 TypeDoc "@param … which was not used" warnings and makes the generated API reference and IDE parameter hints accurate. Correctly documented multi-argument methods (e.g.updateAttribute(id, input)) are unchanged.
[0.10.1] - 2026-05-18
Documentation-only release. No SDK code, types, or runtime behaviour changed
— the published package is byte-identical to 0.10.0 apart from the version
number. Safe to upgrade with no migration.
Changed
- Rebuilt the documentation site from scratch as a Docusaurus 3 app (replacing
the previous TypeDoc default-theme output) so it matches
docs.propeller-commerce.com: branded navbar/footer, dark mode, local search, hand-authored guides plus the full generated API reference. Published to the same URL (https://propeller-commerce.github.io/propeller-sdk-v2/). - Dropped the generated docs from the npm tarball (
documentation/**/*removed frompackage.jsonfiles) — the package is now leaner; the docs live only on GitHub Pages.
[0.10.0] - 2026-05-18
Breaking release. Because the changes below are breaking, pin an exact version (see Reverting in the migration guide).
Architectural overhaul. Single coordinated breaking release addressing the class-wrapper, tree-shaking, and deprecated-symbol debt accumulated through 0.x. See MIGRATION-0.10.0.md for the consumer playbook.
Breaking
- Wrapper classes → interfaces. Every type in
src/type/*.ts(721 files — ~400*Inputtypes and ~321 entity/response types) is nowexport interface Xinstead ofexport class XwithObject.assignconstructors. Removed ~2,291 auto-generated getter methods.new Product(…),product instanceof Product,product.getName('NL')etc. no longer compile. Use direct field access (product.names) and the newgetLocalized()helper for localized fields. Audited consumers (propeller-next,propeller-vue) already used direct field access throughout — call-site change is zero. clientProxy export removed fromsrc/client/GraphQLClient.ts. UsecreateClient(config)and pass the client explicitly to service factories.- Service helpers no longer discard partial responses.
runOperation(andclient.query/client.mutate) now throwGraphQLOperationErroronly when the response has errors AND nodata. If the server returns partial data alongside errors (the normal GraphQL contract), the data is returned and the errors are logged via the client debug channel instead of thrown. Useclient.execute()for the raw{ data, errors }. Previously any non-emptyerrorsarray threw, discarding partial data. - Deprecated
GraphQLClientConfigkeys removed:customFragmentsPath,customQueriesPath,customMutationsPath,allowCustomOverride,skipFragmentResolution. These were no-ops since v0.4.0 (fragment inlining moved to build time) and have been emitting warnings. BaseServiceclass removed. Services no longer inherit from a base class; each imports its own GraphQL documents and uses the internalrunOperation(client, doc, opName, vars)helper.- Service methods aligned to their GraphQL operation's variables. Every
service method whose parameters could not express its operation's full set
of declared variables now takes a single
variables: <Op>Variablesobject whose fields mirror the operation's declared variables and their nullability exactly ($x: T!→x: T,$x: T→x?: T). The canonical example:productService.updateProduct(input)→updateProduct({ productId, input })— theproductUpdatemutation declares$productId: Int!and$input: UpdateProductInput!, but the old signature could only sendinput, soproductIdwas undeliverable. 117 methods across 41 services were converted; both class form and factory form change in lockstep. Scalar-convenience methods that already map 1:1 onto their operation's single variable (e.g.getProductSurcharges(productId),loginService.login(input)) and methods that already took a correct*Variablesobject are unchanged.logoutService.logout()no longer takes auserIdargument — thelogoutmutation declares no variables. The schema-faithful<Op>Variablesinterfaces are exported from the package root; a handful of hand-authored interfaces that only existed to model a too-narrow argument set were retired in favour of the generated ones. - Bundled-map preload removed.
new GraphQLClient(...)no longer eats the full ~46k-linequeries.ts+mutations.tsbundle on construction. WithsideEffects: false(already set) + per-op imports, a consumer that imports onlyproductServiceships only its query strings. generated/queries.ts,generated/mutations.ts,generated/fragments.tsremoved. Replaced by per-op modules undergenerated/operations/<op>.ts, each exporting a singledocumentstring.
Added
createClient(config)— preferred client factory, mirrorsnew GraphQLClient(config).GraphQLClientConfig.defaultLanguage— ISO 639-1 default locale. Service factory methods that accept an optional top-levellanguagevariable and aren't given one now fall back to this value (variables.language ?? client.getDefaultLanguage()) before the request is sent. An explicitlanguageon the call always wins. (In the initial v0.10.0 draft this config field was stored but never read by any call site — now wired through.)GraphQLOperationError.document— the exact GraphQL query/mutation string that produced the error, for verbatim logging of failing operations.getLocalized(values, lang, fallback?)— single helper that replaces ~100 per-classgetNameLocalized('NL')-style methods.AnyAddress— discriminated union ofAddress | CartAddress | OrderAddress | TenderAddress | WarehouseAddressfor UIs that render any address shape.- Service factory functions — every
XxxServiceclass now also has a lower-case factory export (xxxService(client) → { method1, method2, … }). The class form is preserved as a thin BC wrapper. runOperation(client, doc, opName, vars)exported fromsrc/service/— internal helper used by every service factory.scripts/fetch-schema.js— POST-based introspection helper that writesschema.jsonas plain UTF-8 (fixes the legacy UTF-16 LE pitfall). ReadsPROPELLER_ENDPOINT+PROPELLER_API_KEYfrom.env.tests/integration/schemaAlignment.test.ts— validates every one of the 459 generated GraphQL operations against the upstream schema. The allowlist is empty ({}) — every operation matches the live schema with zero drift. The test is now a strict regression guard: any new drift fails the build.tests/integration/treeShakeBudget.test.ts— rewritten to bundle a{ createClient, productService }fixture with esbuild (bundle + minify + tree-shaking) and assert real output size. Measured at v0.10.0: ~73 KB minified, ~11 KB minified+gzipped (budgets 150 / 30 KB). Replaces the prior naive transitive-import file-size sum, which over-counted ~13×.scripts/build-schema-allowlist.js— one-shot to rebuild the allowlist.
Deprecated
createGraphQLClient(config)— usecreateClient(config).initializeClient(config)+getClient()(the global-singleton pattern) — create a client withcreateClient()and pass it explicitly to service factories. All three now carry@deprecatedJSDoc and emit a one-timeconsole.warn(per process, per function) on first use. They remain exported from the package root for backward compatibility and will be removed in a future release.
Removed (dead scripts / files)
scripts/fix-all-jsdoc.jsandscripts/fix-jsdoc.ps1— not wired into build.src/service/BaseService.ts— replaced byrunOperation.MIGRATION-0.9.0.mdfrom repo root — archived todocs/archive/.
Internal
- Build pipeline (
scripts/build-graphql-bundle.js) now emits per-op modules instead of one giant map. Fragment inlining logic is unchanged. - Source maps and declaration maps are no longer emitted to
dist/cjs(consumers don't need them; saves ~600 KB). tsconfig.cjs.jsonsetsdeclarationMap: false,sourceMap: false.- Schema drift fully resolved. The 177-op allowlist (latent v0.9.x drift
exposed by the new alignment test) was burned down to zero: authored 46
missing GraphQL fragments (schema-driven, cycle-broken, required-arg-aware),
added missing variable declarations to 71 operations (resolved from the
schema via
graphqlTypeInfo), and fixed ~10 individual source bugs (wrong fragment-on-type spreads, a missing...spread operator, removed fields,Order!selection-set,Taxcodeschema-casing). - BC class-shim methods are now fully typed. Every
XxxServiceclass wrapper method mirrors its factory counterpart's parameter and return types instead of(arg: any)— consumers on the class form regain full type-checking. - Stale
@extends BaseServiceJSDoc lines stripped from 22 service files; the no-op{ ...variables }defensive spread dropped from factory call sites. - Variables alignment tooling.
scripts/build-graphql-bundle.jsnow emitssrc/generated/operationVariables.ts(one schema-faithful<Op>Variablesper operation) plus an explicitoperationVariablesPublic.tsre-export barrel listing only names the generator declared — so a generated name can never ambiguously re-export against a hand-authored service interface.scripts/lib/align-classify.jsis the shared classifier (single source of truth) used by the generator and, originally, the one-shot AST-surgical alignment codemod (now spent — its output is committed and the driver lives indocs/history/for provenance, not in the build).npm run validatenow runscheck:drift(regenerate + byte-compare) so a stale generated surface fails the build.ClusterService.getClusterConfigis hard-exempt and byte-identical to its prior form.
Migration
See MIGRATION-0.10.0.md. Most consumers change ~10
lines of lib/api.ts and nothing else.
[0.9.0] - 2026-05-15
Full schema-sync release. Re-introspected from the upstream API and removed every point of drift between the SDK and the live schema. Service method names are preserved across the board — only the GraphQL operation strings inside executeQuery/executeMutation and the matching .graphql files were renamed.
Breaking
- 42
@deprecatedfields removed fromsrc/type/*.ts(Product.mediaImages/Videos/Documents, Cart.shopId/userId/user, Order.shopId/externalId/date, Channel.channelId/shop/defaultLetterId, Company.path/slug/inheritProductList, Cluster.drillDowns, OrderStatus.isExportable/Confirmable/Archivable, Pricesheet.contacts/customers/companies, Orderlist.users/companies, Inventory.dateModified, InventoryResponse.dateModified/messages/total, AdminUserTenant.name, Carrier.shippingCost, Contact.debtorId, CompanyContactSearch.debtorId, OrderTotals.orderId, Price.cost, Surcharge.shopId, Tax.shopId, Tender.siteId/shopId, TenderCarrier.amount, Warehouse.shopId, InventoryDeleteResponse.messages, Product.shortName). Their generated getters and fragment-field references are stripped in the same pass. - ~12 service methods removed for operations no longer in the schema:
- Entire services deleted:
ExternalAddressService,SparePartService,SiteService,SparePartsMachineMediaService,MediaService. - Methods removed from kept services:
AddressService.getAddressesByUserId,ClusterService.getClusters,UserService.updateUser/createUserAddress/updateUserAddress/deleteUserAddress,MediaAttachmentService.getMediaAttachment(s),WarehouseAddressService.getWarehouseAddress(es).
- Entire services deleted:
- Return-shape fixes for ~30 services where the TS return type didn't match the schema. Notable cases:
CategoryService.getCategories(): Promise<Category[]>→Promise<CategoryResponse>CategoryService.addProductsClustersToCategory(): Promise<Category>→Promise<CategoryAddProductsClustersResponse>CategoryService.removeProductsClustersFromCategory(): Promise<Category>→Promise<CategoryRemoveProductsClustersResponse>AttributeService.getAttributeResultByX(): Promise<AttributeResult>→Promise<AttributeResultResponse>(6 methods)InventoryService.getInventory(): Promise<Inventory[]>→Promise<InventoryResponse>CompanyService.importCompaniesCsv(): Promise<Company[]>→Promise<CsvImportResponse>DiscountService.importDiscountsCsv(): Promise<any>→Promise<CsvImportResponse>BundleService.addItemsToBundle(): Promise<Bundle>→Promise<BundleItem[]>BusinessRuleService.getBusinessRuleFieldDefinitions(): Promise<any>→Promise<BusinessRuleFieldDefinitionGroup>CartService.bulkUpdateCartItems(): Promise<Cart>→Promise<BulkResponseData>ClusterConfigService.getClusterConfig(): Promise<ClusterConfig>→Promise<ClusterConfigResponse>(also: arg renamed fromidtoclusterConfigId)ClusterConfigService.createClusterConfig(): Promise<ClusterConfig>→Promise<ClusterConfigResponse>ClusterConfigService.updateClusterConfigSetting()— signature now(clusterConfigId, settingId, input); returnsPromise<UpdateClusterConfigSettingResponse>.ClusterService.getClusterConfig(): Promise<Cluster>→Promise<ClusterConfigResponse>(also: arg renamedclusterId→clusterConfigId)EventActionConfigService.getEventActionConfig(): Promise<EventActionConfigResponse>→Promise<IEventActionConfig>EventActionConfigService.getEventActionConfigs(): Promise<EventActionConfigResponse[]>→Promise<EventActionConfigResponse>EventActionConfigService.createEventToEmailConfig()/updateEventToEmailConfig(): Promise<EventActionConfigResponse>→Promise<EventToEmailConfig>EventActionConfigService.createEventToWebHookConfig()/updateEventToWebHookConfig(): Promise<EventActionConfigResponse>→Promise<EventToWebHookConfig>GCIPUserService.getGCIPUser(): Promise<any>→Promise<IBaseUser>Media{Image,Video,Document,Attachment}Service.deleteMediaX(): Promise<boolean>→Promise<DeleteMediaXResponse>OrderService.sendOrderConfirmationEmail(): Promise<boolean>→Promise<SendOrderConfirmResponseType>OrderService.getOrderPDF()/getQuotePDF(): Promise<any>→Promise<Base64File>OrderService.getOrderAddress(): Promise<Address>→Promise<OrderAddress>OrderService.getOrderAddresses(): Promise<Address[]>→Promise<OrderAddress[]>PaymentService.deletePayment(): Promise<boolean>→Promise<Payment>PriceService.getDefaultPrice()/explainPrice(): Promise<ProductPrice>→Promise<ProductPrice[]>ProductService.addSurchargesToProduct(): Promise<boolean>→Promise<ConfirmationResponse>ProductService.getProductSurcharges(): Promise<SurchargesResponse>→Promise<SurchargeProductResponse>PurchaseAuthorizationConfigService.getPurchaseAuthorizationConfigs(): Promise<...Response[]>→Promise<...Response>(single paginated response)SurchargeService.deleteSurcharge(): Promise<boolean>→Promise<Surcharge>TemplateService.renderDocumentTemplateToPDF(): Promise<any>→Promise<Base64File>TenderService.addItemToTender()/addItemsToTender(): Promise<Tender>→Promise<TenderResponse>(signatures now takeid: stringfirst)UserService.triggerContactSendWelcomeEmailEvent()/triggerCustomerSendWelcomeEmailEvent(): Promise<any>→Promise<boolean>VerifyTokenService.verifyToken(): Promise<any>→Promise<VerifyToken>TemplateService.getDocumentTemplate()/getEmailTemplate()— id arg type changed fromnumbertostringto matchtemplate(id: String!).
- Type-shape fixes:
BusinessRuleDateExpression.type: string→BusinessRuleExpressionTypesBusinessRuleDateExpression.operator: string→BusinessRuleDateExpressionOperatorsOrderRevision.snapshot: any→OrderTender.revisions: any[]→OrderRevisionResponse
- Input-shape fixes:
OrderRevisionSearchInput.sortInputs: any[]→OrderRevisionSortInput[]TemplateErrorLogSearchInput.sortInputs: any[]→TemplateErrorLogSortInput[]UpdateMediaDocumentInput.uploadDocument: UploadFileInput→UploadFileInput[]UpdateMediaImageInput.uploadImage: UploadFileInput→UploadFileInput[]UpdateMediaVideoInput.video: UploadVideoInput→UploadVideoInput[]SurchargeSearchInput.taxCode: Taxcode→TaxCodeUpdateSurchargeInput.taxCode: Taxcode→TaxCode
Taxcodeenum renamed toTaxCode(matches schema casing). 27 type files updated to import the new name. The compat aliasexport { Taxcode as TaxCode }is removed — consumers must useTaxCode.- 2 interfaces converted to classes for 0.7.0/0.8.0 consistency (gain generated getter methods):
OrderRevisionResponse,TemplateErrorLogResponse.
Internal (no consumer-visible change to method names)
- 10 GraphQL operations renamed at the wire level to match upstream — service method names are unchanged. Mapping:
clusterGetConfig→clusterConfigdocumentTemplate→template,emailTemplate→template(both methods now hit the unifiedtemplatequery)machineUpdate→machineUpsertregisterContact→contactRegisterregisterCustomer→customerRegistersparePartsMachine→machine,sparePartsMachines→machines,sparePartsMachineCreate→machineCreate,sparePartsMachineUpdate→machineUpserttenderAddItem→tenderAddItems(input type nowTenderAddItemsInput—addItemToTender(id, input)adapts the singular input internally;addItemsToTender(id, input)takes the new shape directly)clusterConfigSettingUpdate→clusterConfigUpdateSetting
- Deprecated
.graphqlfiles removed (alongside their service methods or after rename). - Fragments updated to drop fields the schema marks deprecated.
Deprecated (still callable, schema-marked upstream-deprecated)
Service methods now carry @deprecated JSDoc tags so TS consumers see the warning in their IDE:
CartService.setCartUser— usesetCartContact/setCartCustomer.LogoutService.logout,UserService.logout—signOutmutation coming upstream.AddressService.getAddressesByOrderId,OrderService.getAddressesByOrderId— useOrderService.getOrderAddresses.MediaImageService.getMediaImage(s),MediaVideoService.getMediaVideo(s),MediaDocumentService.getMediaDocument(s)— read viamedia.image/images/video/videos/document/documentson parent resource.ShopService.getShop,ShopService.getShops— shop queries will be removed in a future version.BundleService.addItemsToBundle— preferbundleAddItemsAndReturnBundle.EventActionConfigService.publishPasswordResetEmailEvent— useUserService.sendPasswordResetEmail(routes through the event-action-manager and template engine).
Migration
- Run
tsc --noEmitagainst your consumer. Every breakage is a removed-field or shape-change that maps to a documented replacement above. - For paginated return-shape fixes (e.g. CategoryResponse), unwrap with
.itemsto get the old array shape:(await svc.getCategories()).items. - For
TaxCodeenum: search/replaceTaxcode→TaxCodein your code. - For
getOrderAddress(es): replaceAddress/Address[]consumer types withOrderAddress/OrderAddress[]. - For Template methods: id arg is now
string(wasnumber). - For Tender add-item methods: pass the tender id as the first arg.
[0.8.0] - 2026-05-15
Generates getter methods on every response class in src/type/. Each property gets one matching getter; behavior depends on the property's type. The 0.7.0 release restored the classes; this one fills them in.
Added
- Localized-array getters: properties typed as
LocalizedString[],LocalizedStringArray[],LocalizedImage[],LocalizedDocument[],LocalizedAttachment[],LocalizedVideo[], orLocalizedTemplateContent[]get agetX(language: string = 'NL')method. The method matches the requested language, falls back to'NL', and returns the inner value (.value/.values/.originalUrl/.uri/.contentdepending on the container). Plural field names are singularized (product.names→product.getName('NL')). 87 getters of this form. - Class-typed getters: properties whose type is another
src/type/class (FooorFoo[]) get a coercing+memoizing getter. First call wraps a plain object vianew Foo(...)and writes it back to the field; subsequent calls return the same instance. 173 scalar + 153 array getters of this form (326 total). - Pass-through getters: every other property (scalars, enums, interface refs, primitive arrays) gets a one-line
getX(): T { return this.x; }for a uniform method-based API. 1,878 getters. - Test coverage: 5 new tests in
tests/types/Getters.test.tscovering language matching, NL fallback, undefined behavior, class coercion + memoization, and scalar pass-through.
Total: 2,291 getters across 269 classes.
Unchanged
- The 6
Attribute*Valueclasses keep their existingget value() / set value()accessors. The new generated getters for their data fields (id,type,textValues, etc.) sit alongside. JSON.stringifyoutput is unchanged — methods live on the prototype, not as own enumerable properties.- Service return shape is unchanged. Methods come along for free now that the classes have them.
- The base
AttributeValueis still an interface (074d6fd shim — no methods).
Migration
- Consumers can continue using property access (
product.name,cart.items[0].price) without change. - New surface area:
product.getName('NL')for localized resolution,cart.items[0].getPrice()for class-instance coercion (so calling further methods on the result works without manual wrapping). - Re-hydrating from JSON still requires
new Product(serialized)to get the methods back —JSON.parsealone yields plain objects.
[0.7.0] - 2026-05-15
Restores the wrapper-class pattern that 0.5.0 removed. The reason: classes are the natural home for instance methods, and we plan to add per-property getters (e.g. Product.getName(language = 'NL') for LocalizedString[] fields, Product.getPrice() returning a typed ProductPrice) in follow-up work. Interfaces have no runtime presence, so those methods had nowhere to live.
BREAKING
src/type/*.tstypes are classes again (~270 files). Each declaresexport class X { ... constructor(data: Partial<X> = {}) { Object.assign(this, data); } }with the same property set as 0.6.0 (no fields re-added, no fields removed). Definite-assignment markers (!:) are back on required properties.- Services return class instances: every
return data as Xfrom 0.5.0 reverts toreturn new X(data). Array returns usedata.map(x => new X(x)). Consumers that calledObject.getPrototypeOfon responses or relied on identity-with-plain-objects are affected; literal-construction (const x: Product = { id: 1 } as any) still works becausePartial<X>accepts that shape. - The 6 concrete
Attribute{Text,Color,DateTime,Decimal,Enum,Int}Valuetypes are classes again with their pre-0.5.0get value()/set value()accessor shape delegating to the typed backing field.
Unchanged (kept from 0.5.0 / 0.6.0)
- The base
AttributeValueis still an interface — upstream only exposesid, and ourtype/valuediscriminator fields are a TypeScript-only structural shim (introduced in 074d6fd). ConcreteAttribute*Valueclassesimplements AttributeValue. - Enum top-level exports remain — there is no
Enums.*namespace. The qualified call-site form is preserved byimport * as Enums from 'propeller-sdk-v2'. initializeService()stubs remain deleted from every service.- Every 0.6.0 schema-alignment edit stays (removed fields,
@deprecatedmarkers,user/tax/startSessionoperation shapes,Tax.id: string).
Migration
- Consumers consuming SDK responses: no source change required. Class instances satisfy any
Product/Cart/Ordertype position they already use. - Consumers constructing response types in tests or mocks: use
new Cart({ ... })(matches the ≤0.4.0 pattern; was{ ... } as Cartin 0.5.0–0.6.0). - Runtime check:
(await cartService.getCart(...)).constructor.name === 'Cart'now returnstrue. Methods added in follow-up releases will be reachable on every returned instance.
Tests
tests/service/ProductService.test.tsandLogoutService.test.tsreinstatetoBeInstanceOfassertions removed in 0.5.0. All 36 tests pass.
[0.6.0] - 2026-05-14
Schema alignment pass against the live Propeller v2 GraphQL endpoint (https://api.helice.cloud/v2/graphql). The fetched schema had drifted from the SDK's snapshot since v0.2.0 (Feb 19, 2026). This release applies the deletions, surfaces newly-deprecated fields, and updates schema.json.
BREAKING
- 63 type fields removed across
src/type/*.tsinterfaces. These fields no longer exist upstream; any consumer reading them was already gettingundefinedat runtime. TypeScript will now flag the reads at compile time. - 150 enum values removed across
src/enum/*.ts. Most are concentrated inBusinessRule*,Template*,Inventory*, and other admin/configuration enums. UserSearchInputremoved fromsrc/service/UserService.ts. The upstreamuserquery no longer accepts aUserSearchInput; it now takes directid: Intandlogin: Stringarguments. TheUserService.getUsersignature changes fromgetUser(input: UserSearchInput)togetUser({ id?: number; login?: string }). The previously-exportedUserSearchInputinterface is also removed.UserService.getUsersremoved. The upstreamusersquery no longer exists. The localgetUsersmethod was not wired to a real.graphqlfile and not called by either active consumer.TaxService.getTax(id)signature changed fromid: numbertoid: string. UpstreamTax.idisString!.query tax($shopId: Int)argument removed: upstream no longer acceptsshopId.mutation startSession($siteId: Int)argument removed: upstreamstartSessiontakes zero arguments now.
Deprecated
- 26 type fields gained
@deprecatedJSDoc tags carrying the upstreamdeprecationReasontext. These fields are still served by the API but flagged for removal; consumer code should plan to migrate off them. - 4 enum values gained
@deprecatedJSDoc tags for the same reason.
Updated
schema.jsonrefreshed from upstream. Stored UTF-16 LE with BOM per existing convention. Size grew from 8.5 MB to a tighter representation (UTF-8 source converted on write).- Build pipeline (
scripts/build-graphql-bundle.js) re-ran fragment inlining;src/generated/queries.tsandmutations.tsregenerated.
Not added
- 6 new queries, 11 new mutations, 27 new types, and 6 new enums exist upstream (mostly
agent*andgqlApiKey*/restApiKey*surface for AI-agent and API-key admin). These are inventoried but not added in this release. File issues for any that consumer apps need.
Migration
For each type_field_removed_from_ts entry: if your code reads the field, the cleanest migration is to delete the read (the field has been undefined at runtime ever since the upstream removed it). If you genuinely need the data, check the upstream schema for the replacement field name — most removals correlate with a renamed/restructured equivalent.
For getUser: replace getUser({ userId: 123 } as UserSearchInput) with getUser({ id: 123 }). For email-based lookup, use getUser({ login: 'user@example.com' }).
For getTax: change the type of the id parameter from number to string.
[0.5.0] - 2026-05-14
Coordinated breaking-change release. The SDK is now smaller, honest about its types, and uses idiomatic TypeScript shapes. Two consumer apps (propeller-next, propeller-vue) ship matching migration PRs alongside this tag.
BREAKING
- Response types are now
interfaces, not classes. All 270 wrapper classes insrc/type/*.ts(e.g.Product,Cart,Order,Address,ProductsResponse, etc.) are now TypeScript interfaces. TheObject.assign-style constructor and the!:definite-assignment markers are gone.- Migration: anywhere you wrote
new Cart(data)ornew Product(data), replace withdata as Cart/data as Product. Property access (product.id,cart.items, etc.) keeps working identically. instanceof Cartchecks no longer work because the class is gone. Audit-verified: no consumer in eitherpropeller-nextorpropeller-vueusedinstanceofagainst an SDK type. If you have such code outside those repos, you'll need to switch to a structural check (e.g.__typenamediscriminator on the response).
- Migration: anywhere you wrote
- Enums are now top-level exports. The
export * as Enums from './enum'namespace is removed;export * from './enum'takes its place. Existing consumer code using theEnums.X.Yqualified form keeps working iff the import is switched fromimport { Enums } from 'propeller-sdk-v2'toimport * as Enums from 'propeller-sdk-v2'. No call-site edits are required — only the import line. Both consumer repos ship this change in their 0.5.0 migration PRs. initializeService()no-op method removed from all 54 concrete services. The method has always been an empty stub; calling it was a no-op. Consumers callingsomeService.initializeService()must delete the call. Audit-verified: one such call inpropeller-next/app/checkout/page.tsx:101is removed in the consumer migration PR.
Changed
- Service methods that previously wrapped their responses with
new X(data)now returndata as X. Functionally identical (the 0.3.0 wrapper class only ranObject.assign(this, data)and added a prototype). Net effect: zero per-response allocations and slightly smallerdist/. - The six
Attribute*Valuetypes (AttributeColorValue,AttributeDateTimeValue,AttributeDecimalValue,AttributeEnumValue,AttributeIntValue,AttributeTextValue) hadget value()/set value()getters that aliased their concrete value field. As interfaces these are flat fields now —value: anyis declared on each. Reading.valuecontinues to work; the runtime aliasing to the concrete field (colorValue,dateTimeValue, etc.) is no longer automatic — read the concrete field directly.
Migration recipe
Per consumer file:
-import { Enums } from 'propeller-sdk-v2';
+import * as Enums from 'propeller-sdk-v2';
-return new Cart(cartData);
+return cartData as Cart;
-await cartService.initializeService();
Bump the SDK install ref to #v0.5.0 (or pin to the commit SHA during testing).
[0.4.0] - 2026-05-14
Internal cleanup pass that is intentionally non-breaking for existing consumers. The public surface (wrapper-class shape, service method signatures, Enums.* namespace, client singleton) is preserved. Highlights below.
Added
- Dual ESM + CJS build. Bundlers (Next.js, Vite, etc.) now resolve real
importsyntax viadist/esm/; Noderequire()continues to work viadist/cjs/. Driven by a newexportsfield inpackage.jsonwithimport/require/typesconditions. orderEditorMutationsconfig option. Override the built-in list of mutations routed toorderEditorApiKeyin direct mode without an SDK upgrade. The built-in list (orderSetStatus,passwordResetLink,triggerQuoteSendRequest,triggerOrderSendConfirm) remains the default.- Config-validation warnings at construction.
console.warn(always-on, not gated bydebug) whensecurityMode: 'proxy'is combined withapiKey/orderEditorApiKey, whensecurityMode: 'direct'is set withoutapiKey, and when deprecated config fields are supplied. - CI workflow (
.github/workflows/ci.yml) running typecheck, build, and tests on Node 18 / 20 / 22.
Changed
- Build-time fragment inlining.
scripts/build-graphql-bundle.jsnow parses each operation, transitively inlines all referenced fragments at build time, and writes pre-resolved strings tosrc/generated/queries.tsandmutations.ts. The runtime fragment resolver (src/client/fragmentResolver.ts) and the runtimegraphqlparse/print step are gone.graphqlis now a devDependency, not a runtime dependency — consumer bundles shed roughly thegraphqlpackage's footprint. - HTTP error messages now include the response body (truncated to 500 chars). Previously the SDK threw
HTTP error! status: 400and discarded the body, hiding the GraphQL parse error or upstream error text. extractOperationNamestrips leading#comments before matching thequery NAME/mutation NAMEpattern. Anonymous operations now cleanly returnundefinedinstead of misidentifying themselves.- Deprecated
clientProxy export now bindsthiscorrectly. Methods accessed through the singleton (client.query(...)) used to lose theirthiscontext; they now route through a bound function.getClient()remains the preferred path.
Deprecated
customFragmentsPath,customQueriesPath,customMutationsPath,allowCustomOverride. Build-time inlining replaces the runtime override path. These config fields are now silently ignored at runtime and emit a deprecation warning at construction.GraphQLOperation.skipFragmentResolution. No-op since 0.4.0 — there is no runtime fragment resolution to skip. Kept on the interface for source-compatibility.
Removed
src/client/fragmentResolver.ts— runtime fragment AST registry and inlining. Functionality moved to build script.loadGraphQLDirectory/loadFragments/loadQueries/loadMutationsprivate methods onGraphQLClientand the runtimerequire('fs')/require('path')calls they made. Bundlers no longer flag the SDK for accessing the Node filesystem.
Fixed
- README examples now match the actual API:
client.execute({ query, variables })(object form, not positional args),error.errorsonGraphQLOperationError(noterror.response.errors), accurate service count. SECURITY_SOLUTION.mdremoved. Replaced with a "Proxy contract" section in the README that documents the request/response shape consumers need their proxy to support, without referencing a serverless-proxy directory that never shipped.
Migration notes
- No code changes required for typical consumers. Imports, service instantiation (
new ProductService(client)), method calls, and response object shapes (product.id,product.names, etc.) are unchanged. - Bundler users will see a smaller bundle (
graphqlno longer ends up in your dependency graph through this SDK). - If you used
customFragmentsPathor related runtime overrides, see the new build-time approach: editsrc/graphql/fragment/*.graphqland rerunnpm run build:graphql. The runtime config fields remain typed for source-compat but are ignored. - If you relied on
console.logfromGraphQLClient, passdebug: truein config. All internal logs are gated; the new config-validation warnings always fire because they indicate configuration mistakes.
[0.3.0] - 2026-05-14
Architectural hygiene pass. Two behavior changes worth flagging in BREAKING below; the rest is internal cleanup that consumers should not notice except as better behavior.
BREAKING
- GraphQL errors now throw (
C1). When the server returns a non-emptyerrorsarray, services now throwGraphQLOperationError(new export frompropeller-sdk-v2) instead of returning a response whosedataisundefinedand crashing the wrapper-class constructor with a confusingTypeError. Callers wrapping service calls intry/catchwill now receive a typed error witherrors,operationName, andvariables. Same applies toGraphQLClient.query()/mutate()/queryByName()/mutateByName().GraphQLClient.execute()itself still returns the raw response so low-level callers can inspect partial results. - Wrapper-type classes are now public-field classes (
S1). The ~270 response wrapper classes (e.g.Product,Cart,Order,ProductsResponse) were previously implemented withprivate _field+ getter/setter pairs. The private fields brokeJSON.stringify, Redux DevTools serialization, IndexedDB persistence, SSR rehydration, and postMessage transfer. They now use public fields with!:definite-assignment for required properties. Property names are unchanged — consumer code readingproduct.id,product.names, etc. continues to work identically. ThePartial<T>constructor argument is unchanged. The only observable changes are: (1)JSON.stringify(product)now produces a useful object instead of{}, (2)instanceofstill works, (3)data.field!is no longer silently coerced inside the constructor — a missing required field staysundefinedinstead of being typed as if present.
Changed
GraphQLClient.getAccessToken()is now async (Promise<string | undefined>) andisAuthenticated()is now async. The previous sync return path only worked because the implementation hardcodedlocalStorage; with the newgetAccessTokenconfig callback (see Added) the resolver may be async.BaseServiceconstructor no longer parses ~200 fragment ASTs per instance (C3). The parsed fragment registry is now module-scoped and shared across every service and every client in the process.BaseService.executeQuery/executeMutationno longer log to the consumer's console on every call (C4).GraphQLClientinternal logging is now uniformly gated behindconfig.debug. The previously un-gated★ →debug logs inloadQueriesare now off by default.GraphQLClient.executeno longerconsole.warns GraphQL errors. Callers either throw via the higher-level helpers or inspectresult.errorsdirectly.
Added
GraphQLOperationError— exported error class with typederrors: GraphQLErrorEntry[],operationName, andvariablesfields.GraphQLClientConfig.getAccessToken?: AccessTokenProvider(S2). Supply a custom resolver (sync or async) to integrate the SDK with SSR (Next.js cookies /getServerSession), in-memory token stores, refresh flows, orhttpOnlycookie patterns. Defaults to readinglocalStorage['access_token']in the browser.AccessTokenProvidertype export.
Removed
- The duplicate regex-based fragment resolver inside
GraphQLClient(C2). Fragment inlining now goes through a single AST-based path shared by all callers (BaseService,client.execute,client.query,client.mutate). The previous regex resolver was reachable from publicclient.execute/query/mutatecalls and gave different results than the AST resolverBaseServiceused. - Per-instance fragment AST parsing in
BaseService(C3). - Misleading
moduleandbrowserfields inpackage.json(P3). Both pointed at the CommonJS bundle and could mislead bundlers that honormoduleinto treating CJS as ESM. The package is now plain CJS withmain+types; ESM support will land as a separate change with proper conditional exports.
Fixed
- README claimed "52 service classes"; corrected to 56 (
P6).
[0.2.0] - 2026-02-20
Major schema synchronization pass. The SDK is now aligned with the current Propeller v2 GraphQL API.
Added
Enums (src/enum/)
ALLOW_QUOTE_INVALIDATION—YES | NOAttributeAttributeDescriptionTermField—NAME | DESCRIPTIONClusterBulkMoveErrorCode—VALIDATION | DB_INSERT | UNKNOWNCompanySearchIndexTemplate—COMPANY_SEARCH | CONTACT_SEARCH | TEMP_ADDRESS | TEMP_CONTACT_COMPANY | TEMP_ATTRIBUTEOrderRevisionSortField—REVISION_NUMBER | CREATED_ATProductBulkMoveErrorCode—VALIDATION | DB_INSERT | UNKNOWNRESTRICT_SALES_PRICING_VISIBILITY—YES | NOTaxCode—H | L | N | M | CUSTTemplateErrorLogSortField—CREATED_AT | ERROR_TYPE | TEMPLATE_IDTicketSortField— full set of sort fields for ticketsTicketStatus—OPEN | IN_PROGRESS | COMPLETED | ARCHIVEDWarehouseSortField—LAST_MODIFIED_AT | CREATED_AT
Types (src/type/)
BulkDeleteResponse—deletedIds,failedIdsClaimsResetAllResponse—successCount,errorCount,totalProcessed,completedClusterBulkMoveError,ClusterBulkMoveResponseComputedOrderlistsResponseProductBulkMoveError,ProductBulkMoveResponseTenant,TenantCreateInput,TenantUpdateInputTicket,TicketCreateInput,TicketUpdateInput,TicketSearchInput,TicketSortInput,TicketResponse- 25+ new input types:
AddressBulkItemInput,AttributeBulkCreateInput,CartChildItemBulkInput,ChannelCreateInput,ChannelUpdateInput,ClusterBulkMoveInput,ClusterMoveItem,ComputedOrderlistsInput,OrderAddressBulkInput,OrderItemDeleteInput,OrderRevisionByOrderSearchInput,OrderRevisionsInvalidateInput,OrderRevisionSortInput,PricesheetsEffectiveInput,ProductBulkMoveInput,ProductMoveItem,SourceSearchInput,SourceUpsertInput,TemplateErrorLogSortInput,ValuesetCreateItemInput,WarehouseSortInput, and others
GraphQL fragments (src/graphql/fragment/)
AttributeDescriptionFields(critical — was missing, fixing all attributeDescription queries)TicketFields,TicketResponseFieldsTenantFieldsComputedOrderlistsResponseFieldsClusterBulkMoveErrorFields,ClusterBulkMoveResponseFieldsProductBulkMoveErrorFields,ProductBulkMoveResponseFieldsBulkDeleteResponseFieldsOrderRevisionFields
GraphQL queries (src/graphql/query/)
ticket.graphql,tickets.graphqltenant.graphqleventActionConfig.graphqlcomputedOrderlists.graphqlpricesheetsEffective.graphql
GraphQL mutations (src/graphql/mutation/)
- 99 missing mutations generated from schema (tickets, tenants, channels, bulk-move, admin users, order revisions, etc.)
Services (src/service/)
AttributeService— mergedAttributeDescriptionService+AttributeResultServiceinto one; addedgetAttribute,getAttributes,createAttribute,updateAttribute,deleteAttributeTicketService— full CRUD for ticketsTenantService— full CRUD for tenants
Changed
Types
Channel— addeddescriptions,createdAt,lastModifiedAt,tenantfieldsCluster— addedshortNames: LocalizedString[]Product— addedshortNames: LocalizedString[]TenderCarrier—idcorrected tonumber;amountis now requiredCompany,Customer,User— removed deprecatedparentUsergroupId,usergroup,usergroupPathfields (Usergroup removed from schema)
Services
CartService— replaced deletedCart*Variablesexternal types with locally-defined interfacesCategoryService— replaced deletedCategoryInputwithanyClusterService— replaced deletedCluster*Variableswith local interfaces usingClusterCreateInput/ClusterUpdateInputExternalAddressService— replaced deletedExternalAddress*types withany(type removed from schema)OrderService— replaced deletedOrderInputwithanyPaymentService— replaced deletedPaymentInputwithanySiteService— replaced deletedSitetype withany(type removed from schema)SurchargeService— removed unusedSurchargeInputimport
Removed
Enums
AttributeGroupType,DocumentType,InvoiceStatus,LogLevel,NotificationType,PaymentStatus,QuoteStatus,UsergroupSortableField
Types (~70 files removed — all absent from current schema)
- All
AttributeGroup*,AttributeSet*types - All
Cart*Variables,CartPurchaseAuthorizationRequestInput - All
Category*Input(CategoryInput, CategoryAttributeInput, CategoryAttributeValueInput) - All
Cluster*Input/ClusterInput/Cluster*Variables - All
Company*Input,Contact*Input,Customer*Inputattribute value inputs Document,DocumentInput,ExternalAddress*,Invoice,InvoiceInputLog,LogInput,Notification,NotificationInputOrderInput,PaymentInput,ProductInput,QuoteInput,ShipmentInput,Site,SurchargeInput- All
Usergroup*,UserAddress*Input
GraphQL files
- Queries:
addressesByUserId,clusterGetConfig,externalAddress,site,usergroup,usergroups - Mutations:
externalAddressCreate,externalAddressUpdate,userAddressCreate,userAddressDelete,userAddressUpdate,usergroupCreate,usergroupUpdate
[0.1.23] and earlier
Initial SDK release and iterative improvements prior to the 0.2.0 schema sync.