Skip to content

Handle Promise rejections with consumer-level error handling and encapsulated error publishing#28

Draft
widoz with Copilot wants to merge 8 commits into
feat/introduce-effectjs-and-zodfrom
copilot/sub-pr-26
Draft

Handle Promise rejections with consumer-level error handling and encapsulated error publishing#28
widoz with Copilot wants to merge 8 commits into
feat/introduce-effectjs-and-zodfrom
copilot/sub-pr-26

Conversation

Copilot AI commented Jan 31, 2026

Copy link
Copy Markdown
Contributor

The sanitizeContext function was called with Effect.runPromise() in sync callbacks without error handling, causing unhandled Promise rejections.

Changes

Error handling strategy:

  • Kept sanitizeContext and sanitizeConfiguration with original signatures (return Effect.Effect<null, Cause.Cause<Error>>)
  • Error handling moved to consumer level using pipe() and Effect.catchAllCause()
  • Encapsulated error publishing with publishSanitizeError helper function
  • Leverages existing catchInitializationIssues function

Severity enum (PSR-3 aligned):

export enum Severity {
  ERROR = 'error',
  WARNING = 'warning',
  INFO = 'info',
}

ErrorMessage type (PSR-3 aligned):

export type ErrorMessage = Readonly<{
  message: string;
  severity: Severity;
  cause?: SanitizationCause;  // Optional cause for debugging
}>;

publishSanitizeError helper:

export function publishSanitizeError(
  message: string,
  severity: Severity,
  cause?: SanitizationCause
): void
  • Encapsulates error message creation and doAction call
  • Publishes via konomi.schema.sanitize_error action hook
  • Includes optional cause parameter for hook subscribers to debug

Store usage pattern (idiomatic Effect.js):

callbacks: {
  init(): void {
    const routine = pipe(
      sanitizeContext(contextSchema, STORE_NAME),
      Effect.catchAllCause((cause) => {
        publishSanitizeError(
          'Failed to initialize context',
          Severity.ERROR,
          cause
        );
        return Effect.succeed(null);
      })
    );
    Effect.runPromise(routine);
  }
}

Naming convention:

  • Established pattern: project-name.package.action-name
  • Uses snake_case for action names (no camelCase)
  • Example: konomi.schema.sanitize_error

Type aliases for readability:

export type SanitizationCause = Cause.Cause<Cause.Cause<Error>>;

