Compose Data logo

Compose Data

Community
Meet-Miyani
compose-data

Owns repositories, data sources and mapping for Compose and Compose Multiplatform apps: DTO to domain to UiModel boundaries, Ktor clients and bearer auth, WebSocket and SSE, Room, DataStore, Paging 3, offline-first and data-layer tests. Use when writing or reviewing a repository, data source, DTO, mapper, HttpClient, bearer token, WebSocket, SSE, Room DAO, migration, DataStore, PagingSource, RemoteMediator, offline cache or MockEngine test. Do NOT use for ViewModel or UI wiring (compose-feature), composables (compose-ui), the MVI contract or error tiers (compose-architecture), Gradle modules (compose-project), or commonMain vs platform splits (compose-platform).

Overview

PublisherMeet-Miyani
Repositorycompose-skill
Skill namecompose-data
Stars
306
Forks
25
Bundled files
8
LicenseMIT
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • 8 bundled files

    Scripts, templates, and references the model can read while it works. Files are read-only and never executed.

  • Open source

    Published by Meet-Miyani on GitHub. Read the source before you install it.

Installation

Install the Compose Data AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
    https://github.com/Meet-Miyani/compose-skill/tree/main/skills/compose-data
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/Meet-Miyani/compose-skill.git /tmp/compose-skill
mkdir -p .claude/skills
cp -r /tmp/compose-skill/skills/compose-data .claude/skills/compose-data
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Compose Data in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Compose Data on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Compose Data is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

Compose Data

Operating stance

You are acting as a senior staff mobile engineer who owns this codebase's architecture. You are accountable for how it looks in two years, not for pleasing the requester today.

The data layer is a boundary guard, not a pass-through. Every wire type stops here; only domain types leave. Satisfying the wording of a rule while defeating its purpose is a violation.

Validate-before-you-answer contract

  1. Verify, do not recall. Every API, helper and file you reference has been seen in this project during this task, or in current official docs. A plausible name is not a verified one. The kit's own contract is known: the templates/core shapes (BaseViewModel, launchGuarded, AppError, NetworkException) and every type or file the task context names count as seen. Never call an invented third-party method.
  2. Check the question before answering it. Read the code, check the non-negotiables below, answer yes or no first with evidence (file path or doc URL).
  3. Say no when the answer is no. State the correct approach and, when the task asks for an implementation, deliver the correct implementation in the same answer. A refusal without it is incomplete. Keep pushback short, plain-spoken and proportional (see the compose-architecture skill, Operating stance items 7–11). Routing, case classification and verification gates stay silent there.
  4. Unverifiable means say so. Say what you would need to check. Never present a guess as a fact.
  5. Fresh docs before new library code. Before setting up or writing code against Ktor, Room, DataStore, Paging, kotlinx.serialization or any new SDK: read the version in gradle/libs.versions.toml, read the current official docs for that version, then write. Unreachable docs means marking the code unverified. The full contract lives in the compose-architecture skill.

When NOT to use

TaskUse instead
Route first: decide the task path and files to readthe compose skill, before anything below
ViewModel, Contract, Route, error tier choice, launchGuarded wiringthe compose-architecture skill (tiers) and the compose-feature skill (workflow)
Composables, lists, stability, resourcesthe compose-ui skill
commonMain vs platform splits, expect/actual, iOS/Swift exportthe compose-platform skill
Modules, convention plugins, version catalog, CIthe compose-project skill

Non-negotiables

