npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

pp-iapi-contracts

v1.0.0

Published

Kontrakt komunikacji ICC (api-v3) z workerami ubezpieczeniowymi: schematy zod zadan i wynikow, typy slownikowe oraz fabryka komend pp-command-bus

Downloads

474

Readme

pp-iapi-contracts

Kontrakt komunikacji między ICC (api-v3) a workerami ubezpieczeniowymi. Jedno źródło prawdy dla obu stron kanału pp-command-bus: schematy zod zadań i wyników, typy słownikowe oraz fabryka klas komend.

Paczka zawiera wyłącznie opis kontraktu. Nie ma tu logiki workera, Walk/Step, klienta HTTP, debugowania ani cache - to zakres pp-worker-kernel.

Instalacja

npm install pp-iapi-contracts

Zależności równorzędne (musi je dostarczyć projekt konsumujący):

{
  "peerDependencies": {
    "pp-command-bus": "^2.0.0",
    "zod": "^3.23.8"
  }
}

Zasada nadrzędna: zod jest źródłem, typy są pochodną

Każdy typ opisujący dane kontraktu pochodzi z z.infer. Nie utrzymujemy równolegle interfejsu TypeScript i schematu zod dla tego samego bytu, bo dwa źródła zawsze się rozjeżdżają - a rozjazd jest dokładnie tym problemem, który ta paczka likwiduje.

export const proposalTaskSchema = z.object({ payload: /* ... */ });
export type ProposalTask = z.infer<typeof proposalTaskSchema>;

Wyjątkiem są trzy typy, które nie opisują danych kontraktu, tylko API programistyczne paczki: JsonValue (typ rekurencyjny, którego zod 3 nie potrafi wyprowadzić bez adnotacji), CommandDefinition i InsurerCommands (typy konstruktorów komend).

Skąd wzięły się kształty schematów

Schematy nie są przepisane z modeli jednego workera. Zostały wyprowadzone z modeli worker-interrisk-iapi, a następnie skalibrowane na korpusie realnego ruchu produkcyjnego: 5709 payloadów z 9 towarzystw (balcia, hestia, interrisk, interriskIrys, link4, mtu24, ominimo, trasti, tuz, uniqa) pobranych z katalogu debug stack-iapi.

Kalibracja wykryła w modelach workera braki, których nie widać było w kodzie:

| Znalezisko | Skala w korpusie | |---|---| | subjects[].livingPostalCode - brak w modelu i w walidatorze | 39,7% payloadów proposal | | teryt.poczta - brak w modelu Teryt | 94,7% obiektów teryt | | risks[].subRisk[].code, risks[].name - brak w modelach wyniku | 3073 wystąpienia | | variants[].subjectList[] z płaskim adresem zamiast address | 354 wystąpienia | | insuranceDetails.paymentSettings w ofercie (model ma je tylko w polisie) | 314 wystąpień | | previousData.customization, customizationSchema, insurerVariant | 120 wystąpień | | externalOfferId2, documentList[].fileCloudId | 20 wystąpień |

Reguła wymagalności: pole jest wymagane w kontrakcie tylko wtedy, gdy jest wymagane w modelu źródłowym i występuje w 100% payloadów korpusu. Jeżeli pole jest wymagane wyłącznie dlatego, że potrzebuje go API konkretnego towarzystwa - w kontrakcie jest opcjonalne, a wymóg towarzystwa zostaje w workerze jako osobna walidacja uruchamiana po walidacji kontraktowej.

Dlaczego pola opcjonalne są nullish, a nie optional

ICC używa null i pominięcia pola wymiennie. W korpusie null pojawia się w tysiącach miejsc (subRisk.premium.value - 3039 razy, całe previousData, teryt.poczta, assignmentDetails, customization). Schemat dopuszczający wyłącznie undefined odrzucał na tym korpusie 100% zadań oferty i aktualizacji kalkulacji.

Konsekwencja dla konsumenta: typy z tej paczki są szersze niż stare modele workerów - obsłuż null tam, gdzie wcześniej TypeScript go nie pokazywał. To nie jest rozluźnienie kontraktu, tylko usunięcie fikcji: worker dostawał null już wcześniej, po prostu nie było tego w typach.

Walidacja nie usuwa danych

