From 6817e1cac9d9a50245419067efc24105559a272d Mon Sep 17 00:00:00 2001 From: Dwiki Riyadi Date: Wed, 21 Oct 2026 19:50:40 +0700 Subject: [PATCH] docs: rewrite the README for visitors and add community files Rewrite the README for people discovering Sinew: what it solves, a status note, a Kotlin and Dart code example checked against the real APIs, features, platforms, trying it from source before 1.0, and links to both docs sites. The docs deployment and build details move to CONTRIBUTING.md. Add CODE_OF_CONDUCT.md (the Contributor Covenant 2.1 text the libraries already use), SECURITY.md (private reporting through GitHub security advisories, per repository) and .github/CODEOWNERS, and link them from CONTRIBUTING.md and the README. Co-Authored-By: Claude Opus 5.5 --- .github/CODEOWNERS | 15 ++++ CODE_OF_CONDUCT.md | 81 ++++++++++++++++++++ CONTRIBUTING.md | 21 +++++- README.md | 181 +++++++++++++++++++++++++++++++-------------- SECURITY.md | 51 +++++++++++++ 5 files changed, 289 insertions(+), 60 deletions(-) create mode 100644 .github/CODEOWNERS create mode 100644 CODE_OF_CONDUCT.md create mode 100644 SECURITY.md diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..b0bc0d0 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,15 @@ +# Code owners are requested for review on every pull request that touches their paths. +# Later rules take precedence over earlier ones. + +# Default: everything in this repository +* @dwikiriyadi + +# Documentation sites +/docs/ @dwikiriyadi + +# CI, repository settings and community files +/.github/ @dwikiriyadi + +# Submodule pointers (normally moved by Dependabot) +/kotlin-lib @dwikiriyadi +/dart-lib @dwikiriyadi diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..56c4777 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,81 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our community a harassment‑free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio‑economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our community include: + +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience +- Focusing on what is best not just for us as individuals, but for the overall community + +Examples of unacceptable behavior include: + +- The use of sexualized language or imagery, and sexual attention or advances of any kind +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others’ private information, such as a physical or email address, without their explicit permission +- Other conduct which could reasonably be considered inappropriate in a professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e‑mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. + +## Enforcement + +If you experience or witness unacceptable behavior, report it to the project team at contact@srctool.com. + +When reporting, please include (if possible): +- A description of what happened and why it is unacceptable +- Links, issue numbers, commit references, or screenshots (if available) +- Date/time and location (repository, issue/PR link, chat, etc.) +- Your preferred contact information for follow-up + +For non-sensitive concerns, you may also open an issue referencing "Code of Conduct" for transparency. + +We will review and investigate all reports promptly and fairly. You can expect an initial response within 72 hours. We may ask for additional information to aid the investigation. + +We will keep reports confidential to the extent possible and share details only with individuals who need the information to resolve the issue. All community leaders must respect the privacy and security of reporters. + +If you are in immediate danger, contact local emergency services first. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: + +1. Correction + - Community Impact: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. + - Consequence: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. + +2. Warning + - Community Impact: A violation through a single incident or series of actions. + - Consequence: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. + +3. Temporary Ban + - Community Impact: A serious violation of community standards, including sustained inappropriate behavior. + - Consequence: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. + +4. Permanent Ban + - Community Impact: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. + - Consequence: A permanent ban from any sort of public interaction within the community. + +## Attribution + +This Code of Conduct is adapted from the Contributor Covenant, version 2.1, available at https://www.contributor-covenant.org/version/2/1/code_of_conduct.html + +Community Impact Guidelines were inspired by Mozilla’s code of conduct enforcement ladder. + +For answers to common questions about this code of conduct, see the FAQ at https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 944715e..035d084 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -33,6 +33,20 @@ Run a site locally (Node 20+), from `docs/internal/` or `docs/usage/`: - Start dev server: `npm run start` - Build static site: `npm run build` +The contributor site runs on port 3000 and the usage site on 3001. The usage site's Contributing link goes to the contributor site: set `SINEW_INTERNAL_URL` (default `http://localhost:3000`) when building it, and each site's own address with `SINEW_INTERNAL_URL` / `SINEW_USAGE_URL`. VS Code launch configurations for both are in `.vscode/`. + +Regenerate the Guide after editing the design notes (from `docs/internal/`): `python3 scripts/sync_vault.py `. + +### Deployment +Both sites are deployed to Cloudflare Pages by `.github/workflows/docs.yml` when `docs/` changes on `main` in `srctool/sinew`. Pull requests (including from forks) only build them. + +| Site | Folder | Cloudflare Pages project | Domain | +|---|---|---|---| +| Contributor docs | `docs/internal/` | `sinew-dev` | https://sinew-dev.srctool.com | +| Usage docs | `docs/usage/` | `sinew` | https://sinew.srctool.com | + +The workflow needs two repository secrets: `CLOUDFLARE_API_TOKEN` (a token with *Account → Cloudflare Pages → Edit*) and `CLOUDFLARE_ACCOUNT_ID`. + ## Workflow Sinew is trunk-based: `main` is the only long-lived branch, and every change reaches it through a squash-merged PR. The full guide is the [Git workflow](https://sinew-dev.srctool.com/contributing/git-workflow) page. @@ -66,11 +80,10 @@ Tip for GitKraken users: GitKraken uses the first line of the commit message as - Keep the PR title in Conventional Commits format and ensure the description explains the "what" and "why`. Maintainers may edit the final message for clarity. ## Code of Conduct -Participation in this project is governed by the Code of Conduct in each submodule: -- Kotlin: `kotlin-lib/CODE_OF_CONDUCT.md` -- Dart: `dart-lib/CODE_OF_CONDUCT.md` +Participation in this project is governed by the [Code of Conduct](CODE_OF_CONDUCT.md). The same code applies in `sinew-kotlin` and `sinew-dart`. To report a conduct issue, email contact@srctool.com. -For sensitive reports, email contact@srctool.com. +## Security +Please don't report a vulnerability in a public issue. See [SECURITY.md](SECURITY.md) for how to report one privately. ## License - Root repo (docs & concepts): MIT (see LICENSE) diff --git a/README.md b/README.md index 2232b84..aaea16d 100644 --- a/README.md +++ b/README.md @@ -1,94 +1,163 @@ -[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) -[![Docs](https://img.shields.io/badge/docs-Website-blue.svg)](docs/) +# Sinew -# Sinew - SRC Tool +[![License: MIT (docs)](https://img.shields.io/badge/docs-MIT-blue.svg)](LICENSE) +[![License: Apache 2.0 (libraries)](https://img.shields.io/badge/libraries-Apache%202.0-blue.svg)](https://github.com/srctool/sinew-kotlin/blob/main/LICENSE) +[![Docs](https://img.shields.io/badge/docs-sinew.srctool.com-informational.svg)](https://sinew.srctool.com) +![Status: pre-1.0](https://img.shields.io/badge/status-pre--1.0-orange.svg) -**Sinew** is a plug-and-play app architecture for Kotlin Multiplatform and Flutter, part of the **SRC Tool** family alongside [Camouflage](https://github.com/srctool/camouflage). Sinews connect muscle to bone; Sinew connects an app's layers, data → domain → presentation, so new apps stop rewriting response mapping, paging, error types, the HTTP client with token refresh, and secure storage. Sinew ships the mechanism; the app ships the configuration. +**The app plumbing you rewrite in every project, done once, for Kotlin Multiplatform and Flutter.** -This repository is the umbrella for the documentation and the language-specific implementations, which are included as Git submodules. The libraries live under `kotlin-lib/` and `dart-lib/`. The documentation site is built with Docusaurus under `docs/`. +Every new app needs the same things: map API responses to domain models, turn failures into errors the UI can show, page through lists, keep screen state, refresh tokens without a race, and store secrets safely. Sinew ships these as small packages you add one at a time. Sinew provides the mechanism, and your app provides the configuration (its JSON keys, base URLs, error codes and DI). + +Sinew is part of the **SRC Tool** family, alongside [Camouflage](https://github.com/srctool/camouflage) (UI). Each one works without the other. + +> **Status: pre-1.0.** Nothing is published to Maven Central or pub.dev yet, and the API may change before 1.0. The models, errors, paging, presentation, network, security and storage packages are implemented on both platforms. Testing helpers, the Camouflage glue and the sample apps are in progress. See the [roadmap](https://sinew-dev.srctool.com/guide/project/roadmap). --- -## Repository structure -``` -├── README.md # This overview -├── LICENSE # Main repo license (MIT) -├── docs/ # Two Docusaurus sites, deployed separately -│ ├── internal/ # Contributor docs: design, architecture, implementation (generated Guide + Contributing) -│ └── usage/ # Usage docs: for developers using Sinew in an app -├── kotlin-lib/ # Kotlin Multiplatform implementation (submodule: srctool/sinew-kotlin) -└── dart-lib/ # Dart and Flutter implementation (submodule: srctool/sinew-dart) +## What it looks like + +Declare what your backend sends and what your app needs. Sinew handles the mapping and the error handling in between. + +```kotlin +// Domain: what the app works with +data class Order(val id: String, val total: Long) : Domain + +// Remote: what the API sends. Fields are nullable, and toDomain() picks the fallbacks. +@Serializable +class OrderRemote(val id: String? = null, val total: Long? = null) : Remote() { + override fun toDomain() = Order(id = id.orEmpty(), total = total ?: 0) +} + +// Your backend's response shape. You declare the fields, and the base class maps the payload. +@Serializable +class ObjectResponse, D : Domain>(override val data: T? = null) : ObjectEnvelope() + +// Repository: one guarded call. Any failure becomes a typed SinewException with a code like ORD-R-CANCEL-NIC. +suspend fun cancel(id: String): Result = processApiCall("ORD", "CANCEL") { api.cancel(id) } + +// View model: no try/catch, one immutable state, one-off effects +class OrderViewModel(private val repo: OrderRepository) : + StateEffectViewModel, OrderEffect>(ViewState.Loading()) { + + fun cancel(id: String) = viewModelScope.launch { + repo.cancel(id).fold( + onSuccess = { setState { ViewState.Done(it) }; emitEffect(OrderEffect.Cancelled) }, + onFailure = { setState { ViewState.Failed(dataOrNull, it) } }, + ) + } +} ``` ---- +
+The same in Dart -## Documentation +```dart +class Order extends Domain { + const Order({required this.id, required this.total}); + final String id; + final int total; -There are two documentation sites, each its own Docusaurus project, built and deployed separately: + @override + List get props => [id, total]; +} -- **Contributor docs** (`docs/internal/`): the architecture, the error model, every package with its Kotlin and Flutter implementation, the roadmap and the decisions, plus Contributing. The Guide pages are generated from the Sinew design notes by `docs/internal/scripts/sync_vault.py`; don't edit them by hand. -- **Usage docs** (`docs/usage/`): adding Sinew to an app, wiring it, and building screens on it. Written by hand; they start with the first release. +class OrderRemote extends Remote { + const OrderRemote({this.id, this.total}); + final String? id; + final int? total; -The usage site doesn't link to the contributor docs directly: its **Contributing** link goes to the contributor site's Contributing section, which leads into the Guide. Set `SINEW_INTERNAL_URL` (default `http://localhost:3000`) when building the usage site, and each site's own address with `SINEW_INTERNAL_URL` / `SINEW_USAGE_URL`. + @override + Order toDomain() => Order(id: id ?? '', total: total ?? 0); +} -Regenerate the Guide after editing the design notes (from `docs/internal/`): +class ObjectResponse, D extends Domain> extends ObjectEnvelope { + const ObjectResponse({this.data}); -```bash -python3 scripts/sync_vault.py + @override + final T? data; +} + +Future> cancel(String id) => processApiCall('ORD', 'CANCEL', () => api.cancel(id)); ``` -Run a site locally (Node 20+), from `docs/internal/` or `docs/usage/`: `npm install`, then `npm run start` (the contributor site runs on port 3000, the usage site on 3001); `npm run build` for a production build. VS Code launch configurations for both are in `.vscode/`. +On Flutter, view models are Riverpod notifiers that use the `StateEffectNotifier` and `EventActionNotifier` mixins from `sinew_riverpod`. -### Deployment +
-Both sites are deployed to Cloudflare Pages by `.github/workflows/docs.yml` when `docs/` changes on `main` in `srctool/sinew`. Pull requests (including from forks) only build them. +## Features -| Site | Folder | Cloudflare Pages project | Domain | -|---|---|---|---| -| Contributor docs | `docs/internal/` | `sinew-dev` | https://sinew-dev.srctool.com | -| Usage docs | `docs/usage/` | `sinew` | https://sinew.srctool.com | +- **Envelopes that map themselves.** Declare your response shape once. Every object, list and page is mapped, and you never write a mapper function. Sinew assumes no JSON keys: `success`, `message` and page metadata are read from your own fields. +- **One error model, end to end.** Each layer turns failures into a typed `SinewException` with a stable code, and returns a `Result`. View models never `try`/`catch`. Error messages are localized and re-render when the language changes. English and Indonesian are included. +- **State-driven paging.** `Pager` owns the rules (first load, load more, retry, refresh, end of list, race conditions). A list screen only says how to fetch one page. +- **Presentation base classes.** Event → action → state + effect, framework-neutral, with adapters for AndroidX `ViewModel` and Riverpod. +- **HTTP client layers.** Built on Ktor or Dio, which you keep using directly. It adds single-flight token refresh (ten parallel 401s cause one refresh), timeouts, retry, polling, redacted logging and a network checker. +- **Secure storage and encryption.** `SecureStore`, `KeyValueStore`, field encryption with Tink AEAD, and a biometric vault. No hand-rolled cryptography. +- **Testing helpers.** Fakes for every service, plus paging race helpers. +- **Optional extras.** `sinew_camouflage` connects paging to Camouflage's `CamoPagedList`. DevTools (an in-app inspector, compiled out of release builds) is planned for 1.1. -The workflow needs two repository secrets: `CLOUDFLARE_API_TOKEN` (a token with *Account → Cloudflare Pages → Edit*) and `CLOUDFLARE_ACCOUNT_ID`. +Each package depends only on the ones before it. For example, an app that only wants paging adds `sinew_paging` and gets models and exceptions with it, and nothing else. -The sites include a Kotlin/Dart language switcher. Wrap language-specific content in `KotlinOnly` / `DartOnly`, or code examples in `LangTabs`, so readers keep their preferred language across pages. +## Platforms ---- +| | Kotlin Multiplatform | Flutter / Dart | +|---|---|---| +| Targets | Android, iOS, JVM desktop, wasmJs (also usable from a plain Android app) | Android and iOS. The pure-Dart packages (models, exception, paging, presentation) run anywhere Dart runs. | +| Built on | Ktor 3, kotlinx.serialization, AndroidX ViewModel | Dio 5, Riverpod | +| Coordinates (at 1.0) | `com.srctool.sinew:sinew-*` with `sinew-bom` | `sinew_*` on pub.dev | +| Source | [srctool/sinew-kotlin](https://github.com/srctool/sinew-kotlin) | [srctool/sinew-dart](https://github.com/srctool/sinew-dart) | -## Submodules +## Trying it before 1.0 -Submodule pointers are moved by Dependabot: it checks the libraries' `main` daily and opens a PR that updates `kotlin-lib` and `dart-lib` (see `.github/dependabot.yml`). The submodule URLs use HTTPS, so cloning needs no SSH key: +Until the first release, use Sinew from source. -```bash -git clone --recurse-submodules https://github.com/srctool/sinew.git -``` +**Kotlin.** Clone [sinew-kotlin](https://github.com/srctool/sinew-kotlin) and include it as a composite build. Gradle then resolves the `com.srctool.sinew` modules from the source: -The libraries are built, tested and released in their own repositories; this repository has no release of its own. +```kotlin +// settings.gradle.kts +includeBuild("../sinew-kotlin") +``` -### Kotlin implementation (`kotlin-lib/`) -- Kotlin Multiplatform: Android, iOS, JVM desktop, wasmJs. Maven Central `com.srctool.sinew:sinew-*`, with a BOM. -- Upstream: https://github.com/srctool/sinew-kotlin -- License: Apache 2.0 +**Dart.** Depend on a package through git: -### Dart implementation (`dart-lib/`) -- A pub workspace managed by Melos, one package per Sinew package. pub.dev `sinew_*`. -- Upstream: https://github.com/srctool/sinew-dart -- License: Apache 2.0 +```yaml +dependencies: + sinew_network: + git: + url: https://github.com/srctool/sinew-dart.git + path: packages/sinew_network +``` -The root `settings.gradle.kts` includes `kotlin-lib` as a composite build, so opening the repository root in Android Studio works. +## Documentation ---- +- **[Usage docs](https://sinew.srctool.com):** adding Sinew to an app, wiring it, and building screens. These fill in as the 1.0 API settles. +- **[Design guide](https://sinew-dev.srctool.com/guide):** the architecture, the error model, and every package with its Kotlin and Flutter implementation, plus the roadmap and the design decisions. ## Contributing -See [CONTRIBUTING.md](CONTRIBUTING.md) and the Contributing section of the docs. Changes that affect one implementation go to its submodule repository; documentation and cross-cutting changes go here. +Issues and pull requests are welcome. Start with [CONTRIBUTING.md](CONTRIBUTING.md). Library changes go to [sinew-kotlin](https://github.com/srctool/sinew-kotlin) or [sinew-dart](https://github.com/srctool/sinew-dart). Documentation and cross-platform design changes go to this repository. ---- +Everyone taking part is expected to follow the [Code of Conduct](CODE_OF_CONDUCT.md). To report a security issue, follow [SECURITY.md](SECURITY.md) instead of opening a public issue. -## License +### About this repository -- Main repository (docs and design): MIT License -- Kotlin and Dart implementations: Apache 2.0 License +This is the umbrella repository. It holds the documentation sites and includes both libraries as Git submodules: + +``` +├── docs/usage/ # Usage docs site (Docusaurus) +├── docs/internal/ # Design guide and contributor docs site (Docusaurus) +├── kotlin-lib/ # submodule: srctool/sinew-kotlin +└── dart-lib/ # submodule: srctool/sinew-dart +``` + +```bash +git clone --recurse-submodules https://github.com/srctool/sinew.git +``` + +Opening the root in Android Studio works: `settings.gradle.kts` includes `kotlin-lib` as a composite build. + +## License -## Contact +The libraries are licensed under [Apache 2.0](https://github.com/srctool/sinew-kotlin/blob/main/LICENSE). The documentation and design material in this repository are licensed under [MIT](LICENSE). -contact@srctool.com +Contact: contact@srctool.com diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..eddd301 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,51 @@ +# Security Policy + +Sinew handles HTTP authentication, token refresh, secure storage and encryption, so we take security reports seriously. Thank you for helping keep Sinew and the apps built on it safe. + +## Supported versions + +Sinew is pre-1.0 and has no published release yet. Security fixes go to the `main` branch of each library. + +After 1.0, the latest minor release gets security fixes. This section will list the supported versions then. + +| Version | Supported | +|---|---| +| `main` (pre-1.0) | ✅ | + +## Reporting a vulnerability + +**Please don't report a vulnerability in a public issue, pull request or discussion.** + +Report it privately through GitHub's **Report a vulnerability** form, on the repository where the problem is: + +- Kotlin library: [srctool/sinew-kotlin](https://github.com/srctool/sinew-kotlin/security/advisories/new) +- Dart and Flutter library: [srctool/sinew-dart](https://github.com/srctool/sinew-dart/security/advisories/new) +- Documentation, design, or if you're not sure: [srctool/sinew](https://github.com/srctool/sinew/security/advisories/new) + +If you can't use GitHub, email **contact@srctool.com** with "Security" in the subject. + +Please include: + +- the affected package and version or commit +- the platform (Android, iOS, JVM, wasmJs, Flutter) if it matters +- a description of the issue and its impact +- steps to reproduce, or a proof of concept +- any suggested fix + +## What to expect + +- We aim to acknowledge your report within 3 business days. +- We'll confirm whether the issue is accepted, and keep you updated while we work on a fix. +- We'll publish a GitHub security advisory when the fix is released, and credit you unless you'd prefer not to be named. + +Please give us reasonable time to release a fix before you disclose the issue publicly. + +## Scope + +In scope: the Sinew libraries (`sinew-*` modules and `sinew_*` packages), including the auth and token refresh layer, redacted logging, `SecureStore`, `FieldCipher`, `BiometricVault`, and the guarantee that DevTools is compiled out of release builds. + +Out of scope: + +- vulnerabilities in third-party dependencies (Ktor, Dio, Tink, platform keystores). Report these upstream. Tell us too if Sinew uses the dependency in an unsafe way. +- the security of an app's own configuration, such as its `TokenSource`, backend or stored data +- the documentation websites, unless the issue affects users of the libraries