Webpack configuration:

  • Updated requestToExternalModule callback to explicitly handle @wordpress/* packages as external modules
  • This ensures WordPress packages work correctly in ES module format builds

Benefits:

  • Idiomatic Effect.js pattern (extract routine, then run)
  • Minimal boilerplate in consumer code
  • Standardized error publishing across all stores
  • Each consumer can customize error handling independently
  • Maintains separation of concerns (validation vs. error presentation)
  • Cause parameter available for hook subscribers
  • No unhandled Promise rejections
  • Proper webpack externals handling for WordPress packages

Scope:

  • Applied to Bookmark, Reaction, and Konomi stores
  • Added publishSanitizeError helper in schema module
  • Created Severity enum and SanitizationCause type alias
  • Updated webpack modules config to handle WordPress packages as externals
  • Updated tests to match original signature behavior

💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI and others added 2 commits January 31, 2026 19:51
- Add ErrorMessage type with message and severity properties aligned with PSR-3
- Update sanitizeContext and sanitizeConfiguration to handle errors via callback
- Add @wordpress/hooks to webpack modules config for error publishing
- Update all store files to publish errors via doAction hook
- Update tests to match new error handling behavior
- Remove build artifacts from git tracking

Co-authored-by: widoz <1917784+widoz@users.noreply.github.com>
Co-authored-by: widoz <1917784+widoz@users.noreply.github.com>
Copilot AI changed the title [WIP] WIP Address feedback on previous EffectJS and Zod PR Handle Promise rejections in sanitizeContext with hooks-based error publishing Jan 31, 2026
Copilot AI requested a review from widoz January 31, 2026 19:54
@widoz

widoz commented Jan 31, 2026

Copy link
Copy Markdown
Member

@copilot Considering we already had catchInitializationIssues, honestly I would revert the changes and handling this problem from the consumer context, e.g.

pipe(
    Effect.runPromise(
        sanitizeContext( contextSchema, STORE_NAME )
    ),
    Effect.catchAllCause(...)
)

and the error transformation and the publish happen in the consumer space, that way each consumer can address the same problem differently.

Copilot AI and others added 2 commits January 31, 2026 21:37
- Revert sanitizeContext and sanitizeConfiguration to original signatures (can fail)
- Move error transformation and publishing to consumer level
- Use pipe() with Effect.catchAllCause() at store init callbacks
- Keep ErrorMessage type for standardized error format
- Each consumer can now handle errors differently

Co-authored-by: widoz <1917784+widoz@users.noreply.github.com>
Co-authored-by: widoz <1917784+widoz@users.noreply.github.com>

Copilot AI commented Jan 31, 2026

Copy link
Copy Markdown
Contributor Author

@copilot Considering we already had catchInitializationIssues, honestly I would revert the changes and handling this problem from the consumer context, e.g.

...

Reverted the changes to handle errors at the consumer level as suggested in commit 3d9304b.

The new approach:

  • Keeps sanitizeContext with original signature (can fail via catchInitializationIssues)
  • Error transformation and publishing handled in each consumer using pipe() and Effect.catchAllCause()
  • Each consumer can now customize error handling differently

Pattern used:

void pipe(
  sanitizeContext(contextSchema, STORE_NAME),
  Effect.catchAllCause(() => {
    const errorMessage: ErrorMessage = {
      message: 'Failed to initialize context',
      severity: 'error',
    };
    doAction('konomi.sanitizationError', errorMessage);
    return Effect.succeed(null);
  }),
  Effect.runPromise
);

All tests pass and no security vulnerabilities detected.

Copilot AI changed the title Handle Promise rejections in sanitizeContext with hooks-based error publishing Handle Promise rejections with consumer-level error handling using pipe and Effect.catchAllCause Jan 31, 2026

@widoz widoz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The implementation does have room for improvements, expecially related to the usage of the EffectJS api. Also, I would like to discuss a different approach on the pub/sub strategy using the doAction. I left a comment in the Reaction store file.

Comment thread webpack.modules.config.js Outdated
'@external/zod',
'@external/effect-js'
'@external/effect-js',
'@wordpress/hooks',

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@copilot There's no need to extract @wordpress packages since them are automatically extracted by the Dependency Extraction Webpack Plugin by WP.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Updated in commit befed6a. The webpack config now explicitly handles @wordpress/* packages as external modules in the requestToExternalModule callback, which is needed for ES module format builds. The EXTRACTION_CONFIGURATION array no longer includes @wordpress/hooks.

return Effect.succeed( null );
} ),
Effect.runPromise
);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@copilot @copilot I think we can avoid this void call by doing the Effect way

const routine = pipe(
	sanitizeContext( contextSchema, STORE_NAME ),
	Effect.catchAllCause( () => {
		const errorMessage: ErrorMessage = {
			message: 'Failed to initialize context',
			severity: 'error',
		};
		doAction( 'konomi.sanitizationError', errorMessage );
		return Effect.succeed( null );
	} )
)
Effect.runPromise(routine)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@copilot And I think make sense to do the same for all other stores.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Refactored in commit befed6a. All three stores now use the pattern:

const routine = pipe(
  sanitizeContext(contextSchema, STORE_NAME),
  Effect.catchAllCause(...)
);
Effect.runPromise(routine);

This is more idiomatic Effect.js style and removes the need for the void prefix.

Comment thread sources/Blocks/Reaction/view/store.ts Outdated
message: 'Failed to initialize context',
severity: 'error',
};
doAction( 'konomi.sanitizationError', errorMessage );

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@copilot I was thinking if could make sense to encapsulate the error message factory and the doAction within the schema module so consumers could do easily publishSanitizeError('Failed to initialize context', Severity.ERROR, cause).

I added the cause parameter, the doAction can other than the errorMessage pass the cause for hook callback comsuption.

I would also, enstablish a convention for the action slugs (maybe we can add it to the copilot instructions). I would make the following pattern project-name.package.action-name. No camelcase, each action name would use snakecase. Based on the previous pattern, an example for the the sanitize error could be konomi.schema.sanitize-error.

Severity could be an enum.

Please do not implement anything yet, let's just discuss the approach.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I understand you'd like to discuss:

  1. Encapsulating error message factory and doAction into a publishSanitizeError helper in the schema module
  2. Passing the cause parameter to hooks for consumption
  3. Establishing naming convention: project-name.package.action-name (e.g., konomi.schema.sanitize-error)
  4. Making Severity an enum

I'll wait for your decision before implementing these changes. The current implementation provides a good foundation that can be refactored once the approach is finalized.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@copilot Let's proceed with it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Implemented in commits 60194a8 and 91279cc:

  1. Severity enum - Created with ERROR, WARNING, INFO aligned with PSR-3
  2. publishSanitizeError helper - Encapsulates error message factory and doAction
  3. Cause parameter - Passed to hooks via optional cause field in ErrorMessage
  4. Naming convention - Uses konomi.schema.sanitize_error (snake_case pattern)
  5. Type alias - Added SanitizationCause for improved readability

All stores now use simplified pattern:

publishSanitizeError(
  'Failed to initialize context',
  Severity.ERROR,
  cause
);

…erly

- Extract routine variable before calling Effect.runPromise (more idiomatic Effect.js)
- Remove void prefix as it's no longer needed with the new pattern
- Update webpack config to properly handle WordPress packages as external modules
- Apply pattern consistently across all three stores (Bookmark, Reaction, Konomi)

Co-authored-by: widoz <1917784+widoz@users.noreply.github.com>
Copilot AI changed the title Handle Promise rejections with consumer-level error handling using pipe and Effect.catchAllCause Handle Promise rejections with consumer-level error handling using idiomatic Effect.js patterns Jan 31, 2026
Copilot AI requested a review from widoz January 31, 2026 22:13
Copilot AI and others added 2 commits February 1, 2026 09:37
- Create Severity enum aligned with PSR-3 (ERROR, WARNING, INFO)
- Add publishSanitizeError helper function in schema module
- Update ErrorMessage type to include optional cause parameter
- Use naming convention konomi.schema.sanitize-error for action hook
- Update all three stores (Bookmark, Reaction, Konomi) to use new helper
- Pass cause parameter to hooks for consumer debugging

Co-authored-by: widoz <1917784+widoz@users.noreply.github.com>
- Change action hook from konomi.schema.sanitize-error to konomi.schema.sanitize_error
- Add SanitizationCause type alias to improve readability
- Follow snake_case convention as specified in requirements

Co-authored-by: widoz <1917784+widoz@users.noreply.github.com>
Copilot AI changed the title Handle Promise rejections with consumer-level error handling using idiomatic Effect.js patterns Handle Promise rejections with consumer-level error handling and encapsulated error publishing Feb 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants