@updraft-solutions/mcrit-sdk
v0.2.0
Published
Shared TypeScript types, Zod schemas, and pure helpers for the MCRIT aviation fleet management platform
Readme
@updraft-solutions/mcrit-sdk
Shared TypeScript types, Zod schemas, and pure helpers for the MCRIT
aviation fleet management platform — consumed by the mcrit-app SPA and
the public inflight-homepage site so both speak the same API contract
and apply the same business logic.
The SDK is runtime-agnostic and side-effect free: no Vue, no Nuxt, no Pinia, no HTTP client. Just types and pure functions that operate on the resource shapes returned by the Laravel API.
Install
pnpm add @updraft-solutions/mcrit-sdkPeer dependency:
pnpm add moment-timezonemoment-timezone is only required if you use the format module. If you
want German or another non-English label output, also import the matching
moment locale once at your app entry:
// e.g. a Nuxt plugin or app/main.ts
import "moment/locale/de";Subpath exports
Every helper is tree-shakeable. Import from the narrow subpath you need, or from the root if you want everything in one place.
| Subpath | What's inside |
|---|---|
| @updraft-solutions/mcrit-sdk | Re-exports everything below |
| @updraft-solutions/mcrit-sdk/schemas | Zod schemas for API resources |
| @updraft-solutions/mcrit-sdk/types | Plain TypeScript types (inferred or hand-written) |
| @updraft-solutions/mcrit-sdk/money | Dinero-based fee and price helpers |
| @updraft-solutions/mcrit-sdk/courses | Course variant and total-cost logic |
| @updraft-solutions/mcrit-sdk/format | Date, time, percentage, byte, membership formatters |
| @updraft-solutions/mcrit-sdk/crew | Crew-role constants, predicates, membership helpers |
| @updraft-solutions/mcrit-sdk/media | MIME-type classification + Spatie MediaLibrary mappers |
| @updraft-solutions/mcrit-sdk/pagination | Pagination meta math |
Quick examples
Schemas & types
import { feeSchema, courseSchema } from "@updraft-solutions/mcrit-sdk/schemas";
import type { Course, Fee } from "@updraft-solutions/mcrit-sdk/types";
const fee = feeSchema.parse(apiResponse); // runtime validation
const course: Course = await loadCourse(); // type onlyMoney
import { parseFee, displayFee, fromCents, formatToEuroString } from "@updraft-solutions/mcrit-sdk/money";
displayFee(fee, "gross"); // → "€ 1.234,56"
parseFee(fee)?.currentPrice("net"); // → Dinero instance
formatToEuroString(123456); // → "€ 1.234,56"
fromCents(50000).toFormat(); // → "500,00 €"Courses
import { parseCourse } from "@updraft-solutions/mcrit-sdk/courses";
const variants = parseCourse(course)?.variants("gross") ?? [];
// [{ variant, fees: ParsedCourseFee[], totalCost: Dinero }, ...]
for (const v of variants) {
console.log(v.variant, v.totalCost.toFormat());
}parseCourse handles fees with pivot data nested under pivot OR
flattened directly on the fee object — both API shapes are accepted.
Format
import {
formatDate,
formatDateTime,
bookingDuration,
bytesToHuman,
timeUntil,
} from "@updraft-solutions/mcrit-sdk/format";
formatDate("2026-05-15"); // "15.05.2026"
formatDateTime(booking.etdZ, "Europe/Berlin"); // "15.05.2026 09:00"
bookingDuration(booking); // "01:30"
bytesToHuman(1_572_864); // "1.5 MB"
timeUntil("2026-05-10T12:00:00Z", "de"); // "vor 5 Tagen" (needs moment de locale loaded)Crew
import {
CREW_ROLES,
ROLE_ATTRIBUTES,
PIC_PRIORITY,
isInstructorClassRole,
hasMembershipRole,
defaultCrewRoleForMembership,
} from "@updraft-solutions/mcrit-sdk/crew";
PIC_PRIORITY; // ['examiner', 'instructor', 'pilot', 'student']
isInstructorClassRole("authorizing_instructor"); // true
defaultCrewRoleForMembership(membership); // "student" | "instructor" | "pilot"Mirrors the backend App\Enums\CrewMemberPosition. See
m-crit-api/docs/features/crew-composition.md for the full data model.
Media
import {
mapMediaConversions,
getMediaType,
getMediaIcon,
getMediaDisplayName,
} from "@updraft-solutions/mcrit-sdk/media";
const gallery = mapMediaConversions(model.media);
// [{ href, thumbnail, mediaType, icon, previewable, ... }]
getMediaType("application/pdf"); // "document"
getMediaIcon("video/mp4"); // "mdi-video"
getMediaDisplayName("image/png"); // "Bild" (German default)
getMediaDisplayName("image/png", { image: "Picture" }); // "Picture" (i18n override)Pagination
import { paginationMeta } from "@updraft-solutions/mcrit-sdk/pagination";
paginationMeta({ page: 2, itemsPerPage: 25 }, 137);
// { start: 26, end: 50, total: 137 }API shapes vs *Like shapes
The SDK distinguishes two kinds of types:
- Strict shapes (
Fee,Price,Course,CourseFee, ...) arez.infer<>of the corresponding Zod schemas — what the API actually returns when fully loaded. - Permissive
*Likeshapes (FeeLike,PriceLike,CourseLike,CourseFeeLike) are accepted by the helpers (parseFee,parseCourse,displayFee, ...) so callers can pass partial or legacy payload shapes without casting.
Use the strict types in your own code; rely on *Like only as input to
SDK helpers.
Local development
This repo is a normal pnpm package. From the SDK root:
pnpm install
pnpm typecheck # tsc --noEmit
pnpm build # tsup → dist/{esm,cjs,d.ts}
pnpm dev # tsup --watchInside the MCRIT monorepo
The MCRIT working folder (mcrit-app, inflight-homepage-nuxt-3, and
this repo as sibling git checkouts) uses a pnpm workspace at the parent
folder. With workspace:* (or any matching version range) in the
consuming app's package.json, pnpm symlinks this directory into the
consumer's node_modules — edits show up immediately, no publish loop.
If consumers use the source export condition (Vite via
resolve.conditions: ['source']), they read src/*.ts directly and skip
the tsup build entirely during dev.
Adding a new helper
- Pick or create the appropriate subpath under
src/(e.g.src/money/). - Keep the implementation pure — no framework imports, no I/O.
- Add the file to the
entrylist intsup.config.ts. - Add the subpath under
exportsinpackage.json(mirror the existing pattern:source+import.types+import.default+require.typesrequire.default).
- Re-export from
src/index.tsif it should also be reachable from the root. pnpm typecheck && pnpm build— both must be green.
Releases
Publishes are automated. Push a tag matching v* to trigger
.github/workflows/publish.yml:
npm version patch # bumps package.json and creates v0.1.1 commit + tag
git push && git push --tagsThe workflow verifies the tag matches package.json, runs
pnpm typecheck && pnpm build, then publishes to npm with provenance.
The NPM_TOKEN GitHub repository secret must be configured (an
"Automation" or "Granular" token from npmjs.com with publish rights to
@updraft-solutions/mcrit-sdk).
License
UNLICENSED — internal Updraft Solutions package. Not for redistribution outside MCRIT projects.
