Skip to content

SDK Development Guide

This page is for contributors who maintain the SDK or add new API endpoints. For usage documentation, see the SDK Overview.

Location: ui-clients/sdk/

How adding endpoints / modules works

The SDK pipeline is fully discovery-driven. Two scenarios:

Change What you edit What regenerates automatically
New endpoint in an existing group (e.g. add a @GetMapping to a controller) The controller, nothing else Spec, *.gen.ts, reference docs
New API group (e.g. a brand-new module) One GroupedOpenApi bean in OpenApiConfig.java Spec, generated SDK folder, staff.ts / parent.ts aggregators, package.json exports, reference docs

Both happen during the pre-commit hook (or any backend test run). Nothing in ui-clients/sdk/ is hand-edited.

Naming convention for new groups

The aggregator generator buckets groups by spec filename suffix:

Spec filename Bundled into
*-staff.json ./staff only
*-parent.json ./parent only
anything else both bundles
system, sample neither (excluded)

So a new module that's staff-only should be registered as library-staff; a shared module can be library or customer-library. No code change needed in the SDK to wire it up.

Pretty import aliases

By default, the import alias for a group is the camelCase of its name (canteen-staffcanteenStaff, customer-library-staffcustomerLibraryStaff).

To get a shorter alias for a group, add an entry to ALIAS_OVERRIDES at the top of scripts/generate-aggregators.mjs. This is the only SDK-folder edit that's ever optional, and only for aesthetics — adding a new group does not require touching this file.

Available API groups

Import alias API group Bundle
auth platform-auth Both
audit platform-audit Both
chat platform-chat Both
engagement platform-engagement Both
notifications platform-notifications Both
wallet customer-wallet Both
canteenStaff canteen-staff ./staff
attendanceStaff attendance-staff ./staff
school school-staff ./staff
academicReport customer-academic-report-staff ./staff
invoice customer-invoice-staff ./staff
canteenParent canteen-parent ./parent
attendanceParent attendance-parent ./parent
schoolParent school-parent ./parent

This table is informational — the bundle/alias for each group is derived at generation time from the rules above, not maintained here.

Setup

cd ui-clients/sdk
npm install

Regenerating the SDK

The pre-commit hook regenerates everything when backend files are staged, so manual regen is rarely needed. When you do need it:

# Manual full sync (requires backend running)
cd backend && APP_SEED_DATA=true ./gradlew bootRun --continuous

# Then in another terminal
cd ui-clients/sdk
npm run sync          # fetches specs + regenerates TypeScript + aggregators + exports

Individual steps:

Script What it does Requires backend?
npm run fetch-specs Discovers groups via /v3/api-docs/swagger-config, downloads each one to specs/ Yes
npm run generate Runs openapi-ts per spec, then regenerates staff.ts, parent.ts, and package.json exports No
npm run sync Runs both steps in sequence Yes

How it works

  1. Backend exposes OpenAPI specs at GET /v3/api-docs/{group} via springdoc-openapi. The full group list is at GET /v3/api-docs/swagger-config.
  2. Spec export. Either OpenApiSpecExportTest (during gradle test) or scripts/fetch-specs.sh (against a running backend) writes one JSON file per group into specs/. Both discover the group list dynamically — no hardcoded list.
  3. Per-spec codegen. scripts/generate.sh globs specs/*.json and runs @hey-api/openapi-ts for each, producing src/<group>/{client,types,sdk,zod}.gen.ts.
  4. Aggregator + exports generation. scripts/generate-aggregators.mjs then:
    • Buckets groups via the *-staff / *-parent / shared naming rule.
    • Writes src/staff.ts and src/parent.ts (both marked @generated).
    • Rewrites the package.json exports field with one subpath per group per generated file (client.gen, types.gen, sdk.gen, zod.gen, plus core/auth.gen).
  5. Doc generation. scripts/generate-sdk-docs.py (in the repo root scripts/) discovers groups from the same specs/*.json glob, applies the same suffix convention, and regenerates docs/sdk/{staff,parent}-reference.md. Its ALIAS_OVERRIDES map mirrors the one in generate-aggregators.mjs — keep them in sync if you add an alias override.
  6. Both specs/ and src/ are committed — frontend developers don't need the backend running to build.

The pre-commit hook orchestrates steps 2 → 5 whenever backend files are staged. See .githooks/pre-commit.

Adding a new API module — full walkthrough

  1. Backend. Add a new GroupedOpenApi bean in OpenApiConfig.java:

    @Bean
    public GroupedOpenApi libraryStaffGroup() {
        return GroupedOpenApi.builder()
            .group("library-staff")        // suffix decides the bundle
            .displayName("Library – Staff")
            .packagesToScan("com.smartsapp.system.modules.customer.library.controllers")
            .build();
    }
    
  2. Commit. Stage the controller files and commit. The pre-commit hook will:

    • Run OpenApiSpecExportTest, which writes ui-clients/sdk/specs/library-staff.json.
    • Run npm run generate, which generates src/library-staff/, refreshes src/staff.ts, and adds the new module's subpath exports to package.json.
    • Run generate-sdk-docs.py, which adds the module to docs/sdk/staff-reference.md.
    • Stage every regenerated file.
  3. Use it. Frontend can immediately:

    import { libraryStaff } from '@smartsapp/sdk/staff';
    const books = await libraryStaff.listBooks();
    

    Or import internals directly via the auto-generated subpath exports:

    import type { BookResponse } from '@smartsapp/sdk/library-staff/types.gen';
    import { zCreateBookRequest } from '@smartsapp/sdk/library-staff/zod.gen';
    

(Optional) If the import alias is unwieldy, add 'library-staff': 'library' to ALIAS_OVERRIDES in scripts/generate-aggregators.mjs and re-run npm run generate.