@zirion/ioc · Примеры
Запускаемые примеры
Полные примеры @zirion/ioc v3: HTTP-сервер Node.js, проверка времени жизни providers и повторные попытки асинхронных singleton-фабрик.
Обновлено · · if-else.dev
Как запустить примеры
Это полные JavaScript-модули для Node.js 20 или новее. Фреймворк и компилятор TypeScript не нужны. Установите пакет в отдельной папке примеров:
npm init -y
npm install @zirion/ioc@^3.0.0
Сохраните каждый пример с указанным именем .mjs и запустите через node. Регистрации подробно разобраны в основном руководстве.
HTTP-сервер Node.js
Сохраните как server.mjs:
import { createServer } from 'node:http';
import { randomUUID } from 'node:crypto';
import { Container, InjectScope } from '@zirion/ioc';
class UserStore {
users = [{ id: 1, name: 'Ada' }];
}
class RequestInfo {
constructor(context) {
this.context = context;
}
}
class UserHandler {
id = randomUUID();
constructor(store, config, info) {
this.store = store;
this.config = config;
this.info = info;
}
body() {
return {
requestId: this.info.context.requestId,
handlerId: this.id,
region: this.config.region,
users: this.store.users,
};
}
}
const container = new Container()
.add(UserStore, { scope: InjectScope.SINGLETON })
.add('config', {
scope: InjectScope.SINGLETON,
valueFactory: async () => ({ region: 'eu-central-1' }),
})
.add(RequestInfo, { scope: InjectScope.REQUEST })
.add(UserHandler, { inject: [UserStore, 'config', RequestInfo] });
const server = createServer(async (request, response) => {
const context = { requestId: randomUUID() };
try {
let body;
let status = 200;
if (request.method === 'GET' && request.url === '/health') {
const store = container.getOrFailSync(UserStore);
body = { ok: true, users: store.users.length };
} else if (request.method === 'GET' && request.url === '/users') {
const handler = await container.getOrFail(UserHandler, context);
body = handler.body();
} else {
status = 404;
body = { error: 'Not found' };
}
response.writeHead(status, { 'content-type': 'application/json' });
response.end(JSON.stringify(body));
} catch (error) {
console.error(error);
response.writeHead(500, { 'content-type': 'application/json' });
response.end(JSON.stringify({ error: 'Internal server error' }));
}
});
server.listen(3000, '127.0.0.1', () => {
console.log('http://127.0.0.1:3000/users');
});
process.once('SIGTERM', () => {
server.close();
});
Запустите сервер и выполните два запроса:
node server.mjs
# В другом терминале:
curl http://127.0.0.1:3000/health
curl http://127.0.0.1:3000/users
curl http://127.0.0.1:3000/users
Хранилище и настройки зарегистрированы как singleton. Каждый HTTP-запрос получает свежий context и своё состояние. UserHandler transient: даже два разрешения внутри запроса создадут два обработчика с одним request-экземпляром RequestInfo.
/health использует синхронное разрешение синхронного графа хранилища. /users ожидает граф, поскольку настройки инициализируются асинхронно. В реальном приложении демонстрационную фабрику можно заменить фабрикой подключения. Закрывайте внешние клиенты в обработчике завершения приложения; finalize() их не закрывает.
Проверка времени жизни и повторного внедрения
Сохраните как lifetimes.mjs, затем запустите node lifetimes.mjs:
import assert from 'node:assert/strict';
import { Container, InjectScope } from '@zirion/ioc';
class Store {}
class RequestState {}
class Operation {}
class Pair {
constructor(first, second) {
this.first = first;
this.second = second;
}
}
const container = new Container()
.add(Store, { scope: InjectScope.SINGLETON })
.add(RequestState, { scope: InjectScope.REQUEST })
.add(Operation)
.add(Pair, { inject: [Operation, Operation] });
assert.equal(
container.getOrFailSync(Store),
container.getOrFailSync(Store),
);
const requestA = {};
const requestB = {};
assert.equal(
container.getOrFailSync(RequestState, requestA),
container.getOrFailSync(RequestState, requestA),
);
assert.notEqual(
container.getOrFailSync(RequestState, requestA),
container.getOrFailSync(RequestState, requestB),
);
const pair = container.getOrFailSync(Pair);
assert.notEqual(pair.first, pair.second);
assert.notEqual(pair, container.getOrFailSync(Pair));
console.log('All lifetime checks passed');
Один context переиспользует request-состояние, другой объект получает своё. Повторы transient-selectors в inject сохраняются и создают разные экземпляры. Если заменить Operation на singleton, повторное внедрение передаст один экземпляр.
Повторная попытка асинхронного singleton
Сохраните как retry.mjs, затем запустите node retry.mjs:
import assert from 'node:assert/strict';
import { Container, InjectScope } from '@zirion/ioc';
let attempts = 0;
const container = new Container().add('enabled', {
scope: InjectScope.SINGLETON,
valueFactory: async () => {
attempts += 1;
if (attempts === 1) {
throw new Error('Temporary failure');
}
return false;
},
});
await assert.rejects(container.getOrFail('enabled'), /Temporary failure/);
const [first, second] = await Promise.all([
container.getOrFail('enabled'),
container.getOrFail('enabled'),
]);
assert.equal(first, false);
assert.equal(second, false);
assert.equal(await container.getOrFail('enabled'), false);
assert.equal(attempts, 2);
console.log('Retry, concurrent resolution, and falsy caching verified');
Ошибка фабрики не кешируется. Следующие два потребителя разделяют одну успешную попытку, затем false нормально кешируется. Проверьте аналогичное поведение request-providers с общим context и классов, чей onInitialized() может завершиться ошибкой.
Дальше: миграция v2 → v3 и миграция с других контейнеров.