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

@thg-agentic/hyper-personalisation-spec

v1.22.0

Published

The THG Hyper-Personalisation event specification: versioned JSON Schemas, the event registry, a validator, generated TypeScript, Kotlin, Swift and Java event models, the OpenAPI contracts for collection settings and the public API, and the onsite capture

Readme

@thg-agentic/hyper-personalisation-spec

The event specification for THG Hyper-Personalisation. It defines every event a storefront, app or server sends, what each one carries, and who may send it. The same files drive the ingest's validation, the SDKs' generated models, and the warehouse's landing table, so "valid" means the same thing everywhere.

The spec is Segment-compatible on the wire: track, page and identify calls with the Segment envelope, and since spec 1.2 every Segment Ecommerce V2 event, under its own name, with every property Segment defines on it. Where this spec's shape differs from Segment's, the ingest folds Segment's shape onto it before validation, so an integration written against Segment's documentation is accepted unchanged. What still differs, and why, is on the developer docs' Segment compatibility page (apps/docs/src/content/docs/integrations/segment-compatibility.mdx in the source repository).

What is in the package

| Path | What it is | |---|---| | schemas/core/<schema>/<M-R-A>.json | The JSON Schemas, one immutable file per version | | schemas/RELEASED.json | sha256 of every released schema file | | registry.json | Every event: its call type, its schema, which producers may send it, whether it is the shopper's own activity (activity), and which schema version each spec version uses | | lib/index.mjs | The validator the ingest runs | | generated/sdk/ | Event models for TypeScript, Kotlin, Swift and Java, with the spec's closed vocabularies (enums, context values, consent purposes), and a compact ruleset for in-browser conformance checks. Kotlin and Swift hold only the events an app may send | | generated/destination/message.yaml | The Pub/Sub destination message as a field list (see The destination message) | | assignment/holdout.json | Who is in a property's personalisation holdout: the algorithm and the vectors every SDK and server must reproduce | | assignment/decision-id.json | The decision_id of a server-side assignment: the UUIDv5 namespace, the name format and the vectors HP and every client must reproduce | | assignment/bandit-split.json | Which side of a readout slot's bandit-vs-default 50/50 split a visitor is on: the algorithm and the vectors every implementation must reproduce | | assignment/experiment-arms.json | Which arm of an experiment a visitor is in, the one split every experiment on a property uses: the algorithm and the vectors every implementation must reproduce | | examples/destination-message/ | Example destination messages, each validated by this package's tests | | onsite/creative.schema.json | An onsite form's creative, the payload of each of its decision arms (JSON Schema 2020-12; see Onsite capture) | | onsite/settings.schema.json | hp.onsite in the SDK settings: the live forms' triggers, pages, caps, consent text, questions and prize labels | | onsite/values.json | The shopper values a form's text may show ({ "v": key, "fallback": … }): each value's key, its formats (the first its default) and the words the console offers it in | | onsite/examples/ | Example creatives (*.creative.json) and settings (*.settings.json), each validated by this package's tests | | lib/onsite.mjs (/onsite) | validateCreative and validateOnsiteSettings | | openapi/collection.yaml | The collection host: the settings an SDK boots from | | openapi/ingest.yaml | The collection host: the Segment-compatible endpoints events are sent to | | openapi/public-api.yaml | The public API: recommendations, decisions, server-side assignments and slot registration, audience membership, social proof | | EVENT-CATALOGUE.md | Every event and field, and what each page type owes, generated from the schemas |

