Skip to content

fix: add missing SiteConfig service override typings and definitions - #281

Open
vkumar-sonata wants to merge 7 commits into
openedx:mainfrom
vkumar-sonata:fix/site-config-service-overrides
Open

fix: add missing SiteConfig service override typings and definitions#281
vkumar-sonata wants to merge 7 commits into
openedx:mainfrom
vkumar-sonata:fix/site-config-service-overrides

Conversation

@vkumar-sonata

@vkumar-sonata vkumar-sonata commented Jul 21, 2026

Copy link
Copy Markdown

Description

This PR aligns the SiteConfig TypeScript definitions with the existing runtime implementation.

The runtime initialize() function supports overriding the default service implementations through properties defined on SiteConfig:

  • loggingService
  • analyticsService
  • authService

However, these properties are currently not represented in the SiteConfig TypeScript definitions. As a result, consumers receive TypeScript compilation errors when attempting to register supported service overrides through site configuration.

Fix

Add the missing service override definitions to OptionalSiteConfig so that the TypeScript API matches the existing runtime behavior.

Validation

  • TypeScript compilation succeeds
  • No runtime changes introduced

Context

Discovered while attempting to configure a custom logging service.

LLM usage notice

Built with assistance from Copilot.

Closes #293

@diana-villalvazo-wgu
diana-villalvazo-wgu marked this pull request as ready for review July 22, 2026 15:56

@arbrandes arbrandes left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The problem this PR identifies is real. initialize() reads loggingService, analyticsService, and authService off the site config, but OptionalSiteConfig never declared them, so setting one in a site.config.tsx typed as SiteConfig is an excess-property error.

However, the interfaces added here don't fix it. They declare instance shapes where the runtime requires constructors, and the method names don't correspond to any service in the repo.

The direction that would work is an instance contract per service plus a constructor type wrapping it, with the config keys referencing the constructor type. Logging is the cheap illustration, since runtime/logging/types.ts already has the contract:

export type LoggingServiceClass = new (options: { config: SiteConfig }) => LoggingService;

For the other two, the serviceShape blocks in configureAnalytics and configureAuth are the authoritative method lists.

One smaller pointer. Co-locating each contract with its service rather than in root types.ts would match how SlotOperation is handled at types.ts:4. The tradeoff is reach: root types.ts is already public via index.ts, whereas none of the logging, analytics, or auth barrels export types, so co-locating means wiring that up as well.

On validation: a successful build doesn't exercise any of this. The repo typechecks either way because nothing here assigns to those keys, and consumer builds run ts-loader with transpileOnly: true (tools/webpack/common-config/all/getCodeRules.ts:16-18), so a green consumer build proves nothing either. A site.config.tsx that sets one of these to a real service class, typechecked and then booted, would.

Comment thread types.ts Outdated
Comment thread types.ts Outdated

@arbrandes arbrandes left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

A few more change requests, if you don't mind. Thanks for bearing with me!

Comment thread runtime/auth/types.ts Outdated
setAuthenticatedUser(authUser: Record<string, unknown>): void,
fetchAuthenticatedUser(options?: Record<string, unknown>): Promise<Record<string, unknown> | null>,
ensureAuthenticatedUser(redirectUrl?: string): Promise<Record<string, unknown>>,
hydrateAuthenticatedUser(): Promise<null>,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should return Promise<void>, not Promise<null>.

Neither implementation resolves to null, and as written the type rejects MockAuthService (TS2419: Type 'void' is not assignable to type 'Promise<null>'). MockAuthService.js:270 is a jest.fn() wrapping a callback with no return; AxiosJwtAuthService passes only on a stale JSDoc @returns {Promise<null>} above AxiosJwtAuthService.js:293, while its body returns undefined. runtime/auth/interface.js:249-250 awaits the result and discards it.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed. Changed return type from Promise<null> to Promise<void>.

Comment thread runtime/auth/types.ts Outdated
Comment on lines +8 to +11
getAuthenticatedUser(): Record<string, unknown> | null,
setAuthenticatedUser(authUser: Record<string, unknown>): void,
fetchAuthenticatedUser(options?: Record<string, unknown>): Promise<Record<string, unknown> | null>,
ensureAuthenticatedUser(redirectUrl?: string): Promise<Record<string, unknown>>,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Use User (types.ts:153) instead of Record<string, unknown> for the user-data methods - SiteContext.tsx:23 already does this. It is technically too strict by exactly one field, avatar, but it looks like this is bug in User: feel free to include the fix here (making avatar optional in the type).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed. Replaced Record<string, unknown> with User for getAuthenticatedUser, setAuthenticatedUser, fetchAuthenticatedUser & ensureAuthenticatedUser. Also, made avatar optional in the User interface as suggested.

