Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions web/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- Enable Beam SDK configs to register all client/server services during initialization.
- `Beam.use` and `BeamServer.use` now accept arrays of services or microservice clients for batch registration.

## [0.6.0] - 2025-09-15

Expand Down
3 changes: 2 additions & 1 deletion web/samples/WordWiz/src/beam.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ export async function setupBeam(): Promise<Beam> {

try {
// Register required services with the Beam instance
beam.use(AuthService).use(AccountService).use(StatsService);
// beam.use(AuthService).use(AccountService).use(StatsService);
beam.use([AuthService, AccountService, StatsService]);
return beam;
} catch (error) {
console.error('Beam Error:', error);
Expand Down
65 changes: 44 additions & 21 deletions web/src/core/Beam.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,33 +76,56 @@ export class Beam extends ClientServicesMixin(BeamBase) {
return BeamBase.env;
}

use<T extends ApiService>(Service: ApiServiceCtor<T>): this;
use<T extends BeamMicroServiceClient>(
Client: BeamMicroServiceClientCtor<T>,
use<T extends ApiServiceCtor<any> | BeamMicroServiceClientCtor<any>>(
ctors: readonly T[],
): this;
use(Ctor: any): this {
if (this.isApiService(Ctor)) {
const svc = new Ctor({ beam: this, getPlayer: () => this.player });
const svcName = svc.serviceName;
(this.clientServices as any)[svc.serviceName] = svc;

if (REFRESHABLE_SERVICES.includes(svcName)) {
const refreshKey = `${svcName}.refresh` as keyof RefreshableServiceMap;
this.refreshableRegistry[refreshKey] =
svc as unknown as RefreshableService<any>;
}
} else if (this.isMicroServiceClient(Ctor)) {
const client = new Ctor(this);
const serviceName = client.serviceName;
const serviceNameIdentifier =
serviceName.charAt(0).toLowerCase() + serviceName.slice(1);
const clientName = `${serviceNameIdentifier}Client`;
(this as any)[clientName] = client;
use<T extends ApiServiceCtor<any> | BeamMicroServiceClientCtor<any>>(
ctor: T,
): this;
use(ctorOrCtors: any): this {
const ctors = Array.isArray(ctorOrCtors) ? ctorOrCtors : [ctorOrCtors];

if (this.isApiService(ctors[0])) {
ctors.forEach((c) => this.registerApiService(c));
return this;
}

if (this.isMicroServiceClient(ctors[0])) {
ctors.forEach((c) => this.registerMicroClient(c));
return this;
}

return this;
}

/** Registers an API service with the Beam instance. */
private registerApiService<T extends ApiService>(Ctor: ApiServiceCtor<T>) {
const svc = new Ctor({ beam: this, getPlayer: () => this.player });
const svcName = svc.serviceName;

(this.clientServices as any)[svcName] = svc;

if (REFRESHABLE_SERVICES.includes(svcName)) {
const refreshKey = `${svcName}.refresh` as keyof RefreshableServiceMap;
this.refreshableRegistry[refreshKey] =
svc as unknown as RefreshableService<any>;
}
}

/** Registers a microservice client with the Beam instance. */
private registerMicroClient<T extends BeamMicroServiceClient>(
Ctor: BeamMicroServiceClientCtor<T>,
) {
const client = new Ctor(this);
const serviceName = client.serviceName;

const identifier =
serviceName.charAt(0).toLowerCase() + serviceName.slice(1);
const clientName = `${identifier}Client`;

(this as any)[clientName] = client;
}

/** Connects the client SDK to the Beamable platform. This method is called automatically during `Beam.init()`. */
private async connect(): Promise<void> {
try {
Expand Down
44 changes: 25 additions & 19 deletions web/src/core/BeamBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,35 +69,41 @@ export abstract class BeamBase {
}

/**
* Dynamically adds a service to the Beam SDK instance.
* @param Service The service class to add.
* Dynamically adds multiple api services or microservice clients to the Beam SDK.
* @param ctors - An array of constructors for the api service or microservice client.
* @returns The current instance of BeamBase.
* @example
* ```ts
* // client-side:
* beam.use(StatsService);
* await beam.stats.get({...});
* // server-side:
* beamServer.use(StatsService);
* await beamServer.stats.get({...});
* const beam = await Beam.init({ ... });
* beam.use([LeadboardService, StatsService]);
* ```
* or
* ```ts
* const beam = await Beam.init({ ... });
* beam.use([MyMicroserviceClient, MyOtherMicroserviceClient]);
* ```
*/
abstract use<T extends ApiService>(Service: ApiServiceCtor<T>): this;
abstract use<T extends ApiServiceCtor<any> | BeamMicroServiceClientCtor<any>>(
ctors: readonly T[],
): this;

/**
* Dynamically adds a microservice client to the Beam SDK instance.
* @param Client The microservice client class to add.
* Dynamically adds a single api service or microservice client to the Beam SDK.
* @param ctor - The constructor for the api service or microservice client.
* @returns The current instance of BeamBase.
* @example
* ```ts
* // client-side:
* beam.use(MyMicroServiceClient);
* beam.myMicroServiceClient.serviceName;
* // server-side:
* beamServer.use(MyMicroServiceClient);
* beamServer.myMicroServiceClient.serviceName;
* const beam = await Beam.init({ ... });
* beam.use(StatsService);
* ```
* or
* ```ts
* const beam = await Beam.init({ ... });
* beam.use(MyMicroserviceClient);
* ```
*/
abstract use<T extends BeamMicroServiceClient>(
Client: BeamMicroServiceClientCtor<T>,
abstract use<T extends ApiServiceCtor<any> | BeamMicroServiceClientCtor<any>>(
ctor: T,
): this;

protected constructor(config: BeamBaseConfig) {
Expand Down
57 changes: 40 additions & 17 deletions web/src/core/BeamServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,29 +62,52 @@ export class BeamServer extends ServerServicesMixin(BeamBase) {
return BeamBase.env;
}

use<T extends ApiService>(Service: ApiServiceCtor<T>): this;
use<T extends BeamMicroServiceClient>(
Client: BeamMicroServiceClientCtor<T>,
use<T extends ApiServiceCtor<any> | BeamMicroServiceClientCtor<any>>(
ctors: readonly T[],
): this;
use(Ctor: any): this {
if (this.isApiService(Ctor)) {
const svc = new Ctor({ beam: this });
(this.serverServices as any)[svc.serviceName] = (userId: string) => {
svc.userId = userId;
return svc;
};
} else if (this.isMicroServiceClient(Ctor)) {
const client = new Ctor(this);
const serviceName = client.serviceName;
const serviceNameIdentifier =
serviceName.charAt(0).toLowerCase() + serviceName.slice(1);
const clientName = `${serviceNameIdentifier}Client`;
(this as any)[clientName] = client;
use<T extends ApiServiceCtor<any> | BeamMicroServiceClientCtor<any>>(
ctor: T,
): this;
use(ctorOrCtors: any): this {
const ctors = Array.isArray(ctorOrCtors) ? ctorOrCtors : [ctorOrCtors];

if (this.isApiService(ctors[0])) {
ctors.forEach((c) => this.registerApiService(c));
return this;
}

if (this.isMicroServiceClient(ctors[0])) {
ctors.forEach((c) => this.registerMicroClient(c));
return this;
}

return this;
}

/** Registers an API service with the BeamServer instance. */
private registerApiService<T extends ApiService>(Ctor: ApiServiceCtor<T>) {
const svc = new Ctor({ beam: this });

(this.serverServices as any)[svc.serviceName] = (userId: string) => {
svc.userId = userId;
return svc;
};
}

/** Registers a microservice client with the BeamServer instance. */
private registerMicroClient<T extends BeamMicroServiceClient>(
Ctor: BeamMicroServiceClientCtor<T>,
) {
const client = new Ctor(this);
const serviceName = client.serviceName;

const identifier =
serviceName.charAt(0).toLowerCase() + serviceName.slice(1);
const clientName = `${identifier}Client`;

(this as any)[clientName] = client;
}

/** Connects the server SDK to the Beamable gateway to listen for server-events. This method is called automatically during `BeamServer.init()` if `enableServerEvents` is set to `true`. */
private async connect() {
const tokenData = await this.tokenStorage.getTokenData();
Expand Down
1 change: 1 addition & 0 deletions web/vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ export default defineConfig({
globals: true,
environment: 'node',
setupFiles: ['./vitest.setup.ts'],
exclude: ['node_modules', '**/samples/**'],
},
});
Loading