├── .github ├── config │ ├── configuration.json │ └── labels.yml ├── dependabot.yml ├── release.yml └── workflows │ ├── build.yml │ ├── dependabot-prs.yml │ ├── jextract.yml │ └── sync-labels.yml ├── .gitignore ├── .sdkmanrc ├── README.md ├── backend ├── README.md ├── agent │ ├── jfr │ │ ├── build.gradle.kts │ │ └── src │ │ │ └── main │ │ │ └── kotlin │ │ │ └── dev │ │ │ └── suresh │ │ │ ├── AgentType.kt │ │ │ ├── App.kt │ │ │ └── system │ │ │ ├── BlockSystemExitAgent.kt │ │ │ └── SystemExitInvoker.kt │ └── otel │ │ ├── build.gradle.kts │ │ └── src │ │ └── main │ │ └── java │ │ └── io │ │ └── opentelemetry │ │ └── extensions │ │ └── HttpResponseCustomizer.java ├── boot │ ├── build.gradle.kts │ ├── compose.yaml │ └── src │ │ ├── main │ │ ├── kotlin │ │ │ └── dev │ │ │ │ └── suresh │ │ │ │ ├── App.kt │ │ │ │ ├── aot │ │ │ │ └── AotConfig.kt │ │ │ │ └── books │ │ │ │ └── Books.kt │ │ └── resources │ │ │ ├── application.yml │ │ │ └── schema.sql │ │ └── test │ │ └── kotlin │ │ └── dev │ │ └── suresh │ │ └── AppTest.kt ├── data │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── kotlin │ │ └── dev │ │ └── suresh │ │ ├── DataApp.kt │ │ └── gen │ │ └── GenArt.kt ├── jvm │ ├── build.gradle.kts │ └── src │ │ ├── main │ │ ├── java │ │ │ └── module-info.java.txt │ │ ├── kotlin │ │ │ └── dev │ │ │ │ └── suresh │ │ │ │ ├── App.kt │ │ │ │ ├── config │ │ │ │ └── AppConfig.kt │ │ │ │ ├── lang │ │ │ │ ├── FFM.kt │ │ │ │ ├── TM.kt │ │ │ │ └── VThread.kt │ │ │ │ ├── log │ │ │ │ └── RespLogger.kt │ │ │ │ ├── plugins │ │ │ │ ├── Error.kt │ │ │ │ ├── Http.kt │ │ │ │ ├── OTel.kt │ │ │ │ ├── Security.kt │ │ │ │ └── custom │ │ │ │ │ ├── CookieSession.kt │ │ │ │ │ └── Plugins.kt │ │ │ │ ├── routes │ │ │ │ ├── Admin.kt │ │ │ │ ├── Mgmt.kt │ │ │ │ ├── Scheduler.kt │ │ │ │ ├── Service.kt │ │ │ │ └── Webapp.kt │ │ │ │ └── wasm │ │ │ │ └── Wasm.kt │ │ └── resources │ │ │ ├── META-INF │ │ │ └── native-image │ │ │ │ ├── native-image.properties │ │ │ │ └── reachability-metadata.json │ │ │ ├── app │ │ │ └── index.html │ │ │ ├── application.conf │ │ │ ├── backend-jvm-res.txt │ │ │ ├── logback.xml │ │ │ ├── openapi │ │ │ └── documentation.yaml │ │ │ ├── otel │ │ │ └── sdk-config.yaml │ │ │ ├── templates │ │ │ └── FileBrowser.kte │ │ │ └── wasm │ │ │ ├── factorial.wasm │ │ │ └── hello-wasi.wasm │ │ └── test │ │ ├── kotlin │ │ └── dev │ │ │ └── suresh │ │ │ ├── AppTests.kt │ │ │ ├── DnsTest.kt │ │ │ ├── K8STests.kt │ │ │ ├── TestLogger.kt │ │ │ └── http │ │ │ └── TestHttpClient.kt │ │ └── resources │ │ └── hosts_disabled ├── native │ ├── build.gradle.kts │ └── src │ │ ├── appleMain │ │ └── kotlin │ │ │ └── Env.kt │ │ ├── linuxMain │ │ └── kotlin │ │ │ └── Env.kt │ │ ├── mingwMain │ │ └── kotlin │ │ │ └── Env.kt │ │ ├── nativeMain │ │ └── kotlin │ │ │ ├── Main.kt │ │ │ ├── io │ │ │ └── IO.kt │ │ │ └── wasm │ │ │ └── Wasm.kt │ │ └── nativeTest │ │ └── kotlin │ │ └── NativeTest.kt ├── profiling │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── kotlin │ │ └── dev │ │ └── suresh │ │ ├── JFR.kt │ │ └── Profiling.kt └── security │ ├── api │ ├── security.api │ └── security.klib.api │ ├── build.gradle.kts │ └── src │ └── main │ └── kotlin │ └── dev │ └── suresh │ ├── SecApp.kt │ ├── TrustStore.kt │ ├── cert │ ├── CertScan.kt │ └── PemExtns.kt │ ├── security │ └── PasswordGen.kt │ └── tls │ ├── AliasKeyManager.kt │ ├── CustomSSLSocketFactory.kt │ ├── SavingTrustManager.kt │ └── TLSProp.kt ├── benchmark ├── README.md ├── build.gradle.kts └── src │ ├── commonMain │ └── kotlin │ │ └── CommonBenchmark.kt │ └── jvmMain │ └── kotlin │ └── JvmBenchmark.kt ├── build.gradle.kts ├── compose ├── README.md ├── cli │ ├── build.gradle.kts │ └── src │ │ ├── commonMain │ │ └── kotlin │ │ │ └── dev │ │ │ └── suresh │ │ │ ├── Counter.kt │ │ │ └── Gradient.kt │ │ └── jvmMain │ │ └── kotlin │ │ └── dev │ │ └── suresh │ │ └── App.kt ├── cmp │ ├── build.gradle.kts │ └── src │ │ ├── assets │ │ ├── common │ │ │ └── splash.jpg │ │ ├── icons │ │ │ ├── linux.png │ │ │ ├── mac.icns │ │ │ └── win.ico │ │ └── macos-arm64 │ │ │ └── resource.txt │ │ ├── commonMain │ │ ├── composeResources │ │ │ ├── drawable │ │ │ │ ├── animated_svg │ │ │ │ │ ├── audio.svg │ │ │ │ │ ├── ball-triangle.svg │ │ │ │ │ ├── bars.svg │ │ │ │ │ ├── circles.svg │ │ │ │ │ ├── grid.svg │ │ │ │ │ ├── hearts.svg │ │ │ │ │ ├── oval.svg │ │ │ │ │ ├── puff.svg │ │ │ │ │ ├── rings.svg │ │ │ │ │ ├── spinning-circles.svg │ │ │ │ │ ├── tail-spin.svg │ │ │ │ │ └── three-dots.svg │ │ │ │ ├── compose-logo.svg │ │ │ │ ├── compose-multiplatform.xml │ │ │ │ ├── ic-fluent-rocket.svg │ │ │ │ ├── idea-logo.svg │ │ │ │ └── particles.gif │ │ │ └── files │ │ │ │ ├── .keep │ │ │ │ └── lottie │ │ │ │ └── anim.json │ │ └── kotlin │ │ │ ├── App.kt │ │ │ ├── nav │ │ │ ├── NavRoot.kt │ │ │ └── Screen.kt │ │ │ └── ui │ │ │ ├── Image.kt │ │ │ ├── Modifier.kt │ │ │ ├── Theme.kt │ │ │ ├── birds │ │ │ ├── BirdsScreen.kt │ │ │ └── BirdsViewModel.kt │ │ │ ├── file │ │ │ └── FileBrowser.kt │ │ │ ├── home │ │ │ └── HomeScreen.kt │ │ │ ├── lottie │ │ │ └── Animation.kt │ │ │ └── misc │ │ │ └── Window.kt │ │ ├── commonTest │ │ └── kotlin │ │ │ └── ui │ │ │ └── ComposeTest.kt │ │ ├── jvmMain │ │ └── kotlin │ │ │ ├── Main.kt │ │ │ └── ui │ │ │ ├── Image.jvm.kt │ │ │ ├── crash │ │ │ └── CrashDialog.kt │ │ │ └── file │ │ │ ├── FileBrowser.jvm.kt │ │ │ └── FileChooser.jvm.kt │ │ └── wasmJsMain │ │ ├── kotlin │ │ ├── Main.kt │ │ └── ui │ │ │ └── file │ │ │ └── FileBrowser.wasmJs.kt │ │ └── resources │ │ ├── images │ │ └── compose.svg │ │ ├── index.html │ │ └── manifest.json └── html │ ├── .kobweb │ └── conf.yaml │ ├── build.gradle.kts │ └── src │ └── jsMain │ ├── kotlin │ └── dev │ │ └── suresh │ │ ├── MyApp.kt │ │ ├── components │ │ ├── layouts │ │ │ └── PageLayout.kt │ │ └── sections │ │ │ ├── Footer.kt │ │ │ └── NavHeader.kt │ │ └── pages │ │ └── Index.kt │ └── resources │ ├── markdown │ └── About.md │ ├── public │ ├── favicon.ico │ └── kobweb-logo.png │ └── test.html ├── dep-mgmt ├── bom │ └── build.gradle.kts └── catalog │ └── build.gradle.kts ├── gradle.properties ├── gradle ├── .githooks │ └── pre-commit ├── build-logic │ ├── README.md │ ├── build.gradle.kts │ ├── gradle.properties │ ├── settings.gradle.kts │ └── src │ │ └── main │ │ ├── jte │ │ ├── BuildConfig.kte │ │ └── TableConfig.kte │ │ ├── kotlin │ │ ├── common │ │ │ ├── CommonExtns.kt │ │ │ ├── GithubAction.kt │ │ │ ├── Multiplatform.kt │ │ │ ├── PatchModuleArgProvider.kt │ │ │ ├── Platform.kt │ │ │ ├── ProjectExtns.kt │ │ │ └── SettingsExtns.kt │ │ ├── dev.suresh.plugin.catalog.gradle.kts │ │ ├── dev.suresh.plugin.common.gradle.kts │ │ ├── dev.suresh.plugin.graalvm.gradle.kts │ │ ├── dev.suresh.plugin.kotlin.benchmark.gradle.kts │ │ ├── dev.suresh.plugin.kotlin.docs.gradle.kts │ │ ├── dev.suresh.plugin.kotlin.jvm.gradle.kts │ │ ├── dev.suresh.plugin.kotlin.mpp.gradle.kts │ │ ├── dev.suresh.plugin.publishing.gradle.kts │ │ ├── dev.suresh.plugin.repos.settings.gradle.kts │ │ ├── dev.suresh.plugin.root.gradle.kts │ │ ├── plugins │ │ │ ├── DepReportsPlugin.kt │ │ │ ├── GenericPlugin.kt │ │ │ └── XvfbServer.kt │ │ └── tasks │ │ │ ├── BuildConfig.kt │ │ │ ├── ExecTask.kt │ │ │ ├── Jdeprscan.kt │ │ │ ├── MultiReleaseJar.kt │ │ │ ├── ReallyExecJar.kt │ │ │ └── SampleTask.kt │ │ └── resources │ │ └── exec-jar-stub.sh ├── gradle-daemon-jvm.properties ├── kotlin-js-store │ ├── package-lock.json │ └── wasm │ │ └── package-lock.json ├── libs.versions.toml ├── verification-keyring.keys └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── meta ├── README.md ├── compiler │ └── plugin │ │ ├── build.gradle.kts │ │ └── src │ │ └── test │ │ └── kotlin │ │ └── PluginTest.kt ├── ksp │ └── processor │ │ ├── build.gradle.kts │ │ └── src │ │ └── main │ │ └── kotlin │ │ └── TestProcessor.kt └── scripts │ ├── JavaSingleFile │ ├── Mach-O-Bin.sh │ ├── cpu-mem-viz.sh │ ├── jextract │ ├── c │ │ ├── ioctl.h │ │ └── windows.h │ ├── gen │ │ ├── linux.sh │ │ ├── macos.sh │ │ ├── set-package.sh │ │ └── windows.sh │ └── jextract.sh │ ├── jrtserver │ ├── mkdocs │ ├── mkdocs.sh │ └── mkdocs.yml │ ├── openjdk-ea.sh │ ├── otel │ ├── OTel-Config.md │ ├── grafana │ │ ├── datasource.yaml │ │ └── grafana.ini │ ├── otel-collector-config.yml │ ├── otel-compose.yml │ └── systemd │ │ ├── README.md │ │ ├── docker-cleanup.service │ │ ├── docker-cleanup.timer │ │ └── docker-compose@.service │ ├── quine.jshell │ ├── rsocket.http │ └── script.main.kts ├── settings.gradle.kts ├── shared ├── README.md ├── api │ ├── shared.api │ └── shared.klib.api ├── build.gradle.kts ├── data.ipynb └── src │ ├── commonMain │ ├── kotlin │ │ └── dev │ │ │ └── suresh │ │ │ ├── Greeting.kt │ │ │ ├── Platform.kt │ │ │ ├── flow │ │ │ └── Timer.kt │ │ │ ├── http │ │ │ ├── Config.kt │ │ │ ├── CurlLogging.kt │ │ │ ├── HttpClient.kt │ │ │ └── MediaApiClient.kt │ │ │ ├── lang │ │ │ ├── Features.kt │ │ │ ├── HackersDelight.kt │ │ │ └── Person.kt │ │ │ ├── lc │ │ │ └── Tree.kt │ │ │ └── serde │ │ │ ├── AnySerializer.kt │ │ │ └── ByteArrayAsBase64Serializer.kt │ └── resources │ │ └── common-main-res.txt │ ├── commonTest │ └── kotlin │ │ └── dev │ │ └── suresh │ │ └── CommonTest.kt │ ├── jsMain │ ├── kotlin │ │ └── dev │ │ │ └── suresh │ │ │ ├── Extensions.kt │ │ │ ├── Platform.kt │ │ │ ├── http │ │ │ └── HttpClient.js.kt │ │ │ └── tz │ │ │ └── JsJodaTZ.kt │ └── resources │ │ └── common-js-res.txt │ ├── jsTest │ └── kotlin │ │ └── dev │ │ └── suresh │ │ └── PlatformTest.kt │ ├── jvmMain │ ├── java │ │ └── dev │ │ │ └── suresh │ │ │ ├── DOP.java │ │ │ ├── Expr.java │ │ │ ├── Gatherers.java │ │ │ ├── Lang.java │ │ │ └── Result.java │ ├── kotlin │ │ └── dev │ │ │ └── suresh │ │ │ ├── Extns.kt │ │ │ ├── FFM.kt │ │ │ ├── Platform.kt │ │ │ ├── ReentrantLazy.kt │ │ │ ├── cert │ │ │ ├── CertExtn.kt │ │ │ ├── PemFormat.kt │ │ │ └── RootCA.kt │ │ │ ├── http │ │ │ └── HttpClient.jvm.kt │ │ │ └── qos │ │ │ └── FFMQosSetter.kt │ └── resources │ │ ├── ca │ │ └── cacert.pem │ │ └── common-jvm-res.txt │ ├── jvmTest │ └── kotlin │ │ └── dev │ │ └── suresh │ │ └── PlatformTest.kt │ ├── nativeMain │ ├── kotlin │ │ └── dev │ │ │ └── suresh │ │ │ ├── Platform.kt │ │ │ └── http │ │ │ └── HttpClient.native.kt │ └── resources │ │ └── common-native-res.txt │ ├── nativeTest │ └── kotlin │ │ └── dev.suresh │ │ └── PlatformTest.kt │ ├── wasmJsMain │ ├── kotlin │ │ └── dev │ │ │ └── suresh │ │ │ ├── Extensions.kt │ │ │ ├── FilePicker.kt │ │ │ ├── Platform.kt │ │ │ └── http │ │ │ └── HttpClient.wasmJs.kt │ └── resources │ │ └── common-wasm-res.txt │ └── wasmJsTest │ └── kotlin │ └── dev │ └── suresh │ └── PlatformTest.kt └── web ├── build.gradle.kts └── src ├── jsMain ├── kotlin │ ├── App.kt │ ├── hljs │ │ └── Highlight.kt │ ├── interop │ │ └── JsInterop.kt │ ├── play │ │ └── KotlinPlayground.kt │ └── xterm │ │ ├── Xterm.kt │ │ └── lib.es5.kt └── resources │ ├── css │ ├── app.css │ ├── bulma.min.css │ ├── hljs │ │ ├── github-dark.min.css │ │ └── github.min.css │ └── xterm.css │ ├── favicon.ico │ ├── index.html │ └── logo │ └── kotlin-logo.svg ├── jsTest └── kotlin │ └── AppTest.kt └── wasmJsMain ├── kotlin └── App.kt └── resources ├── css └── app.css ├── favicon.ico ├── index.html └── logo ├── kodee-excited.png ├── kodee-loving.png ├── kodee-regular.svg ├── kodee-sharing.svg ├── kodee-walking.gif ├── kotlin-full-logo.svg └── kotlin-logo.svg /.github/config/configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "categories": [ 3 | { 4 | "title": "## 🚀 Features", 5 | "labels": [ 6 | "feat" 7 | ] 8 | }, 9 | { 10 | "title": "## 🐛 Fixes", 11 | "labels": [ 12 | "fix" 13 | ] 14 | }, 15 | { 16 | "title": "## 🧰 Maintenance", 17 | "labels": [ 18 | "chore" 19 | ] 20 | }, 21 | { 22 | "title": "## 🧪 Tests", 23 | "labels": [ 24 | "test" 25 | ] 26 | }, 27 | { 28 | "title": "## 🖍️ Documentation", 29 | "labels": [ 30 | "doc", 31 | "docs" 32 | ] 33 | }, 34 | { 35 | "title": "## 📦 Dependencies", 36 | "labels": [ 37 | "deps", 38 | "dependencies", 39 | "chore(deps)", 40 | "build(deps)" 41 | ] 42 | }, 43 | { 44 | "title": "## 💬 Other", 45 | "labels": [ 46 | "other", 47 | "misc" 48 | ] 49 | } 50 | ], 51 | "sort": "ASC", 52 | "template": "${{CHANGELOG}}\n\n
\nUncategorized\n\n${{UNCATEGORIZED}}\n
", 53 | "pr_template": "${{TITLE}}", 54 | "empty_template": "- no changes", 55 | "label_extractor": [ 56 | { 57 | "pattern": "(.+): (.+)", 58 | "target": "$1" 59 | } 60 | ], 61 | "exclude_merge_branches": [ 62 | "merge pull request", 63 | "Merge pull request" 64 | ] 65 | } 66 | -------------------------------------------------------------------------------- /.github/config/labels.yml: -------------------------------------------------------------------------------- 1 | - name: bug 2 | description: Something isn't working 3 | color: 'd73a4a' 4 | - name: doc 5 | description: Improvements to documentation 6 | color: 'd4c5f9' 7 | - name: duplicate 8 | description: This issue or pull request already exists 9 | color: 'cfd3d7' 10 | - name: feature 11 | color: '1d76db' 12 | description: New features 13 | - name: enhancement 14 | description: Enhancement of existing functionality 15 | color: '84b6eb' 16 | - name: deprecated 17 | description: Deprecating API 18 | color: 'f4c21d' 19 | - name: removed 20 | description: Removing API 21 | color: 'e4b21d' 22 | - name: tests 23 | description: Enhancement of tests 24 | color: '0e8a16' 25 | - name: java 26 | description: Java/JDK changes 27 | color: '03d0d6' 28 | - name: gradle 29 | description: Gradle changes 30 | color: 'd0d603' 31 | - name: maven 32 | description: Maven changes 33 | color: 'd60366' 34 | - name: compose 35 | description: Jetbrains Compose issues 36 | color: '3cdc84' 37 | - name: help 38 | description: Help Wanted 39 | color: '0e8a16' 40 | - name: question 41 | description: Questions and discussions 42 | color: 'cc317c' 43 | - name: dependencies 44 | description: Changes that affect dependencies 45 | color: '5319e7' 46 | - name: docker 47 | description: Container changes 48 | color: 'e99695' 49 | - name: github-actions 50 | description: Github action workflow changes 51 | color: 'ff7619' 52 | - name: graalvm 53 | description: GraalVM changes 54 | color: '387E65' 55 | - name: native-image 56 | description: GraalVM native-image changes 57 | color: 'A720DE' 58 | - name: kotlin 59 | description: Kotlin changes 60 | color: '5319E7' 61 | - name: kotlin-kmp 62 | description: Kotlin Multiplatform changes 63 | color: '7f52ff' 64 | - name: kotlin-multiplatform 65 | description: Kotlin Multiplatform changes 66 | color: '7f52ff' 67 | - name: build-logic 68 | description: Build Logic changes 69 | color: '4CAF50' 70 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "gradle" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | open-pull-requests-limit: 5 8 | labels: 9 | - "gradle" 10 | - "dependencies" 11 | reviewers: 12 | - sureshg 13 | 14 | - package-ecosystem: "gradle" 15 | directory: "/gradle/build-logic" 16 | schedule: 17 | interval: "daily" 18 | open-pull-requests-limit: 5 19 | labels: 20 | - "gradle" 21 | - "dependencies" 22 | reviewers: 23 | - sureshg 24 | 25 | - package-ecosystem: "github-actions" 26 | directory: "/" 27 | schedule: 28 | interval: "daily" 29 | open-pull-requests-limit: 5 30 | labels: 31 | - "github-actions" 32 | - "dependencies" 33 | reviewers: 34 | - sureshg 35 | 36 | - package-ecosystem: "docker" 37 | directory: "/" 38 | schedule: 39 | interval: "daily" 40 | labels: 41 | - "docker" 42 | - "dependencies" 43 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | changelog: 2 | exclude: 3 | labels: 4 | - ignore-for-release 5 | - ci 6 | - skip-ci 7 | authors: 8 | - octocat 9 | categories: 10 | - title: New Features 🎉 11 | labels: 12 | - feat 13 | - enhancement 14 | - title: Breaking Changes 🛠 15 | labels: 16 | - Semver-Major 17 | - breaking-change 18 | - title: Fixes 🐛 19 | labels: 20 | - fix 21 | - bug 22 | - title: Maintenance 🧰 23 | labels: 24 | - chore 25 | - maintenance 26 | - title: Tests 🧪 27 | labels: 28 | - test 29 | - title: Documentation 🖍️ 30 | labels: 31 | - doc 32 | - title: Dependencies 📦️ 33 | labels: 34 | - deps 35 | - dependencies 36 | - chore(deps) 37 | - build(deps) 38 | - title: Other Changes 🖇️ 39 | labels: 40 | - "*" 41 | -------------------------------------------------------------------------------- /.github/workflows/dependabot-prs.yml: -------------------------------------------------------------------------------- 1 | name: Dependabot Auto Approve 2 | 3 | on: pull_request_target 4 | 5 | permissions: write-all 6 | 7 | jobs: 8 | dependabot: 9 | runs-on: ubuntu-latest 10 | if: github.actor == 'dependabot[bot]' 11 | steps: 12 | - name: 🔧Dependabot metadata 13 | id: dependabot-metadata 14 | uses: dependabot/fetch-metadata@v2.4.0 15 | with: 16 | github-token: "${{ secrets.GITHUB_TOKEN }}" 17 | 18 | - name: Approve a PR 19 | run: gh pr review --approve "$PR_URL" 20 | env: 21 | PR_URL: ${{github.event.pull_request.html_url}} 22 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 23 | 24 | - name: Enable auto-merge for Dependabot PRs 25 | if: steps.dependabot-metadata.outputs.update-type != 'version-update:semver-major' 26 | run: gh pr merge --auto --merge "$PR_URL" 27 | env: 28 | PR_URL: ${{github.event.pull_request.html_url}} 29 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 30 | 31 | - name: Add a label for dependencies 32 | if: steps.dependabot-metadata.outputs.dependency-type == 'direct:production' 33 | run: gh pr edit "$PR_URL" --add-label "dependencies" 34 | env: 35 | PR_URL: ${{github.event.pull_request.html_url}} 36 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 37 | -------------------------------------------------------------------------------- /.github/workflows/sync-labels.yml: -------------------------------------------------------------------------------- 1 | name: Sync Labels 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - .github/config/labels.yml 9 | workflow_dispatch: 10 | 11 | permissions: 12 | issues: write 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v4 19 | with: 20 | sparse-checkout: .github/config/labels.yml 21 | 22 | - uses: EndBug/label-sync@v2 23 | with: 24 | config-file: | 25 | .github/config/labels.yml 26 | delete-other-labels: false 27 | token: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Mac OSX 2 | .DS_Store 3 | 4 | # IntelliJ IDEA 5 | .idea/ 6 | *.iml 7 | out/ 8 | 9 | # Gradle 10 | .gradle 11 | build 12 | reports 13 | !docs/reports 14 | !gradle-wrapper.jar 15 | 16 | # Java 17 | hs_err_pid* 18 | classes.lst 19 | *.jsa 20 | *.aot* 21 | jre/ 22 | 23 | # Kotlin 24 | **/.kotlin 25 | 26 | # Kobweb ignores 27 | **/.kobweb/* 28 | !**/.kobweb/conf.yaml 29 | 30 | # Gradle release 31 | cx.json 32 | -------------------------------------------------------------------------------- /.sdkmanrc: -------------------------------------------------------------------------------- 1 | # Enable auto-env through the sdkman_auto_env config 2 | # Add key=value pairs of SDKs to use below 3 | java=openjdk-ea 4 | -------------------------------------------------------------------------------- /backend/README.md: -------------------------------------------------------------------------------- 1 | ### Run the backend JVM app 2 | 3 | ```bash 4 | # JVM 5 | $ ./gradlew :backend:jvm:run 6 | ``` 7 | 8 | ### OpenTelemetry 9 | 10 | * [Prometheus Java client Sample](https://prometheus.github.io/client_java/otel/otlp/) 11 | 12 | ### Misc 13 | 14 | * [JFR Speedscope](https://github.com/parttimenerd/jfrtofp/blob/main/src/main/kotlin/me/bechberger/jfrtofp/other) 15 | * [JFR Spring Starter](https://github.com/mirkosertic/flight-recorder-starter) 16 | * [Kotlin Native Samples](https://github.com/JetBrains/kotlin/tree/master/kotlin-native/backend.native/tests/samples) 17 | -------------------------------------------------------------------------------- /backend/agent/jfr/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import common.* 2 | import kotlin.collections.plus 3 | 4 | plugins { 5 | dev.suresh.plugin.kotlin.jvm 6 | application 7 | com.gradleup.shadow 8 | dev.suresh.plugin.publishing 9 | } 10 | 11 | description = "JVM JFR Agent!" 12 | 13 | application { 14 | mainClass = libs.versions.app.mainclass.get() 15 | applicationDefaultJvmArgs += "" 16 | } 17 | 18 | tasks { 19 | jar { 20 | manifest { 21 | attributes( 22 | "Premain-Class" to application.mainClass, 23 | "Agent-Class" to application.mainClass, 24 | "Launcher-Agent-Class" to application.mainClass, 25 | "Can-Redefine-Classes" to "true", 26 | "Can-Retransform-Classes" to "true", 27 | "Can-Set-Native-Method-Prefix" to "true", 28 | "Implementation-Title" to project.name, 29 | "Implementation-Version" to version, 30 | ) 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /backend/agent/jfr/src/main/kotlin/dev/suresh/AgentType.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh 2 | 3 | import jdk.jfr.* 4 | 5 | @Name("dev.suresh.agent.AgentType") 6 | @Label("JVM Agent Type") 7 | @Description("JVM agent attach type") 8 | @Category("JVM Agent", "Agent") 9 | @Period("5 s") 10 | @StackTrace(false) 11 | class AgentType(@Label("Agent Type") val type: String = "static") : Event() 12 | -------------------------------------------------------------------------------- /backend/agent/jfr/src/main/kotlin/dev/suresh/system/SystemExitInvoker.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh.system 2 | 3 | object SystemExitInvoker { 4 | operator fun invoke() = System.exit(0) 5 | } 6 | -------------------------------------------------------------------------------- /backend/agent/otel/src/main/java/io/opentelemetry/extensions/HttpResponseCustomizer.java: -------------------------------------------------------------------------------- 1 | package io.opentelemetry.extensions; 2 | 3 | import com.google.auto.service.AutoService; 4 | import io.opentelemetry.api.trace.Span; 5 | import io.opentelemetry.context.Context; 6 | import io.opentelemetry.javaagent.bootstrap.http.HttpServerResponseCustomizer; 7 | import io.opentelemetry.javaagent.bootstrap.http.HttpServerResponseMutator; 8 | 9 | /** 10 | * Customizes the HTTP response by adding the trace ID as a header. 11 | */ 12 | @AutoService(HttpServerResponseCustomizer.class) 13 | public class HttpResponseCustomizer implements HttpServerResponseCustomizer { 14 | 15 | private static final String TRACE_ID_HEADER = "X-Trace-Id"; 16 | 17 | 18 | @Override 19 | public void customize(Context serverContext, T response, HttpServerResponseMutator responseMutator) { 20 | var span = Span.fromContextOrNull(serverContext); 21 | if (span != null) { 22 | var spanContext = span.getSpanContext(); 23 | if (spanContext.isValid() && spanContext.isSampled()) { 24 | responseMutator.appendHeader(response, TRACE_ID_HEADER, spanContext.getTraceId()); 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /backend/boot/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import common.* 2 | 3 | plugins { 4 | dev.suresh.plugin.kotlin.jvm 5 | alias(libs.plugins.spring.boot) 6 | alias(libs.plugins.spring.depmgmt) 7 | `kotlin-spring` 8 | // org.graalvm.buildtools.native 9 | // dev.suresh.plugin.publishing 10 | } 11 | 12 | description = "Kotlin SpringBoot app" 13 | 14 | springBoot { buildInfo {} } 15 | 16 | dependencies { 17 | implementation(projects.shared) 18 | implementation("org.springframework.boot:spring-boot-starter-web") 19 | implementation("org.jetbrains.kotlin:kotlin-reflect") 20 | runtimeOnly("org.postgresql:postgresql") 21 | // implementation("org.springframework.boot:spring-boot-starter-jdbc") 22 | // implementation("org.springframework.boot:spring-boot-starter-security") 23 | // implementation("com.fasterxml.jackson.module:jackson-module-kotlin") 24 | // developmentOnly("org.springframework.boot:spring-boot-docker-compose") 25 | testImplementation("org.springframework.boot:spring-boot-starter-test") 26 | testImplementation("org.jetbrains.kotlin:kotlin-test-junit5") 27 | testImplementation("org.springframework.security:spring-security-test") 28 | testImplementation("org.springframework.boot:spring-boot-testcontainers") 29 | testImplementation("org.testcontainers:junit-jupiter") 30 | testImplementation("org.testcontainers:postgresql") 31 | testRuntimeOnly("org.junit.platform:junit-platform-launcher") 32 | } 33 | 34 | // Fix for https://github.com/Kotlin/dokka/issues/3472 35 | // configurations 36 | // .matching { it.name.startsWith("dokka") } 37 | // .configureEach { 38 | // resolutionStrategy.eachDependency { 39 | // if (requested.group.startsWith("com.fasterxml.jackson")) { 40 | // useVersion("2.15.3") 41 | // } 42 | // } 43 | // } 44 | 45 | tasks { bootRun { jvmArgs = project.runJvmArgs } } 46 | -------------------------------------------------------------------------------- /backend/boot/compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | postgres: 3 | image: 'postgres:latest' 4 | environment: 5 | - 'POSTGRES_DB=mydb' 6 | - 'POSTGRES_USER=user' 7 | - 'POSTGRES_PASSWORD=secret' 8 | ports: 9 | - '5432:5432' 10 | -------------------------------------------------------------------------------- /backend/boot/src/main/kotlin/dev/suresh/App.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh 2 | 3 | import org.springframework.boot.autoconfigure.SpringBootApplication 4 | import org.springframework.boot.runApplication 5 | 6 | @SpringBootApplication class App 7 | 8 | fun main(args: Array) { 9 | System.setProperty("spring.classformat.ignore", "true") 10 | runApplication(*args) 11 | } 12 | -------------------------------------------------------------------------------- /backend/boot/src/main/kotlin/dev/suresh/aot/AotConfig.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh.aot 2 | 3 | import org.springframework.aot.hint.RuntimeHints 4 | import org.springframework.aot.hint.RuntimeHintsRegistrar 5 | import org.springframework.context.annotation.Configuration 6 | import org.springframework.context.annotation.ImportRuntimeHints 7 | 8 | @Configuration @ImportRuntimeHints(Hints::class) class AotConfig 9 | 10 | class Hints : RuntimeHintsRegistrar { 11 | override fun registerHints(hints: RuntimeHints, classLoader: ClassLoader?) { 12 | hints.reflection().apply { 13 | // registerType(Klass::class.java, *MemberCategory.entries.toTypedArray()) 14 | // registerType(Klass::class.java, MemberCategory.INVOKE_DECLARED_METHODS) 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /backend/boot/src/main/kotlin/dev/suresh/books/Books.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh.books 2 | 3 | import kotlinx.datetime.LocalDateTime 4 | import kotlinx.serialization.Serializable 5 | import org.springframework.stereotype.Controller 6 | import org.springframework.web.bind.annotation.GetMapping 7 | import org.springframework.web.bind.annotation.RequestMapping 8 | import org.springframework.web.bind.annotation.ResponseBody 9 | 10 | @Serializable data class Author(val id: Long, val name: String) 11 | 12 | @Serializable 13 | data class Book( 14 | val id: Long, 15 | val title: String, 16 | val description: String, 17 | val created: LocalDateTime, 18 | val author: Author 19 | ) 20 | 21 | @Controller 22 | @ResponseBody 23 | @RequestMapping("/books") 24 | class BooksController { 25 | 26 | @GetMapping fun books() = emptyList() 27 | } 28 | -------------------------------------------------------------------------------- /backend/boot/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8080 3 | 4 | logging: 5 | level: 6 | root: INFO 7 | org.springframework.web: INFO 8 | org.springframework.security: INFO 9 | 10 | spring: 11 | application: 12 | name: boot 13 | 14 | threads: 15 | virtual: 16 | enabled: true 17 | 18 | sql: 19 | init: 20 | mode: always 21 | 22 | datasource: 23 | url: jdbc:postgresql://localhost:5432/mydb 24 | username: user 25 | password: secret 26 | 27 | 28 | -------------------------------------------------------------------------------- /backend/boot/src/main/resources/schema.sql: -------------------------------------------------------------------------------- 1 | create table if not exists author 2 | ( 3 | id bigserial primary key, 4 | name varchar not null 5 | ); 6 | 7 | create table if not exists book 8 | ( 9 | id serial primary key, 10 | title varchar not null, 11 | description varchar(256) not null default '', 12 | created timestamp with time zone default current_timestamp, 13 | author bigint not null references author (id) 14 | ); -------------------------------------------------------------------------------- /backend/boot/src/test/kotlin/dev/suresh/AppTest.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh 2 | 3 | import kotlin.test.Test 4 | import org.springframework.boot.test.context.SpringBootTest 5 | 6 | @SpringBootTest 7 | class AppTest { 8 | 9 | @Test fun contextLoads() {} 10 | } 11 | -------------------------------------------------------------------------------- /backend/data/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import common.jvmTarget 2 | 3 | plugins { 4 | dev.suresh.plugin.kotlin.mpp 5 | dev.suresh.plugin.publishing 6 | } 7 | 8 | description = "Kotlin Data Science!" 9 | 10 | kotlin { 11 | jvmTarget(project) 12 | 13 | sourceSets { 14 | commonMain { dependencies { implementation(projects.shared) } } 15 | 16 | jvmMain { 17 | dependencies { 18 | implementation(libs.pty4j) 19 | // implementation(libs.graal.polyglot) 20 | // implementation(libs.graal.wasm) 21 | // implementation(fileTree("lib") { include("*.jar") }) 22 | } 23 | 24 | kotlin.srcDir("src/main/kotlin") 25 | resources.srcDir("src/main/resources") 26 | } 27 | 28 | jvmTest { 29 | kotlin.srcDir("src/test/kotlin") 30 | resources.srcDir("src/test/resources") 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /backend/data/src/main/kotlin/dev/suresh/DataApp.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh 2 | 3 | import com.pty4j.PtyProcessBuilder 4 | import java.util.concurrent.CountDownLatch 5 | 6 | fun main() { 7 | println("Hello Kotlin Data! ${Greeting().greeting()}") 8 | ssh() 9 | } 10 | 11 | fun ssh() { 12 | val ptyProcess = 13 | PtyProcessBuilder().run { 14 | setCommand(arrayOf("/bin/sh", "-l")) 15 | setEnvironment(mapOf("TERM" to "xterm-256color")) 16 | setDirectory(System.getProperty("user.home")) 17 | start() 18 | } 19 | 20 | println("PTY child process started ${ptyProcess.pid()}, size: ${ptyProcess.winSize}") 21 | println(ptyProcess.isConsoleMode) 22 | println(ptyProcess.isAlive) 23 | val tos = ptyProcess.outputStream 24 | val tis = ptyProcess.inputReader() 25 | 26 | val size = 10 27 | val latch = CountDownLatch(size) 28 | 29 | Thread.ofVirtual().start { 30 | runCatching { 31 | for (i in 1..size) { 32 | tos.write("echo $i".encodeToByteArray()) 33 | tos.write(byteArrayOf(ptyProcess.enterKeyCode)) 34 | tos.flush() 35 | Thread.sleep(100) 36 | latch.countDown() 37 | } 38 | } 39 | } 40 | 41 | Thread.ofVirtual().start { runCatching { tis.forEachLine { println(it) } } } 42 | 43 | latch.await() 44 | tos.close() 45 | tis.close() 46 | 47 | // wait until the PTY child process is terminated 48 | val result = ptyProcess.waitFor() 49 | println("PTY child process terminated with exit code $result") 50 | ptyProcess.destroyForcibly() 51 | } 52 | 53 | // fun wasm() { 54 | // val source = 55 | // Source.newBuilder( 56 | // "wasm", 57 | // """ 58 | // (module 59 | // (func (export "add") (param i32 i32) (result i32) 60 | // local.get 0 61 | // local.get 1 62 | // i32.add) 63 | // ) 64 | // """ 65 | // .trimIndent(), 66 | // "test") 67 | // .build() 68 | // Context.newBuilder("wasm").build().use { ctx -> 69 | // val wasm = ctx.eval(source) 70 | // val add = wasm.getMember("add") 71 | // val result = add.execute(10, 20) 72 | // println(result) 73 | // } 74 | // } 75 | -------------------------------------------------------------------------------- /backend/data/src/main/kotlin/dev/suresh/gen/GenArt.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh.gen 2 | 3 | import java.awt.Color 4 | import java.awt.geom.Ellipse2D 5 | import java.awt.image.BufferedImage 6 | import java.awt.image.BufferedImage.TYPE_INT_ARGB 7 | import javax.imageio.ImageIO 8 | import kotlin.math.PI 9 | 10 | object GenArt { 11 | 12 | const val S = 1024 13 | 14 | fun flower() { 15 | BufferedImage(S, S, TYPE_INT_ARGB).apply { 16 | createGraphics().apply { 17 | color = Color(0, 0, 0, 25) 18 | for (angle in 0..<360 step 15) { 19 | val old = transform 20 | val centerX = S / 2.0 21 | val centerY = S / 2.0 22 | val eWidth = S / 8.0 23 | val eHeight = S * 7.0 / 16 24 | translate(centerX, centerY) 25 | rotate(angle.toRadians()) 26 | fill(Ellipse2D.Double(eHeight - centerX, eWidth - centerY, eWidth, eHeight)) 27 | transform = old 28 | } 29 | dispose() 30 | } 31 | ImageIO.write(this, "png", java.io.File("flower.png")) 32 | } 33 | } 34 | } 35 | 36 | fun Double.toRadians() = this / 180 * PI 37 | 38 | fun Int.toRadians() = toDouble().toRadians() 39 | 40 | fun Double.toDegrees() = this * 180 / PI 41 | -------------------------------------------------------------------------------- /backend/jvm/src/main/java/module-info.java.txt: -------------------------------------------------------------------------------- 1 | module dev.suresh.kmp { 2 | requires kotlin.stdlib; 3 | requires kotlinx.serialization.core; 4 | } 5 | -------------------------------------------------------------------------------- /backend/jvm/src/main/kotlin/dev/suresh/App.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh 2 | 3 | import BuildConfig 4 | import dev.suresh.config.AppConfig 5 | import dev.suresh.plugins.* 6 | import dev.suresh.routes.* 7 | import io.ktor.server.application.* 8 | import io.ktor.server.netty.* 9 | import io.ktor.server.routing.* 10 | import io.ktor.util.logging.* 11 | import kotlin.io.path.Path 12 | import kotlin.io.path.exists 13 | 14 | fun main(args: Array) = 15 | try { 16 | initProps() 17 | EngineMain.main(args) 18 | } catch (e: Throwable) { 19 | val log = KtorSimpleLogger("main") 20 | log.error("Failed to start ${BuildConfig.description}: ${e.message}", e) 21 | } 22 | 23 | fun Application.module() { 24 | log.info("Starting ${BuildConfig.description} v${BuildConfig.version}...") 25 | AppConfig.init(environment.config) 26 | configureOTel() 27 | configureInterceptors() 28 | configureHTTP() 29 | configureSecurity() 30 | errorRoutes() 31 | scheduledTasks() 32 | 33 | routing { 34 | adminRoutes() 35 | webApp() 36 | services() 37 | mgmtRoutes() 38 | } 39 | // CoroutineScope(coroutineContext).launch {} 40 | } 41 | 42 | /** 43 | * Initializes the system properties required for the application to run. This should be invoked 44 | * before the Engine main() method is called. 45 | */ 46 | fun initProps() { 47 | val logDir = 48 | System.getProperty("LOG_DIR", System.getenv("LOG_DIR")).orEmpty().ifBlank { 49 | when { 50 | Path("/log").exists() -> "/log" 51 | else -> System.getProperty("user.dir") 52 | } 53 | } 54 | 55 | System.setProperty("jdk.tls.maxCertificateChainLength", "15") 56 | System.setProperty("jdk.includeInExceptions", "hostInfo") 57 | System.setProperty("slf4j.internal.verbosity", "WARN") 58 | System.setProperty("LOG_DIR", logDir) 59 | 60 | println("⚡ ${BuildConfig.description} v${BuildConfig.version} ⚡") 61 | println("Log Dir: $logDir") 62 | 63 | // Redirect JUL to SLF4J 64 | // SLF4JBridgeHandler.removeHandlersForRootLogger() 65 | // SLF4JBridgeHandler.install() 66 | } 67 | -------------------------------------------------------------------------------- /backend/jvm/src/main/kotlin/dev/suresh/config/AppConfig.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh.config 2 | 3 | import com.typesafe.config.ConfigFactory 4 | import io.github.oshai.kotlinlogging.KotlinLogging 5 | import io.ktor.server.config.ApplicationConfig 6 | import kotlin.reflect.full.withNullability 7 | import kotlin.reflect.typeOf 8 | import kotlin.time.Duration 9 | import kotlinx.serialization.hocon.* 10 | 11 | /** 12 | * Initializes the config data classes from the application config. Since HOCON is used for 13 | * application configuration, `kotlinx.serialization.hocon` is used to deserialize the config. The 14 | * [AppConfig.init] method should be called before accessing the config values. 15 | */ 16 | data object AppConfig { 17 | 18 | val log = KotlinLogging.logger {} 19 | 20 | private lateinit var appConfig: ApplicationConfig 21 | 22 | private val hocon = Hocon { 23 | serializersModule = Hocon.serializersModule 24 | encodeDefaults = true 25 | } 26 | 27 | /** Initializes application config */ 28 | fun init(config: ApplicationConfig) { 29 | log.info { "Initializing App configurations..." } 30 | appConfig = config 31 | } 32 | 33 | /** Application authn/authz configuration. */ 34 | val auth by lazy { 35 | val config = ConfigFactory.parseMap(appConfig.config("app.auth").toMap()) 36 | hocon.decodeFromConfig>(config) 37 | } 38 | } 39 | 40 | /** 41 | * Extension function to get and convert config values to their respective type. Nullability is 42 | * disabled to support java types 43 | */ 44 | @Suppress("IMPLICIT_CAST_TO_ANY") 45 | inline fun ApplicationConfig.prop(prop: String) = 46 | when (typeOf().withNullability(false)) { 47 | typeOf() -> property(prop).getString() 48 | typeOf>() -> property(prop).getList() 49 | typeOf() -> property(prop).getString().toBoolean() 50 | typeOf() -> property(prop).getString().toInt() 51 | typeOf() -> property(prop).getString().toLong() 52 | typeOf() -> property(prop).getString().toDouble() 53 | typeOf() -> Duration.parse(property(prop).getString().lowercase()) 54 | else -> throw IllegalArgumentException("Unsupported type: ${typeOf()}") 55 | } 56 | as T 57 | -------------------------------------------------------------------------------- /backend/jvm/src/main/kotlin/dev/suresh/lang/TM.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh.lang 2 | 3 | import java.lang.foreign.MemoryLayout 4 | import java.lang.foreign.MemorySegment 5 | import java.lang.foreign.ValueLayout 6 | 7 | /** 8 | * Time representing the number of seconds since the Unix Epoch (January 1, 1970, 00:00:00 UTC) into 9 | * a broken-down UTC representation 10 | */ 11 | class TM(private val segment: MemorySegment) { 12 | companion object { 13 | val LAYOUT = 14 | MemoryLayout.structLayout( 15 | ValueLayout.JAVA_INT.withName("sec"), 16 | ValueLayout.JAVA_INT.withName("min"), 17 | ValueLayout.JAVA_INT.withName("hour"), 18 | ValueLayout.JAVA_INT.withName("mday"), 19 | ValueLayout.JAVA_INT.withName("mon"), 20 | ValueLayout.JAVA_INT.withName("year"), 21 | ValueLayout.JAVA_INT.withName("wday"), 22 | ValueLayout.JAVA_INT.withName("yday"), 23 | ValueLayout.JAVA_BOOLEAN.withName("isdst"), 24 | MemoryLayout.paddingLayout(24)) 25 | 26 | private val yearVH = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("year")) 27 | private val monthVH = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("mon")) 28 | private val dayVH = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("mday")) 29 | private val hourVH = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("hour")) 30 | private val minVH = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("min")) 31 | private val secVH = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("sec")) 32 | } 33 | 34 | val year: Int 35 | get() = yearVH.get(segment, 0L) as Int + 1900 36 | 37 | val month: Int 38 | get() = monthVH.get(segment, 0L) as Int + 1 39 | 40 | val day: Int 41 | get() = dayVH.get(segment, 0L) as Int 42 | 43 | val hour: Int 44 | get() = hourVH.get(segment, 0L) as Int 45 | 46 | val min: Int 47 | get() = minVH.get(segment, 0L) as Int 48 | 49 | val sec: Int 50 | get() = secVH.get(segment, 0L) as Int 51 | 52 | override fun toString(): String { 53 | return "TM(year=$year, month=$month, day=$day, hour=$hour, min=$min, sec=$sec)" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /backend/jvm/src/main/kotlin/dev/suresh/log/RespLogger.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh.log 2 | 3 | import dev.suresh.log 4 | import io.github.oshai.kotlinlogging.KLogger 5 | import java.io.Writer 6 | 7 | class RespLogger(private val out: Writer, private val logger: KLogger = log) : KLogger by logger { 8 | override fun info(message: () -> Any?) { 9 | super.info(message) 10 | out.appendLine(message().toString()) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /backend/jvm/src/main/kotlin/dev/suresh/plugins/Error.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh.plugins 2 | 3 | import dev.suresh.http.* 4 | import io.ktor.http.HttpStatusCode 5 | import io.ktor.server.application.* 6 | import io.ktor.server.plugins.* 7 | import io.ktor.server.plugins.statuspages.* 8 | import io.ktor.server.request.* 9 | import io.ktor.server.response.* 10 | 11 | fun Application.errorRoutes() { 12 | 13 | install(StatusPages) { 14 | status(HttpStatusCode.Unauthorized) { call, status -> 15 | when { 16 | call.isApi -> 17 | call.respondError( 18 | HttpStatusCode.Unauthorized, "Authorization is required to access this resource") 19 | } 20 | } 21 | 22 | status(HttpStatusCode.NotFound) { call, _ -> 23 | call.respondRedirect("/app", permanent = true) // 301 24 | } 25 | 26 | exception { call, cause -> 27 | val status = 28 | when (cause) { 29 | is BadRequestException -> HttpStatusCode.BadRequest 30 | else -> HttpStatusCode.InternalServerError 31 | } 32 | 33 | call.application.log.error(status.description, cause) 34 | call.respondError(status, cause.message ?: "Unknown error", cause) 35 | } 36 | 37 | unhandled { 38 | it.respondError( 39 | HttpStatusCode.NotFound, "The requested URL ${it.request.path()} was not found") 40 | } 41 | } 42 | } 43 | 44 | fun userError(message: Any): Nothing = throw BadRequestException(message.toString()) 45 | 46 | suspend fun ApplicationCall.respondError( 47 | status: HttpStatusCode, 48 | message: String, 49 | cause: Throwable? = null 50 | ) = 51 | respond( 52 | status = status, 53 | message = 54 | ErrorStatus( 55 | code = status.value, 56 | message = message, 57 | details = if (debug) cause?.stackTraceToString() else cause?.message)) 58 | -------------------------------------------------------------------------------- /backend/jvm/src/main/kotlin/dev/suresh/plugins/Security.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh.plugins 2 | 3 | import io.ktor.server.application.* 4 | import io.ktor.server.auth.* 5 | 6 | fun Application.configureSecurity() { 7 | authentication { 8 | bearer("auth-bearer") { 9 | realm = "Ktor App" 10 | authenticate { tokenCredential -> 11 | when (tokenCredential.token) { 12 | "token" -> UserIdPrincipal("admin") 13 | else -> null 14 | } 15 | } 16 | } 17 | 18 | basic("admin") { 19 | realm = "App Admin" 20 | validate { credentials -> 21 | when (credentials.name == "admin" && credentials.password == "admin") { 22 | true -> UserIdPrincipal(credentials.name) 23 | else -> null 24 | } 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /backend/jvm/src/main/kotlin/dev/suresh/plugins/custom/CookieSession.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh.plugins.custom 2 | 3 | import io.ktor.server.sessions.SessionSerializer 4 | 5 | /** Custom Cookie Session serializer sample. */ 6 | data class CookieSession(val text: String) 7 | 8 | object CookieSessionSerializer : SessionSerializer { 9 | override fun deserialize(text: String): CookieSession = CookieSession(text = text) 10 | 11 | override fun serialize(session: CookieSession): String = session.text 12 | } 13 | -------------------------------------------------------------------------------- /backend/jvm/src/main/kotlin/dev/suresh/plugins/custom/Plugins.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh.plugins.custom 2 | 3 | import dev.suresh.plugins.isApi 4 | import io.ktor.server.application.* 5 | import io.ktor.util.* 6 | import io.opentelemetry.api.trace.Span 7 | 8 | /** 9 | * A custom ktor plugin to automatically add OpenTelemetry trace id to response headers for API 10 | * endpoints. 11 | */ 12 | val OTelExtnPlugin = 13 | createApplicationPlugin(name = "OTelExtnPlugin", createConfiguration = ::OTelExtnPluginConfig) { 14 | val onCallTimeKey = AttributeKey("onCallTimeKey") 15 | onCall { call -> 16 | val onCallTime = System.currentTimeMillis() 17 | call.attributes.put(onCallTimeKey, onCallTime) 18 | 19 | if (pluginConfig.enabled && call.isApi) { 20 | Span.current()?.let { span -> 21 | call.response.headers.append(pluginConfig.traceIdHeader, span.spanContext.traceId) 22 | // span.setAttribute("custom-attribute", "TestPlugin") 23 | // span.addEvent("TestPluginEvent") 24 | } 25 | } 26 | } 27 | 28 | // on(MonitoringEvent(ApplicationStarted)) { 29 | // it.log.info("Application started - ${it.isActive}") 30 | // } 31 | // 32 | // on(CallFailed) { call, error -> 33 | // if (pluginConfig.enabled) { 34 | // call.application.log.error("Failed call: $call", error) 35 | // } 36 | // } 37 | // 38 | // onCallReceive { call, body -> 39 | // val onCallTime = call.attributes[onCallTimeKey] 40 | // if (pluginConfig.enabled) { 41 | // call.application.log.info("Received: $body") 42 | // } 43 | // } 44 | // 45 | // onCallRespond { call, res -> 46 | // if (pluginConfig.enabled) { 47 | // call.application.log.info("Responded: $res") 48 | // } 49 | // } 50 | } 51 | 52 | class OTelExtnPluginConfig { 53 | var enabled: Boolean = false 54 | var traceIdHeader: String = "X-TraceId" 55 | } 56 | -------------------------------------------------------------------------------- /backend/jvm/src/main/kotlin/dev/suresh/routes/Scheduler.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh.routes 2 | 3 | import dev.suresh.virtualThreadScope 4 | import io.github.kevincianfarini.cardiologist.* 5 | import io.ktor.server.application.* 6 | import io.ktor.util.logging.* 7 | import io.opentelemetry.instrumentation.annotations.* 8 | import kotlin.time.Duration.Companion.seconds 9 | import kotlinx.coroutines.* 10 | import kotlinx.datetime.* 11 | 12 | fun Application.scheduledTasks() { 13 | log.info("Starting scheduled tasks...") 14 | virtualThreadScope.launch { 15 | Clock.System.fixedPeriodPulse(10.seconds).beat(PulseBackpressureStrategy.SkipNext) { scheduled 16 | -> 17 | context(log) { task("Task at ${scheduled.toLocalDateTime(TimeZone.currentSystemDefault())}") } 18 | } 19 | } 20 | } 21 | 22 | @WithSpan("scheduled-task") 23 | context(log: Logger) 24 | fun task(name: String) { 25 | try { 26 | log.warn("Running $name") 27 | // log.info("Scheduled task: ${MediaApiClient().images().size}") 28 | } catch (e: Exception) { 29 | log.error("Failed to run scheduled task: ${e.message}", e) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /backend/jvm/src/main/kotlin/dev/suresh/routes/Webapp.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh.routes 2 | 3 | import io.ktor.server.http.content.* 4 | import io.ktor.server.request.* 5 | import io.ktor.server.response.* 6 | import io.ktor.server.routing.* 7 | 8 | fun Routing.webApp() { 9 | webApp("/app", "app") 10 | } 11 | 12 | fun Routing.webApp(remotePath: String, basePackage: String) { 13 | staticResources(remotePath, basePackage) { 14 | exclude { it.path.endsWith(".log") } 15 | default("index.html") 16 | modify { url, call -> 17 | when (call.request.path()) { 18 | remotePath -> call.respondRedirect("$remotePath/") 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /backend/jvm/src/main/kotlin/dev/suresh/wasm/Wasm.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh.wasm 2 | 3 | import com.dylibso.chicory.compiler.MachineFactoryCompiler 4 | import com.dylibso.chicory.runtime.HostFunction 5 | import com.dylibso.chicory.runtime.Instance 6 | import com.dylibso.chicory.wasm.Parser 7 | import com.dylibso.chicory.wasm.types.FunctionType 8 | import com.dylibso.chicory.wasm.types.ValType 9 | import io.ktor.server.response.* 10 | import io.ktor.server.routing.* 11 | 12 | /** 13 | * More wasm modules can be found in 14 | * - [wasm-corpus](https://github.com/dylibso/chicory/tree/main/wasm-corpus/src/main/resources/compiled) 15 | * - [extism](https://github.com/extism/plugins/releases) 16 | */ 17 | val factWasmInst: Instance by lazy { 18 | val wasmRes = Thread.currentThread().contextClassLoader.getResourceAsStream("wasm/factorial.wasm") 19 | // val wasmRes = object {}.javaClass.getResourceAsStream("wasm/factorial.wasm") 20 | val wasmMod = Parser.parse(wasmRes) 21 | Instance.builder(wasmMod).withMachineFactory(MachineFactoryCompiler::compile).build() 22 | } 23 | 24 | fun Routing.wasm() { 25 | route("/wasm") { 26 | get("fact") { 27 | val num = call.parameters["num"]?.toLongOrNull() ?: 5 28 | 29 | val iterFact = factWasmInst.export("iterFact") 30 | val fact = iterFact.apply(num)[0] 31 | 32 | call.respondText("WASM: Factorial($num): $fact") 33 | } 34 | } 35 | } 36 | 37 | fun logFunction() = 38 | HostFunction( 39 | "console", "log", FunctionType.of(listOf(ValType.I32, ValType.I32), emptyList())) { 40 | instance, 41 | args -> 42 | val msg = instance.memory().readString(args[0].toInt(), args[1].toInt()) 43 | println("WASM: $msg") 44 | // Value.i32(0) 45 | longArrayOf() 46 | } 47 | -------------------------------------------------------------------------------- /backend/jvm/src/main/resources/META-INF/native-image/native-image.properties: -------------------------------------------------------------------------------- 1 | # Args=--initialize-at-build-time=com.github.ajalt.mordant.internal.nativeimage.NativeImagePosixMppImpls -------------------------------------------------------------------------------- /backend/jvm/src/main/resources/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Kotlin Multiplatform web application 12 | 13 | 14 |
15 |
Kotlin Multiplatform web application
16 | JS App 17 | Wasm App 18 | ComposeWeb App 19 |
20 | 38 | 39 | -------------------------------------------------------------------------------- /backend/jvm/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | ktor { 2 | deployment { 3 | host = 0.0.0.0 4 | port = 8080 5 | port = ${?PORT} 6 | // The url limit including query parameters 7 | maxInitialLineLength = 2048 8 | maxHeaderSize = 4096 9 | shutdownGracePeriod = 200 10 | shutdownTimeout = 200 11 | shutdown.url = "/shutdown" 12 | // tcpKeepAlive = true 13 | } 14 | 15 | development = false 16 | 17 | application { 18 | modules = [dev.suresh.AppKt.module] 19 | } 20 | } -------------------------------------------------------------------------------- /backend/jvm/src/main/resources/backend-jvm-res.txt: -------------------------------------------------------------------------------- 1 | Resource from "Backend jvmMain" module ${version}! -------------------------------------------------------------------------------- /backend/jvm/src/main/resources/openapi/documentation.yaml: -------------------------------------------------------------------------------- 1 | openapi: "3.1.0" 2 | info: 3 | title: "{project.name} api" 4 | description: "{project.name} api" 5 | version: "{project.version}" 6 | servers: 7 | - url: "https://kotlin_mpp_playground" 8 | paths: 9 | /: 10 | get: 11 | description: "" 12 | responses: 13 | "302": 14 | description: "Found
Redirect" 15 | content: 16 | text/plain: 17 | schema: 18 | type: "string" 19 | examples: 20 | Example#1: 21 | value: "/swagger" 22 | /browse/{param}: 23 | get: 24 | description: "" 25 | parameters: 26 | - name: "param" 27 | in: "path" 28 | required: true 29 | schema: 30 | type: "array" 31 | items: 32 | type: "string" 33 | responses: 34 | "200": 35 | description: "OK
A file response" 36 | content: 37 | application/*: 38 | schema: 39 | type: "object" 40 | format: "binary" 41 | "404": 42 | description: "Not Found" 43 | content: 44 | text/html: 45 | schema: 46 | type: "string" 47 | /ffm: 48 | get: 49 | description: "" 50 | /heapdump: 51 | get: 52 | description: "" 53 | responses: 54 | "200": 55 | description: "OK
A file response" 56 | content: 57 | application/*: 58 | schema: 59 | type: "object" 60 | format: "binary" 61 | /info: 62 | get: 63 | description: "" 64 | responses: 65 | "200": 66 | description: "OK" 67 | content: 68 | '*/*': 69 | schema: 70 | type: "string" 71 | /jfr: 72 | get: 73 | description: "" 74 | /profile: 75 | get: 76 | description: "" 77 | responses: 78 | "200": 79 | description: "OK
A file response" 80 | content: 81 | application/*: 82 | schema: 83 | type: "object" 84 | format: "binary" 85 | /vthreads: 86 | get: 87 | description: "" -------------------------------------------------------------------------------- /backend/jvm/src/main/resources/wasm/factorial.wasm: -------------------------------------------------------------------------------- 1 | asm` iterFact 2 | *(A!@ E @  l! Aj"E   -------------------------------------------------------------------------------- /backend/jvm/src/main/resources/wasm/hello-wasi.wasm: -------------------------------------------------------------------------------- 1 | asm ``#wasi_snapshot_preview1fd_writememory_start 2 | AA6AA 6AAAA A hello world 3 | -------------------------------------------------------------------------------- /backend/jvm/src/test/kotlin/dev/suresh/DnsTest.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh 2 | 3 | import com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig 4 | import com.github.tomakehurst.wiremock.junit5.WireMockExtension 5 | import com.marcinziolo.kotlin.wiremock.get 6 | import com.marcinziolo.kotlin.wiremock.like 7 | import com.marcinziolo.kotlin.wiremock.returns 8 | import dev.suresh.http.testHttpClient 9 | import io.ktor.client.plugins.defaultRequest 10 | import io.ktor.client.request.get 11 | import io.ktor.client.statement.bodyAsText 12 | import java.net.InetAddress 13 | import kotlin.test.Test 14 | import kotlin.test.assertEquals 15 | import kotlinx.coroutines.test.runTest 16 | import org.junit.jupiter.api.condition.EnabledIfSystemProperty 17 | import org.junit.jupiter.api.extension.RegisterExtension 18 | 19 | @EnabledIfSystemProperty(named = "jdk.net.hosts.file", matches = "true") 20 | class DnsTest { 21 | 22 | companion object { 23 | 24 | @JvmField 25 | @RegisterExtension 26 | val wireMock = 27 | WireMockExtension.newInstance() 28 | .options( 29 | wireMockConfig().httpDisabled(true).httpsPort(8888) 30 | // .keystorePath("src/test/resources/keystore.jks") 31 | // .keystorePassword("changeit") 32 | // .keyManagerPassword("changeit") 33 | // .keystoreType("PKCS12") 34 | // .trustStorePath("src/test/resources/truststore.jks") 35 | // .trustStorePassword("changeit") 36 | // .trustStoreType("PKCS12") 37 | // .needClientAuth(true) 38 | ) 39 | .build() 40 | } 41 | 42 | @Test 43 | fun hostFileTest() = runTest { 44 | val addr = InetAddress.getByName("test.dev") 45 | assertEquals("127.0.0.1", addr.hostAddress, "Address should be 127.0.0.1") 46 | } 47 | 48 | @Test 49 | fun wireMockTest() = runTest { 50 | wireMock.get { url like "/users/.*" } returns 51 | { 52 | header = "Content-Type" to "application/json" 53 | statusCode = 200 54 | body = 55 | """ 56 | { 57 | "id": 1, 58 | "name": "Suresh" 59 | } 60 | """ 61 | } 62 | 63 | val client = testHttpClient.config { defaultRequest { url(wireMock.baseUrl()) } } 64 | println(client.get("/users/1").bodyAsText()) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /backend/jvm/src/test/kotlin/dev/suresh/K8STests.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh 2 | 3 | import io.kubernetes.client.openapi.apis.CoreV1Api 4 | import io.kubernetes.client.util.Config 5 | import kotlin.test.Test 6 | import kotlin.test.assertTrue 7 | import org.junit.jupiter.api.condition.EnabledIfSystemProperty 8 | import org.slf4j.LoggerFactory 9 | import org.testcontainers.containers.output.Slf4jLogConsumer 10 | import org.testcontainers.junit.jupiter.Container 11 | import org.testcontainers.junit.jupiter.Testcontainers 12 | import org.testcontainers.k3s.K3sContainer 13 | import org.testcontainers.utility.DockerImageName 14 | 15 | @Testcontainers 16 | @EnabledIfSystemProperty(named = "k8sTest", matches = "true") 17 | class K8STests { 18 | 19 | companion object { 20 | 21 | val logger = LoggerFactory.getLogger("k8s") 22 | 23 | @Container 24 | val k3s = 25 | K3sContainer(DockerImageName.parse("rancher/k3s:latest")) 26 | .withLogConsumer(Slf4jLogConsumer(logger, false)) 27 | .withReuse(true) 28 | // .withCommand("server", "--disable=traefik", 29 | // "--tls-san=${DockerClientFactory.instance().dockerHostIpAddress()}") 30 | } 31 | 32 | @Test 33 | fun testK8S() { 34 | assertTrue(k3s.isRunning) 35 | } 36 | 37 | @Test 38 | fun testK8SClient() { 39 | val client = Config.fromConfig(k3s.kubeConfigYaml.reader()) 40 | val api = CoreV1Api(client) 41 | api.listNode().execute().items.forEach { println("K8S NodeName: ${it.metadata?.name}") } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /backend/jvm/src/test/kotlin/dev/suresh/TestLogger.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh 2 | 3 | import org.slf4j.Logger 4 | import org.slf4j.LoggerFactory 5 | import org.slf4j.MDC 6 | 7 | object TestLogger : Logger by LoggerFactory.getLogger("ktor.test") { 8 | 9 | private val _messages = mutableListOf() 10 | 11 | val messages: List 12 | get() = _messages 13 | 14 | override fun trace(message: String?) = add("TRACE: $message") 15 | 16 | override fun debug(message: String?) = add("DEBUG: $message") 17 | 18 | override fun debug(message: String?, cause: Throwable) = add("DEBUG: $message") 19 | 20 | override fun info(message: String?) = add("INFO: $message") 21 | 22 | private fun add(message: String?) { 23 | if (message != null) { 24 | val mdcText = 25 | MDC.getCopyOfContextMap()?.let { mdc -> 26 | if (mdc.isNotEmpty()) { 27 | mdc.entries 28 | .sortedBy { it.key } 29 | .joinToString(prefix = " [", postfix = "]") { "${it.key}=${it.value}" } 30 | } else { 31 | "" 32 | } 33 | } ?: "" 34 | _messages.add("$message$mdcText") 35 | } 36 | } 37 | 38 | fun clear() = _messages.clear() 39 | } 40 | -------------------------------------------------------------------------------- /backend/jvm/src/test/kotlin/dev/suresh/http/TestHttpClient.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh.http 2 | 3 | import io.ktor.client.HttpClient 4 | import io.ktor.client.engine.java.Java 5 | import nl.altindag.ssl.SSLFactory 6 | 7 | val testSSLFactory by lazy { SSLFactory.builder().withUnsafeTrustMaterial().build() } 8 | 9 | val testHttpClient by lazy { 10 | HttpClient(Java) { engine { config { sslContext(testSSLFactory.sslContext) } } } 11 | } 12 | -------------------------------------------------------------------------------- /backend/jvm/src/test/resources/hosts_disabled: -------------------------------------------------------------------------------- 1 | 127.0.0.1 test.dev www.test.dev -------------------------------------------------------------------------------- /backend/native/src/appleMain/kotlin/Env.kt: -------------------------------------------------------------------------------- 1 | import kotlinx.cinterop.toKString 2 | import platform.posix.getpass 3 | 4 | actual fun readPassword(prompt: String) = getpass(prompt)?.toKString() 5 | -------------------------------------------------------------------------------- /backend/native/src/linuxMain/kotlin/Env.kt: -------------------------------------------------------------------------------- 1 | import kotlin.time.Instant 2 | import kotlinx.cinterop.* 3 | import kotlinx.io.files.Path 4 | import platform.posix.* 5 | 6 | actual fun readPassword(prompt: String) = getpass(prompt)?.toKString() 7 | 8 | fun getMTime(path: Path): Instant { 9 | memScoped { 10 | val stat = alloc() 11 | if (lstat(path.toString(), stat.ptr) != 0) { 12 | throw IllegalStateException("Failed to get mtime for $path") 13 | } 14 | return Instant.fromEpochSeconds(stat.st_mtim.tv_sec, stat.st_mtim.tv_nsec) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /backend/native/src/mingwMain/kotlin/Env.kt: -------------------------------------------------------------------------------- 1 | import kotlinx.cinterop.* 2 | import platform.windows.* 3 | 4 | actual fun readPassword(prompt: String): String? = memScoped { 5 | print(prompt) 6 | 7 | val handle = GetStdHandle(STD_INPUT_HANDLE) 8 | if (handle == INVALID_HANDLE_VALUE) { 9 | error("Standard input not available!") 10 | } 11 | 12 | val record = alloc() 13 | val read = alloc() 14 | 15 | buildString { 16 | while (true) { 17 | val numberOfRecordsToRead = 1.convert() 18 | if (ReadConsoleInput?.let { it(handle, record.ptr, numberOfRecordsToRead, read.ptr) } == 0) { 19 | error("Could not read console input") 20 | } 21 | 22 | if (record.EventType == KEY_EVENT.toUShort() && record.Event.KeyEvent.bKeyDown != 0) { 23 | val char = record.Event.KeyEvent.uChar.UnicodeChar 24 | if (char == '\r'.code.toUShort()) { 25 | break 26 | } else if (char != 0.toUShort()) { 27 | append(char) 28 | } 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /backend/native/src/nativeMain/kotlin/io/IO.kt: -------------------------------------------------------------------------------- 1 | import kotlin.test.* 2 | import kotlinx.io.* 3 | import kotlinx.io.files.* 4 | 5 | fun buffer() { 6 | val buffer = Buffer() 7 | buffer.write(byteArrayOf(70, 64, -26, -74)) 8 | assertEquals(12345.678F.toBits(), buffer.readFloat().toBits()) 9 | } 10 | 11 | fun dir() { 12 | println("SystemPathSeparator = $SystemPathSeparator") 13 | println("SystemTemporaryDirectory: $SystemTemporaryDirectory") 14 | SystemFileSystem.list(Path(".")).forEach { println(it) } 15 | } 16 | 17 | fun Path.append(data: String) { 18 | SystemFileSystem.sink(this, append = true).buffered().use { f -> f.writeString(data) } 19 | } 20 | -------------------------------------------------------------------------------- /backend/native/src/nativeMain/kotlin/wasm/Wasm.kt: -------------------------------------------------------------------------------- 1 | package wasm 2 | 3 | import io.github.charlietap.chasm.embedding.instance 4 | import io.github.charlietap.chasm.embedding.invoke 5 | import io.github.charlietap.chasm.embedding.module 6 | import io.github.charlietap.chasm.embedding.shapes.getOrNull 7 | import io.github.charlietap.chasm.embedding.shapes.map 8 | import io.github.charlietap.chasm.embedding.store 9 | import io.github.charlietap.chasm.runtime.value.NumberValue 10 | import kotlinx.io.buffered 11 | import kotlinx.io.files.Path 12 | import kotlinx.io.files.SystemFileSystem 13 | import kotlinx.io.readByteArray 14 | 15 | fun execWasm(path: Path, arg: Int = 5) { 16 | try { 17 | val exists = SystemFileSystem.metadataOrNull(path)?.isRegularFile == true 18 | if (exists) { 19 | println("Executing wasm: $path") 20 | val module = module(bytes = SystemFileSystem.source(path).buffered().readByteArray()) 21 | val store = store() 22 | val instance = 23 | instance(store = store, module = module.getOrNull()!!, imports = emptyList()) 24 | .getOrNull()!! 25 | 26 | val result = 27 | invoke(store, instance, "iterFact", listOf(NumberValue.I32(arg))).map { 28 | (it.first() as NumberValue.I32).value 29 | } 30 | println("Result: $result") 31 | } else { 32 | println("Wasm file not found: $path!") 33 | } 34 | } catch (e: Exception) { 35 | println("Failed to execute wasm: ${e.message}") 36 | e.printStackTrace() 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /backend/native/src/nativeTest/kotlin/NativeTest.kt: -------------------------------------------------------------------------------- 1 | import dev.suresh.platform 2 | import kotlin.test.Test 3 | import kotlin.test.assertTrue 4 | 5 | class NativeTest { 6 | @Test 7 | fun test() { 8 | assertTrue(platform.name == "Native") 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /backend/profiling/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import common.jvmTarget 2 | 3 | plugins { 4 | dev.suresh.plugin.kotlin.mpp 5 | dev.suresh.plugin.publishing 6 | } 7 | 8 | description = "JVM Profiling and Monitoring!" 9 | 10 | kotlin { 11 | jvmTarget(project) 12 | 13 | sourceSets { 14 | commonMain { dependencies { implementation(projects.shared) } } 15 | 16 | jvmMain { 17 | dependencies { 18 | implementation(libs.jmc.common) 19 | implementation(libs.jmc.jfr) 20 | implementation(libs.ap.jfr.converter) 21 | implementation(libs.bytesize) 22 | // implementation(libs.ap.loader.all) 23 | } 24 | kotlin.srcDir("src/main/kotlin") 25 | resources.srcDir("src/main/resources") 26 | } 27 | 28 | jvmTest { 29 | kotlin.srcDir("src/test/kotlin") 30 | resources.srcDir("src/test/resources") 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /backend/security/api/security.klib.api: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sureshg/kotlin-mpp-playground/275d22919a7e9da80a3897f551293a841ac1d3c0/backend/security/api/security.klib.api -------------------------------------------------------------------------------- /backend/security/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import common.jvmTarget 2 | 3 | plugins { 4 | dev.suresh.plugin.kotlin.mpp 5 | dev.suresh.plugin.publishing 6 | `binary-compatibility-validator` 7 | } 8 | 9 | description = "Certificate and Security!" 10 | 11 | kotlin { 12 | jvmTarget(project) 13 | 14 | sourceSets { 15 | commonMain { dependencies { implementation(projects.shared) } } 16 | 17 | jvmMain { 18 | kotlin.srcDir("src/main/kotlin") 19 | resources.srcDir("src/main/resources") 20 | } 21 | 22 | jvmTest { 23 | kotlin.srcDir("src/test/kotlin") 24 | resources.srcDir("src/test/resources") 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /backend/security/src/main/kotlin/dev/suresh/TrustStore.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh 2 | 3 | import com.github.marschall.directorykeystore.* 4 | import java.nio.file.Path 5 | import java.security.KeyStore 6 | import java.security.Security 7 | import javax.net.ssl.TrustManagerFactory 8 | import javax.net.ssl.X509TrustManager 9 | 10 | /** 11 | * JVM can be switched to use a different truststore using **-Djavax.net.ssl.trustStoreType=xxx** 12 | */ 13 | object TrustStore { 14 | 15 | fun allTrustStores(): List = 16 | Security.getProviders() 17 | .flatMap { it.entries } 18 | .map { it.key.toString() } 19 | .filter { it.startsWith("KeyStore.") && it.endsWith("ImplementedIn").not() } 20 | .map { it.substringAfter("KeyStore.").trim() } 21 | .distinct() 22 | 23 | fun systemTrustStore(type: TrustStoreType): KeyStore = 24 | when (type) { 25 | is TrustStoreType.Directory -> { 26 | if (Security.getProvider(DirectoryKeystoreProvider.NAME) == null) { 27 | Security.addProvider(DirectoryKeystoreProvider()) 28 | } 29 | KeyStore.getInstance(type.name).apply { load(DirectoryLoadStoreParameter(type.path)) } 30 | } 31 | else -> KeyStore.getInstance(type.name).apply { load(null, null) } 32 | } 33 | 34 | val caCerts = 35 | TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()).run { 36 | init(null as KeyStore?) 37 | // trustManagers.filterIsInstance().flatMap { it.acceptedIssuers.toList() 38 | // } 39 | } 40 | 41 | /** Returns the default trust managers. This is initialized using JDK's `cacerts` trust store. */ 42 | val cacertsTrustManager by lazy { 43 | TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()).run { 44 | // Use the JDK cacerts 45 | init(null as KeyStore?) 46 | trustManagers.filterIsInstance() 47 | } 48 | } 49 | } 50 | 51 | sealed class TrustStoreType(val name: String) { 52 | 53 | data object WIN_USER : TrustStoreType("Windows-MY") 54 | 55 | data object WIN_SYSTEM : TrustStoreType("Windows-ROOT") 56 | 57 | data object MACOS_USER : TrustStoreType("KeychainStore") 58 | 59 | data object MACOS_SYSTEM : TrustStoreType("KeychainStore-ROOT") 60 | 61 | class Directory(val path: Path) : TrustStoreType("Directory") 62 | } 63 | -------------------------------------------------------------------------------- /backend/security/src/main/kotlin/dev/suresh/cert/CertScan.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh.cert 2 | 3 | import dev.suresh.tls.SavingTrustManager 4 | import dev.suresh.tls.newTLSSocket 5 | import java.net.InetSocketAddress 6 | import java.security.cert.X509Certificate 7 | import javax.net.ssl.SNIHostName 8 | import kotlin.time.Duration 9 | import kotlin.time.Duration.Companion.seconds 10 | 11 | object CertScan { 12 | 13 | fun scan( 14 | host: String, 15 | port: Int = 443, 16 | sni: String? = null, 17 | timeout: Duration = 2.seconds 18 | ): List { 19 | val trustManager = SavingTrustManager() 20 | val socket = trustManager.newTLSSocket() 21 | return socket.use { sock -> 22 | val handshake = runCatching { 23 | sni?.let { 24 | // sock.sslParameters will create a new copy 25 | val sslParams = sock.sslParameters 26 | sslParams.serverNames = listOf(SNIHostName(sni)) 27 | sock.sslParameters = sslParams 28 | } 29 | sock.soTimeout = timeout.inWholeMilliseconds.toInt() 30 | sock.connect(InetSocketAddress(host, port), timeout.inWholeMilliseconds.toInt()) 31 | sock.startHandshake() 32 | 33 | // Peer has to be authenticated this to work 34 | // sock.session.peerCertificates.filterIsInstance() 35 | } 36 | trustManager.chain 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /backend/security/src/main/kotlin/dev/suresh/security/PasswordGen.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh.security 2 | 3 | /** Password generator. This is not thread safe! */ 4 | class PasswordGen( 5 | private val lowercase: Boolean = true, 6 | private val uppercase: Boolean = true, 7 | private val number: Boolean = true, 8 | private val special: Boolean = true, 9 | ) { 10 | 11 | private var allowedChars: List 12 | 13 | init { 14 | check(lowercase || uppercase || number || special) { "At least one char type must be enabled" } 15 | allowedChars = buildList { 16 | if (lowercase) addAll('a'..'z') 17 | if (uppercase) addAll('A'..'Z') 18 | if (number) addAll('0'..'9') 19 | if (special) addAll("~!@#$%^&*+=".toList()) 20 | } 21 | } 22 | 23 | fun generate(len: Int = 15): String { 24 | check(len > 0) { "Password length must be greater than 0" } 25 | allowedChars = allowedChars.shuffled() 26 | return buildString { repeat(len) { append(allowedChars.random()) } } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /backend/security/src/main/kotlin/dev/suresh/tls/AliasKeyManager.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh.tls 2 | 3 | import java.net.Socket 4 | import java.security.Principal 5 | import javax.net.ssl.X509KeyManager 6 | 7 | /** 8 | * A [X509KeyManager] implementation which selects the client private key for client authentication 9 | * based on given key alias name. 10 | */ 11 | class AliasKeyManager(private val delegate: X509KeyManager, private val aliasName: String) : 12 | X509KeyManager by delegate { 13 | override fun chooseClientAlias( 14 | keyType: Array, 15 | issuers: Array, 16 | socket: Socket 17 | ): String = aliasName 18 | } 19 | -------------------------------------------------------------------------------- /backend/security/src/main/kotlin/dev/suresh/tls/CustomSSLSocketFactory.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh.tls 2 | 3 | import java.net.InetAddress 4 | import java.net.Socket 5 | import javax.net.ssl.SSLSocket 6 | import javax.net.ssl.SSLSocketFactory 7 | 8 | class CustomSSLSocketFactory(private val delegate: SSLSocketFactory) : SSLSocketFactory() { 9 | override fun getDefaultCipherSuites(): Array = delegate.defaultCipherSuites 10 | 11 | override fun getSupportedCipherSuites(): Array = delegate.supportedCipherSuites 12 | 13 | override fun createSocket(s: Socket, host: String, port: Int, autoClose: Boolean): Socket = 14 | delegate.createSocket(s, host, port, autoClose).apply { reconfigureSSLSocket() } 15 | 16 | override fun createSocket(host: String, port: Int): Socket = 17 | delegate.createSocket(host, port).apply { reconfigureSSLSocket() } 18 | 19 | override fun createSocket( 20 | host: String, 21 | port: Int, 22 | localHost: InetAddress, 23 | localPort: Int 24 | ): Socket = 25 | delegate.createSocket(host, port, localHost, localPort).apply { reconfigureSSLSocket() } 26 | 27 | override fun createSocket(host: InetAddress, port: Int): Socket = 28 | delegate.createSocket(host, port).apply { reconfigureSSLSocket() } 29 | 30 | override fun createSocket( 31 | address: InetAddress, 32 | port: Int, 33 | localAddress: InetAddress, 34 | localPort: Int 35 | ): Socket = 36 | delegate.createSocket(address, port, localAddress, localPort).apply { reconfigureSSLSocket() } 37 | } 38 | 39 | fun Socket.reconfigureSSLSocket() { 40 | val sslSock = this as SSLSocket 41 | val sslParams = sslSock.sslParameters 42 | // Disable SNI 43 | sslParams.serverNames = emptyList() 44 | // Disable Hostname verification (Same as -Djdk.internal.httpclient.disableHostnameVerification) 45 | sslParams.endpointIdentificationAlgorithm = null 46 | sslParameters = sslParams 47 | } 48 | -------------------------------------------------------------------------------- /backend/security/src/main/kotlin/dev/suresh/tls/SavingTrustManager.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh.tls 2 | 3 | import java.security.cert.X509Certificate 4 | import javax.net.ssl.* 5 | 6 | class SavingTrustManager : X509TrustManager { 7 | 8 | private val _chain = mutableListOf() 9 | 10 | val chain: List 11 | get() = _chain 12 | 13 | override fun checkClientTrusted(chain: Array, authType: String) { 14 | _chain.addAll(chain) 15 | } 16 | 17 | override fun checkServerTrusted(chain: Array, authType: String) { 18 | _chain.addAll(chain) 19 | } 20 | 21 | override fun getAcceptedIssuers(): Array = emptyArray() 22 | } 23 | 24 | fun SavingTrustManager.newTLSSocket(): SSLSocket { 25 | val tm = this 26 | return SSLContext.getInstance("TLS").run { 27 | init(null, arrayOf(tm), null) 28 | socketFactory.createSocket() as SSLSocket 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /backend/security/src/main/kotlin/dev/suresh/tls/TLSProp.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh.tls 2 | 3 | import java.net.JarURLConnection 4 | import java.security.Security 5 | import java.util.jar.Manifest 6 | import kotlin.reflect.KClass 7 | 8 | /** 9 | * Enumeration of JSSE (Java Secure Socket Extension) system and security properties used for 10 | * configuring TLS/SSL connections. 11 | */ 12 | enum class TLSProp(val prop: String, val desc: String, val system: Boolean = true) { 13 | Debug("javax.net.debug", "Debugging SSL/TLS Connections."), 14 | KeyStore("javax.net.ssl.keyStore", "Default keystore"), 15 | KeyStoreType("javax.net.ssl.keyStoreType", "Default keystore type"), 16 | KeyStorePassword("javax.net.ssl.keyStorePassword", "Default keystore password"), 17 | KeyStoreProvider("javax.net.ssl.keyStoreProvider", "Default keystore provider"), 18 | TrustStore("javax.net.ssl.trustStore", "Default truststore"), 19 | TrustStoreType("javax.net.ssl.trustStoreType", "Default truststore type"), 20 | TrustStorePassword("javax.net.ssl.trustStorePassword", "Default truststore password"), 21 | TrustStoreProvider("javax.net.ssl.trustStoreProvider", "Default truststore provider"), 22 | ProxyHost("https.proxyHost", "Default HTTPS proxy host"), 23 | ProxyPort("https.proxyPort", "Default HTTPS proxy port"), 24 | HttpsCipherSuites("https.cipherSuites", "Default cipher suites"), 25 | HttpsProtocols("https.protocols", "Default HTTPS handshaking protocols"), 26 | TLSProtocols("jdk.tls.client.protocols", "Default Enabled TLS Protocols"), 27 | CertPathDisabledAlgos( 28 | "jdk.certpath.disabledAlgorithms", 29 | "Disabled certificate verification cryptographic algorithms", 30 | false), 31 | TLSDisabledAlgos("jdk.tls.disabledAlgorithms", "Disabled/Restricted Algorithms", false); 32 | 33 | /** Sets the JSSE system/security property to the given value. */ 34 | fun set(value: String) { 35 | when (system) { 36 | true -> System.setProperty(prop, value) 37 | else -> Security.setProperty(prop, value) 38 | } 39 | } 40 | } 41 | 42 | /** 43 | * Returns the jar [Manifest] of the class. Returns `null` if the class is not bundled in a jar 44 | * (Classes in an unpacked class hierarchy). 45 | */ 46 | val KClass.jarManifest: Manifest? 47 | get() { 48 | val res = java.getResource("${java.simpleName}.class") 49 | val conn = res?.openConnection() 50 | return if (conn is JarURLConnection) conn.manifest else null 51 | } 52 | -------------------------------------------------------------------------------- /benchmark/README.md: -------------------------------------------------------------------------------- 1 | ### Run Benchmarks 2 | 3 | ```bash 4 | $ ./gradlew :benchmark:benchmark 5 | ``` 6 | 7 | ### Resources 8 | 9 | - [Kotlin Multiplatform Benchmarking](https://github.com/Kotlin/kotlinx-benchmark/tree/master/examples) 10 | -------------------------------------------------------------------------------- /benchmark/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { dev.suresh.plugin.kotlin.benchmark } 2 | 3 | dependencies { commonMainImplementation(projects.shared) } 4 | -------------------------------------------------------------------------------- /benchmark/src/commonMain/kotlin/CommonBenchmark.kt: -------------------------------------------------------------------------------- 1 | package bench 2 | 3 | import kotlin.math.* 4 | import kotlinx.benchmark.* 5 | 6 | @State(Scope.Benchmark) 7 | @Measurement(iterations = 3, time = 1, timeUnit = BenchmarkTimeUnit.SECONDS) 8 | @OutputTimeUnit(BenchmarkTimeUnit.MILLISECONDS) 9 | @BenchmarkMode(Mode.AverageTime) 10 | class CommonBenchmark { 11 | private var data = 0.0 12 | private lateinit var text: String 13 | 14 | @Setup 15 | fun setUp() { 16 | data = 3.0 17 | text = "Hello!" 18 | } 19 | 20 | @TearDown fun teardown() {} 21 | 22 | @Benchmark 23 | fun exception() { 24 | try { 25 | fail() 26 | } catch (e: Throwable) { 27 | throw Exception("I failed!", e) 28 | } 29 | } 30 | 31 | private fun fail() { 32 | error("Not implemented") 33 | } 34 | 35 | @Benchmark 36 | fun mathBenchmark(): Double { 37 | return log(sqrt(data) * cos(data), 2.0) 38 | } 39 | 40 | @Benchmark 41 | fun longBenchmark(): Double { 42 | var value = 1.0 43 | repeat(1000) { value *= text.length } 44 | return value 45 | } 46 | 47 | @Benchmark 48 | fun longBlackholeBenchmark(bh: Blackhole) { 49 | repeat(1000) { bh.consume(text.length) } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /benchmark/src/jvmMain/kotlin/JvmBenchmark.kt: -------------------------------------------------------------------------------- 1 | package bench 2 | 3 | import java.util.concurrent.TimeUnit 4 | import jdk.incubator.vector.LongVector 5 | import org.openjdk.jmh.annotations.* 6 | 7 | const val WARMUP_ITERATIONS = 5 8 | 9 | @State(Scope.Benchmark) 10 | @BenchmarkMode(Mode.AverageTime) 11 | @OutputTimeUnit(TimeUnit.NANOSECONDS) 12 | @Warmup(iterations = WARMUP_ITERATIONS, time = 1, timeUnit = TimeUnit.SECONDS) 13 | @Measurement(iterations = 1, time = 1, timeUnit = TimeUnit.SECONDS) 14 | @Fork(value = 1, jvmArgsAppend = ["--add-modules=jdk.incubator.vector"]) 15 | class JvmTestBenchmark { 16 | 17 | private var data = 0.0 18 | 19 | @Setup 20 | fun setUp() { 21 | data = 3.0 22 | } 23 | 24 | @Benchmark 25 | fun sqrtBenchmark(): Double { 26 | return Math.sqrt(data) 27 | } 28 | 29 | @Benchmark 30 | fun cosBenchmark(): Double { 31 | return Math.cos(data) 32 | } 33 | 34 | @Benchmark fun vectorAPI() = LongVector.SPECIES_PREFERRED.length() 35 | } 36 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | import common.* 2 | 3 | plugins { 4 | dev.suresh.plugin.root 5 | // alias(libs.plugins.kotlin.multiplatform) apply false 6 | // id(libs.plugins.kotlin.multiplatform.get().pluginId) 7 | } 8 | 9 | description = "Kotlin Multiplatform Playground!" 10 | 11 | nmcp { 12 | centralPortal { 13 | username = mavenCentralUsername 14 | password = mavenCentralPassword 15 | publishingType = "AUTOMATIC" 16 | } 17 | } 18 | 19 | dependencies { 20 | dokka(projects.shared) 21 | dokka(projects.meta.ksp.processor) 22 | dokka(projects.meta.compiler.plugin) 23 | dokka(projects.backend.jvm) 24 | dokka(projects.backend.data) 25 | dokka(projects.backend.profiling) 26 | dokka(projects.backend.security) 27 | dokka(projects.web) 28 | 29 | nmcpAggregation(projects.shared) 30 | nmcpAggregation(projects.depMgmt.bom) 31 | nmcpAggregation(projects.depMgmt.catalog) 32 | nmcpAggregation(projects.meta.ksp.processor) 33 | nmcpAggregation(projects.meta.compiler.plugin) 34 | nmcpAggregation(projects.backend.jvm) 35 | nmcpAggregation(projects.backend.data) 36 | nmcpAggregation(projects.backend.profiling) 37 | nmcpAggregation(projects.backend.security) 38 | nmcpAggregation(projects.web) 39 | 40 | // Optional modules 41 | findProject(":backend:native")?.let { nmcpAggregation(it) } 42 | findProject(":compose:cmp")?.let { nmcpAggregation(it) } 43 | findProject(":compose:html")?.let { nmcpAggregation(it) } 44 | } 45 | -------------------------------------------------------------------------------- /compose/README.md: -------------------------------------------------------------------------------- 1 | ## [JetBrains Compose Multiplatform][1] Projects 2 | 3 | ### Resources 4 | 5 | * [CSS2KobWeb](https://opletter.github.io/css2kobweb/) 6 | * [Jetbrains Compose Multiplatform Core Repo](https://github.com/JetBrains/compose-multiplatform-core) 7 | 8 | [1]: https://github.com/JetBrains/compose-multiplatform 9 | -------------------------------------------------------------------------------- /compose/cli/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import common.* 2 | 3 | plugins { 4 | application 5 | com.gradleup.shadow 6 | dev.suresh.plugin.kotlin.mpp 7 | dev.suresh.plugin.publishing 8 | alias(libs.plugins.kotlin.compose.compiler) 9 | // alias(libs.plugins.detekt) 10 | } 11 | 12 | description = "Compose mosaic CLI app!" 13 | 14 | kotlin { 15 | jvmTarget(project) 16 | 17 | sourceSets { 18 | commonMain.dependencies { 19 | implementation(projects.shared) 20 | implementation(libs.mosaic.runtime) 21 | } 22 | } 23 | } 24 | 25 | application { 26 | mainClass = libs.versions.app.mainclass.get() 27 | applicationDefaultJvmArgs += project.runJvmArgs 28 | } 29 | -------------------------------------------------------------------------------- /compose/cli/src/commonMain/kotlin/dev/suresh/Counter.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh 2 | 3 | import androidx.compose.runtime.* 4 | import com.jakewharton.mosaic.ui.Box 5 | import com.jakewharton.mosaic.ui.Column 6 | import com.jakewharton.mosaic.ui.Text 7 | import kotlinx.coroutines.delay 8 | 9 | @Composable 10 | fun Counter() { 11 | var counter by remember { mutableStateOf(0) } 12 | Text("The count is: $counter") 13 | 14 | Column { Box { Text("The count is: $counter") } } 15 | 16 | LaunchedEffect(Unit) { 17 | for (i in 1..10) { 18 | counter = i 19 | delay(1000) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /compose/cli/src/jvmMain/kotlin/dev/suresh/App.kt: -------------------------------------------------------------------------------- 1 | package dev.suresh 2 | 3 | import com.jakewharton.mosaic.runMosaicBlocking 4 | 5 | fun main() = runMosaicBlocking { GradientsUI() } 6 | -------------------------------------------------------------------------------- /compose/cmp/src/assets/common/splash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sureshg/kotlin-mpp-playground/275d22919a7e9da80a3897f551293a841ac1d3c0/compose/cmp/src/assets/common/splash.jpg -------------------------------------------------------------------------------- /compose/cmp/src/assets/icons/linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sureshg/kotlin-mpp-playground/275d22919a7e9da80a3897f551293a841ac1d3c0/compose/cmp/src/assets/icons/linux.png -------------------------------------------------------------------------------- /compose/cmp/src/assets/icons/mac.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sureshg/kotlin-mpp-playground/275d22919a7e9da80a3897f551293a841ac1d3c0/compose/cmp/src/assets/icons/mac.icns -------------------------------------------------------------------------------- /compose/cmp/src/assets/icons/win.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sureshg/kotlin-mpp-playground/275d22919a7e9da80a3897f551293a841ac1d3c0/compose/cmp/src/assets/icons/win.ico -------------------------------------------------------------------------------- /compose/cmp/src/assets/macos-arm64/resource.txt: -------------------------------------------------------------------------------- 1 | Desktop App for MacOS Arm64 -------------------------------------------------------------------------------- /compose/cmp/src/commonMain/composeResources/drawable/animated_svg/audio.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 11 | 15 | 16 | 17 | 21 | 22 | 23 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /compose/cmp/src/commonMain/composeResources/drawable/animated_svg/ball-triangle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 17 | 18 | 19 | 25 | 31 | 32 | 33 | 38 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /compose/cmp/src/commonMain/composeResources/drawable/animated_svg/circles.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 12 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /compose/cmp/src/commonMain/composeResources/drawable/animated_svg/grid.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 13 | 14 | 15 | 19 | 20 | 21 | 25 | 26 | 27 | 31 | 32 | 33 | 37 | 38 | 39 | 43 | 44 | 45 | 49 | 50 | 51 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /compose/cmp/src/commonMain/composeResources/drawable/animated_svg/hearts.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /compose/cmp/src/commonMain/composeResources/drawable/animated_svg/oval.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /compose/cmp/src/commonMain/composeResources/drawable/animated_svg/puff.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 19 | 20 | 21 | 28 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /compose/cmp/src/commonMain/composeResources/drawable/animated_svg/rings.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 14 | 18 | 19 | 20 | 25 | 29 | 33 | 34 | 35 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /compose/cmp/src/commonMain/composeResources/drawable/animated_svg/tail-spin.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 20 | 21 | 22 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /compose/cmp/src/commonMain/composeResources/drawable/animated_svg/three-dots.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 12 | 13 | 14 | 18 | 22 | 23 | 24 | 28 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /compose/cmp/src/commonMain/composeResources/drawable/ic-fluent-rocket.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | -------------------------------------------------------------------------------- /compose/cmp/src/commonMain/composeResources/drawable/particles.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sureshg/kotlin-mpp-playground/275d22919a7e9da80a3897f551293a841ac1d3c0/compose/cmp/src/commonMain/composeResources/drawable/particles.gif -------------------------------------------------------------------------------- /compose/cmp/src/commonMain/composeResources/files/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sureshg/kotlin-mpp-playground/275d22919a7e9da80a3897f551293a841ac1d3c0/compose/cmp/src/commonMain/composeResources/files/.keep -------------------------------------------------------------------------------- /compose/cmp/src/commonMain/kotlin/App.kt: -------------------------------------------------------------------------------- 1 | import androidx.compose.runtime.Composable 2 | import nav.NavRoot 3 | import ui.AppTheme 4 | 5 | @Composable 6 | fun App() { 7 | AppTheme { NavRoot() } 8 | } 9 | -------------------------------------------------------------------------------- /compose/cmp/src/commonMain/kotlin/nav/NavRoot.kt: -------------------------------------------------------------------------------- 1 | package nav 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.navigation.NavType 5 | import androidx.navigation.compose.NavHost 6 | import androidx.navigation.compose.composable 7 | import androidx.navigation.compose.rememberNavController 8 | import androidx.navigation.navArgument 9 | import ui.birds.BirdImages 10 | import ui.file.FileBrowser 11 | import ui.home.Home 12 | 13 | @Composable 14 | fun NavRoot() { 15 | val navController = rememberNavController() 16 | NavHost(navController = navController, startDestination = Screen.Home.route) { 17 | composable(Screen.Home.route) { 18 | Home( 19 | navToFile = { navController.navigate(Screen.FileBrowser.route) }, 20 | navToImage = { navController.navigate(Screen.Image.route) }) 21 | } 22 | composable(Screen.FileBrowser.route) { 23 | FileBrowser(navToHome = { navController.popBackStack() }) 24 | } 25 | 26 | composable(Screen.Image.route) { BirdImages(navToHome = { navController.popBackStack() }) } 27 | 28 | composable( 29 | route = Screen.Profile.route, 30 | arguments = listOf(navArgument("userId") { type = NavType.StringType })) { 31 | // val userId = it.arguments?.getString("userId") 32 | // Profile(userId = userId) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /compose/cmp/src/commonMain/kotlin/nav/Screen.kt: -------------------------------------------------------------------------------- 1 | package nav 2 | 3 | sealed class Screen(val route: String) { 4 | data object Home : Screen("home") 5 | 6 | data object FileBrowser : Screen("fileBrowser") 7 | 8 | data object Image : Screen("image") 9 | 10 | data object Video : Screen("video") 11 | 12 | data object Profile : Screen("profile?userId={userId}") 13 | } 14 | -------------------------------------------------------------------------------- /compose/cmp/src/commonMain/kotlin/ui/Image.kt: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.foundation.clickable 5 | import androidx.compose.foundation.layout.Box 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.runtime.rememberCoroutineScope 8 | import androidx.compose.ui.Modifier 9 | import androidx.compose.ui.draw.drawWithContent 10 | import androidx.compose.ui.graphics.* 11 | import androidx.compose.ui.graphics.layer.drawLayer 12 | import kotlinx.coroutines.launch 13 | import org.jetbrains.compose.resources.* 14 | 15 | /** 16 | * Image decoders for [ByteArray] 17 | * 18 | * @see [JPEG, PNG, BMP, WEBP to ImageBitmap][ByteArray.decodeToImageBitmap] 19 | * @see [Vector XML file to an ImageVector][ByteArray.decodeToImageVector] 20 | * @see [SVG file to a compose Painter][ByteArray.decodeToSvgPainter] 21 | */ 22 | fun ImageBitmap.toByteArray(): ByteArray? = asSkiaBitmap().readPixels() 23 | 24 | @Composable 25 | fun SaveToBitmap( 26 | modifier: Modifier = Modifier, 27 | onSave: (ImageBitmap) -> Unit, 28 | content: @Composable () -> Unit 29 | ) { 30 | val coroutineScope = rememberCoroutineScope() 31 | val graphicsLayer = rememberGraphicsLayer() 32 | Box( 33 | modifier = 34 | modifier 35 | .drawWithContent { 36 | graphicsLayer.record { 37 | // Draw the content of the composable to the graphics layer. 38 | this@drawWithContent.drawContent() 39 | } 40 | drawLayer(graphicsLayer) 41 | } 42 | .clickable { 43 | coroutineScope.launch { 44 | val bitmap = graphicsLayer.toImageBitmap() 45 | onSave(bitmap) 46 | } 47 | } 48 | .background(Color.White)) { 49 | content() 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /compose/cmp/src/commonMain/kotlin/ui/Modifier.kt: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.Modifier 5 | import androidx.compose.ui.composed 6 | import androidx.compose.ui.draw.DrawModifier 7 | import androidx.compose.ui.draw.drawWithCache 8 | import androidx.compose.ui.geometry.CornerRadius 9 | import androidx.compose.ui.graphics.Color 10 | import androidx.compose.ui.graphics.PathEffect 11 | import androidx.compose.ui.graphics.drawscope.ContentDrawScope 12 | import androidx.compose.ui.graphics.drawscope.Stroke 13 | import androidx.compose.ui.graphics.drawscope.scale 14 | import androidx.compose.ui.platform.LocalDensity 15 | import androidx.compose.ui.unit.Dp 16 | import androidx.compose.ui.unit.dp 17 | import kotlinx.coroutines.MainScope 18 | 19 | val debug = true 20 | 21 | val mainScope = MainScope() 22 | 23 | fun Modifier.debug(color: Color = Color.Red) = 24 | then(if (debug) dashedBorder(1.dp, color, 8.dp) else this) 25 | 26 | fun Modifier.dashedBorder(strokeWidth: Dp, color: Color, cornerRadius: Dp): Modifier = composed { 27 | val density = LocalDensity.current 28 | val strokeWidthPx = density.run { strokeWidth.toPx() } 29 | val cornerRadiusPx = density.run { cornerRadius.toPx() } 30 | 31 | then( 32 | Modifier.drawWithCache { 33 | onDrawBehind { 34 | val stroke = 35 | Stroke( 36 | width = strokeWidthPx, 37 | pathEffect = PathEffect.dashPathEffect(floatArrayOf(10f, 10f), 0f)) 38 | drawRoundRect(color = color, style = stroke, cornerRadius = CornerRadius(cornerRadiusPx)) 39 | } 40 | } 41 | // .border(border = BorderStroke(strokeWidth, color),shape = RoundedCornerShape(cornerRadius)) 42 | ) 43 | } 44 | 45 | @Composable 46 | fun buildModifier(builder: @Composable MutableList.() -> Unit): Modifier { 47 | val modifiers = mutableListOf() 48 | modifiers.builder() 49 | // The first Modifier is the empty, starter Modifier object 50 | return modifiers.fold(Modifier as Modifier) { acc, modifier -> acc then modifier } 51 | } 52 | 53 | fun Modifier.ifTrue(value: Boolean, builder: Modifier.() -> Modifier) = 54 | then(if (value) builder() else Modifier) 55 | 56 | fun Modifier.flipped() = then(FlippedModifier()) 57 | 58 | class FlippedModifier : DrawModifier { 59 | override fun ContentDrawScope.draw() { 60 | scale(1f, -1f) { this@draw.drawContent() } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /compose/cmp/src/commonMain/kotlin/ui/Theme.kt: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import androidx.compose.foundation.shape.RoundedCornerShape 4 | import androidx.compose.material3.MaterialTheme 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.ui.graphics.Color 7 | import androidx.compose.ui.unit.dp 8 | 9 | @Composable 10 | fun AppTheme(content: @Composable () -> Unit) { 11 | MaterialTheme( 12 | colorScheme = MaterialTheme.colorScheme.copy(primary = Color.Black), 13 | shapes = 14 | MaterialTheme.shapes.copy( 15 | small = RoundedCornerShape(0.dp), 16 | medium = RoundedCornerShape(0.dp), 17 | large = RoundedCornerShape(0.dp))) { 18 | content() 19 | } 20 | } 21 | 22 | object FileColors { 23 | val default = Color.Gray 24 | val active = Color(29, 117, 223, 255) 25 | val fileItemBg = Color(233, 30, 99, 255) 26 | val fileItemFg = Color.White 27 | } 28 | -------------------------------------------------------------------------------- /compose/cmp/src/commonMain/kotlin/ui/birds/BirdsScreen.kt: -------------------------------------------------------------------------------- 1 | package ui.birds 2 | 3 | import androidx.compose.foundation.layout.* 4 | import androidx.compose.foundation.rememberScrollState 5 | import androidx.compose.foundation.text.selection.SelectionContainer 6 | import androidx.compose.foundation.verticalScroll 7 | import androidx.compose.material3.CircularProgressIndicator 8 | import androidx.compose.material3.MaterialTheme 9 | import androidx.compose.material3.Text 10 | import androidx.compose.runtime.Composable 11 | import androidx.compose.runtime.LaunchedEffect 12 | import androidx.compose.runtime.collectAsState 13 | import androidx.compose.runtime.getValue 14 | import androidx.compose.ui.Alignment 15 | import androidx.compose.ui.Modifier 16 | import androidx.compose.ui.graphics.Color 17 | import androidx.compose.ui.unit.dp 18 | import androidx.lifecycle.viewmodel.compose.viewModel 19 | import ui.debug 20 | import ui.home.HomeButton 21 | 22 | @Composable 23 | fun BirdImages(modifier: Modifier = Modifier, navToHome: () -> Unit) { 24 | val birdsViewModel = viewModel { BirdsViewModel() } 25 | val state by birdsViewModel.uiState.collectAsState() 26 | val scrollState = rememberScrollState() 27 | 28 | // Load the BirdUiState 29 | LaunchedEffect(birdsViewModel) { birdsViewModel.update() } 30 | 31 | Column( 32 | modifier = 33 | modifier 34 | .fillMaxSize() 35 | .padding(10.dp) 36 | .debug(color = Color.Black) 37 | .verticalScroll(scrollState), 38 | horizontalAlignment = Alignment.CenterHorizontally, 39 | verticalArrangement = Arrangement.SpaceEvenly) { 40 | if (state.images.isEmpty()) { 41 | CircularProgressIndicator( 42 | modifier = Modifier.width(64.dp), 43 | color = MaterialTheme.colorScheme.secondary, 44 | trackColor = MaterialTheme.colorScheme.surfaceVariant, 45 | ) 46 | Text("Loading images...") 47 | } else { 48 | state.images.forEach { image -> 49 | SelectionContainer { Text(image.path) } 50 | Spacer(modifier = Modifier.width(5.dp)) 51 | } 52 | } 53 | Spacer(modifier = Modifier.height(5.dp)) 54 | HomeButton(navToHome) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /compose/cmp/src/commonMain/kotlin/ui/birds/BirdsViewModel.kt: -------------------------------------------------------------------------------- 1 | package ui.birds 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import dev.suresh.http.Image 6 | import dev.suresh.http.MediaApiClient 7 | import dev.suresh.http.Video 8 | import dev.suresh.log 9 | import kotlinx.coroutines.flow.MutableStateFlow 10 | import kotlinx.coroutines.flow.asStateFlow 11 | import kotlinx.coroutines.flow.update 12 | import kotlinx.coroutines.launch 13 | 14 | data class BirdUiState( 15 | val images: List = emptyList(), 16 | val videos: List