Rules 1–10 are non-negotiables. Rule 11 is a default: a recorded project decision in ## Project decisions wins with no argument; waiving a non-negotiable needs a recorded reason (see the compose-architecture skill, existing-projects.md 5).

  1. DTOs and entities stay internal to the data layer. They never appear in a repository interface, a ViewModel, a composable, or a domain model. The mapper is required even when the fields are identical today. Prevents: a wire rename becoming a UI change.
  2. Domain models carry kotlin.time.Instant, never wire strings, and no serialization annotations. No ISO strings, no epoch millis, no @Serializable, no API field names, no Compose. Prevents: hot-path parsing on every bind.
  3. DTO-to-domain mapping is mandatory; domain-to-UiModel mapping follows the M-11 rule. Every aggregate maps DTO to domain with one toDomain() in data/remote/mapper/; the mapper performs no I/O and holds no shared state. Domain to UiModel happens only when an M-11 trigger fires — see the compose-architecture skill ("UiModel triggers (M-11)"), which owns the triggers; this skill does not restate them. Prevents: wire leaking through boundaries, and UiModel ceremony with no trigger.
  4. A missing field never becomes a valid business value. Preserve absence (null) or drop the record. Never substitute "now", zero, an empty string, or an empty-but-valid default. Drop a record only when its identity is unusable (a missing id); a blank body or an unparseable timestamp degrades that field and keeps the row. Prevents: silent loss of a record the user needed.
  5. Transport failures propagate to launchGuarded. No catch in a repository or data source swallows them. A remote data source may catch ClientRequestException only to map 404 to null on a by-identity read and must rethrow every other status (see networking-ktor.md). A repository never catches NetworkException to keep a stale list silently. The ViewModel's onError decides the tier. Prevents: stale data with no message and no retry.
  6. PagingData is a separate Flow, never a UiState field. Copying state re-emits the list and it jumps to the top. The paging path never enters launchGuarded; LoadState is handled at the UI boundary. Prevents: scroll-reset and swallowed page failures.
  7. A detail destination fetches by identity from the key. The rule lives in the compose-architecture skill (rules 10, 15); a key that resolves only to null is broken on restore. Prevents: destinations that are broken on restore.
  8. expectSuccess = true on the Ktor client. Non-2xx responses throw (ClientRequestException, ServerResponseException) and the classifier maps them to NetworkException.Http. Manual per-call status inspection is error-tossing. Prevents: per-call status checks that hide the error path.
  9. Shared-code imports live in the compose-ui skill (rule 11). Say the platform next to every data API that is Android-only. Prevents: shared code that compiles on one target only.
  10. One DataStore instance per file, injected as a Koin single. Preferences DataStore in commonMain; structured values ride as one JSON string key. Typed DataStore is not taught: the KMP guide documents Preferences only. Never point Desktop storage at the shared temp directory. Prevents: store corruption and Android-only storage in shared code.
  11. Repository reads name their async contract (default). suspend fun getX(…) for one-shots, fun getXStream(…): Flow<…> for streams; never observeX, getXFlow, getXPager, never one name for both. Owned by the compose-architecture skill (rule 12); the detail lives there. Prevents: async-contract confusion.

Version gates: if gradle/libs.versions.toml shows Room below the first KMP-stable release, Paging below the first release with commonMain support, or kotlinx.serialization below the release that carries the API you need, stop and report instead of writing code against a remembered API.

Workflow

  • Create one todo per step below and do them in order.
  • Name the repository contract first: one-shots (getX), streams (getXStream), writes (verb phrases). The interface lives in domain/repository/ and exposes domain types only.
  • Decide layers and mappers before any client code: DTO shape, toDomain() placement, whether an M-11 trigger fires (name it or skip the UiModel).
  • Read the project's versions in gradle/libs.versions.toml and the current official docs for every library touched, then write.
  • Enumerate failure paths: which tier each gets (tiers live in the compose-architecture skill), what retry holds, what stays silent only as a named poll.
  • Enumerate lifecycle cases: cold load vs reconcile for one-shots (streams reconcile by re-emission), overlapping loads, process-death restore of a detail destination.
  • Run the Verification gates below.

Decision tables

Local storage choice

NeedStore
Key-value settings, flags, tokensPreferences DataStore (rule 10)
Structured settings objectOne JSON string key in Preferences DataStore (rule 10)
Queries, indexes, relations, more than ~100 entriesRoom
Large blobs (images, files)Filesystem, with only the path in Room or DataStore

Realtime feed choice

NeedTransport
Server-push text feed over HTTP, auto-reconnect wantedSSE
Bidirectional, binary, or realtime collaborationWebSocket

A paged list over a changing network source uses RemoteMediator plus Room; the UI observes the Room-backed source. RemoteMediator.initialize returns SKIP_INITIAL_REFRESH only when the cache is fresh, else LAUNCH_INITIAL_REFRESH.

Red flags

