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

@adatechnology/scheduling-contracts

v0.1.0

Published

Shared types, zod schemas and port interfaces for the scheduling trio (contracts only — no runtime behavior)

Readme

@adatechnology/scheduling-contracts

Tipos, schemas e contratos de porta para o módulo de agendamento. TypeScript, sem dependências de runtime (só Zod para validação em boundaries).

  • Tipos de domínioResource, Service, Booking, AvailabilityRule, AvailabilityException
  • Schemas Zod — validação de entrada para todo endpoint e worker
  • Interfaces de porta — contratos de inversão que o host implementa (VideoMeetingPort, CalendarSyncPort, etc)
  • Eventos e hooks — sete eventos de ciclo de vida, com hooks opcionais para regra comercial

Pacote-irmão do scheduling-module (a implementação); consumido também pelo host (API, BFF, worker).


Instalação

npm i @adatechnology/scheduling-contracts
# ou: pnpm add / bun add

Nenhuma dependência de runtime além de zod.


O que este pacote exporta

Tipos de domínio

Toda entidade de agendamento tem seu tipo TypeScript correspondente:

import type {
  Resource,
  Service,
  Booking,
  BookingSlot,
  AvailabilityRule,
  AvailabilityException,
} from '@adatechnology/scheduling-contracts'

Enums são constantes (as const): RESOURCE_KIND, BOOKING_STATUS, BOOKING_PARTICIPANT_RESPONSE_STATUS, etc.

Schemas de validação

Zod schemas para toda entrada (body, query, path, eventos):

import {
  createResourceSchema,
  updateResourceSchema,
  requestBookingSchema,
  rescheduleBookingSchema,
  cancelBookingSchema,
  listBookingsQuerySchema,
  getAvailabilityQuerySchema,
} from '@adatechnology/scheduling-contracts'

// Validar entrada de API
const input = createResourceSchema.parse(req.body)

Portas (interfaces de integração)

Portas são contratos que o host implementa — o módulo não conhece a implementação concreta, só chama pelo contrato. Ausência de uma porta = capacidade desligada (não é erro, é opção).

AuthContextResolverPort

Resolve identidade do usuário a partir de headers HTTP:

import type { AuthContextResolverPort } from '@adatechnology/scheduling-contracts'

const resolver: AuthContextResolverPort = {
  async resolve({ headers }) {
    // Validação de token, JWT, sessão — já feita fora
    // Retorna contexto ou undefined (não autenticado)
    return {
      companyId: '...',
      userId: '...',
      scopes: ['scheduling:read', 'scheduling:write'],
    }
  },
}

AuthContext já vem validado pelo host — o módulo não verifica token nem descobre o emissor. Vem apenas companyId (obrigatório) e userId (opcional para máquina-a-máquina).

VideoMeetingPort

Criar/deletar links de reunião (Zoom, Google Meet, etc):

import type { VideoMeetingPort } from '@adatechnology/scheduling-contracts'

const videoMeeting: VideoMeetingPort = {
  async createMeeting({ bookingId, title, startsAt, endsAt }) {
    // Chamar Zoom, Google Calendar, etc
    // Retorna meetingUrl ou erro
    return {
      outcome: 'created',
      meetingUrl: 'https://zoom.us/j/...',
    }
  },
  
  async deleteMeeting(meetingUrl) {
    // Remover a reunião
  },
}

Ausente = presencial. Se você não injetar VideoMeetingPort, o módulo não cria link. Falha ao criar link não bloqueia a confirmação de reserva — o módulo loga e segue.

CalendarSyncPort

Espelho unidirecional (push) em Google Calendar, Outlook, etc:

import type { CalendarSyncPort } from '@adatechnology/scheduling-contracts'

const calendarSync: CalendarSyncPort = {
  async upsertEvent({ externalCalendarId, title, startsAt, endsAt, notes }) {
    // Criar ou atualizar evento no calendário externo
    return {
      outcome: 'synced',
      externalEventId: 'event-123',
    }
  },
  
  async deleteEvent(externalEventId) {
    // Remover evento do calendário externo
  },
  
  async readEvents({ from, until }) {
    // Listar eventos no intervalo (implementado na v2)
    // Por enquanto retorna array vazio
    return []
  },
}

Push-only na v1 — sem webhook do fornecedor, sem sync bidirecional, sem resolução de conflito. readEvents já vem declarado (sem implementação) para não virar breaking change depois.

Ausente = sem espelho. Config calendarSync.enabled: true sem porta plugada = erro no boot (CalendarSyncDisabledError).

ClockPort

Relógio injetável (para teste, para lidar com clock skew):

import type { ClockPort } from '@adatechnology/scheduling-contracts'

const clock: ClockPort = {
  now() {
    return new Date()
  },
}

LoggerPort

Logger estruturado (máscara de PII obrigatória):

import type { LoggerPort } from '@adatechnology/scheduling-contracts'

const logger: LoggerPort = {
  debug(message, meta) { /* ... */ },
  info(message, meta) { /* ... */ },
  warn(message, meta) { /* ... */ },
  error(message, meta) { /* ... */ },
}

Configuração do módulo

SchedulingModuleConfig agrupa opções de negócio e capacidade:

import type { SchedulingModuleConfig } from '@adatechnology/scheduling-contracts'

const config: SchedulingModuleConfig = {
  // Teto de dias consultáveis numa janela de disponibilidade
  maxLookaheadDays: 90,
  
  // Prazo mínimo para cancelamento (em minutos), 0 = desliga
  defaultMinCancellationNoticeMinutes: 1440, // 24h
  
  // Tolerância para "horário no passado" em criação/remarcação
  pastBookingToleranceMinutes: 0,
  
  // Janela de antecedência para lembrete (padrão 1440 = 24h)
  reminderAdvanceMinutes: 1440,
  
  // Ligar espelho em calendário externo
  calendarSync: {
    enabled: true,
  },
}

Eventos e hooks

O módulo dispara sete eventos ao longo do ciclo de vida de uma reserva. Hooks são opcionais — você só implementa o que precisa.

Hooks são void-tolerantes

Se um hook lançar erro, o módulo captura, loga e segue. Regra comercial (notificar por WhatsApp, gravar em CRM, multa por no-show) nunca vira if dentro do agendamento — sempre um hook plugado aqui.

import type { SchedulingHooks } from '@adatechnology/scheduling-contracts'

const hooks: SchedulingHooks = {
  async onBookingRequested(event) {
    // Reserva foi solicitada (ainda não confirmada)
    // Integrar com CRM, notificar, aplicar política de sinal
  },
  
  async onBookingConfirmed(event) {
    // Reserva foi confirmada
    // Notificar cliente, criar tarefa, registrar contato
  },
  
  async onBookingRescheduled(event) {
    // Reserva foi remarcada (event.previousDuring tem a faixa anterior)
    // Notificar: "mudou de X para Y"
  },
  
  async onBookingCancelled(event) {
    // Reserva foi cancelada (event.cancelledBy é quem pediu)
    // Notificar, reembolsar, aplicar multa por no-show
  },
  
  async onBookingReminderDue(event) {
    // Reminder foi disparado (geralmente 24h antes)
    // Enviar SMS, WhatsApp, notificação push
  },
  
  async onBookingCompleted(event) {
    // Reserva transcorreu (passou a hora)
    // Registrar no histórico, liberar para feedback
  },
  
  async onBookingNoShow(event) {
    // Cliente não compareceu
    // Multa, entrada em blacklist, CRM updated
  },
}

Cada hook recebe evento tipado com metadados — companyId, bookingId, resourceIds, serviceId (quando houver).


Erros

Toda operação falha com uma das erros especializados:

import {
  SchedulingError,
  SCHEDULING_ERROR_CODES,
  ResourceNotFoundError,
  ServiceNotFoundError,
  AvailabilityExceptionNotFoundError,
  BookingNotFoundError,
  SlotUnavailableError,
  CancellationTooLateError,
  BookingInPastError,
  ResourceUnavailableError,
  ServiceNotOfferedByResourceError,
  ConfigMissingError,
  LookaheadWindowTooLargeError,
  CalendarSyncDisabledError,
} from '@adatechnology/scheduling-contracts'

Cada erro estende SchedulingError e carrega:

  • statusCode — HTTP apropriado (404, 409, 400, 500)
  • code — chave estável para tratamento na UI (SCHEDULING_SLOT_UNAVAILABLE, etc)
  • details — contexto tipado (ex: { resourceId, during })

| Erro | Causa | Status | |---|---|---| | ResourceNotFoundError | Recurso não existe ou foi deletado | 404 | | ServiceNotFoundError | Serviço não existe | 404 | | AvailabilityExceptionNotFoundError | Exceção de disponibilidade não existe | 404 | | BookingNotFoundError | Reserva não existe | 404 | | SlotUnavailableError | Horário já foi ocupado por outra reserva (constraint booking_slot_no_overlap) | 409 | | CancellationTooLateError | Prazo mínimo de cancelamento passou | 409 | | BookingInPastError | Tentou agendar/remarcar para o passado | 400 | | ResourceUnavailableError | Recurso inativo, deletado ou fora da janela de validade | 409 | | ServiceNotOfferedByResourceError | Este recurso não oferece este serviço | 400 | | ConfigMissingError | Campo obrigatório da config falta no boot | 500 | | LookaheadWindowTooLargeError | Consultou mais dias que maxLookaheadDays | 400 | | CalendarSyncDisabledError | Config habilitou sync mas porta não foi plugada | 409 |


Utilitários exportados

| Tipo | Uso | |---|---| | Tipos de domínio | Resource, Service, Booking, AvailabilityRule, AvailabilityException, BookingSlot, BookingParticipant | | Schemas Zod | createResourceSchema, requestBookingSchema, listBookingsQuerySchema, getAvailabilityQuerySchema, etc | | Portas | AuthContextResolverPort, VideoMeetingPort, CalendarSyncPort, ClockPort, LoggerPort | | Tipos de porta | AuthContext, VideoMeetingRequest, CalendarEventPayload, LogMeta, SchedulingModuleConfig | | Eventos | SCHEDULING_EVENT (constantes), SchedulingHooks, BookingRequestedEvent, BookingConfirmedEvent, etc | | Erros | SchedulingError, SCHEDULING_ERROR_CODES, 12 erros especializados |


Pré-requisitos

  • TypeScript 5.0+
  • Zod 3.x — importado automaticamente em schemas
  • Conhecimento de portas/inversão de controle — o pacote é contrato puro

Como consumir em scheduling-module

O módulo é uma função fábrica que injeta portas e hooks — não uma classe:

import { createSchedulingModule } from '@adatechnology/scheduling-module'
import type { SchedulingModuleConfig } from '@adatechnology/scheduling-contracts'

const scheduling = createSchedulingModule({
  db,
  config,
  providers: {
    clock,
    logger,
    videoMeeting, // opcional
    calendarSync, // opcional
  },
  hooks, // opcional
})

Quando uma porta está ausente, a capacidade é desligada — sem flag, sem null check em callback.


Licença

MIT © Ada Technology