CHANGELOG.md (what each spec version added, and the released descriptions it made wrong), PRODUCT-IDS.md (which id is product_id and which is sku for variants, product pages and colour families, with THG's mapping) and tests/vectors/ are in the repository, not the package.

Versions and what they guarantee

The spec is versioned <major>.<minor>, currently 1.22. Every SDK stamps it on each event:

{ "context": { "spec": { "version": "1.22" } } }

Each schema is versioned <model>-<revision>-<addition> and has an $id of the form urn:thg-hyper-personalisation:schema:<namespace>:<schema_name>:<M-R-A>, for example urn:thg-hyper-personalisation:schema:core:order_completed:1-0-0. References between schemas use these absolute ids. registry.json maps each spec version to the schema version every event and shared definition uses in it.

The guarantees:

  1. A released schema never changes. Its bytes are checksummed in schemas/RELEASED.json, and CI fails if one moves. A correction is a new version beside it.
  2. A spec minor only adds. Between 1.0 and 1.1, a schema may gain optional properties and the registry may gain events. Nothing becomes required, nothing is removed or renamed, and no type, enum or bound narrows. An event valid under 1.0 is valid under every later 1.x. An older validator still refuses a field it has not seen on an object that allows no others, so a minor that adds one is safe only for events that bypass the gateway, or once the gateway that knows it is released.
  3. Breaking changes wait for a major. A new schema model — a required field, a rename, a removal — ships only in a new spec major.
  4. The previous major keeps working. The ingest validates each event against the major it declares, and accepts the current and the previous major, so an app still in the field on the old major keeps landing while it upgrades.

How an event's version is chosen:

  • context.spec.version names a released version: that version is used.
  • It names a minor this build has not seen (an SDK ahead of the ingest): the latest minor of the same major is used.
  • It names a major that is neither current nor previous: the event is refused.
  • It is absent: the current version is used, and the ingest records the version as inferred.

Who may send what

Each event lists its producers: web and mobile (a browser or app, holding a publishable key) and server (a server integration, holding a secret key). The ingest takes the producer from the key, never from the payload, and refuses an event from a producer not on its list. Most events have one system of record — an order is reported by the service that placed it, a product view by the page that showed it — because an event two systems both send under different messageIds is counted twice. Where a server owns a fact the client also sees (sign-in, sign-up, sign-out, newsletter opt-in, a completed checkout step, payment details), both may send it: under one messageId the warehouse keeps the server's copy. Each event's producers_note says which.

The ingest also stamps fields of its own onto every accepted event (the organisation, source, key, property, spec version, and when it arrived). Those are described by schemas/core/server_envelope/1-0-1.json and are not part of what a client sends.

The destination message

What a Pub/Sub destination receives is a contract of its own, separate from what a producer sends: schemas/core/destination_message/<version>.json, the version named by contracts in registry.json. It describes the message data (the flattened row the platform's landing table also receives) and the message attributes. The body is closed, so a new field is a new schema version, and the gateway's tests fail if what it publishes drifts from the version named here.

| File | What it is for | |---|---| | schemas/core/destination_message/1-0-1.json | The JSON Schema. #/definitions/body and #/definitions/attributes validate each half alone | | generated/destination/message.yaml | The same contract as a field list (name, type, optional, enum, doc), for consumers that keep message contracts in that form. Generated by pnpm run generate:destination; never edited | | examples/destination-message/*.json | Example messages, { data, attributes, orderingKey? }, each validated by this package's tests |

validateDestinationMessage(message) checks a message against the current contract.

contracts.product_record names the catalogue record version on the wire, the body of a kind=catalogue message, and validateProductRecord(record) checks one against it. It is also in each spec version's definitions, frozen at 1-0-0: no event references it, so a new version moves the contract rather than the spec.

Pinning it from another repository. Pin an exact package version and take the files from it, for example:

npm install --save-exact @thg-agentic/hyper-personalisation-spec@<version>
cp node_modules/@thg-agentic/hyper-personalisation-spec/schemas/core/destination_message/1-0-1.json \
   node_modules/@thg-agentic/hyper-personalisation-spec/generated/destination/message.yaml \
   contracts/hp/

Commit what you copied together with the version, and in CI run your consumer (a router, a silver model's extraction) over examples/destination-message/ from the same version. A consumer that generates its own schema file from message.yaml should fail its build when the pinned file changes and the generated one does not.

Onsite capture

An onsite form (a sign-up popup, a spin-to-win, a mobile sheet) is a decision slot of kind capture, and each of its versions is an arm whose payload is a creative document. The creative says what the form shows, step by step; it carries no HTML, CSS or script, its text is a small tree of bold, italic and link runs, and its links and images are https only. Consent text, questions and prize labels are not in the creative: the SDK settings publish them, so every arm shows the same approved words. Odds and codes are in neither: prizes are drawn on the server.

import { validateCreative } from '@thg-agentic/hyper-personalisation-spec/onsite';
import creativeSchema from '@thg-agentic/hyper-personalisation-spec/onsite/creative.schema.json' with { type: 'json' };

const { valid, errors } = validateCreative(arm.payload);

validateCreative runs the schema, then what JSON Schema cannot say: step ids are unique, every step a creative names exists, each step has what its kind needs (an email step its field, the consent and a submit button), and a whole heading or text stays within 120 or 600 characters. validateOnsiteSettings checks the hp.onsite index. Both pull in Ajv, so keep them out of browser bundles, as the event validator.

What each page type owes

page_expectations in the registry says which events a page of each context.page.type must emit, so that a missing event is caught rather than merely an invalid one. It follows Segment's lifecycle: a page call on every page, and on each commerce page the Ecommerce V2 event that describes it.

| Page type | Required | Required on entry | |---|---|---| | list | page, Product List Viewed | | | product | page, Product Viewed | | | search | page, Products Searched | | | basket | page, Cart Viewed | | | checkout | page, Checkout Step Viewed | Checkout Started | | every other type | page | |

An event required on entry is owed by the first page of a run of pages of that type, so checkout sends Checkout Started once and Checkout Step Viewed on every step. EVENT-CATALOGUE.md also lists the recommended events. The web SDK's conformance checker reports what a page did not send.

Extending the spec with your own events

The core catalogue is the same for every organisation. Your own events and context go in your organisation's namespace, which is its slug:

{ "type": "track", "event": "acme.Shade Finder Matched", "properties": { … } }
{ "context": { "extensions": { "acme": { "store_id": 410 } } } }

Declare them in your organisation's tracking plan in the console: a JSON Schema per event name. The ingest validates namespaced events and extension context against the tracking plan, and each source's violation policy decides what happens to an event that does not match (quarantine it, accept it with a flag, or drop the unplanned properties). A tracking-plan schema may reuse the core definitions by $id, for example { "$ref": "urn:thg-hyper-personalisation:schema:core:product:1-0-0" }.

An event with no namespace that is not in the registry is refused, and so is a namespace the sending source's organisation does not own. core is never an organisation namespace.

If an event would be useful to every retailer, propose it for the core spec instead.

Validating locally

npm install --save-dev @thg-agentic/hyper-personalisation-spec
import { SPEC_VERSION, validate } from '@thg-agentic/hyper-personalisation-spec';

const result = validate(event, { producer: 'web' });
if (!result.valid) console.error(result.errors);
// result.specVersion is the version the event was judged by; result.specInferred is true when the
// event did not declare one.

Options:

| Option | Meaning | |---|---| | producer | 'web', 'mobile' or 'server': refuse an event this producer may not send | | specVersion | Validate against this version instead of the one the event declares | | namespaces | Organisation namespaces the source may use. The validator checks the namespace only; the payload is checked against your tracking plan |

Use the validator in development and tests. It pulls in Ajv, so keep it out of production browser bundles; the web SDK takes it as an injected validate function for that reason.

tests/vectors/ (in the repository, not the package) holds the conformance vectors: plain JSON events, each stating whether it is valid and why. They are the portable definition of conformance, so an implementation in another language can assert against them unchanged.

Generated models

generated/sdk/ is generated from the registry and the schemas and must not be edited by hand. Each language's file exports SPEC_VERSION for stamping context.spec.version:

| Language | File | Package | |---|---|---| | TypeScript | typescript/events.ts | — | | Kotlin | kotlin/CdpEvents.kt | com.thg.agentic.hyperpersonalisation.events | | Swift | swift/CdpEvents.swift | — | | Java | java/CdpEvents.java | com.thg.agentic.hyperpersonalisation.events |

Changing the spec

  1. Add a new version file beside the current one — never edit a released file — and point the registry's next spec version at it. An additive change is a new revision or addition in a new spec minor; a breaking change is a new model in a new spec major.
  2. pnpm run release:schemas records the new file's checksum. It refuses a change to a released file, and a successor that does more than add optional properties.
  3. pnpm run generate regenerates the SDK models and EVENT-CATALOGUE.md.
  4. pnpm test runs the vectors, the consistency checks, and the release checks.

Licence

MIT. See LICENSE.