@datalayer/core
Version:
[](https://datalayer.io)
209 lines (208 loc) • 8.17 kB
JavaScript
/*
* Copyright (c) 2023-2025 Datalayer, Inc.
* Distributed under the terms of the Modified BSD License.
*/
/**
* Base SDK class providing core configuration and token management.
* @module client/base
*/
import { DEFAULT_SERVICE_URLS } from '../api/constants';
import { AuthenticationManager } from './auth';
/** Base Client class providing core configuration and token management. */
export class DatalayerClientBase {
/** URL for IAM service */
iamRunUrl;
/** URL for Runtimes service */
runtimesRunUrl;
/** URL for Spacer service */
spacerRunUrl;
/** Authentication token */
token;
/** Environments */
environments = [];
/** Method lifecycle handlers */
handlers;
/** Authentication manager */
auth;
/**
* Create a DatalayerClient base instance.
* @param config - Client configuration options
*/
constructor(config) {
this.iamRunUrl = config.iamRunUrl || DEFAULT_SERVICE_URLS.IAM;
this.runtimesRunUrl =
config.runtimesRunUrl || DEFAULT_SERVICE_URLS.RUNTIMES;
this.spacerRunUrl = config.spacerRunUrl || DEFAULT_SERVICE_URLS.SPACER;
this.token = config.token;
this.handlers = config.handlers;
// Initialize authentication manager
this.auth = new AuthenticationManager(this.iamRunUrl);
// If a token was provided, store it in the auth manager
if (this.token) {
this.auth.storeToken(this.token);
}
}
/**
* Get the current configuration including service URLs and token.
* @returns Current configuration
*/
getConfig() {
return {
iamRunUrl: this.iamRunUrl,
runtimesRunUrl: this.runtimesRunUrl,
spacerRunUrl: this.spacerRunUrl,
token: this.token,
};
}
/** Get the IAM service URL. */
getIamRunUrl() {
return this.iamRunUrl;
}
/** Get the Runtimes service URL. */
getRuntimesRunUrl() {
return this.runtimesRunUrl;
}
/** Get the Spacer service URL. */
getSpacerRunUrl() {
return this.spacerRunUrl;
}
/** Get the current authentication token. */
getToken() {
return this.token;
}
/**
* set the authentication token for all API requests.
* @param token - New authentication token
*/
async setToken(token) {
this.token = token;
}
/**
* Wrap all SDK methods with handlers for cross-cutting concerns.
* Called automatically by the DatalayerClient constructor.
*
* @internal
*/
wrapAllMethods() {
if (!this.handlers) {
return; // No handlers configured, nothing to wrap
}
// Get all method names from the prototype chain
const methodNames = this.getAllMethodNames();
// Wrap each method with handlers
methodNames.forEach(methodName => {
const original = this[methodName];
if (typeof original !== 'function') {
return;
}
// Detect if the original method is async by checking if it's an AsyncFunction
const isAsync = original.constructor.name === 'AsyncFunction';
if (isAsync) {
// Create async wrapped version for originally async methods
this[methodName] = async (...args) => {
// Call beforeCall handler if defined
if (this.handlers?.beforeCall) {
await Promise.resolve(this.handlers.beforeCall(methodName, args));
}
try {
// Call the original async method
const result = await original.apply(this, args);
// Call afterCall handler if defined
if (this.handlers?.afterCall) {
await Promise.resolve(this.handlers.afterCall(methodName, result));
}
return result;
}
catch (error) {
// Call onError handler if defined
if (this.handlers?.onError) {
await Promise.resolve(this.handlers.onError(methodName, error));
}
throw error;
}
};
}
else {
// Create sync wrapped version for originally sync methods
this[methodName] = (...args) => {
// Call beforeCall handler if defined (sync version)
if (this.handlers?.beforeCall) {
const beforeResult = this.handlers.beforeCall(methodName, args);
// If beforeCall returns a Promise, we can't await it in sync context
if (beforeResult instanceof Promise) {
// Promise ignored in sync context
}
}
try {
// Call the original sync method
const result = original.apply(this, args);
// Call afterCall handler if defined (sync version)
if (this.handlers?.afterCall) {
const afterResult = this.handlers.afterCall(methodName, result);
if (afterResult instanceof Promise) {
// Promise ignored in sync context
}
}
return result;
}
catch (error) {
// Call onError handler if defined (sync version)
if (this.handlers?.onError) {
const errorResult = this.handlers.onError(methodName, error);
if (errorResult instanceof Promise) {
// Promise ignored in sync context
}
}
throw error;
}
};
}
});
}
/**
* Get all method names from mixins only.
* @returns Array of mixin method names to wrap
* @internal
*/
getAllMethodNames() {
const methodNames = new Set();
// First, collect all base class methods to exclude
const baseClassMethods = new Set();
const basePrototype = DatalayerClientBase.prototype;
Object.getOwnPropertyNames(basePrototype).forEach(name => {
baseClassMethods.add(name);
});
// Also exclude methods from the concrete SDK class itself
const sdkPrototype = Object.getPrototypeOf(this).constructor.prototype;
Object.getOwnPropertyNames(sdkPrototype).forEach(name => {
baseClassMethods.add(name);
});
// Now walk the prototype chain and only include mixin methods
let obj = Object.getPrototypeOf(this);
while (obj && obj !== Object.prototype) {
const names = Object.getOwnPropertyNames(obj);
names.forEach(name => {
// Only include if:
// 1. Not a constructor
// 2. Not a private method (starts with _)
// 3. Not a base class method
// 4. Is actually a function
if (name !== 'constructor' &&
!name.startsWith('_') &&
!baseClassMethods.has(name)) {
const descriptor = Object.getOwnPropertyDescriptor(obj, name);
if (descriptor && typeof descriptor.value === 'function') {
methodNames.add(name);
}
}
});
obj = Object.getPrototypeOf(obj);
}
return Array.from(methodNames);
}
// Utility Methods
calculateCreditsFromMinutes(minutes, burningRate) {
const burningRatePerMinute = burningRate * 60;
return Math.ceil(minutes * burningRatePerMinute);
}
}