@e22m4u/js-service
Version:
Реализация принципа инверсии управления для JavaScript
476 lines (470 loc) • 13.3 kB
JavaScript
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.js
var index_exports = {};
__export(index_exports, {
DebuggableService: () => DebuggableService,
SERVICE_CLASS_NAME: () => SERVICE_CLASS_NAME,
SERVICE_CONTAINER_CLASS_NAME: () => SERVICE_CONTAINER_CLASS_NAME,
Service: () => Service,
ServiceContainer: () => ServiceContainer,
isServiceContainer: () => isServiceContainer
});
module.exports = __toCommonJS(index_exports);
// src/service-container.js
var import_js_format = require("@e22m4u/js-format");
var SERVICE_CONTAINER_CLASS_NAME = "ServiceContainer";
var ServiceContainer = class _ServiceContainer {
static {
__name(this, "ServiceContainer");
}
/**
* Kinds.
*
* @type {string[]}
*/
static kinds = [SERVICE_CONTAINER_CLASS_NAME];
/**
* Services map.
*
* @type {Map<any, any>}
* @private
*/
_services = /* @__PURE__ */ new Map();
/**
* Parent container.
*
* @type {ServiceContainer}
* @private
*/
_parent;
/**
* Constructor.
*
* @param {ServiceContainer|undefined} parent
*/
constructor(parent = void 0) {
if (parent != null) {
if (!(parent instanceof _ServiceContainer)) {
throw new import_js_format.InvalidArgumentError(
'Parameter "parent" must be an instance of ServiceContainer, but %v given.',
parent
);
}
this._parent = parent;
}
}
/**
* Получить родительский сервис-контейнер или выбросить ошибку.
*
* @returns {ServiceContainer}
*/
getParent() {
if (!this._parent) {
throw new import_js_format.InvalidArgumentError("Service container has no parent.");
}
return this._parent;
}
/**
* Проверить наличие родительского сервис-контейнера.
*
* @returns {boolean}
*/
hasParent() {
return Boolean(this._parent);
}
/**
* Получить существующий или новый экземпляр.
*
* @param {*} ctor
* @param {*} args
* @returns {*}
*/
get(ctor, ...args) {
if (!ctor || typeof ctor !== "function") {
throw new import_js_format.InvalidArgumentError(
'Parameter "ctor" must be a class constructor, but %v given.',
ctor
);
}
const isCtorRegistered = this._services.has(ctor);
let service = this._services.get(ctor);
if (!service && !isCtorRegistered && this._parent && this._parent.has(ctor)) {
return this._parent.get(ctor, ...args);
}
if (!service || args.length) {
service = Array.isArray(ctor.kinds) && ctor.kinds.includes(SERVICE_CLASS_NAME) ? new ctor(this, ...args) : new ctor(...args);
this._services.set(ctor, service);
} else if (typeof service === "function") {
service = service();
this._services.set(ctor, service);
}
return service;
}
/**
* Получить существующий или новый экземпляр,
* только если конструктор зарегистрирован.
*
* @param {*} ctor
* @param {*} args
* @returns {*}
*/
getRegistered(ctor, ...args) {
if (!this.has(ctor)) {
throw new import_js_format.InvalidArgumentError("Constructor %v is not registered.", ctor);
}
return this.get(ctor, ...args);
}
/**
* Проверить существование конструктора в контейнере.
*
* @param {*} ctor
* @returns {boolean}
*/
has(ctor) {
if (this._services.has(ctor)) {
return true;
}
if (this._parent) {
return this._parent.has(ctor);
}
return false;
}
/**
* Добавить конструктор в контейнер.
*
* @param {*} ctor
* @param {*} args
* @returns {this}
*/
add(ctor, ...args) {
if (!ctor || typeof ctor !== "function") {
throw new import_js_format.InvalidArgumentError(
'Parameter "ctor" must be a class constructor, but %v given.',
ctor
);
}
const factory = /* @__PURE__ */ __name(() => Array.isArray(ctor.kinds) && ctor.kinds.includes(SERVICE_CLASS_NAME) ? new ctor(this, ...args) : new ctor(...args), "factory");
this._services.set(ctor, factory);
return this;
}
/**
* Добавить конструктор и создать экземпляр.
*
* @param {*} ctor
* @param {*} args
* @returns {this}
*/
use(ctor, ...args) {
if (!ctor || typeof ctor !== "function") {
throw new import_js_format.InvalidArgumentError(
'Parameter "ctor" must be a class constructor, but %v given.',
ctor
);
}
const service = Array.isArray(ctor.kinds) && ctor.kinds.includes(SERVICE_CLASS_NAME) ? new ctor(this, ...args) : new ctor(...args);
this._services.set(ctor, service);
return this;
}
/**
* Добавить конструктор и связанный экземпляр.
*
* @param {*} ctor
* @param {*} service
* @returns {this}
*/
set(ctor, service) {
if (!ctor || typeof ctor !== "function") {
throw new import_js_format.InvalidArgumentError(
'Parameter "ctor" must be a class constructor, but %v given.',
ctor
);
}
if (!service || typeof service !== "object" || Array.isArray(service)) {
throw new import_js_format.InvalidArgumentError(
'Parameter "service" must be an Object, but %v given.',
service
);
}
this._services.set(ctor, service);
return this;
}
};
// src/utils/is-service-container.js
function isServiceContainer(container) {
return Boolean(
container && typeof container === "object" && typeof container.constructor === "function" && Array.isArray(container.constructor.kinds) && container.constructor.kinds.includes(SERVICE_CONTAINER_CLASS_NAME)
);
}
__name(isServiceContainer, "isServiceContainer");
// src/service.js
var import_js_format2 = require("@e22m4u/js-format");
var SERVICE_CLASS_NAME = "Service";
var Service = class _Service {
static {
__name(this, "Service");
}
/**
* Kinds.
*
* @type {string[]}
*/
static kinds = [SERVICE_CLASS_NAME];
/**
* Container.
*
* @protected
* @type {ServiceContainer}
*/
_container;
/**
* Container.
*
* @type {ServiceContainer}
*/
get container() {
return this._container;
}
/**
* Constructor.
*
* @param {ServiceContainer} [container]
*/
constructor(container = void 0) {
if (isServiceContainer(container)) {
this._container = container;
} else if (container !== void 0) {
throw new import_js_format2.InvalidArgumentError(
'Parameter "container" must be an instance of ServiceContainer, but %v was given.',
container
);
} else {
this._container = new ServiceContainer();
if (this.constructor !== _Service) {
this._container.set(this.constructor, this);
}
}
}
/**
* Получить существующий или новый экземпляр.
*
* @param {*} ctor
* @param {*} args
* @returns {*}
*/
getService(ctor, ...args) {
return this._container.get(ctor, ...args);
}
/**
* Получить существующий или новый экземпляр,
* только если конструктор зарегистрирован.
*
* @param {*} ctor
* @param {*} args
* @returns {*}
*/
getRegisteredService(ctor, ...args) {
return this._container.getRegistered(ctor, ...args);
}
/**
* Проверка существования конструктора в контейнере.
*
* @param {*} ctor
* @returns {boolean}
*/
hasService(ctor) {
return this._container.has(ctor);
}
/**
* Добавить конструктор в контейнер.
*
* @param {*} ctor
* @param {*} args
* @returns {this}
*/
addService(ctor, ...args) {
this._container.add(ctor, ...args);
return this;
}
/**
* Добавить конструктор и создать экземпляр.
*
* @param {*} ctor
* @param {*} args
* @returns {this}
*/
useService(ctor, ...args) {
this._container.use(ctor, ...args);
return this;
}
/**
* Добавить конструктор и связанный экземпляр.
*
* @param {*} ctor
* @param {*} service
* @returns {this}
*/
setService(ctor, service) {
this._container.set(ctor, service);
return this;
}
};
// src/debuggable-service.js
var import_js_debug = require("@e22m4u/js-debug");
var import_js_format3 = require("@e22m4u/js-format");
var DebuggableService = class _DebuggableService extends import_js_debug.Debuggable {
static {
__name(this, "DebuggableService");
}
/**
* Kinds.
*
* @type {string[]}
*/
static kinds = [...Service.kinds];
/**
* Service.
*
* @protected
* @type {Service}
*/
_service;
/**
* Container.
*
* @type {ServiceContainer}
*/
get container() {
return this._service.container;
}
/**
* Получить существующий или новый экземпляр.
*
* @param {*} ctor
* @param {*} args
* @returns {*}
*/
getService(ctor, ...args) {
return this._service.getService(ctor, ...args);
}
/**
* Получить существующий или новый экземпляр,
* только если конструктор зарегистрирован.
*
* @param {*} ctor
* @param {*} args
* @returns {*}
*/
getRegisteredService(ctor, ...args) {
return this._service.getRegisteredService(ctor, ...args);
}
/**
* Проверка существования конструктора в контейнере.
*
* @param {*} ctor
* @returns {boolean}
*/
hasService(ctor) {
return this._service.hasService(ctor);
}
/**
* Добавить конструктор в контейнер.
*
* @param {*} ctor
* @param {*} args
* @returns {this}
*/
addService(ctor, ...args) {
this._service.addService(ctor, ...args);
return this;
}
/**
* Добавить конструктор и создать экземпляр.
*
* @param {*} ctor
* @param {*} args
* @returns {this}
*/
useService(ctor, ...args) {
this._service.useService(ctor, ...args);
return this;
}
/**
* Добавить конструктор и связанный экземпляр.
*
* @param {*} ctor
* @param {*} service
* @returns {this}
*/
setService(ctor, service) {
this._service.setService(ctor, service);
return this;
}
/**
* Constructor.
*
* @param {ServiceContainer|import('@e22m4u/js-debug').DebuggableOptions} [containerOrOptions]
* @param {import('@e22m4u/js-debug').DebuggableOptions} [options]
*/
constructor(containerOrOptions, options) {
let container;
if (isServiceContainer(containerOrOptions)) {
container = containerOrOptions;
} else if (containerOrOptions !== void 0) {
if (!containerOrOptions || typeof containerOrOptions !== "object" || Array.isArray(containerOrOptions)) {
throw new import_js_format3.InvalidArgumentError(
"First parameter must be an Object or an instance of ServiceContainer, but %v was given.",
containerOrOptions
);
}
if (options === void 0) {
options = containerOrOptions;
} else {
throw new import_js_format3.InvalidArgumentError("Constructor signature mismatch.");
}
} else if (options !== void 0) {
if (!options || typeof options !== "object" || Array.isArray(options)) {
throw new import_js_format3.InvalidArgumentError(
"Second parameter must be an Object, but %v was given.",
options
);
}
}
super(options);
if (container === void 0) {
container = new ServiceContainer();
this._service = new Service(container);
if (this.constructor !== _DebuggableService) {
container.set(this.constructor, this);
}
} else {
this._service = new Service(container);
}
}
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
DebuggableService,
SERVICE_CLASS_NAME,
SERVICE_CONTAINER_CLASS_NAME,
Service,
ServiceContainer,
isServiceContainer
});