Skip to content

Product Mutation Subscriptions (CDC)#3639

Merged
CarsonF merged 1 commit intodevelopfrom
cdc/product
Feb 12, 2026
Merged

Product Mutation Subscriptions (CDC)#3639
CarsonF merged 1 commit intodevelopfrom
cdc/product

Conversation

@CarsonF
Copy link
Member

@CarsonF CarsonF commented Feb 10, 2026

No description provided.

Base automatically changed from eng-mut-specialization to develop February 10, 2026 23:41
@coderabbitai
Copy link

coderabbitai bot commented Feb 11, 2026

📝 Walkthrough

Walkthrough

Consolidates product mutation DTOs into a single product-mutations.dto.ts with type-specific Created/Updated/Deleted variants; adds ProductChannels and subscriptions; new resolvers for mutation links and updatedKeys; several resolvers/services updated to publish typed mutation events and to load products via ProductLoader.

Changes

Cohort / File(s) Summary
Product DTO consolidation & exports
src/components/product/dto/product-mutations.dto.ts, src/components/product/dto/index.ts, src/components/product/dto/create-product.dto.ts, src/components/product/dto/update-product.dto.ts, src/components/product/dto/delete-product.dto.ts
Added consolidated per-type mutation DTOs (Created/Updated/Deleted) and updated re-exports; removed older wrapper DTOs and adjusted nullability/field typings in create/update DTOs.
Resolvers: mutations, links, updatedKeys, progress
src/components/product/product.resolver.ts, src/components/product/product-mutation-links.resolver.ts, src/components/product/product-updated.resolver.ts, src/components/product-progress/create-product-connection.resolver.ts
Mutation return types switched to per-type DTOs; added resolver to load product on ProductMutation via @Loader(ProductLoader); added updatedKeys field resolver; availableVariants now receives productId and uses Loader to load product.
Channels & subscriptions
src/components/product/product.channels.ts, src/components/product/product-mutation-subscriptions.resolver.ts
Introduced ProductChannels with typed created/updated/deleted channels and publish helpers; added subscription resolver exposing per-type and aggregated product mutation subscriptions with permission filtering and payload mapping.
Service & module wiring
src/components/product/product.service.ts, src/components/product/product.module.ts
ProductService now injects ProductChannels and publishes typed create/update/delete events (using DateTime/Identity metadata); ProductModule registers new resolvers and channels as providers.
New public resolvers/helpers & utilities
src/components/product/product.channels.ts, src/components/product/product-mutation-links.resolver.ts, src/components/product/product-updated.resolver.ts
New exported classes and resolver methods to support mutation linking, broadcasting, and updated-key introspection.
Misc imports & loader usage
src/components/product/...
Widespread import changes: added Loader/LoaderOf usage, ProductLoader, Identity, and DateTime across product-related files to load products and enrich mutation payloads.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description check ⚠️ Warning The pull request description is entirely empty. The template requires at least a Monday task link or reason, and a description of the changes. Neither section has been completed by the author. Add a comprehensive description including a Monday task link (or reason if no task exists), and describe the changes being made, impact, and any testing performed.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Product Mutation Subscriptions (CDC)' clearly summarizes the main change - adding subscription functionality for product mutations using change data capture.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch cdc/product

No actionable comments were generated in the recent review. 🎉

🧹 Recent nitpick comments
src/components/product/product-mutation-subscriptions.resolver.ts (1)

53-109: Consider extracting the repetitive payload-mapping into a helper.

All nine per-type subscription methods (directScriptureProductCreated, derivativeScriptureProductCreated, otherProductCreated, and similarly for updated/deleted) follow an identical pattern — only __typename and the channel method differ. A small helper (e.g., mapPayload<T>(__typename, payload)) would reduce ~150 lines of near-identical destructuring/mapping.

src/components/product/product.resolver.ts (2)

279-294: No-change default payload is a pragmatic approach, but clients will see a "successful update" with empty diffs.

When the service detects no changes and returns without payload, the default provides previous: {}, updated: {}, at: DateTime.now(), by: currentUser. This means updatedKeys will return [], signaling a no-op — which is reasonable. However, at and by will reflect the current context rather than an actual mutation event.