Schematy używają domyślnego zachowania zod (strip), więc parse zwraca obiekt bez pól spoza kontraktu. Jeżeli ICC doda pole, którego dana wersja paczki nie zna, worker po parse go nie zobaczy. Do przekazywania danych dalej używaj oryginalnego payloadu, a wynik safeParse traktuj jako werdykt walidacji. Test golden-file pilnuje, że dla realnych payloadów zbiór usuwanych pól jest pusty - każde nowe pole ICC ujawni się jako czerwony test, a nie jako cicha utrata danych na produkcji.

Zalecenie dla projektu konsumującego

Deklaracje typów wyprowadzone z zod są obszerne - typ ZodObject niesie strukturę schematu trzykrotnie (kształt, wejście, wyjście). Ustaw w swoim tsconfig.json:

{
  "compilerOptions": {
    "skipLibCheck": true
  }
}

TypeScript pominie wtedy sprawdzanie deklaracji z node_modules, co skraca czas kompilacji i podpowiedzi w edytorze. Nie wpływa to na sprawdzanie typów w Twoim kodzie - użycie schematów i typów z paczki jest walidowane normalnie.

Eksporty

Zadania (ICC -> worker)

| Schemat | Typ | |---|---| | cepikTaskSchema | CepikTask | | proposalTaskSchema | ProposalTask | | proposalUpdateTaskSchema | ProposalUpdateTask | | offerTaskSchema | OfferTask | | policyTaskSchema | PolicyTask | | taskSchema | Task (unia pięciu powyższych) |

Modele zadań zaczynają się od payload. Pola koperty transportowej RabbitMQ (__time, __name, __id, __eventStore, __eventChain) nie należą do kontraktu - w pp-command-bus są kopertą komendy, a dane biznesowe siedzą w __payload.

Wyniki (worker -> ICC)

| Schemat | Typ | |---|---| | cepikTaskResultSchema | CepikTaskResult | | proposalTaskResultSchema | ProposalTaskResult | | proposalUpdateTaskResultSchema | ProposalUpdateTaskResult | | offerTaskResultSchema | OfferTaskResult | | policyTaskResultSchema | PolicyTaskResult | | taskResultSchema | TaskResult (unia pięciu powyższych) |

Schematy wspólne

messageSchema, credentialsSchema, getCredentialsResponseSchema, terytSchema, addressSchema, moneySchema, documentSchema, identifiedDocumentSchema, consentSchema, identifiedConsentSchema, resultConsentSchema, offerFlagSchema, insurancePeriodSchema, insuranceBrandSchema, assignmentDetailsSchema, infoEkspertDetailsSchema, eurotaxVehicleSchema, eurotaxDetailsSchema, dictionaryVehicleDetailsSchema, jsonValueSchema, jsonObjectSchema.

Słowniki

Każdy słownik eksportuje trzy rzeczy pod jedną nazwą bytu: obiekt wartości, schemat i typ.

import { MessageType, messageTypeSchema } from 'pp-iapi-contracts';

MessageType.Error; // 'ERROR'
messageTypeSchema.safeParse('ERROR').success; // true

const type: MessageType = MessageType.Error;

Dostępne słowniki: AssistanceVariantType, CepikOwnerType, CompanyType, CreationLocationType, CurrencyType, DocumentType, FirstRegistrationCountryCodeType, InstallmentsNumberType, InsuranceSummaryType, LossAdjustmentType, ManualVehicleValueType, MaritalStatusType, MessageSourceType, MessageType, NnwVariantType, OccupationType, OfferFlagNameType, ParkingPlaceType, PaymentMethodType, ProcessStatusType, RiskNameType, RiskProductType, RoleType, SaleType, SteeringWheelSideType, SubjectType, TimeAbroadType, VehicleCategoryType, VehicleType, VehicleUsageType.

Komendy

createInsurerCommands(commandPrefix: string): InsurerCommands
defineCommand<TPayload>(commandName: string): CommandDefinition<TPayload>

Komendy: dlaczego jawny prefiks

pp-command-bus routuje komendy po nazwie klasy: Command ustawia this.__name = this.constructor.name, a CommandBus.handle() rejestruje handler pod kluczem commandDefinition.name. Jeżeli nazwa klasy po stronie ICC i po stronie workera różni się choćby jednym znakiem, komenda nigdy nie dotrze - bez błędu i bez logu.