Comment thread runtime/analytics/types.ts Outdated
@@ -0,0 +1,7 @@
export interface AnalyticsService {
sendTrackingLogEvent(eventName: string, properties: object): Promise<void>,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should return Promise<unknown>, not Promise<void>.

Promise<void> rejects the reference implementation's own shape - SegmentAnalyticsService.js:140 does return this.httpClient.post(...). It passes today only because that file is untyped JS, so httpClient is implicitly any; the same service written in TypeScript would fail.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed. Changed return type from Promise<void> to Promise<unknown>.

Comment on lines +1 to +18
import { SiteConfig } from '../types';
import NewRelicLoggingService from '../runtime/logging/NewRelicLoggingService';
import SegmentAnalyticsService from '../runtime/analytics/SegmentAnalyticsService';
import AxiosJwtAuthService from '../runtime/auth/AxiosJwtAuthService';

const config: SiteConfig = {
loggingService: NewRelicLoggingService,
analyticsService: SegmentAnalyticsService,
authService: AxiosJwtAuthService,
siteId: '',
siteName: '',
baseUrl: '',
lmsBaseUrl: '',
loginUrl: '',
logoutUrl: '',
}

export default config; No newline at end of file

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Remove test-types/ and the eslint.config.js:16 ignore.

The real fix is typing runtime/initialize.js. If that were TypeScript, getSiteConfig().loggingService would tie the declarations to real usage. But this is obviously out of scope, here.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Removed. Deleted the test-types/ folder and the corresponding eslint.config.js ignore entry.

Comment thread eslint.config.js Outdated
'test-site/*',
'config/*',
'docs/*',
'test-types/*',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This ignore comes back out along with test-types/ - see the comment on the fixture file.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Resolved

Comment thread types.ts Outdated
export type LocalizedMessages = Record<string, Record<string, string>>;
export type SiteMessages = LocalizedMessages[];

export type { LoggingService, AnalyticsService, AuthService };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Export the new types from their barrels with export type * from './types', as runtime/slots/index.ts:2 does, rather than re-exporting here. Both reach consumers; the barrel keeps the layering consistent.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed. Moved the re-exports to the runtime barrel files using export type * from './types' in runtime/logging/index.ts, runtime/analytics/index.ts and runtime/auth/index.ts.

Comment thread types.ts Outdated

export type { LoggingService, AnalyticsService, AuthService };

// Logging instantiated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Drop the // Logging instantiated / // Analytics instantiated / // Auth instantiated comments here and at 72 and 79 - these are constructor types, nothing is instantiated. ExternalScriptLoaderClass at types.ts:45 carries no comment.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Removed.

Comment thread types.ts Outdated
Comment on lines +81 to +91
config: {
baseUrl: string,
lmsBaseUrl: string,
loginUrl: string,
logoutUrl: string,
refreshAccessTokenApiPath: string,
accessTokenCookieName: string,
csrfTokenApiPath: string,
},
loggingService: object,
middleware?: unknown[],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Use config: SiteConfig like the other two rather than the inlined seven-field literal - initialize() passes the whole getSiteConfig(). middleware? on line 91 is also always supplied (it defaults to [] in the initialize signature), so it isn't optional.

@vkumar-sonata vkumar-sonata Aug 31, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed. Replaced the inlined config literal with config: SiteConfig to match the other two service class types and reflect that initialize() passes the whole getSiteConfig(). Also made middleware non-optional since initialize() always supplies it, defaulting to [].

Comment thread types.ts Outdated
accessTokenCookieName: string,
csrfTokenApiPath: string,
},
loggingService: object,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Use LoggingService, not object - line 75 already does for the same value, and initialize() passes getLoggingService() to both.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed. Changed loggingService: object to loggingService: LoggingService in AuthServiceClass, consistent with AnalyticsServiceClass.

@vkumar-sonata

Copy link
Copy Markdown
Author

A few more change requests, if you don't mind. Thanks for bearing with me!

@arbrandes Acknowledged and I have made the suggested changes. Please review the changes.

@arbrandes arbrandes left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Almost there! Just a few typing and linting adjustments. Thanks again!

Comment thread types.ts
Comment thread runtime/auth/types.ts Outdated
Comment thread runtime/auth/index.ts Outdated
Comment thread types.ts Outdated
Comment thread types.ts Outdated
@vkumar-sonata

Copy link
Copy Markdown
Author

Almost there! Just a few typing and linting adjustments. Thanks again!

@arbrandes Acknowledged and made the changes as per the feedback. Please review the changes.

@arbrandes arbrandes left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Still a couple of issues.

Comment thread types.ts
Comment thread runtime/analytics/types.ts Outdated
Comment thread runtime/analytics/types.ts Outdated
@vkumar-sonata

Copy link
Copy Markdown
Author

Acknowledged and made the changes as per the feedback. Please review the changes.

@arbrandes Acknowledged. Made the fixes as per the feedback. Please review the changes.

@arbrandes arbrandes left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks again. One last thing and I'll merge it.

Comment thread runtime/auth/MockAuthService.js Outdated
* @returns {Promise<null>}
* @returns {Promise<void>}
*/
hydrateAuthenticatedUser = jest.fn(() => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

authService: MockAuthService still doesn't compile - the async half of the last round's fix didn't make it in. Change this to jest.fn(async () => {.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed. The async keyword was missed in the previous commit, hydrateAuthenticatedUser is now jest.fn(async () => {, matching the Promise<void> return type.

@vkumar-sonata

Copy link
Copy Markdown
Author

Thanks again. One last thing and I'll merge it.

@arbrandes Apologies. I am not sure how I missed that. This time I have addressed that. Please kindly review the changes. Thanks.

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.

SiteConfig type is missing the logging, analytics and auth service overrides

2 participants