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

@crvouga/mockingbird-service-google-calendar

v1.8.0

Published

Stateful mock of the Google Calendar v3 API and Google OAuth our EMR calls: events list/insert/update/delete/watch, channels.stop, calendarList/calendars, token exchange/refresh/revoke and userinfo, with signed-by-header push notifications.

Downloads

2,060

Readme

@crvouga/mockingbird-service-google-calendar

Familiar calls. Faithful echoes. Part of Mockingbird.

Stateful mock of Google Calendar v3 and Google OAuth 2.0 for test suites, covering what our EMR backend calls: calendarList.list, calendars.insert, events.list (time windows, q, orderBy, paging, incremental sync with 410 fullSyncRequired), events.get/insert/ update/delete, events.watch with real push notifications to the channel's address, and channels.stop; plus the token endpoint (authorization code and refresh grants), revoke, and OpenID userinfo. Practitioners connect, sync EMR appointments and receive calendar pushes without a Google account.

  • Operation coverage: SUPPORT.md
  • The contract (openapi.yaml) is trimmed from Google's Calendar v3 discovery document and OAuth docs to what google-calendar-service.ts, authorize.business.ts and the webhook controller use.

Install

npm install -D @crvouga/mockingbird-service-google-calendar

ESM only. Node >= 22 or Bun >= 1.2. No native dependencies. Serve it with npx mockingbird-google-calendar serve, createServer from ./server (Node), or createRuntime with any Fetch server.

Usage

Seams (the app hardcodes Google today): give the calendar client a rootUrl (google.calendar({version: "v3", auth, rootUrl: "<mock>/"})), give OAuth2Client endpoints: {oauth2TokenUrl: "<mock>/token", oauth2RevokeUrl: "<mock>/revoke"}, and point the userinfo fetch at <mock>/oauth2/v3/userinfo. GOOGLE_WEBHOOK_URL can stay as it is: the mock posts pushes to whatever address the app registers (http is allowed unless --require-https-webhooks).

npx mockingbird-google-calendar serve --port 8820 --client "$GOOGLE_CLIENT_ID:$GOOGLE_CLIENT_SECRET"
import { auth, calendar } from "@googleapis/calendar"
import { OAuth2Client } from "google-auth-library"
import { createServer } from "@crvouga/mockingbird-service-google-calendar/server"

const google = await createServer()
const oauth = new OAuth2Client({
  clientId: "c",
  clientSecret: "s",
  redirectUri: "postmessage",
  endpoints: { oauth2TokenUrl: `${google.url}/token`, oauth2RevokeUrl: `${google.url}/revoke` },
})
// Authorization code "4/mock-<name>" signs in <name>@example.com ("4/mock-<email>" for others).
const { tokens } = await oauth.getToken("4/mock-dr.house")
const client = new auth.OAuth2()
client.setCredentials({ access_token: tokens.access_token ?? null })
const cal = calendar({ version: "v3", auth: client, rootUrl: `${google.url}/` })
await cal.events.insert({
  calendarId: "primary",
  requestBody: { summary: "Rounds", start: { dateTime: "2026-10-01T15:00:00Z" }, end: { dateTime: "2026-10-01T16:00:00Z" } },
})

An event created "in Google's UI" (admin route) is what the app then lists and syncs:

import { createServer } from "@crvouga/mockingbird-service-google-calendar/server"

const google = await createServer()
await fetch(`${google.url}/__admin/events`, {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({
    email: "[email protected]",
    event: { summary: "Rounds", start: { dateTime: "2026-10-01T15:00:00Z" }, end: { dateTime: "2026-10-01T16:00:00Z" } },
  }),
})
const stored = await (await fetch(`${google.url}/__admin/[email protected]`)).json()
await google.close()

Routes

| Route | Behaviour | | --- | --- | | POST /token | Form-encoded (client_secret_post or Basic). authorization_code: 4/mock-<name or email> → {access_token, expires_in: 3599, refresh_token, scope, token_type, id_token}; codes are single-use; anything else is 400 invalid_grant. refresh_token → a new access token (+ id_token); revoked or foreign tokens are invalid_grant. Unknown client (with clients set) 401 invalid_client. | | POST /revoke?token= | Revokes an access or refresh token; unknown / already revoked 400 invalid_token. | | GET /oauth2/v3/userinfo | {sub, email, email_verified, name, given_name, family_name, picture} (derived from the email, or set with PUT /__admin/users/:email). | | GET /calendar/v3/users/me/calendarList | The account's primary calendar (id = email) and its secondary calendars. | | POST /calendar/v3/calendars | Create a secondary calendar (…@group.calendar.google.com). | | GET /calendar/v3/calendars/{id}/events | timeMin/timeMax (overlap), q, showDeleted, singleEvents, orderBy (startTime needs singleEvents), updatedMin, maxResults (≤ 2500) with nextPageToken; the last page carries nextSyncToken (not with q/orderBy/updatedMin). syncToken returns only changes since, deletions as {id, status: "cancelled"}; combined with a window or q it is 400; invalid or invalidated it is 410 fullSyncRequired. | | POST …/events, GET/PUT/DELETE …/events/{eventId} | Base32hex ids (client ids allowed; duplicates 409). Missing start/end 400, an empty range 400 timeRangeEmpty. PUT replaces (sequence +1 when the time moves). DELETE 204, again 410 deleted. | | POST …/events/watch | {id, type: "web_hook", address, token?, expiration?} → {kind: "api#channel", id, resourceId, resourceUri, expiration} (capped at 30 days). A sync push follows immediately. Duplicate live channel ids are 400. | | POST /calendar/v3/channels/stop | {id, resourceId} → 204; unknown 404. |