Fabryka przyjmuje jawny prefiks w PascalCase, a nie nazwę towarzystwa do przekształcenia. Nazwa towarzystwa w metadanych to interriskIrys, a nazwy klas to InterriskIrys*Command - gdyby fabryka wyprowadzała prefiks sama, wynik zależałby od implementacji pascalCase (InterriskIrys czy Interriskirys?), a pomyłka oznaczałaby cicho martwy kanał. Prefiks jest walidowany schematem; wartość spoza PascalCase kończy się wyjątkiem przy starcie, a nie milczeniem w runtime.

defineCommand ustawia nazwę klasy jawnie przez Object.defineProperty, więc jest odporny na minifikację i na nazwę zmiennej, do której klasa zostanie przypisana.

Worker: rejestracja handlera

import { createInsurerCommands, proposalTaskSchema } from 'pp-iapi-contracts';
import { CommandBus, CommandBusConfig } from 'pp-command-bus';

const commands = createInsurerCommands('InterriskIrys');
const commandBus = new CommandBus(new CommandBusConfig({ redisUrl }));

commandBus.handle(commands.proposal, async (command) => {
  const validation = proposalTaskSchema.safeParse(command.__payload);

  if (!validation.success) {
    return proposalHandler.rejectWithContractError(validation.error.issues);
  }

  return proposalHandler.handle(command.__payload);
});

Walidację kontraktową uruchamiamy przed walidacją wymagań towarzystwa. Pierwsza odpowiada na pytanie "czy ICC przysłał to, co obiecał", druga na "czy da się z tego zbudować żądanie do API konkretnego TU". Rozdzielenie ich jest celem tej paczki.

ICC: wywołanie komendy

import { createInsurerCommands, type ProposalTask } from 'pp-iapi-contracts';

const commands = createInsurerCommands('InterriskIrys');

const task: ProposalTask = { payload: { data, metadata } };
const result = await commandBus.call(new commands.proposal(task), 120_000);

Obie strony wywołują tę samą fabrykę z tym samym prefiksem, więc zgodność nazwy kanału wynika z definicji, a nie z dyscypliny przy przepisywaniu.

Wersjonowanie

Wersjonowanie semantyczne, wydania przez semantic-release (conventional commits).

| Zmiana kontraktu | Wersja | Typ commita | |---|---|---| | Dodanie pola opcjonalnego | minor | feat | | Dodanie wartości do słownika | minor | feat | | Rozszerzenie typu pola (np. dopuszczenie null) | minor | feat | | Zmiana typu pola na węższy | major | feat! / BREAKING CHANGE | | Zmiana pola opcjonalnego na wymagane | major | feat! / BREAKING CHANGE | | Usunięcie pola lub wartości słownika | major | feat! / BREAKING CHANGE | | Zmiana nazwy klasy komendy | major | feat! / BREAKING CHANGE |

Zmiana nazwy komendy jest zawsze major - rozjazd nazw zrywa kanał po cichu, więc obie strony muszą zaktualizować paczkę w tym samym wdrożeniu.

Build to czysty tsc. Bundlery i minifikacja są zakazane: nazwy klas komend mają znaczenie w runtime.

Testy

npm test

Trzy grupy testów uruchamiane w CI:

  1. Nazwy komend - new Cmd(payload).__name równa się nazwie kontraktowej, fabryka odtwarza istniejące nazwy InterriskIrys*Command, prefiks spoza PascalCase jest odrzucany.
  2. Golden-file - 20 zanonimizowanych payloadów realnego ruchu (po kilka na każdą z pięciu par zadanie/wynik, z różnych towarzystw) przechodzi walidację, nie zawiera pól nieznanych kontraktowi i nie zawiera koperty transportowej.
  3. Testy negatywne - brak pola wymaganego kończy się success === false ze wskazaniem ścieżki błędu; wartości spoza słownika są odrzucane.

Fixtures leżą w src/__fixtures__. Dane osobowe (PESEL, imię, nazwisko, adres, numer rejestracyjny, VIN, telefon, e-mail, REGON, NIP) są zanonimizowane, treści dokumentów i tokeny zastąpione wartościami testowymi.