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

@superlikers/microsites-api

v0.2.0

Published

Typed Angular services for the Superlikers microsites backend

Readme

@superlikers/microsites-api

Typed Angular services for the Superlikers microsites backend (session, participant, entries, prizes, goals, ranking…).

Pre-1.0: the API may still change between minor versions (0.1 → 0.2). Pin a minor range (~0.1.0) and check the changelog before upgrading.

Compatibility

| @superlikers/microsites-api | Angular | RxJS | | ----------------------------- | ------- | ------ | | 0.x | ^22.0 | ^7.8 |

Installation

pnpm add @superlikers/microsites-api
# or: npm install @superlikers/microsites-api

Setup

Register the library once in app.config.ts:

import { ApplicationConfig, inject } from '@angular/core'
import { Router } from '@angular/router'
import { provideMicrositesApi } from '@superlikers/microsites-api'

import { environment } from '../environments/environment'

export const appConfig: ApplicationConfig = {
  providers: [
    provideMicrositesApi({
      baseUrl: environment.baseUrl, // e.g. https://4x.superlikerslabs.com
      onUnauthorized: () => inject(Router).navigate(['/login']) // optional
    })
  ]
}

| Option | Required | Description | | ---------------- | -------- | -------------------------------------------------------------------------------------------------------------------- | | baseUrl | yes | Absolute http(s) URL of the microsite backend. A trailing slash is removed. An invalid value throws on startup. | | onUnauthorized | no | Called on every 401 response, in an injection context (inject() works). The error is still thrown to the caller. |

HttpClient needs no extra setup. Every request is sent with withCredentials: true (session cookie) and Accept: application/json.

Usage

Inject a service and subscribe (or convert to a signal / promise). Every method returns a cold Observable, so nothing is sent until you subscribe.

import { Component, inject, signal } from '@angular/core'
import { isApiError, SessionService } from '@superlikers/microsites-api'

@Component({ selector: 'app-login', templateUrl: './login.html' })
export class Login {
  readonly #session = inject(SessionService)
  protected readonly error = signal('')

  protected login(email: string, password: string) {
    this.#session.login({ email, password }).subscribe({
      next: () => this.error.set(''),
      error: (error: unknown) => this.error.set(isApiError(error) ? error.message : 'Unexpected error')
    })
  }
}

Services

| Service | Methods | | ---------------------- | ---------------------------------------------------------------------------------------------------------------------- | | SessionService | getLoginForm, login, logout, getSsoLoginUrl, checkLoginRequirements | | RegistrationsService | getSignupForm, signup, updateParticipant, sendEmailVerificationToken, validateEmailToken, confirmEmail | | PasswordService | changePassword, validateChannel, sendResetToken, resetPassword | | ParticipantService | getCurrent, getRank, list, getByUid, savePushSubscription, removePushSubscriptions | | EntriesService | listExternals, listRedemptions, listPhotos, listDocuments, listPurchases, list, search, createExternal | | ExternalFormsService | listForms, getFormFields, submit, listSubmissions, getSubmission, updateSubmission | | ActivitiesService | uploadPhoto, uploadDocument | | PrizesService | list, checkRedeem, redeem, getWishlist, toggleWishlist, getExternalCouponData | | GoalsService | listParticipantGoals, list, accept, decline | | AchievementsService | list | | BadgesService | list, get, getStats | | RankingService | getSegment | | MetricsService | list, calculate | | BoardsService | list, listReports, executeReport | | BlogsService | list, get, create | | CommentsService | list, create | | VotesService | get, create, update, delete | | TeamsService | list, get, create, removeMember | | ReferralsService | changeCode |

Each method documents its endpoint and parameters in its JSDoc (visible on hover in the editor). A method whose campaign module is disabled fails with an ApiError (see Errors).

Campaign-specific fields

Models hold the fields every campaign has; a field some campaigns don't return, such as email in campaigns that don't ask for one, is optional. Campaigns add their own fields (participant properties, goal data, external action data…), so the services that return them take a type parameter. Extend the Base* model and pass your full interface:

import { BaseCurrentParticipant, ParticipantService } from '@superlikers/microsites-api'

interface CurrentParticipant extends BaseCurrentParticipant {
  distributor_code: string
  region: string
}

inject(ParticipantService)
  .getCurrent<CurrentParticipant>()
  .subscribe(({ participation }) => console.log(participation.region))

| Type parameter of | Default | | ---------------------------------------------------------- | ------------------------- | | ParticipantService.getCurrent | BaseCurrentParticipant | | ParticipantService.list / getByUid | BaseParticipant | | ParticipantService.getRank | BaseParticipantRankData | | GoalsService.list / listParticipantGoals (goal_data) | GoalData | | EntriesService.listExternals (data) | EntryData | | ExternalFormsService.listSubmissions / getSubmission | EntryData | | BlogsService / CommentsService (embedded participant) | ActivityParticipant |

