@programmergeek/tuesday
v1.0.0
Published
Type-safe standard library for building monday.com apps
Maintainers
Readme
Tuesday
A type-safe standard library for monday.com apps, with explicit API versions, inferred GraphQL results, secure runtime boundaries, and deterministic testing.
Tuesday will be published as @programmergeek/tuesday. The unscoped npm
package tuesday is unrelated to this project.
Installation
pnpm add @programmergeek/tuesdayBuild the local workspace package from the repository root with
pnpm --filter @programmergeek/tuesday build.
The package runtime targets ES2022, standard web APIs, and Node 18 or newer. Repository development requires Node 20.19 or newer. Optional integrations require their framework peers:
pnpm add react react-dom # @programmergeek/tuesday/react
pnpm add react @tanstack/react-query # @programmergeek/tuesday/tanstack-queryScaffold a New App
Generate a vanilla browser view, React view, or server starter with independently selected monday capabilities:
pnpm dlx @programmergeek/tuesday init --name board-view --runtime browser --api-version 2026-10
pnpm dlx @programmergeek/tuesday init --name react-view --runtime browser --api-version 2026-10 --features react
pnpm dlx @programmergeek/tuesday init --name full-stack-app --runtime browser --api-version 2026-10 \
--include-server --features react,oauth,webhook
pnpm dlx @programmergeek/tuesday init --name app-server --runtime server --api-version 2026-10 \
--features oauth,webhook,integrationUse --dry-run to inspect the project plan. The CLI refuses to overwrite any
existing file and never writes credential values to .env.example. An included
server is isolated under src/server and runs with pnpm dev:server.
Quickstart
Keep monday API tokens on a trusted server. Set MONDAY_API_TOKEN, then create
an explicitly versioned client:
import { createClient } from "@programmergeek/tuesday";
const token = process.env.MONDAY_API_TOKEN;
if (!token) throw new Error("MONDAY_API_TOKEN is required");
const monday = createClient({
apiVersion: "2026-10",
token,
});
const result = await monday.query({
me: { id: true, name: true, email: true },
});
console.log(result.unwrap().me);
console.log(result.metadata?.requestId);The selected fields determine the result type. API versions are never selected implicitly, so an upstream schema release cannot silently change the client's compile-time contract.
Create an item with a typed mutation:
const created = await monday.mutate({
create_item: {
__args: { board_id: "123456789", item_name: "Created by Tuesday" },
id: true,
name: true,
},
});
console.log(created.unwrap().create_item);Queries may retry classified transient failures. Mutations are never retried automatically.
Project Overview
- Typed GraphQL query and mutation selections for each supported API version.
- Static or request-scoped access tokens with injectable
fetch. - Structured errors, partial GraphQL data, redacted diagnostics, and monday request metadata.
- Timeout, cancellation, bounded read retries, pagination, and bulk execution.
- Browser SDK wrappers with typed context, events, storage, UI actions, and cleanup.
- Server OAuth, token verification, installation lifecycle, webhook, and integration-action primitives.
- Focused column codecs, selection builders, multipart uploads, and scoped storage helpers.
- React and TanStack Query integrations behind dedicated entry points.
- Public GraphQL and browser SDK test doubles with no required network access.
- Schema lifecycle, CLI scaffolding, upgrade reports, and package drift checks.
Contributors should start with CONTRIBUTING.md, then read the internal architecture, design decisions, and maintenance guide.
Runnable projects progress from a basic typed client to browser, React,
TanStack Query, OAuth, and webhook flows in the workspace
examples directory.
Core Client
Create a client with a static token or a request-scoped token provider. Tests can
inject MockGraphQLTransport.fetch from @programmergeek/tuesday/testing; the
client never needs network access during unit tests.
import { GraphQLResponseError, createClient } from "@programmergeek/tuesday";
import { z } from "zod";
const client = createClient({
apiVersion: "2026-10",
token: () => process.env.MONDAY_API_TOKEN!,
timeoutMs: 10_000,
});
try {
const me = await client.query({ me: { id: true, name: true } });
const user = me.parse(
z.object({ me: z.object({ id: z.string(), name: z.string() }).nullable() }),
);
console.log(user.unwrap(), me.metadata?.complexity);
await client.mutate({
create_item: {
__args: { board_id: "123", item_name: "Created by Tuesday" },
id: true,
},
});
} catch (error) {
if (error instanceof GraphQLResponseError) {
console.error(error.errors, error.data, error.metadata?.requestId);
}
}Queries use bounded retries for retryable failures. Mutations are never retried
automatically. Per-request headers, AbortSignal, timeout, operation name, and
redacted diagnostics are accepted as the second argument to query, mutate,
and raw.
Cursor iteration requires explicit item and request limits:
for await (const item of client.itemsPage({
maxItems: 1_000,
maxRequests: 10,
selection: (cursor) => ({
boards: {
__args: { ids: ["123"] },
items_page: {
__args: { cursor, limit: 100 },
cursor: true,
items: { id: true, name: true },
},
},
}),
page: ({ boards }) => ({
items: boards?.[0]?.items_page.items ?? [],
cursor: boards?.[0]?.items_page.cursor,
}),
})) {
console.log(item.id, item.name);
}Runtime Entry Points
@programmergeek/tuesday: universal typed GraphQL core.@programmergeek/tuesday/browser: lazily resolved monday iframe SDK wrappers.@programmergeek/tuesday/server: OAuth, signed tokens, installations, webhooks, and actions.@programmergeek/tuesday/domain: column codecs, mutation builders, uploads, storage, and bulk helpers.@programmergeek/tuesday/react: provider and hooks for browser client context, theme, locale, and timezone.@programmergeek/tuesday/tanstack-query: typed options and tenant/API-version-scoped cache keys.@programmergeek/tuesday/testing: GraphQL transport and browser SDK test doubles.@programmergeek/tuesday/codegen: reproducible schema metadata and drift checks.@programmergeek/tuesday/adapters: thin framework-neutral handler and external-store adapters.@programmergeek/tuesday/cli: scaffolding and API-version upgrade reports.
Ecosystem and DX
@programmergeek/tuesday/cli: in-memory and filesystem scaffolding plus API-version upgrade reports.@programmergeek/tuesday/adapters: standard web, Node-style server, and external-store adaptation primitives with no React, Next.js, Express, or Fastify dependency.docs/reference.md: API and CLI reference.docs/security.md: credential, tenant, OAuth, replay, and logging guidance.docs/recipes.md: runnable framework-neutral recipes.
Programmatic dry run:
import {
createScaffoldPlan,
formatScaffoldPlan,
} from "@programmergeek/tuesday/cli";
const plan = createScaffoldPlan({
name: "my-monday-app",
runtime: "server",
apiVersion: "2026-10",
features: ["oauth", "webhook"],
});
console.log(formatScaffoldPlan(plan));The generated .env.example never contains a credential. Scaffold application
refuses to overwrite existing files.
React and TanStack Query
Provide one browser client at the view boundary. Context hooks use
useSyncExternalStore, subscribe lazily, and clean up the monday SDK listener
when the last component unmounts.
import { createBrowserClient } from "@programmergeek/tuesday/browser";
import {
MondayProvider,
useMondayContext,
} from "@programmergeek/tuesday/react";
const client = createBrowserClient({ apiVersion: "2026-10" });
function View() {
const context = useMondayContext();
if (context.status === "loading") return null;
if (context.status === "error") throw context.error;
return <p>Board {context.data.boardId}</p>;
}
export const App = () => (
<MondayProvider client={client}>
<View />
</MondayProvider>
);Generated selections retain their inferred result types in TanStack Query. Query cancellation is forwarded to the monday client. Keys require a non-secret tenant/installation scope and an application-owned operation key.
import { useQuery } from "@tanstack/react-query";
import { mondayQueryOptions } from "@programmergeek/tuesday/tanstack-query";
const selection = { me: { id: true, name: true } } as const;
const result = useQuery(
mondayQueryOptions(apiClient, selection, {
cacheScope: ["account", accountId],
queryKey: ["me"],
}),
);API versions
API versions are never selected implicitly. Keep a reviewed API manifest for the current and target versions, run the upgrade report with the fields used by the app, then run type and integration tests before changing the selected version. See the tooling guide.