Errors use Google's envelope {error: {errors: [{domain, reason, message}], code, message, status?}}. Invalid or expired access tokens are 401 authError (userinfo: invalid_request).

Push notifications

Every change to a calendar's events (through the API or the admin plane) posts to each live channel on that calendar: an empty body with X-Goog-Channel-ID, X-Goog-Channel-Token (when set), X-Goog-Channel-Expiration (RFC 1123), X-Goog-Message-Number (1 for sync, then +1), X-Goog-Resource-ID, X-Goog-Resource-URI and X-Goog-Resource-State (sync, then exists). Stopped or expired (mock clock) channels get nothing. Non-2xx answers are retried (immediately, 1 s, 10 s, 1 min, 10 min). GET /__admin/webhooks lists deliveries, and /__admin/webhooks/events, …/replay, …/flush work as usual.

Admin (beyond the standard contract)

| Route | Effect | | --- | --- | | POST /__admin/events | {email, calendarId?, event}: create an event as the account would in Google's UI (pushes). | | PUT /__admin/events/:id | {email, calendarId?, event}: replace it (pushes). | | DELETE /__admin/events/:id?email=&calendarId= | Delete it (pushes). | | GET /__admin/events?email= | Stored events (with change sequences). | | PUT /__admin/users/:email | Set the userinfo profile. | | GET /__admin/channels | Every channel with its message number, expiry and stopped flag. | | POST /__admin/sync-tokens/invalidate | Every sync token issued so far answers 410. | | GET/PUT /__admin/settings | {clients?, tokenTtlSeconds?, requireHttpsWebhooks?, maxChannelTtlSeconds?, scope?}. |

Fault presets (POST /__admin/faults {"preset": "<name>", "count"?: n}): invalid_grant, token_expired, rate_limited (403 rateLimitExceeded), too_many_requests (429), calendar_api_disabled (403 accessNotConfigured), insufficient_scopes, sync_token_gone, backend_error (503), connection_drop, push_duplicate, push_drop. gaxios retries 429 and 5xx itself, so pass a count above its budget (3 retries) to surface them.

Namespaces

By account: access tokens carry their email, so PUT /__admin/credentials {"credentials": {"[email protected]": "<namespace>"}} routes every calendar and userinfo call. x-mockingbird-namespace works as usual. A /ns/<name> prefix works for the token, revoke and userinfo URLs (they are full URLs) but not for the calendar client: googleapis resolves paths against rootUrl's origin and drops a path prefix.

SDK

The tests drive the real client stack of googleapis@149: @googleapis/[email protected] (the same generated calendar module over googleapis-common 7 / gaxios 6; the full googleapis package is ~125 MB unpacked, so only the module is installed) and [email protected].

Deliberately not modelled

  • Recurring events (recurrence is stored, never expanded), attendees' responses and email invitations, Meet links, free/busy, ACLs, colors and settings.
  • Real Google identity: id_tokens are JWT-shaped but not signed with Google's keys.
  • X-Goog-Changed (Google omits it for calendar event channels) and not_exists states.

API

| Export | Kind | Description | | --- | --- | --- | | GoogleCalendarAPI | class | The in-process mock: fetch(request), reset(), user(email), ensurePrimary(email), resolveCalendar(email, id), createEvent, updateEvent, deleteEvent, findEvent, notify(calendarId), channels(), events(email?), invalidateSyncTokens(). Options: sqlite, now, namespace, settings, onPush, onChannels. | | createRuntime | function | The mock with the full service contract and push delivery. Options: settings, push: {retryDelaysMs?, fetch?}, clock, seed, adminKey, onLog, sqlite. | | GOOGLE_CALENDAR_PRESETS | object | Every named fault preset. | | GOOGLE_CALENDAR_NAMESPACE | string | The service name, "google-calendar". | | DEFAULT_SCOPE | string | The scope the token endpoint reports. | | pushHeaders | function | The X-Goog-* headers of a push notification. | | issueAccessToken | function | Mint an access token the mock accepts for an email. | | parseToken | function | Decode a mock access or refresh token. | | accessTokenCredential | function | The account an access token belongs to (how credentials map to namespaces). | | emailFromCode | function | The account a 4/mock-… authorization code signs in. | | googleError | function | Build Google's error envelope. | | document, operationIds, supportedOperationIds | values | The OpenAPI contract and its operation ids. | | createServer, serveTarget, DEFAULT_PORT (./server) | Node | Serve over node:http; the serve CLI target (--client, --require-https-webhooks); port 8820. |

Part of mockingbird.