Entries

EntriesService has one method per common entry type (listExternals, listRedemptions, listPhotos, listDocuments, listPurchases), each typed with its own model. For other types, or several at once, use list() and narrow each item with isEntryType:

import { EntriesService, isEntryType } from '@superlikers/microsites-api'

inject(EntriesService)
  .list({ _type: 'ExtraPoints' })
  .subscribe(({ data }) => {
    for (const entry of data) {
      if (isEntryType(entry, 'ExtraPoints')) console.log(entry.concept)
    }
  })

Every entries method uses the backend's detailed show view, where the external action slug arrives in atype.

Entries of a type the library doesn't model yet are returned as UnknownEntry: the common fields are typed and the rest are unknown.

Query parameters

Parameters are serialized the way the backend expects: null and undefined are skipped, arrays are sent as key[]=a&key[]=b, and objects as key[sub]=value, for example date_filter[start]=….

Errors

Every failure is thrown as an ApiError. This includes the failures the backend reports with HTTP 200: the ones with state: 'error', and a rejected redemption (success: false). So every error is handled in the error callback, and a response that reaches the next callback is a success.

Its message is meant to be shown to the participant: it is the backend's message, or a Spanish default when the backend sends none.

| Property | Description | | ------------------- | ------------------------------------------------------------------------------------------------------------- | | message | Backend message, or a default one, ready to show to the participant. | | status | HTTP status. 0 = the request never reached the server. | | code | Backend error code (code_error), or null. | | fieldErrors | Validation errors by field, e.g. { email: 'is invalid' }. Empty object when there are none. | | url | URL of the failed request. | | isNetworkError | status === 0 (offline, CORS, DNS…). | | isUnauthorized | status === 401. | | isInvalidResponse | The server answered with something that is not JSON, usually because the module is disabled for the campaign. | | cause | The original HttpErrorResponse, whose error holds the backend's body. |

Use isApiError(error) to narrow an unknown error.

A rejected redemption (not enough points, over the limit…) arrives as an ApiError whose message is the backend's missing_message; the whole body, typed by RedeemFailure, is in cause:

prizes.redeem(prize._id).subscribe({
  next: redemption => this.code.set(redemption.code),
  error: (error: unknown) => {
    if (!isApiError(error)) return
    this.message.set(error.message) // e.g. "Te faltan 500 puntos"
    const body = (error.cause as HttpErrorResponse).error as RedeemFailure
    this.missingPoints.set(body.missing.points)
  }
})

Testing (@superlikers/microsites-api/testing)

Helpers for your app's unit tests. Import them only from spec files.

import { HttpTestingController } from '@angular/common/http/testing'
import { TestBed } from '@angular/core/testing'
import { SessionService } from '@superlikers/microsites-api'
import { MICROSITES_API_TEST_BASE_URL, provideMicrositesApiTesting } from '@superlikers/microsites-api/testing'

TestBed.configureTestingModule({ providers: [provideMicrositesApiTesting()] })
const http = TestBed.inject(HttpTestingController)

TestBed.inject(SessionService).logout().subscribe()
http.expectOne(`${MICROSITES_API_TEST_BASE_URL}/sessions/logout`).flush({ state: 'success', message: 'ok' })

| Export | Description | | ------------------------------ | ---------------------------------------------------------------------------------------------------------- | | provideMicrositesApiTesting | Library config + HttpClient + HttpTestingController. Accepts config overrides (e.g. onUnauthorized). | | MICROSITES_API_TEST_BASE_URL | The baseUrl used in tests, to match requests. | | createApiError | Builds an ApiError as the backend would, to simulate failures with mocked services. |

import { throwError } from 'rxjs'
import { createApiError } from '@superlikers/microsites-api/testing'

{ provide: SessionService, useValue: { login: () => throwError(() => createApiError({ code: 141, message: 'Wrong password' })) } }

Forms (@superlikers/microsites-api/forms)

Helpers for Angular reactive forms. This entry point requires @angular/forms, an optional peer dependency that Angular apps usually already have.

channelValidator(channel, options?) is an async validator that checks, against the backend, that a participant exists for an email or phone number. It is meant for "forgot password" forms. Call it in an injection context, such as a field initializer.

import { channelValidator } from '@superlikers/microsites-api/forms'

readonly form = inject(FormBuilder).nonNullable.group({
  email: ['', [Validators.required, Validators.email], [channelValidator('email')]]
})
@if (form.controls.email.errors?.['channel']; as error) {
<p>{{ error.message }}</p>
}

It waits 500 ms after the last change before calling the backend ({ debounceMs } to change it) and leaves empty values to Validators.required.

Versioning

The package follows semver, independent of Angular's version. While in 0.x:

  • 0.MINOR.0: may contain breaking changes.
  • 0.x.PATCH: new features and fixes, no breaking changes.

Every release is documented in the changelog.