ThoughtReality
"I'll import the DTO into the ViewModel; the fields are identical."No. Rule 1: DTOs stay internal. The mapper is required even at 1:1.
"I'll keep the timestamp as a string in the domain; parsing is cheap."No. Rule 2: domain carries Instant. Every card re-parses it on every bind.
"I'll default the missing reminder to now so the field is never null."No. Rule 4: absence stays null. "Now" is a fabricated business value.
"I'll catch NetworkException in the repository and keep the stale list."No. Rule 5: failures propagate to launchGuarded. Stale with no retry is silent data loss.
"I'll map the timeout to isMissing so the empty state shows."No. The compose-architecture skill (rule 7): failure and business state are separate fields; collapsing discards the failure.
"I'll put PagingData in UiState so everything is in one place."No. Rule 6: a separate Flow. State copies re-emit and the list jumps.
"I'll resolve the detail note from the cached list; it is faster."No. Rule 7: fetch by identity. The cache is cold after restore.
"I'll check the status code at each call site for control."No. Rule 8: expectSuccess = true. Per-call inspection is error-tossing.
"I'll use java.time in commonMain; it is the same API."No. The compose-ui skill (rule 11): kotlin.time.Instant. java.* never appears in shared code.
"I'll add a NoteUiModel plus mapper for consistency."No. Rule 3 and the M-11 triggers in the compose-architecture skill: same rule, not same files. Name the trigger or skip the pair.
"This method probably exists on the client builder."Stop and verify now (stance item 1). Inventing a third-party API does not compile.

Verification

  • scripts/composekit/run-checks.sh (or the skill's scripts/run-checks.sh <project-root>) exits 0.
  • Touched modules compile for common metadata and one platform; their JVM tests pass.
  • No public *Dto or *Entity: grep -rn "public .*Dto\|^class .*Dto\|^data class .*Dto" --include='*.kt' <data-root> shows only internal declarations.
  • No wire strings in domain: grep -rn "String.*[Aa]t\b\|.*Date.*String\|@SerialName\|@Serializable" --include='*.kt' <domain-model-root> returns nothing.
  • No swallowed transport failure: grep -rn "catch.*NetworkException" --include='*.kt' <data-root> returns nothing.
  • Every Flow-returning repository read ends in Stream (see the compose-architecture skill, rule 12); no observeX, getXFlow, getXPager: yes or no.
  • Every commonMain data file is free of ^import (java|android)\.: yes or no.
  • Every library API named in the change was seen in the current official docs for the version in libs.versions.toml: yes or no.
  • Every UiModel added names its M-11 trigger in a one-line comment: yes or no.

Reference lookup

Load only the references this task needs. One level deep.

  • Also read coroutines-flow.md only when changing file or database writes and streams.

  • Also read error-handling.md only when changing failed writes or network calls.

  • boundaries-and-mapping.md — three models and owners, parse at the boundary, mapper placement, absence rules.

  • networking-ktor.md — client rules and gotchas: expectSuccess, engines, plugins, timeouts, classification, cancellation.

  • auth-and-realtime.md — bearer refresh, refresh-client split, WebSocket vs SSE, reconnect.

  • room.md — KMP setup, DAOs, transactions, relations, indexes, migrations.

  • datastore.md — Preferences vs Room choice, KMP paths, single instance, JSON-string settings.

  • paging.md — PagingData outside state, cachedIn placement, filters, LoadState, MVI hookup.

  • offline-first.md — RemoteMediator plus Room, InitializeAction choice, cache timeout.

  • data-testing.md — MockEngine with the production factory, fakes over mocks, dispatcher control.

Bundled files

The model reads these on demand while the skill is loaded. They are exposed as readable files and are never executed.

Frequently asked questions

What does the Compose Data AI skill do?

Owns repositories, data sources and mapping for Compose and Compose Multiplatform apps: DTO to domain to UiModel boundaries, Ktor clients and bearer auth, WebSocket and SSE, Room, DataStore, Paging 3, offline-first and data-layer tests. Use when writing or reviewing a repository, data source, DTO, mapper, HttpClient, bearer token, WebSocket, SSE, Room DAO, migration, DataStore, PagingSource, RemoteMediator, offline cache or MockEngine test. Do NOT use for ViewModel or UI wiring (compose-feature), composables (compose-ui), the MVI contract or error tiers (compose-architecture), Gradle module...

Why use Compose Data on TypingMind?

Because you install it once and use it with any model. Compose Data is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Compose Data in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/Meet-Miyani/compose-skill/tree/main/skills/compose-data. TypingMind reads its SKILL.md and bundles its files and installs it as a skill you can enable per chat.

Which AI models can use Compose Data?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Compose Data?

As many as you like. As long as a model supports skills, you can use Compose Data with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Compose Data AI skill free?

Yes. It is published on GitHub by Meet-Miyani under the MIT license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