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

@microboxlabs/miot-calendar-client

v0.5.0

Published

[![npm](https://img.shields.io/npm/v/@microboxlabs/miot-calendar-client)](https://www.npmjs.com/package/@microboxlabs/miot-calendar-client) [![License](https://img.shields.io/npm/l/@microboxlabs/miot-calendar-client)](./LICENSE)

Readme

@microboxlabs/miot-calendar-client

npm License

TypeScript client for the ModularIoT Calendar API. Zero dependencies — uses the native fetch API.

Installation

npm install @microboxlabs/miot-calendar-client

Quick Start

A complete workflow: create a calendar, define a time window, generate slots, and book one.

import { createMiotCalendarClient } from "@microboxlabs/miot-calendar-client";

const client = createMiotCalendarClient({
  baseUrl: "https://your-api-host.com",
});

// 1. Create a calendar
const calendar = await client.calendars.create({
  code: "maintenance",
  name: "Vehicle Maintenance",
  timezone: "America/New_York", // defaults to "UTC" if omitted
});

// 2. Add a time window (Mon–Fri, 8 AM – 5 PM)
const timeWindow = await client.calendars.createTimeWindow(calendar.id, {
  name: "Business Hours",
  startHour: 8,
  endHour: 17,
  validFrom: "2026-03-01",
  daysOfWeek: "1,2,3,4,5",        // Mon–Fri
  capacity: 2,                     // default: 1
});

// 3. Generate slots for the first two weeks
const result = await client.slots.generate({
  calendarId: calendar.id,
  startDate: "2026-03-01",
  endDate: "2026-03-14",           // max range: 90 days
});
console.log(`Created ${result.slotsCreated} slots`);

// 4. Find available slots
const { data: slots } = await client.slots.list({
  calendarId: calendar.id,         // required
  available: true,
  startDate: "2026-03-03",
  endDate: "2026-03-07",
});

// 5. Book the first available slot
const booking = await client.bookings.create(
  {
    calendarId: calendar.id,
    resource: { id: "truck-42", type: "vehicle", label: "Truck 42" },
    slot: { date: slots[0].slotDate, hour: slots[0].slotHour, minutes: slots[0].slotMinutes },
  },
  { userId: "user-abc" },          // sets X-User-Id header → stored as createdBy
);

Configuration

const client = createMiotCalendarClient({
  baseUrl: "https://your-api-host.com",  // Required — API base URL
  headers: { Authorization: "Bearer ..." }, // Optional — merged into every request
  fetch: customFetch,                       // Optional — custom fetch implementation
});

| Option | Type | Required | Description | |--------|------|----------|-------------| | baseUrl | string | Yes | Base URL of the Calendar API | | headers | Record<string, string> | No | Default headers sent with every request | | fetch | typeof fetch | No | Custom fetch implementation (defaults to globalThis.fetch) |

API Reference

Calendars

calendars.list(params?)

List all calendars, optionally filtered by active status or group membership.

| Param | Type | Required | Description | |-------|------|----------|-------------| | active | boolean | No | Filter by active status | | groupCode | string | No | Filter calendars belonging to this group code |

Returns: CalendarResponse[]

calendars.get(id)

Get a single calendar by ID.

| Param | Type | Required | Description | |-------|------|----------|-------------| | id | string | Yes | Calendar ID |

Returns: CalendarResponse

Throws: MiotCalendarApiError with status 404 if not found.

calendars.create(body)

Create a new calendar.

| Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | code | string | Yes | — | Unique code identifier | | name | string | Yes | — | Display name | | description | string | No | — | Optional description | | timezone | string | No | "UTC" | IANA timezone | | active | boolean | No | true | Whether the calendar is active | | parallelism | number | No | 1 | Parallel resources per slot (e.g. loading docks) | | groups | string[] | No | — | Group codes to assign. null = no change; [] = remove all; ["code"] = replace all |

Returns: CalendarResponse

calendars.update(id, body)

Replace a calendar's fields. Takes the same body as create.

| Param | Type | Required | Description | |-------|------|----------|-------------| | id | string | Yes | Calendar ID | | body | CalendarRequest | Yes | Updated calendar data (include groups to reassign group membership) |

Returns: CalendarResponse

calendars.deactivate(id)

Deactivate a calendar (soft delete).

| Param | Type | Required | Description | |-------|------|----------|-------------| | id | string | Yes | Calendar ID |

Returns: void (HTTP 204 — no content)


Time Windows

Time windows are managed through the calendars namespace.

calendars.listTimeWindows(calendarId)

List all time windows for a calendar.

| Param | Type | Required | Description | |-------|------|----------|-------------| | calendarId | string | Yes | Calendar ID |

Returns: TimeWindowResponse[]

calendars.createTimeWindow(calendarId, body)

Create a time window within a calendar.

| Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | name | string | Yes | — | Time window name | | startHour | number | Yes | — | Start hour (0–23) | | endHour | number | Yes | — | End hour (0–23, must be > startHour) | | validFrom | string | Yes | — | Start date (YYYY-MM-DD) | | validTo | string | No | — | End date (YYYY-MM-DD) | | capacity | number | No | 1 | Total number of services this window can handle across all slots | | daysOfWeek | string | No | — | Comma-separated days (1=Mon … 7=Sun) | | active | boolean | No | true | Whether the time window is active |

Returns: TimeWindowResponse

calendars.updateTimeWindow(calendarId, timeWindowId, body)

Update a time window. Takes the same body as createTimeWindow.

| Param | Type | Required | Description | |-------|------|----------|-------------| | calendarId | string | Yes | Calendar ID | | timeWindowId | string | Yes | Time window ID | | body | TimeWindowRequest | Yes | Updated time window data |

Returns: TimeWindowResponse


Groups

Calendar groups let you organize calendars into named collections. Calendars can belong to multiple groups; use CalendarRequest.groups on create or update to manage membership.

groups.list(params?)

List all calendar groups, optionally filtered by active status.

| Param | Type | Required | Description | |-------|------|----------|-------------| | active | boolean | No | Filter by active status |

Returns: CalendarGroupResponse[]

groups.get(id)

Get a single calendar group by ID.

| Param | Type | Required | Description | |-------|------|----------|-------------| | id | string | Yes | Group ID |

Returns: CalendarGroupResponse

Throws: MiotCalendarApiError with status 404 if not found.

groups.create(body)

Create a new calendar group.

| Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | code | string | Yes | — | Unique code identifier (max 50 chars) | | name | string | Yes | — | Display name (max 255 chars) | | description | string | No | — | Optional description | | active | boolean | No | true | Whether the group is active |

Returns: CalendarGroupResponse

Throws: MiotCalendarApiError with status 400 if the code is already taken.

groups.update(id, body)

Replace a calendar group's fields. Takes the same body as create.

| Param | Type | Required | Description | |-------|------|----------|-------------| | id | string | Yes | Group ID | | body | CalendarGroupRequest | Yes | Updated group data |

Returns: CalendarGroupResponse

groups.deactivate(id)

Deactivate a calendar group (soft delete). Calendars in the group are not affected.

| Param | Type | Required | Description | |-------|------|----------|-------------| | id | string | Yes | Group ID |

Returns: void (HTTP 204 — no content)


Slots

slots.list(params)

List slots for a calendar. The calendarId parameter is required.

| Param | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | calendarId | string | Yes | — | Calendar ID | | available | boolean | No | — | Filter by availability | | startDate | string | No | today | Start of date range (YYYY-MM-DD) | | endDate | string | No | today + 7 days | End of date range (YYYY-MM-DD) |

Returns: SlotListResponse{ data: SlotResponse[], total: number }

slots.get(id)

Get a single slot by ID.

| Param | Type | Required | Description | |-------|------|----------|-------------| | id | string | Yes | Slot ID |

Returns: SlotResponse

Throws: MiotCalendarApiError with status 404 if not found.

slots.generate(body)

Generate slots for a calendar based on its time windows.

| Field | Type | Required | Description | |-------|------|----------|-------------| | calendarId | string | Yes | Calendar ID | | startDate | string | Yes | Start date (YYYY-MM-DD) | | endDate | string | Yes | End date (YYYY-MM-DD) |

Note: The date range must not exceed 90 days. The API returns 400 Bad Request if the range is larger.

Returns: GenerateSlotsResponse{ slotsCreated: number, slotsSkipped: number, message: string }

slots.updateStatus(id, body)

Manually change a slot's status.

| Field | Type | Required | Description | |-------|------|----------|-------------| | id | string | Yes | Slot ID | | status | "OPEN" \| "CLOSED" | Yes | New status |

Note: Only OPEN and CLOSED are accepted. FULL is managed automatically by the system based on occupancy and cannot be set manually. Passing FULL returns 400 Bad Request.

Returns: SlotResponse


Slot Managers

Slot managers automate slot generation for calendars. Each manager keeps slots generated a configurable number of days in advance and can be triggered manually or by an external scheduler (e.g. K8s CronJob).

slotManagers.list(params?)

List all slot managers, optionally filtered by active status.

| Param | Type | Required | Description | |-------|------|----------|-------------| | active | boolean | No | When true, return only active managers |

Returns: SlotManagerResponse[]

slotManagers.create(body)

Create an automatic slot generation manager for a calendar.

| Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | calendarId | string | Yes | — | Calendar ID to manage | | active | boolean | No | true | Enable or disable automatic generation | | daysInAdvance | number | No | 30 | Days ahead to keep slots generated | | batchDays | number | No | 7 | Max days to generate per scheduler run | | reprocessFrom | string | No | — | Force regeneration from this date (YYYY-MM-DD) | | reprocessTo | string | No | — | Force regeneration through this date (required with reprocessFrom) |

Returns: SlotManagerResponse

Throws: MiotCalendarApiError with status 400 if invalid or duplicate.

slotManagers.get(id)

Get a slot manager by ID.

| Param | Type | Required | Description | |-------|------|----------|-------------| | id | string | Yes | Slot manager ID |

Returns: SlotManagerResponse

Throws: MiotCalendarApiError with status 404 if not found.

slotManagers.update(id, body)

Update a slot manager's configuration. Takes the same body as create. Set reprocessFrom + reprocessTo to schedule one-shot slot regeneration.

| Param | Type | Required | Description | |-------|------|----------|-------------| | id | string | Yes | Slot manager ID | | body | SlotManagerRequest | Yes | Updated configuration |

Returns: SlotManagerResponse

slotManagers.deactivate(id)

Deactivate a slot manager (soft delete — disables automatic generation without removing history).

| Param | Type | Required | Description | |-------|------|----------|-------------| | id | string | Yes | Slot manager ID |

Returns: void (HTTP 204 — no content)

slotManagers.runAll()

Synchronously run all active slot managers. Returns when all managers have completed.

Returns: SlotManagerRunResponse[]

slotManagers.run(id)

Synchronously run a single slot manager and return its run record.

| Param | Type | Required | Description | |-------|------|----------|-------------| | id | string | Yes | Slot manager ID |

Returns: SlotManagerRunResponse

Throws: MiotCalendarApiError with status 404 if not found.

slotManagers.listAllRuns(params?)

List recent runs across all managers.

| Param | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | limit | number | No | 50 | Maximum number of records to return |

Returns: SlotManagerRunResponse[]

slotManagers.listRuns(id, params?)

List runs for a specific manager.

| Param | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | id | string | Yes | — | Slot manager ID | | limit | number | No | 20 | Maximum number of records to return |

Returns: SlotManagerRunResponse[]

Throws: MiotCalendarApiError with status 404 if manager not found.


Bookings

bookings.list(params?)

List bookings, optionally filtered by calendar and date range.

| Param | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | calendarId | string | No | — | Filter by calendar | | startDate | string | No | today | Start of date range (YYYY-MM-DD) | | endDate | string | No | today + 30 days | End of date range (YYYY-MM-DD) |

Returns: BookingListResponse{ data: BookingResponse[], total: number }

bookings.get(id)

Get a single booking by ID.

| Param | Type | Required | Description | |-------|------|----------|-------------| | id | string | Yes | Booking ID |

Returns: BookingResponse

Throws: MiotCalendarApiError with status 404 if not found.

bookings.create(body, options?)

Create a booking for a specific slot.

Body (BookingRequest):

| Field | Type | Required | Description | |-------|------|----------|-------------| | calendarId | string | Yes | Calendar ID | | resource | ResourceData | Yes | The resource being booked | | slot | SlotData | Yes | Target slot (date + time) |

Options:

| Field | Type | Required | Description | |-------|------|----------|-------------| | userId | string | No | Sets X-User-Id header — stored as createdBy in the response |

Returns: BookingResponse

Throws:

  • 404 — Calendar or slot not found
  • 409 — Slot is full (no available capacity)

bookings.cancel(id)

Cancel a booking.

| Param | Type | Required | Description | |-------|------|----------|-------------| | id | string | Yes | Booking ID |

Returns: void (HTTP 204 — no content)

bookings.listByResource(resourceId)

List all bookings for a specific resource.

| Param | Type | Required | Description | |-------|------|----------|-------------| | resourceId | string | Yes | Resource ID |

Returns: BookingListResponse

Types

CalendarGroupRequest

interface CalendarGroupRequest {
  code: string;           // Unique code identifier (max 50 chars)
  name: string;           // Display name (max 255 chars)
  description?: string;   // Optional description
  active?: boolean;       // Active status (default: true)
}

CalendarGroupResponse

interface CalendarGroupResponse {
  id: string;
  code: string;
  name: string;
  description?: string;
  active: boolean;
  createdAt: string;      // ISO 8601
  updatedAt: string;      // ISO 8601
}

CalendarRequest

interface CalendarRequest {
  code: string;           // Unique code identifier
  name: string;           // Display name
  description?: string;   // Optional description
  timezone?: string;      // IANA timezone (default: "UTC")
  active?: boolean;       // Active status (default: true)
  parallelism?: number;   // Parallel resources per slot (default: 1, e.g. loading docks)
  groups?: string[];      // Group codes to assign. null = no change; [] = remove all; ["code"] = replace all
}

CalendarResponse

interface CalendarResponse {
  id: string;
  code: string;
  name: string;
  description?: string;
  timezone: string;                    // Always present (default: "UTC")
  active: boolean;                     // Always present (default: true)
  parallelism: number;                 // Parallel resources per slot (min: 1)
  createdAt: string;                   // ISO 8601
  updatedAt: string;                   // ISO 8601
  groups?: CalendarGroupResponse[];    // Groups this calendar belongs to
}

TimeWindowRequest

interface TimeWindowRequest {
  name: string;                 // Time window name
  startHour: number;            // 0–23
  endHour: number;              // 0–23 (must be > startHour)
  validFrom: string;            // YYYY-MM-DD
  validTo?: string;             // YYYY-MM-DD
  capacity?: number;            // Default: 1 — total services this window can handle
  daysOfWeek?: string;          // Comma-separated: "1,2,3,4,5" (1=Mon, 7=Sun)
  active?: boolean;             // Default: true
}

TimeWindowResponse

interface TimeWindowResponse {
  id: string;
  calendarId: string;
  name: string;
  startHour: number;
  endHour: number;
  slotDurationMinutes: number;  // Read-only, derived from capacity model
  capacity: number;             // Total services this window can handle
  daysOfWeek: string;
  validFrom: string;
  validTo?: string;
  active: boolean;              // Always present (default: true)
  createdAt: string;            // ISO 8601
  updatedAt: string;            // ISO 8601
}

SlotResponse

interface SlotResponse {
  id: string;
  calendarId: string;
  timeWindowId?: string;        // May be absent for manually created slots
  slotDate: string;             // YYYY-MM-DD
  slotHour: number;             // 0–23
  slotMinutes: number;          // 0–59
  capacity: number;             // Total capacity
  currentOccupancy: number;     // Current bookings count
  availableCapacity: number;    // capacity - currentOccupancy
  status: SlotStatus;           // "OPEN" | "FULL" | "CLOSED"
  createdAt: string;            // ISO 8601
  updatedAt: string;            // ISO 8601
}

SlotListResponse

interface SlotListResponse {
  data: SlotResponse[];
  total: number;
}

GenerateSlotsRequest

interface GenerateSlotsRequest {
  calendarId: string;
  startDate: string;            // YYYY-MM-DD
  endDate: string;              // YYYY-MM-DD (max 90 days from startDate)
}

GenerateSlotsResponse

interface GenerateSlotsResponse {
  slotsCreated: number;
  slotsSkipped: number;
  message: string;
}

UpdateSlotStatusRequest

interface UpdateSlotStatusRequest {
  status: SlotStatus;           // Only "OPEN" or "CLOSED" accepted
}

SlotManagerRequest

interface SlotManagerRequest {
  calendarId: string;           // Calendar ID to manage
  active?: boolean;             // Enable/disable (default: true)
  daysInAdvance?: number;       // Days ahead to keep generated (default: 30)
  batchDays?: number;           // Max days per scheduler run (default: 7)
  reprocessFrom?: string;       // YYYY-MM-DD — one-shot regeneration start
  reprocessTo?: string;         // YYYY-MM-DD — one-shot regeneration end
}

SlotManagerResponse

interface SlotManagerResponse {
  id: string;
  calendarId: string;
  calendarCode: string;
  calendarName: string;
  active: boolean;
  daysInAdvance: number;
  batchDays: number;
  reprocessFrom?: string;       // Cleared after successful run
  reprocessTo?: string;         // Cleared after successful run
  lastRunAt?: string;           // ISO 8601
  lastRunStatus?: string;       // IDLE, RUNNING, SUCCESS, FAILED, SKIPPED
  lastRunError?: string;        // Error message from last failed run
  generatedThrough?: string;    // YYYY-MM-DD — latest date with generated slots
  createdAt: string;            // ISO 8601
  updatedAt: string;            // ISO 8601
}

SlotManagerRunResponse

interface SlotManagerRunResponse {
  id: string;
  managerId: string;
  triggeredBy: string;          // SCHEDULER, API, CLI
  startedAt: string;            // ISO 8601
  finishedAt?: string;          // ISO 8601
  status: string;               // RUNNING, SUCCESS, FAILED, SKIPPED
  slotsCreated: number;
  slotsSkipped: number;
  generatedFrom?: string;       // YYYY-MM-DD
  generatedThrough?: string;    // YYYY-MM-DD
  errorMessage?: string;
}

BookingRequest

interface BookingRequest {
  calendarId: string;
  resource: ResourceData;
  slot: SlotData;
}

BookingResponse

interface BookingResponse {
  id: string;
  calendarId: string;
  resource: ResourceData;
  slot: SlotData;
  createdAt: string;            // ISO 8601
  createdBy?: string;           // Set via X-User-Id header on create
}

BookingListResponse

interface BookingListResponse {
  data: BookingResponse[];
  total: number;
}

ResourceData

interface ResourceData {
  id: string;                            // Resource identifier
  type?: string;                         // Resource type (e.g. "vehicle", "room")
  label?: string;                        // Human-readable label
  data?: Record<string, unknown>;        // Arbitrary metadata
}

SlotData

interface SlotData {
  date: string;     // YYYY-MM-DD
  hour: number;     // 0–23
  minutes: number;  // 0–59
}

SlotStatus

type SlotStatus = "OPEN" | "FULL" | "CLOSED";

ErrorResponse

interface ErrorResponse {
  error: string;      // Error type (e.g. "Bad Request")
  message: string;    // Detailed error message
  status: number;     // HTTP status code
  timestamp: string;  // ISO 8601
}

ClientConfig

interface ClientConfig {
  baseUrl: string;
  headers?: Record<string, string>;
  fetch?: typeof fetch;
}

Error Handling

All API errors throw MiotCalendarApiError.

import { MiotCalendarApiError } from "@microboxlabs/miot-calendar-client";

try {
  await client.bookings.create({
    calendarId: "cal-123",
    resource: { id: "truck-42", type: "vehicle", label: "Truck 42" },
    slot: { date: "2026-03-01", hour: 10, minutes: 0 },
  });
} catch (error) {
  if (error instanceof MiotCalendarApiError) {
    console.log(error.status);   // HTTP status code (e.g. 409)
    console.log(error.message);  // Error message string
    console.log(error.body);     // ErrorResponse object or raw string
  }
}

Error Codes

| Status | Meaning | Common Causes | |--------|---------|---------------| | 400 | Bad Request | Validation errors, slot generation range > 90 days, setting status to FULL manually | | 404 | Not Found | Calendar, slot, or booking does not exist | | 409 | Conflict | Slot is full (no available capacity for booking) |

Error Body

The body property on MiotCalendarApiError can be either:

  • An ErrorResponse object with error, message, status, and timestamp fields — returned when the API sends a JSON error response.
  • A plain string — returned as a fallback when the API response is not valid JSON (e.g. gateway errors, load balancer timeouts).
if (error instanceof MiotCalendarApiError) {
  if (typeof error.body === "string") {
    // Non-JSON error (gateway, timeout, etc.)
    console.log("Raw error:", error.body);
  } else {
    // Structured API error
    console.log(error.body.error);     // "Conflict"
    console.log(error.body.message);   // "Slot is full"
    console.log(error.body.timestamp); // "2026-03-01T10:00:00Z"
  }
}

Custom Fetch

Pass a custom fetch implementation for server-side rendering, auth interceptors, or testing.

Auth interceptor

const client = createMiotCalendarClient({
  baseUrl: "https://your-api-host.com",
  fetch: async (input, init) => {
    const token = await getAccessToken(); // your auth logic
    const headers = new Headers(init?.headers);
    headers.set("Authorization", `Bearer ${token}`);
    return globalThis.fetch(input, { ...init, headers });
  },
});

Testing with a mock fetch

import { createMiotCalendarClient } from "@microboxlabs/miot-calendar-client";

const mockFetch = async (input: RequestInfo | URL, init?: RequestInit) => {
  return new Response(JSON.stringify({ id: "cal-1", code: "test", name: "Test" }), {
    status: 200,
    headers: { "Content-Type": "application/json" },
  });
};

const client = createMiotCalendarClient({
  baseUrl: "https://mock-api",
  fetch: mockFetch,
});

const calendar = await client.calendars.get("cal-1");

License

Apache-2.0