fix: add missing SiteConfig service override typings and definitions - #281
fix: add missing SiteConfig service override typings and definitions#281vkumar-sonata wants to merge 7 commits into
Conversation
arbrandes
left a comment
There was a problem hiding this comment.
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.
arbrandes
left a comment
There was a problem hiding this comment.
A few more change requests, if you don't mind. Thanks for bearing with me!
| 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>, |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Fixed. Changed return type from Promise<null> to Promise<void>.
| 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>>, |
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
Fixed. Replaced Record<string, unknown> with User for getAuthenticatedUser, setAuthenticatedUser, fetchAuthenticatedUser & ensureAuthenticatedUser. Also, made avatar optional in the User interface as suggested.
| @@ -0,0 +1,7 @@ | |||
| export interface AnalyticsService { | |||
| sendTrackingLogEvent(eventName: string, properties: object): Promise<void>, | |||
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Fixed. Changed return type from Promise<void> to Promise<unknown>.
| 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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Removed. Deleted the test-types/ folder and the corresponding eslint.config.js ignore entry.
| 'test-site/*', | ||
| 'config/*', | ||
| 'docs/*', | ||
| 'test-types/*', |
There was a problem hiding this comment.
This ignore comes back out along with test-types/ - see the comment on the fixture file.
| export type LocalizedMessages = Record<string, Record<string, string>>; | ||
| export type SiteMessages = LocalizedMessages[]; | ||
|
|
||
| export type { LoggingService, AnalyticsService, AuthService }; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
|
|
||
| export type { LoggingService, AnalyticsService, AuthService }; | ||
|
|
||
| // Logging instantiated |
There was a problem hiding this comment.
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.
| config: { | ||
| baseUrl: string, | ||
| lmsBaseUrl: string, | ||
| loginUrl: string, | ||
| logoutUrl: string, | ||
| refreshAccessTokenApiPath: string, | ||
| accessTokenCookieName: string, | ||
| csrfTokenApiPath: string, | ||
| }, | ||
| loggingService: object, | ||
| middleware?: unknown[], |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 [].
| accessTokenCookieName: string, | ||
| csrfTokenApiPath: string, | ||
| }, | ||
| loggingService: object, |
There was a problem hiding this comment.
Use LoggingService, not object - line 75 already does for the same value, and initialize() passes getLoggingService() to both.
There was a problem hiding this comment.
Fixed. Changed loggingService: object to loggingService: LoggingService in AuthServiceClass, consistent with AnalyticsServiceClass.
@arbrandes Acknowledged and I have made the suggested changes. Please review the changes. |
arbrandes
left a comment
There was a problem hiding this comment.
Almost there! Just a few typing and linting adjustments. Thanks again!
… fix linting issues
@arbrandes Acknowledged and made the changes as per the feedback. Please review the changes. |
arbrandes
left a comment
There was a problem hiding this comment.
Still a couple of issues.
@arbrandes Acknowledged. Made the fixes as per the feedback. Please review the changes. |
arbrandes
left a comment
There was a problem hiding this comment.
Thanks again. One last thing and I'll merge it.
| * @returns {Promise<null>} | ||
| * @returns {Promise<void>} | ||
| */ | ||
| hydrateAuthenticatedUser = jest.fn(() => { |
There was a problem hiding this comment.
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 () => {.
There was a problem hiding this comment.
Fixed. The async keyword was missed in the previous commit, hydrateAuthenticatedUser is now jest.fn(async () => {, matching the Promise<void> return type.
@arbrandes Apologies. I am not sure how I missed that. This time I have addressed that. Please kindly review the changes. Thanks. |
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:
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
Context
Discovered while attempting to configure a custom logging service.
LLM usage notice
Built with assistance from Copilot.
Closes #293