If this is intentional (treat it as an acknowledged no-op), this is fine. Otherwise, consider having the service return a distinct result that the resolver can handle differently (e.g., return the same product without the mutation wrapper).


345-366: Delete mutation __typename resolution with ternary chain is correct but could use a mapping.

The ternary chain maps resolveProductType output to the deleted __typename. A lookup map would be slightly more maintainable:

Optional: map-based approach
-    const productType = resolveProductType(product);
-    return {
-      __typename:
-        productType === DirectScriptureProduct
-          ? 'DirectScriptureProductDeleted'
-          : productType === DerivativeScriptureProduct
-            ? 'DerivativeScriptureProductDeleted'
-            : 'OtherProductDeleted',
+    return {
+      __typename: `${resolveProductType(product).name}Deleted` as const,

Though this couples to the class naming convention, so the explicit ternary is arguably safer.

src/components/product/product.channels.ts (2)

40-44: Type aliases are identical today — consider a clarifying comment.

DirectScriptureProductMutationPayload, DerivativeScriptureProductMutationPayload, and OtherProductMutationPayload are all plain aliases for ProductMutationPayload. If the intent is to let them diverge later, a brief comment would signal that to future readers and prevent accidental collapse back into a single type.


157-169: Unconstrained generic T on private helpers.

forAllActionChannels<T> and forAction<T> accept an unconstrained generic that is disconnected from both payload and the channel's actual event type. This works at runtime because the channels are structurally typed at publish time, but it silently erases type safety within these private methods — any misuse inside the class would not be caught.

Since these are private and the public surface is correctly typed, this is low-risk, but constraining T extends ProductMutationPayload (or removing the generic and returning Channel<unknown>) would make the intent clearer.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/components/product/product.service.ts`:
- Around line 434-445: The previous-value payload is built with
DerivativeScriptureProductUpdate.pickPrevious but mistakenly passes
simpleChanges (which omits produces and scriptureReferencesOverride) in
updateDerivative; make it consistent with updateDirect by passing the full
changes object to pickPrevious so that previous includes prior produces and
scriptureReferencesOverride values, i.e. update the call in updateDerivative to
use changes (not simpleChanges) and verify
DerivativeScriptureProductUpdate.fromInput(changes) and
pickPrevious(currentProduct, changes) align.
🧹 Nitpick comments (2)
src/components/product/product-mutation-subscriptions.resolver.ts (1)

53-109: Consider extracting the repeated payload-mapping logic.

All 9 per-type subscription methods share the same destructure-and-remap pattern (project→projectId, engagement→engagementId, product→productId, ...rest). A small helper could reduce the boilerplate:

♻️ Example helper extraction
private mapPayload<T extends string>(
  __typename: T,
  { project, engagement, product, ...rest }: ProductMutationPayload,
) {
  return { __typename, projectId: project, engagementId: engagement, productId: product, ...rest } as const;
}

Then each subscription becomes:

- map(({ project, engagement, product, ...rest }): DirectScriptureProductCreated => ({
-   __typename: 'DirectScriptureProductCreated',
-   projectId: project,
-   engagementId: engagement,
-   productId: product,
-   ...rest,
- })),
+ map((p) => this.mapPayload('DirectScriptureProductCreated', p)),

Also applies to: 120-176

src/components/product/product.channels.ts (1)

62-92: The conditional type on the payload parameter is impressive but dense.

The ReturnType<ProductChannels[Method]> extends Channel<infer T extends ProductMutationPayload> ? Omit<T, 'by'> : never construct ensures compile-time correctness of the payload shape per action/type combination, which is great. The trade-off is readability — a brief doc comment explaining the intent of this type gymnastics would help future readers.

@CarsonF CarsonF added the 💥 Breaking Change Introduces changes that are not backward compatible and may require modifications in other systems label Feb 11, 2026
@CarsonF CarsonF merged commit 402bb85 into develop Feb 12, 2026
16 of 17 checks passed
@CarsonF CarsonF deleted the cdc/product branch February 12, 2026 15:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

💥 Breaking Change Introduces changes that are not backward compatible and may require modifications in other systems

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant