kubricate
Version:
A TypeScript framework for building reusable, type-safe Kubernetes infrastructure — without the YAML mess.
135 lines • 4.55 kB
JavaScript
import { cloneDeep, get, isPlainObject, merge, set } from 'lodash-es';
import { validateId } from '../internal/utils.js';
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
export class ResourceComposer {
_entries = {};
_override = {};
inject(resourceId, path, value) {
const composed = cloneDeep(this._entries[resourceId]);
if (!composed) {
throw new Error(`Cannot inject, resource with ID ${resourceId} not found.`);
}
if (!(composed.entryType === 'object' || composed.entryType === 'class')) {
throw new Error(`Cannot inject, resource with ID ${resourceId} is not an object or class.`);
}
const existingValue = get(composed.config, path);
if (existingValue === undefined) {
// No value yet — safe to set directly
set(composed.config, path, value);
this._entries[resourceId] = composed;
return;
}
if (Array.isArray(existingValue) && Array.isArray(value)) {
// Append array elements (e.g. env vars, volumeMounts)
const mergedArray = [...existingValue, ...value];
set(composed.config, path, mergedArray);
this._entries[resourceId] = composed;
return;
}
if (isPlainObject(existingValue) && isPlainObject(value)) {
// Deep merge objects
const mergedObject = merge({}, existingValue, value);
set(composed.config, path, mergedObject);
this._entries[resourceId] = composed;
return;
}
// Fallback: do not overwrite primitive or incompatible types
throw new Error(`Cannot inject, resource "${resourceId}" already has a value at path "${path}". ` + `Existing: ${JSON.stringify(existingValue)}. New value: ${JSON.stringify(value)}`);
}
build() {
const result = {};
for (const resourceId of Object.keys(this._entries)) {
validateId(resourceId, 'resourceId');
const {
type,
entryType: kind
} = this._entries[resourceId];
const {
config
} = this._entries[resourceId];
if (kind === 'instance') {
result[resourceId] = config;
continue;
}
const mergedConfig = merge({}, config, this._override ? this._override[resourceId] : {});
if (kind === 'object') {
result[resourceId] = mergedConfig;
continue;
}
if (!type) continue;
// Create the resource
result[resourceId] = new type(mergedConfig);
}
return result;
}
/**
* Add a resource to the composer, extracting the type and data from the arguments.
*
* @deprecated This method is deprecated and will be removed in the future. Use `addClass` instead.
*/
add(params) {
this._entries[params.id] = {
type: params.type,
config: params.config,
entryType: 'class'
};
return this;
}
/**
* Add a resource to the composer, extracting the type and data from the arguments.
*/
addClass(params) {
this._entries[params.id] = {
type: params.type,
config: params.config,
entryType: 'class'
};
return this;
}
/**
* Add an object to the composer directly. Using this method will support overriding the resource.
*/
addObject(params) {
this._entries[params.id] = {
config: params.config,
entryType: 'object'
};
return this;
}
/**
* Add an instance to the composer directly. Using this method will not support overriding the resource.
*
* @deprecated This method is deprecated and will be removed in the future. Use `addObject` instead for supporting overrides.
*/
addInstance(params) {
this._entries[params.id] = {
config: params.config,
entryType: 'instance'
};
return this;
}
override(overrideResources) {
this._override = overrideResources;
return this;
}
/**
* @interal Find all resource IDs of a specific kind.
* This method is useful for filtering resources based on their kind.
*/
findResourceIdsByKind(kind) {
const resourceIds = [];
const buildResources = Object.values(this.build());
const entryIds = Object.keys(this._entries);
for (let i = 0; i < buildResources.length; i++) {
const resource = buildResources[i];
const resourceId = entryIds[i];
if (typeof resource === 'object' && resource !== null && 'kind' in resource && typeof resource.kind === 'string') {
if (resource.kind.toLowerCase() === kind.toLowerCase()) {
resourceIds.push(resourceId);
}
}
}
return resourceIds;
}
}
//# sourceMappingURL=ResourceComposer.js.map