Skip to content

KMPPT-91 Integrate SQLDlight in KMP Template#134

Open
TheKalpeshPawar wants to merge 7 commits intoopenMF:devfrom
TheKalpeshPawar:sqldelight_integration
Open

KMPPT-91 Integrate SQLDlight in KMP Template#134
TheKalpeshPawar wants to merge 7 commits intoopenMF:devfrom
TheKalpeshPawar:sqldelight_integration

Conversation

@TheKalpeshPawar
Copy link

@TheKalpeshPawar TheKalpeshPawar commented Mar 9, 2026

Jira Ticket: KMPPR-91

Summary

This PR integrates SQLDelight as the primary database solution for the Mifos KMP project template. It establishes a robust, type-safe, and offline-first data layer compatible across Android, Desktop (JVM), iOS/Native, and Web (JS/Wasm) platforms. This implementation provides the foundation for the generic offline-first data
layer module using Store5.

Key Changes

🏗️ Core Infrastructure

  • KMPSQLDelightConventionPlugin: Created a new convention plugin to standardize SQLDelight configuration, including:
    • Automatic dependency injection of platform-specific drivers.
    • Asynchronous code generation enabled by default.
    • Schema output directory management.
  • Dependency Injection (Koin):
    • Defined sqlDelightModule in core:database.
    • Implemented MifosSQLDatabaseInitializer to handle schema creation and migrations across all platforms.
    • Integrated platform-specific driverModule (expect/actual) for driver instantiation.

📱 Multiplatform Driver Support

  • Android: Integrated AndroidSqliteDriver with support for local SQLite.
  • Desktop (JVM): Integrated JdbcSqliteDriver using an in-memory or file-based SQLite database.
  • Native (iOS/macOS): Integrated NativeSqliteDriver for high-performance persistence on Apple platforms.
  • Web (JS/Wasm): Integrated WebWorkerDriver with SQL.js and Web Worker support for non-blocking database operations in the browser.

🧪 Testing & Validation

  • Common Test Suite: Implemented SQLDelightRepositoryTest in commonTest to verify CRUD operations (Insert, Delete, Retrieve) using Flow/Turbine.
  • Platform-Specific Test Runners:
    • Added dedicated test drivers for Android, Desktop, Native, JS, and Wasm.
    • Configured TestSQLDelightModule to use in-memory drivers for isolated unit testing.
  • CI Readiness: Added Karma and Webpack configurations (sqljs.js) to ensure JS/Wasm database tests run correctly in headless browser environments.

🧹 Code Quality & Maintenance

  • Spotless Integration: Applied consistent code formatting and added license headers to all new database-related files.

Summary by CodeRabbit

  • New Features

    • Cross-platform SQLDelight database with sample repository, async initialization, DI bindings and comprehensive platform tests.
  • Chores

    • Gradle upgraded to 9.1.0; SQLDelight plugin and library versions added.
    • Web build updated to include SQL.js wasm asset for browser tests; JS target now supports both browser and Node.
    • Podspec version bumped.

…rt (openMF#128)

- Add `KMPSQLDelightConventionPlugin` to manage SQLDelight configuration across platforms.
- Implement platform-specific `SqlDriver` setups for Android, iOS (Native), Desktop (SQLite), and Web (JS/Wasm).
- Introduce `MifosSQLDatabaseInitializer` to handle asynchronous schema creation.
- Add `SampleRepository` and `SQLDelightSampleRepositoryImpl` to demonstrate database operations.
- Update `DatabaseModule` and `DataModule` to include SQLDelight dependencies via Koin.
- Configure Web workers and `sql.js` for JS/Wasm support in `cmp-web`.
- Upgrade Gradle wrapper to version 9.1.0 and update project dependencies.
- Added copyright and license headers to all database module files.
- Applied formatting across the module, including trailing commas and whitespace cleanup.
- Standardized Koin module definitions in platform-specific SQLDelight implementations.
…tform configuration (openMF#128)

* Add `SQLDelightRepositoryTest` common base class and platform-specific test implementations for Android, Desktop, Native, JS, and WasmJs.
* Update `testSQLDelightPlatformModule` across all platforms to use `single` instances for `SqlDriver` and ensure proper schema initialization.
* Refactor Koin module organization by moving `TestDatabaseModule` to `nonJsCommonTest` and removing the `DatabaseModule` include from `DataModule`.
* Update Android and Native test modules to use `Dispatchers.IO` directly for Room query contexts.
* Enhance JS/WasmJs test infrastructure with `karma.config.d` and `webpack.config.d` for `sql.js` WASM support.
* Enable browser testing for JS and WasmJs in `build.gradle.kts` with automatic browser detection.
* Update `libs.versions.toml` with `androidx-core` and `core-ktx` dependencies.
@coderabbitai
Copy link

coderabbitai bot commented Mar 9, 2026

📝 Walkthrough

Walkthrough

Adds SQLDelight support to the Kotlin Multiplatform project: new Gradle plugin and version entries, a KMPSQLDelight convention plugin, platform-specific SqlDriver DI modules, SQL schema, repository implementation, cross-platform tests, and webpack/karma handling for sql.js wasm.

Changes

Cohort / File(s) Summary
Build config & plugin registration
build.gradle.kts, gradle/libs.versions.toml, build-logic/convention/build.gradle.kts
Added SQLDelight plugin alias/version entries, added compileOnly gradle-plugin dep, and registered mifos.kmp.sqldelight (KMPSQLDelight) in build-logic.
Build logic implementation
build-logic/convention/src/main/kotlin/KMPSQLDelightConventionPlugin.kt, build-logic/convention/src/main/kotlin/org/convention/KotlinMultiplatform.kt, build-logic/convention/src/main/kotlin/KMPCoreBaseLibraryConventionPlugin.kt
New KMPSQLDelightConventionPlugin configures SQLDelight extension and platform dependencies; JS IR target now includes browser(); minor formatting tweak.
Module build files
core/database/build.gradle.kts, core-base/database/build.gradle.kts
Integrated sqldelight plugin and drivers into module builds, added platform dependencies and test/browser selection helpers, moved/adjusted sourceSet dependency placement.
SQL schema & repository
core/database/src/commonMain/sqldelight/.../Sample.sq, core/database/src/commonMain/kotlin/.../repository/SampleRepository.kt, .../SQLDelightSampleRepositoryImpl.kt
Added sample table SQLDelight schema, SampleRepository interface and SampleItem, and SQLDelight-backed repository implementation using Flow and suspend operations.
DI modules & initializer
core/database/src/commonMain/kotlin/org/mifos/core/database/di/SQLDelightModule.kt, .../MifosSQLDatabaseInitializer.kt, .../DatabaseModule.kt
Introduced sqlDelightModule, DB_FILE_NAME, expect/actual driver pattern, async schema initializer, and wired module into DatabaseModule.
Platform driver implementations
core/database/src/*Main/.../SQLDelightModule.*.kt (androidMain, desktopMain, nativeMain, jsMain, wasmJsMain)
Added actual driverModule per platform (AndroidSqliteDriver, JdbcSqliteDriver, NativeSqliteDriver, web-worker driver, etc.).
Test suites & test DI
core/database/src/commonTest/.../SQLDelightRepositoryTest.kt, core/database/src/*Test/.../SQLDelightRepository*.kt, core/database/src/*Test/.../TestSQLDelightModule*.kt, core/database/src/*Test/.../TestDatabaseModule.*.kt
Added abstract cross-platform test suite, platform-specific test subclasses, and Koin test modules providing in-memory or platform test drivers with schema initialization.
Webpack / Karma sql.js asset handling
cmp-web/webpack.config.d/sqljs.js, cmp-web/karma.config.d/sqljs.js, core/database/src/webpack.config.d/sqljs-config.js, core/database/karma.config.d/sqljs-config.js
Added CopyWebpackPlugin and Karma config to copy/serve sql-wasm.wasm, adjust webpack output and resolution for SQL.js in tests.
Miscellaneous
gradle/wrapper/gradle-wrapper.properties, cmp-shared/cmp_shared.podspec
Updated Gradle wrapper to 9.1.0 and bumped podspec version.

Sequence Diagram(s)

sequenceDiagram
    participant Gradle as Gradle (KMPSQLDelight)
    participant App as Application (Koin Init)
    participant Koin as Koin DI
    participant Driver as SqlDriver (platform)
    participant Schema as MifosSQLDelightDatabase.Schema
    participant Repo as SQLDelightSampleRepositoryImpl

    Gradle->>Gradle: Configure SQLDelight extension & drivers
    App->>Koin: Initialize Koin (includes sqlDelightModule)
    Koin->>Driver: Create platform-specific SqlDriver
    Driver-->>Koin: Return SqlDriver
    Koin->>Schema: Launch async schema creation (awaitCreate / synchronous)
    Schema-->>Koin: Schema ready
    Koin->>Repo: Provide MifosSQLDelightDatabase -> repository instantiated
    App->>Repo: getAllSamples()
    Repo->>Schema: Execute selectAll query
    Schema-->>Repo: Return rows -> Flow<List<SampleItem>>
    Repo-->>App: Emit samples
Loading
sequenceDiagram
    participant Test as Test Suite
    participant Koin as Koin Test DI
    participant Driver as Test SqlDriver (in-memory)
    participant Schema as Schema Init
    participant Repo as SampleRepository

    Test->>Koin: Start test Koin (includes testSQLDelightPlatformModule)
    Koin->>Driver: Create in-memory driver
    Driver-->>Koin: Driver ready
    Koin->>Schema: Await schema creation
    Schema-->>Koin: Schema initialized
    Koin->>Repo: Provide repository
    Test->>Repo: insertSample / query / delete
    Repo->>Schema: Run SQL operations
    Schema-->>Repo: Results
    Repo-->>Test: Assertions
    Test->>Koin: Stop Koin & close driver
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • niyajali

Poem

🐇 I hopped through gradle, schema, and test,
Planted a table and queries at best,
Drivers on platforms wake with a cheer,
Repos and flows now gather near,
Hooray — the database garden grows clear! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'KMPPT-91 Integrate SQLDlight in KMP Template' clearly and concisely describes the main objective of the changeset: integrating SQLDelight as the primary database solution for the Kotlin Multiplatform project template.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

🟡 Minor comments (9)
core/database/build.gradle.kts-1-10 (1)

1-10: ⚠️ Potential issue | 🟡 Minor

Remove the duplicated MPL header.

The new 2026 block was added above the existing 2025 block, so this file now carries two license headers. Keep a single header here to avoid noisy Spotless/license-header churn.

As per coding guidelines "Apply code formatting with Spotless before committing code in Kotlin Multiplatform projects".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/database/build.gradle.kts` around lines 1 - 10, This file currently
contains a duplicated MPL license header (the new 2026 block added above an
existing 2025 block); remove the older/duplicate header and keep a single
license header (prefer the updated 2026 block) so the file has only one comment
header at the top, then run Spotless formatting to normalize the file; look for
the top comment blocks starting with "Copyright 2026" and the older "Copyright
2025" and delete the redundant one.
core/database/src/androidUnitTest/kotlin/org/mifos/core/database/SQLDelightRepositoryAndroidTest.kt-8-8 (1)

8-8: ⚠️ Potential issue | 🟡 Minor

Minor typo in license header: duplicate "See".

- * See See https://github.com/openMF/kmp-project-template/blob/main/LICENSE
+ * See https://github.com/openMF/kmp-project-template/blob/main/LICENSE
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@core/database/src/androidUnitTest/kotlin/org/mifos/core/database/SQLDelightRepositoryAndroidTest.kt`
at line 8, The license header contains a duplicated word "See See" — update the
header text to remove the duplicate so it reads "See
https://github.com/openMF/kmp-project-template/blob/main/LICENSE"; this change
is in the file near the top of the test class SQLDelightRepositoryAndroidTest.kt
(the license header comment above the class).
core/database/src/nativeTest/kotlin/org/mifos/core/database/di/TestSQLDelightModule.native.kt-8-8 (1)

8-8: ⚠️ Potential issue | 🟡 Minor

Minor typo in license header: duplicate "See".

- * See See https://github.com/openMF/kmp-project-template/blob/main/LICENSE
+ * See https://github.com/openMF/kmp-project-template/blob/main/LICENSE
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@core/database/src/nativeTest/kotlin/org/mifos/core/database/di/TestSQLDelightModule.native.kt`
at line 8, Fix the duplicate word in the file's license header by removing the
extra "See" so the header reads "See
https://github.com/openMF/kmp-project-template/blob/main/LICENSE"; update the
top-of-file license comment in TestSQLDelightModule.native.kt (the header
comment block) to eliminate the repeated "See".
core/database/src/wasmJsTest/kotlin/org/mifos/core/database/SQLDelightRepositoryWasmJsTest.kt-8-8 (1)

8-8: ⚠️ Potential issue | 🟡 Minor

Minor typo in license header: duplicate "See".

- * See See https://github.com/openMF/kmp-project-template/blob/main/LICENSE
+ * See https://github.com/openMF/kmp-project-template/blob/main/LICENSE
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@core/database/src/wasmJsTest/kotlin/org/mifos/core/database/SQLDelightRepositoryWasmJsTest.kt`
at line 8, The license header contains a duplicated word "See See" at the top of
SQLDelightRepositoryWasmJsTest.kt; edit the file's header comment to remove the
extra "See" so it reads "See
https://github.com/openMF/kmp-project-template/blob/main/LICENSE" (locate the
header comment in SQLDelightRepositoryWasmJsTest.kt and correct the duplicated
word).
core/database/src/nativeMain/kotlin/org/mifos/core/database/di/SQLDelightModule.native.kt-8-8 (1)

8-8: ⚠️ Potential issue | 🟡 Minor

Minor typo in license header: "See See" should be "See".

The license header contains a duplicate word.

- * See See https://github.com/openMF/kmp-project-template/blob/main/LICENSE
+ * See https://github.com/openMF/kmp-project-template/blob/main/LICENSE
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@core/database/src/nativeMain/kotlin/org/mifos/core/database/di/SQLDelightModule.native.kt`
at line 8, Fix the license header typo by removing the duplicate word so the
header reads "See
https://github.com/openMF/kmp-project-template/blob/main/LICENSE" instead of
"See See ..."; update the header comment in SQLDelightModule.native.kt (the
top-of-file license comment) to replace "See See" with a single "See".
core/database/src/androidMain/kotlin/org/mifos/core/database/di/SQLDelightModule.android.kt-8-8 (1)

8-8: ⚠️ Potential issue | 🟡 Minor

Minor typo in license header: duplicate "See".

- * See See https://github.com/openMF/kmp-project-template/blob/main/LICENSE
+ * See https://github.com/openMF/kmp-project-template/blob/main/LICENSE
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@core/database/src/androidMain/kotlin/org/mifos/core/database/di/SQLDelightModule.android.kt`
at line 8, The license header contains a duplicated word "See See" — update the
header by removing the extra "See" so it reads "See
https://github.com/openMF/kmp-project-template/blob/main/LICENSE"; locate the
string "See See" in the file
(core/database/src/androidMain/kotlin/org/mifos/core/database/di/SQLDelightModule.android.kt)
and replace it with the corrected single "See".
core/database/src/desktopTest/kotlin/org/mifos/core/database/di/TestSQLDelightModule.desktop.kt-8-8 (1)

8-8: ⚠️ Potential issue | 🟡 Minor

Minor typo in license header: duplicate "See".

- * See See https://github.com/openMF/kmp-project-template/blob/main/LICENSE
+ * See https://github.com/openMF/kmp-project-template/blob/main/LICENSE
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@core/database/src/desktopTest/kotlin/org/mifos/core/database/di/TestSQLDelightModule.desktop.kt`
at line 8, The license header contains a duplicated word "See See" — remove the
extra "See" so the header reads "See
https://github.com/openMF/kmp-project-template/blob/main/LICENSE"; update the
header comment in TestSQLDelightModule.desktop.kt (look for the string "See
See") to fix the typo.
core/database/src/wasmJsMain/kotlin/org/mifos/core/database/di/SQLDelightModule.wasmJs.kt-8-8 (1)

8-8: ⚠️ Potential issue | 🟡 Minor

Minor typo in license header: duplicate "See".

- * See See https://github.com/openMF/kmp-project-template/blob/main/LICENSE
+ * See https://github.com/openMF/kmp-project-template/blob/main/LICENSE
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@core/database/src/wasmJsMain/kotlin/org/mifos/core/database/di/SQLDelightModule.wasmJs.kt`
at line 8, The license header contains a duplicated word "See See"—remove the
extra "See" so the header reads "See
https://github.com/openMF/kmp-project-template/blob/main/LICENSE"; update the
header comment in SQLDelightModule.wasmJs.kt (the top-of-file license header) to
remove the duplicate word while preserving the rest of the license text and
link.
core/database/src/commonTest/kotlin/org/mifos/core/database/SQLDelightRepositoryTest.kt-8-8 (1)

8-8: ⚠️ Potential issue | 🟡 Minor

Minor typo: Duplicate "See" in license header.

- * See See https://github.com/openMF/kmp-project-template/blob/main/LICENSE
+ * See https://github.com/openMF/kmp-project-template/blob/main/LICENSE
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@core/database/src/commonTest/kotlin/org/mifos/core/database/SQLDelightRepositoryTest.kt`
at line 8, The license header contains a duplicated word "See See" in the header
comment; edit the header comment (the license header line containing "See See
https://github.com/openMF/kmp-project-template/blob/main/LICENSE") and remove
the extra "See" so it reads "See
https://github.com/openMF/kmp-project-template/blob/main/LICENSE".
🧹 Nitpick comments (4)
core/database/src/commonMain/sqldelight/org/mifos/core/database/Sample.sq (1)

9-10: OR REPLACE does not buy anything here.

id is autogenerated and name is not unique, so this currently behaves like a plain insert. Keeping REPLACE is misleading, and if a unique constraint is added later it will switch to delete+insert semantics that can unexpectedly change row ids.

Suggested fix
 insertSample:
-INSERT OR REPLACE INTO sample (name) VALUES (:name);
+INSERT INTO sample (name) VALUES (:name);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/database/src/commonMain/sqldelight/org/mifos/core/database/Sample.sq`
around lines 9 - 10, The SQL statement for insertSample should not use "OR
REPLACE" since id is autogenerated and name is not unique; update the
insertSample query to a plain INSERT INTO sample (name) VALUES (:name) (remove
"OR REPLACE") so it performs a normal insert into the sample table for the name
parameter.
core/database/src/androidUnitTest/kotlin/org/mifos/core/database/SQLDelightRepositoryAndroidTest.kt (1)

35-38: Redundant override: teardown() only delegates to super.

This override can be removed since it adds no platform-specific cleanup logic.

♻️ Suggested removal
-    `@AfterTest`
-    override fun teardown() {
-        super.teardown()
-    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@core/database/src/androidUnitTest/kotlin/org/mifos/core/database/SQLDelightRepositoryAndroidTest.kt`
around lines 35 - 38, Remove the redundant `@AfterTest` override teardown() in
SQLDelightRepositoryAndroidTest (the method that only calls super.teardown()),
since it adds no platform-specific cleanup; delete the entire override
(annotation and method) so the superclass implementation is used directly and
no-op delegation remains.
core/database/src/commonTest/kotlin/org/mifos/core/database/SQLDelightRepositoryTest.kt (1)

80-99: Consider simplifying the delete test by extracting ID retrieval.

The current pattern of capturing aliceId via reassignment inside a Turbine block is functional but somewhat awkward. Consider extracting the ID retrieval logic for clarity.

♻️ Suggested refactor using a helper function
+    private suspend fun getIdByName(name: String): Long {
+        var id = 0L
+        repository.getAllSamples().test {
+            id = awaitItem().first { it.name == name }.id
+            cancelAndIgnoreRemainingEvents()
+        }
+        return id
+    }
+
     `@Test`
     fun deleteSampleRemovesItFromDatabase() = runTest {
         repository.insertSample("Alice")
         repository.insertSample("Bob")

-        var aliceId = 0L
-        repository.getAllSamples().test {
-            aliceId = awaitItem().first { it.name == "Alice" }.id
-            cancelAndIgnoreRemainingEvents()
-        }
+        val aliceId = getIdByName("Alice")

         repository.deleteById(aliceId)

         repository.getAllSamples().test {
             val remaining = awaitItem()
             assertEquals(1, remaining.size)
             assertEquals("Bob", remaining[0].name)
             cancelAndIgnoreRemainingEvents()
         }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@core/database/src/commonTest/kotlin/org/mifos/core/database/SQLDelightRepositoryTest.kt`
around lines 80 - 99, Refactor the test deleteSampleRemovesItFromDatabase by
extracting the logic that retrieves Alice's id from the flow into a small helper
used by the test (e.g., a suspend function like getIdByName(name: String) that
collects repository.getAllSamples(), awaits the first emitted list, finds the
item with the given name and returns its id); then call this helper to obtain
aliceId before calling repository.deleteById(aliceId), keeping the assertions in
the Turbine blocks unchanged for clarity and reuse.
core/database/src/webpack.config.d/sqljs-config.js (1)

9-15: Resolve the wasm asset via module resolution.

CopyWebpackPlugin resolves relative from values from webpack's context, not from this snippet file, so '../../node_modules/...' is tightly coupled to the generated build layout. Using require.resolve("sql.js/dist/sql-wasm.wasm") is more stable across Gradle/Kotlin JS output locations. (webpack.js.org)

Suggested refactor
 const CopyWebpackPlugin = require('copy-webpack-plugin');
 config.plugins.push(
     new CopyWebpackPlugin({
         patterns: [
-            '../../node_modules/sql.js/dist/sql-wasm.wasm'
+            require.resolve("sql.js/dist/sql-wasm.wasm")
         ]
     })
 );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/database/src/webpack.config.d/sqljs-config.js` around lines 9 - 15, In
the CopyWebpackPlugin patterns array, replace the hardcoded relative path string
`'../../node_modules/sql.js/dist/sql-wasm.wasm'` with a dynamic module
resolution using require.resolve() to resolve the sql.js package and its wasm
file path. This will decouple the configuration from the specific build layout
and make it more robust across different Gradle/Kotlin JS output locations.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@build-logic/convention/src/main/kotlin/KMPSQLDelightConventionPlugin.kt`:
- Around line 17-25: The SQLDelight configuration currently disables migration
verification in the SqlDelightExtension block created via
databases.create(DATABASE_NAME) by setting verifyMigrations.set(false); update
this to enable verification (e.g., set verifyMigrations.set(true) or remove the
explicit false override) so modules using the KMPSQLDelightConventionPlugin
inherit migration checks; ensure the change is made in the
configure<SqlDelightExtension> -> databases.create(DATABASE_NAME) section where
packageName, generateAsync and schemaOutputDirectory are set.

In `@core/database/build.gradle.kts`:
- Around line 44-52: The CHROME_BIN environment is only set when
isInstalled("brave") is true, missing cases where the binary is named
"brave-browser"; update both testTask blocks that currently call
environment("CHROME_BIN", "brave") to handle either binary: check
isInstalled("brave") || isInstalled("brave-browser") and set CHROME_BIN to
"brave" if isInstalled("brave") otherwise "brave-browser" (use the same logic in
both places where environment("CHROME_BIN", "brave") is currently set).
- Around line 98-103: The build uses a non-existent source set jsCommonMain so
the npm dependencies aren’t attached to any target; either move the dependency
blocks into the actual JS targets by adding the npm/sql deps to
jsMain.dependencies and wasmJsMain.dependencies, or explicitly create a
jsCommonMain source set (val jsCommonMain by creating {
dependsOn(commonMain.get()) }) and wire it into the hierarchy so that
applyProjectHierarchyTemplate() will propagate those dependencies; update the
blocks referencing implementation(npm(...))/implementation(devNpm(...))
accordingly to ensure the npm packages are available at runtime.

In `@core/database/karma.config.d/sqljs-config.js`:
- Around line 3-15: The wasm path resolution and proxy URL construction are
fragile: replace the manual path.resolve logic with require.resolve to locate
the wasm file (use require.resolve("sql.js/dist/sql-wasm.wasm") to set wasm),
ensure the filesystem path is converted to a URL-friendly form by
normalizing/backslash-replacing (e.g., replace backslashes with forward slashes)
before building the proxy string, and guard access to config.proxies (create it
if falsy) before assigning config.proxies["/sql-wasm.wasm"] =
`/absolute${normalizedWasmPath}`; update references to dist, wasm, config.files
and config.proxies accordingly.

In
`@core/database/src/commonMain/kotlin/org/mifos/core/database/di/MifosSQLDatabaseInitializer.kt`:
- Around line 22-25: The initialize() function currently launches schema
creation asynchronously (scope.launch {
MifosSQLDelightDatabase.Schema.awaitCreate(driver) }) which returns immediately
and can allow DB access before the schema exists; change initialize() to perform
schema creation synchronously or provide an awaitable completion: either make
initialize() a suspend function and call
MifosSQLDelightDatabase.Schema.awaitCreate(driver) directly, or have
initialize() return the launching Job/Deferred so callers can await it; update
any callers and DI wiring to call/await the new suspend initialize() (or await
the returned Job) instead of relying on scope.launch.

In
`@core/database/src/commonMain/kotlin/org/mifos/core/database/di/SQLDelightModule.kt`:
- Around line 23-31: The DI currently starts MifosSQLDatabaseInitializer
asynchronously (MifosSQLDatabaseInitializer.initialize() launches a coroutine)
but then immediately registers MifosSQLDelightDatabase, causing a race; change
the registration so initialization completes before creating
MifosSQLDelightDatabase: make the initializer expose a suspend/blocking init (or
call initialize() inside runBlocking) in the single(createdAtStart = true) block
and only after that return/create MifosSQLDelightDatabase, or else make the
MifosSQLDelightDatabase single depend on a synchronous result from
MifosSQLDatabaseInitializer.initialize() so Koin blocks until schema creation
finishes before resolving MifosSQLDelightDatabase.

In `@core/database/src/commonMain/sqldelight/org/mifos/core/database/Sample.sq`:
- Around line 6-7: The selectAll query in Sample.sq returns rows without a
stable order; update the selectAll SQL to include an explicit ORDER BY using a
stable column (e.g., the primary key or a timestamp) so results are
deterministic across drivers — for example change the selectAll query in
Sample.sq to SELECT * FROM sample ORDER BY <primary_key_or_created_at> ASC (use
the actual PK column name or rowid if no explicit PK).

In
`@core/database/src/desktopMain/kotlin/org/mifos/core/database/di/SQLDelightModule.desktop.kt`:
- Around line 21-24: The desktop driver currently calls
MifosSQLDelightDatabase.Schema.create(...) during JdbcSqliteDriver instantiation
which is non-idempotent and will fail if the DB file exists; remove that call
and either wrap the schema with MifosSQLDelightDatabase.Schema.synchronous(...)
when passing it to the driver (matching Android/Native), or omit schema creation
here entirely and let MifosSQLDatabaseInitializer perform initialization via its
coroutine flow; update the code around JdbcSqliteDriver(...) to stop invoking
Schema.create and use Schema.synchronous or defer to MifosSQLDatabaseInitializer
accordingly.

In
`@core/database/src/jsTest/kotlin/org/mifos/core/database/di/TestSQLDelightModule.js.kt`:
- Around line 21-26: The JS test module returns the SqlDriver before schema
creation completes because schema setup runs asynchronously in
MainScope().launch; change the initialization inside the single<SqlDriver>
provider to perform schema creation synchronously by using runBlocking and
awaiting MifosSQLDelightDatabase.Schema.awaitCreate(driver) before returning the
driver (replace the MainScope().launch block with runBlocking around the await
call for createDefaultWebWorkerDriver()), ensuring the driver is only returned
after the schema is created.

In
`@core/database/src/nativeTest/kotlin/org/mifos/core/database/di/TestDatabaseModule.native.kt`:
- Around line 14-15: Remove the invalid top-level import kotlinx.coroutines.IO
and instead reference Dispatchers.IO directly where used (keep the existing
import kotlinx.coroutines.Dispatchers), and remove the unnecessary explicit cast
to CoroutineContext so code uses Dispatchers.IO as a CoroutineContext without
casting (look for usages in TestDatabaseModule.native.kt referencing
Dispatchers, IO, and CoroutineContext).

In
`@core/database/src/wasmJsTest/kotlin/org/mifos/core/database/di/TestSQLDelightModule.wasmJs.kt`:
- Around line 21-26: The driver is returned before schema creation completes
because createDefaultWebWorkerDriver() uses MainScope().launch; replace the
detached coroutine with a blocking wait so initialization finishes before
returning: in the single<SqlDriver> provider that calls
createDefaultWebWorkerDriver(), invoke runBlocking {
MifosSQLDelightDatabase.Schema.awaitCreate(driver) } (using the returned driver
variable) instead of MainScope().launch, ensuring the test driver is fully
initialized before being returned.

In `@core/database/src/webpack.config.d/sqljs-config.js`:
- Around line 1-7: The current assignment replaces the entire config.resolve
object and loses defaults; instead merge the fallback into the existing resolve
object by updating config.resolve rather than overwriting it (e.g., set
config.resolve = { ...config.resolve, fallback: { fs: false, path: false,
crypto: false } } or assign to config.resolve.fallback), ensuring existing
resolve properties (like extensions and modules) are preserved while adding the
fallback entries.

---

Minor comments:
In `@core/database/build.gradle.kts`:
- Around line 1-10: This file currently contains a duplicated MPL license header
(the new 2026 block added above an existing 2025 block); remove the
older/duplicate header and keep a single license header (prefer the updated 2026
block) so the file has only one comment header at the top, then run Spotless
formatting to normalize the file; look for the top comment blocks starting with
"Copyright 2026" and the older "Copyright 2025" and delete the redundant one.

In
`@core/database/src/androidMain/kotlin/org/mifos/core/database/di/SQLDelightModule.android.kt`:
- Line 8: The license header contains a duplicated word "See See" — update the
header by removing the extra "See" so it reads "See
https://github.com/openMF/kmp-project-template/blob/main/LICENSE"; locate the
string "See See" in the file
(core/database/src/androidMain/kotlin/org/mifos/core/database/di/SQLDelightModule.android.kt)
and replace it with the corrected single "See".

In
`@core/database/src/androidUnitTest/kotlin/org/mifos/core/database/SQLDelightRepositoryAndroidTest.kt`:
- Line 8: The license header contains a duplicated word "See See" — update the
header text to remove the duplicate so it reads "See
https://github.com/openMF/kmp-project-template/blob/main/LICENSE"; this change
is in the file near the top of the test class SQLDelightRepositoryAndroidTest.kt
(the license header comment above the class).

In
`@core/database/src/commonTest/kotlin/org/mifos/core/database/SQLDelightRepositoryTest.kt`:
- Line 8: The license header contains a duplicated word "See See" in the header
comment; edit the header comment (the license header line containing "See See
https://github.com/openMF/kmp-project-template/blob/main/LICENSE") and remove
the extra "See" so it reads "See
https://github.com/openMF/kmp-project-template/blob/main/LICENSE".

In
`@core/database/src/desktopTest/kotlin/org/mifos/core/database/di/TestSQLDelightModule.desktop.kt`:
- Line 8: The license header contains a duplicated word "See See" — remove the
extra "See" so the header reads "See
https://github.com/openMF/kmp-project-template/blob/main/LICENSE"; update the
header comment in TestSQLDelightModule.desktop.kt (look for the string "See
See") to fix the typo.

In
`@core/database/src/nativeMain/kotlin/org/mifos/core/database/di/SQLDelightModule.native.kt`:
- Line 8: Fix the license header typo by removing the duplicate word so the
header reads "See
https://github.com/openMF/kmp-project-template/blob/main/LICENSE" instead of
"See See ..."; update the header comment in SQLDelightModule.native.kt (the
top-of-file license comment) to replace "See See" with a single "See".

In
`@core/database/src/nativeTest/kotlin/org/mifos/core/database/di/TestSQLDelightModule.native.kt`:
- Line 8: Fix the duplicate word in the file's license header by removing the
extra "See" so the header reads "See
https://github.com/openMF/kmp-project-template/blob/main/LICENSE"; update the
top-of-file license comment in TestSQLDelightModule.native.kt (the header
comment block) to eliminate the repeated "See".

In
`@core/database/src/wasmJsMain/kotlin/org/mifos/core/database/di/SQLDelightModule.wasmJs.kt`:
- Line 8: The license header contains a duplicated word "See See"—remove the
extra "See" so the header reads "See
https://github.com/openMF/kmp-project-template/blob/main/LICENSE"; update the
header comment in SQLDelightModule.wasmJs.kt (the top-of-file license header) to
remove the duplicate word while preserving the rest of the license text and
link.

In
`@core/database/src/wasmJsTest/kotlin/org/mifos/core/database/SQLDelightRepositoryWasmJsTest.kt`:
- Line 8: The license header contains a duplicated word "See See" at the top of
SQLDelightRepositoryWasmJsTest.kt; edit the file's header comment to remove the
extra "See" so it reads "See
https://github.com/openMF/kmp-project-template/blob/main/LICENSE" (locate the
header comment in SQLDelightRepositoryWasmJsTest.kt and correct the duplicated
word).

---

Nitpick comments:
In
`@core/database/src/androidUnitTest/kotlin/org/mifos/core/database/SQLDelightRepositoryAndroidTest.kt`:
- Around line 35-38: Remove the redundant `@AfterTest` override teardown() in
SQLDelightRepositoryAndroidTest (the method that only calls super.teardown()),
since it adds no platform-specific cleanup; delete the entire override
(annotation and method) so the superclass implementation is used directly and
no-op delegation remains.

In `@core/database/src/commonMain/sqldelight/org/mifos/core/database/Sample.sq`:
- Around line 9-10: The SQL statement for insertSample should not use "OR
REPLACE" since id is autogenerated and name is not unique; update the
insertSample query to a plain INSERT INTO sample (name) VALUES (:name) (remove
"OR REPLACE") so it performs a normal insert into the sample table for the name
parameter.

In
`@core/database/src/commonTest/kotlin/org/mifos/core/database/SQLDelightRepositoryTest.kt`:
- Around line 80-99: Refactor the test deleteSampleRemovesItFromDatabase by
extracting the logic that retrieves Alice's id from the flow into a small helper
used by the test (e.g., a suspend function like getIdByName(name: String) that
collects repository.getAllSamples(), awaits the first emitted list, finds the
item with the given name and returns its id); then call this helper to obtain
aliceId before calling repository.deleteById(aliceId), keeping the assertions in
the Turbine blocks unchanged for clarity and reuse.

In `@core/database/src/webpack.config.d/sqljs-config.js`:
- Around line 9-15: In the CopyWebpackPlugin patterns array, replace the
hardcoded relative path string `'../../node_modules/sql.js/dist/sql-wasm.wasm'`
with a dynamic module resolution using require.resolve() to resolve the sql.js
package and its wasm file path. This will decouple the configuration from the
specific build layout and make it more robust across different Gradle/Kotlin JS
output locations.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c72371c2-a1ea-4c50-b5df-f70c23b9582d

📥 Commits

Reviewing files that changed from the base of the PR and between c4a85c9 and b48e700.

⛔ Files ignored due to path filters (2)
  • kotlin-js-store/wasm/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
  • kotlin-js-store/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (42)
  • build-logic/convention/build.gradle.kts
  • build-logic/convention/src/main/kotlin/KMPCoreBaseLibraryConventionPlugin.kt
  • build-logic/convention/src/main/kotlin/KMPSQLDelightConventionPlugin.kt
  • build-logic/convention/src/main/kotlin/org/convention/KotlinMultiplatform.kt
  • build.gradle.kts
  • cmp-shared/cmp_shared.podspec
  • cmp-web/karma.config.d/sqljs.js
  • cmp-web/webpack.config.d/sqljs.js
  • core-base/database/build.gradle.kts
  • core/database/build.gradle.kts
  • core/database/karma.config.d/sqljs-config.js
  • core/database/src/androidMain/kotlin/org/mifos/core/database/di/SQLDelightModule.android.kt
  • core/database/src/androidUnitTest/kotlin/org/mifos/core/database/SQLDelightRepositoryAndroidTest.kt
  • core/database/src/androidUnitTest/kotlin/org/mifos/core/database/di/TestDatabaseModule.android.kt
  • core/database/src/androidUnitTest/kotlin/org/mifos/core/database/di/TestSQLDelightModule.android.kt
  • core/database/src/commonMain/kotlin/org/mifos/core/database/di/DatabaseModule.kt
  • core/database/src/commonMain/kotlin/org/mifos/core/database/di/MifosSQLDatabaseInitializer.kt
  • core/database/src/commonMain/kotlin/org/mifos/core/database/di/SQLDelightModule.kt
  • core/database/src/commonMain/kotlin/org/mifos/core/database/repository/SQLDelightSampleRepositoryImpl.kt
  • core/database/src/commonMain/kotlin/org/mifos/core/database/repository/SampleRepository.kt
  • core/database/src/commonMain/sqldelight/org/mifos/core/database/Sample.sq
  • core/database/src/commonTest/kotlin/org/mifos/core/database/SQLDelightRepositoryTest.kt
  • core/database/src/commonTest/kotlin/org/mifos/core/database/di/TestSQLDelightModule.kt
  • core/database/src/desktopMain/kotlin/org/mifos/core/database/di/SQLDelightModule.desktop.kt
  • core/database/src/desktopTest/kotlin/org/mifos/core/database/SQLDelightRepositoryDesktopTest.kt
  • core/database/src/desktopTest/kotlin/org/mifos/core/database/di/TestDatabaseModule.desktop.kt
  • core/database/src/desktopTest/kotlin/org/mifos/core/database/di/TestSQLDelightModule.desktop.kt
  • core/database/src/jsMain/kotlin/org/mifos/core/database/di/SQLDelightModule.js.kt
  • core/database/src/jsTest/kotlin/org/mifos/core/database/SQLDelightRepositoryJsTest.kt
  • core/database/src/jsTest/kotlin/org/mifos/core/database/di/TestSQLDelightModule.js.kt
  • core/database/src/nativeMain/kotlin/org/mifos/core/database/di/DatabaseModule.native.kt
  • core/database/src/nativeMain/kotlin/org/mifos/core/database/di/SQLDelightModule.native.kt
  • core/database/src/nativeTest/kotlin/org/mifos/core/database/SQLDelightRepositoryNativeTest.kt
  • core/database/src/nativeTest/kotlin/org/mifos/core/database/di/TestDatabaseModule.native.kt
  • core/database/src/nativeTest/kotlin/org/mifos/core/database/di/TestSQLDelightModule.native.kt
  • core/database/src/nonJsCommonTest/kotlin/org/mifos/core/database/di/TestDatabaseModule.kt
  • core/database/src/wasmJsMain/kotlin/org/mifos/core/database/di/SQLDelightModule.wasmJs.kt
  • core/database/src/wasmJsTest/kotlin/org/mifos/core/database/SQLDelightRepositoryWasmJsTest.kt
  • core/database/src/wasmJsTest/kotlin/org/mifos/core/database/di/TestSQLDelightModule.wasmJs.kt
  • core/database/src/webpack.config.d/sqljs-config.js
  • gradle/libs.versions.toml
  • gradle/wrapper/gradle-wrapper.properties
💤 Files with no reviewable changes (1)
  • build-logic/convention/src/main/kotlin/KMPCoreBaseLibraryConventionPlugin.kt

Comment on lines +98 to +103
jsCommonMain.dependencies {
implementation(libs.sqldelight.runtime)
implementation(npm("@cashapp/sqldelight-sqljs-worker", "2.2.1"))
implementation(npm("sql.js", "1.10.3"))
implementation(devNpm("copy-webpack-plugin", "12.0.2"))
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether `jsCommonMain` is declared anywhere in the Gradle sources.
rg -n -C2 '\bjsCommonMain\b' --glob '*.gradle.kts'

# If it is custom, there should also be an explicit creation/dependsOn chain nearby.
rg -n -C2 'by creating|dependsOn\s*\(' --glob '*.gradle.kts'

Repository: openMF/kmp-project-template

Length of output: 1038


🏁 Script executed:

cat -n core/database/build.gradle.kts

Repository: openMF/kmp-project-template

Length of output: 4605


🏁 Script executed:

cat -n build.gradle.kts

Repository: openMF/kmp-project-template

Length of output: 3506


🏁 Script executed:

cat -n settings.gradle.kts

Repository: openMF/kmp-project-template

Length of output: 3148


🏁 Script executed:

find build-logic -name "*.gradle.kts" -o -name "*.kt" | head -20

Repository: openMF/kmp-project-template

Length of output: 1485


🏁 Script executed:

rg -r 'jsCommonMain' build-logic/

Repository: openMF/kmp-project-template

Length of output: 171


🏁 Script executed:

cat -n build-logic/convention/src/main/kotlin/KMPLibraryConventionPlugin.kt

Repository: openMF/kmp-project-template

Length of output: 2406


🏁 Script executed:

cat -n build-logic/convention/src/main/kotlin/KMPSQLDelightConventionPlugin.kt

Repository: openMF/kmp-project-template

Length of output: 2054


🏁 Script executed:

cat -n build-logic/convention/src/main/kotlin/org/convention/Detekt.kt

Repository: openMF/kmp-project-template

Length of output: 1839


🏁 Script executed:

cat -n build-logic/convention/src/main/kotlin/org/convention/KotlinMultiplatform.kt

Repository: openMF/kmp-project-template

Length of output: 1627


Remove or replace jsCommonMain — this source set does not exist in the default KMP hierarchy.

The convention plugin uses applyProjectHierarchyTemplate() which creates jsMain and wasmJsMain as separate targets, not a shared jsCommonMain. Dependencies declared here will not attach to either JS or WASM targets, leaving the npm dependencies unavailable at runtime.

Add these dependencies directly to both targets instead:

jsMain.dependencies { /* ... */ }
wasmJsMain.dependencies { /* ... */ }

Or explicitly create the custom jsCommonMain source set with val jsCommonMain by creating { dependsOn(commonMain.get()) } and update the source set hierarchy if truly needed for sharing.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/database/build.gradle.kts` around lines 98 - 103, The build uses a
non-existent source set jsCommonMain so the npm dependencies aren’t attached to
any target; either move the dependency blocks into the actual JS targets by
adding the npm/sql deps to jsMain.dependencies and wasmJsMain.dependencies, or
explicitly create a jsCommonMain source set (val jsCommonMain by creating {
dependsOn(commonMain.get()) }) and wire it into the hierarchy so that
applyProjectHierarchyTemplate() will propagate those dependencies; update the
blocks referencing implementation(npm(...))/implementation(devNpm(...))
accordingly to ensure the npm packages are available at runtime.

Comment on lines +3 to +15
const dist = path.resolve("../../node_modules/sql.js/dist/")
const wasm = path.join(dist, "sql-wasm.wasm")

config.files.push({
pattern: wasm,
served: true,
watched: false,
included: false,
nocache: false,
});

config.proxies["/sql-wasm.wasm"] = `/absolute${wasm}`

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -name "sqljs-config.js" -o -name "karma.config.d" -type d

Repository: openMF/kmp-project-template

Length of output: 226


🏁 Script executed:

git ls-files "**/karma.config.d/**" "**/sqljs-config.js"

Repository: openMF/kmp-project-template

Length of output: 198


🏁 Script executed:

find . -path "*/karma.config.d/*" -type f

Repository: openMF/kmp-project-template

Length of output: 151


🏁 Script executed:

cat -n core/database/karma.config.d/sqljs-config.js

Repository: openMF/kmp-project-template

Length of output: 1088


🏁 Script executed:

head -30 core/database/karma.config.d/sqljs-config.js

Repository: openMF/kmp-project-template

Length of output: 878


🏁 Script executed:

rg "config\.proxies" --type js -B 2 -A 2

Repository: openMF/kmp-project-template

Length of output: 449


🏁 Script executed:

find . -name "karma.conf.js" -o -name "karma.config.js" | head -5

Repository: openMF/kmp-project-template

Length of output: 53


🏁 Script executed:

rg "karma.config.d" --type js -l

Repository: openMF/kmp-project-template

Length of output: 53


🏁 Script executed:

find . -name "karma*" -type f | grep -E "\.(js|conf)" | head -20

Repository: openMF/kmp-project-template

Length of output: 53


🏁 Script executed:

ls -la core/database/ | grep -i karma

Repository: openMF/kmp-project-template

Length of output: 135


🏁 Script executed:

cat core/database/karma.conf.js 2>/dev/null || cat core/database/karma.config.js 2>/dev/null || echo "No karma config found in core/database"

Repository: openMF/kmp-project-template

Length of output: 109


🏁 Script executed:

find . -name "package.json" | xargs rg "karma" -l | head -5

Repository: openMF/kmp-project-template

Length of output: 141


🏁 Script executed:

cat core/database/package.json 2>/dev/null | head -50

Repository: openMF/kmp-project-template

Length of output: 53


🏁 Script executed:

find . -path "*/node_modules" -prune -o -name "*.js" -type f -exec grep -l "karma.config.d" {} \; | head -10

Repository: openMF/kmp-project-template

Length of output: 53


🏁 Script executed:

rg "karma.config.d" --type js -B 5 -A 5

Repository: openMF/kmp-project-template

Length of output: 53


🏁 Script executed:

find . -name "*.gradle.kts" -o -name "build.gradle.kts" | xargs grep -l "karma" 2>/dev/null | head -3

Repository: openMF/kmp-project-template

Length of output: 53


🏁 Script executed:

ls -la core/database/

Repository: openMF/kmp-project-template

Length of output: 558


🏁 Script executed:

cat core/database/build.gradle.kts

Repository: openMF/kmp-project-template

Length of output: 3807


🏁 Script executed:

cat core/database/karma.config.d/sqljs-config.js

Repository: openMF/kmp-project-template

Length of output: 878


🏁 Script executed:

rg "require.resolve" --type js | head -10

Repository: openMF/kmp-project-template

Length of output: 53


🏁 Script executed:

cat core/database/src/webpack.config.d/sqljs-config.js

Repository: openMF/kmp-project-template

Length of output: 384


🏁 Script executed:

rg "sql.js" --type json | head -20

Repository: openMF/kmp-project-template

Length of output: 53


🏁 Script executed:

node -e "console.log(require('path').resolve('../../node_modules/sql.js/dist/'))"

Repository: openMF/kmp-project-template

Length of output: 101


🏁 Script executed:

python3 << 'EOF'
# Demonstrate Windows backslash issue
import os
test_path = "C:\\path\\to\\sql-wasm.wasm"
url = f"/absolute{test_path}"
print(f"On Windows, direct concatenation would produce: {url}")
print(f"This is invalid as a URL because of backslashes")

# Show the fix
fixed_url = f"/absolute{test_path.replace(chr(92), '/')}"
print(f"With replace fix: {fixed_url}")
EOF

Repository: openMF/kmp-project-template

Length of output: 252


🏁 Script executed:

rg "__dirname" core/database/ --type js

Repository: openMF/kmp-project-template

Length of output: 53


🏁 Script executed:

find . -path "*/node_modules" -prune -o -type f -name "*.js" -exec grep -l "require.resolve.*sql" {} \;

Repository: openMF/kmp-project-template

Length of output: 53


Fix wasm path resolution and URL normalization for Windows compatibility.

Line 3 resolves ../../node_modules/... from the current working directory, making the path dependent on how Karma is launched. Line 14 concatenates that filesystem path directly into a URL string, which produces invalid proxy paths with backslashes on Windows (e.g., /absoluteC:\path\to\file.wasm). Use require.resolve() to reliably locate the wasm file and normalize backslashes when constructing the proxy URL. Additionally, add a defensive check before accessing config.proxies to ensure the object exists.

Suggested fix
-const dist = path.resolve("../../node_modules/sql.js/dist/")
-const wasm = path.join(dist, "sql-wasm.wasm")
+const wasm = require.resolve("sql.js/dist/sql-wasm.wasm");
+const wasmProxyPath = `/absolute${wasm.replace(/\\/g, "/")}`;

 config.files.push({
   pattern: wasm,
   served: true,
   watched: false,
   included: false,
   nocache: false,
 });

-config.proxies["/sql-wasm.wasm"] = `/absolute${wasm}`
+config.proxies = config.proxies ?? {};
+config.proxies["/sql-wasm.wasm"] = wasmProxyPath;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/database/karma.config.d/sqljs-config.js` around lines 3 - 15, The wasm
path resolution and proxy URL construction are fragile: replace the manual
path.resolve logic with require.resolve to locate the wasm file (use
require.resolve("sql.js/dist/sql-wasm.wasm") to set wasm), ensure the filesystem
path is converted to a URL-friendly form by normalizing/backslash-replacing
(e.g., replace backslashes with forward slashes) before building the proxy
string, and guard access to config.proxies (create it if falsy) before assigning
config.proxies["/sql-wasm.wasm"] = `/absolute${normalizedWasmPath}`; update
references to dist, wasm, config.files and config.proxies accordingly.

Comment on lines +22 to +25
fun initialize() {
scope.launch {
MifosSQLDelightDatabase.Schema.awaitCreate(driver)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, let's read the full file to understand the context
cat -n core/database/src/commonMain/kotlin/org/mifos/core/database/di/MifosSQLDatabaseInitializer.kt

Repository: openMF/kmp-project-template

Length of output: 1088


🏁 Script executed:

# Search for how initialize() is called in the codebase
rg "\.initialize\(\)" --type kt -B 2 -A 2

Repository: openMF/kmp-project-template

Length of output: 97


🏁 Script executed:

# Search for MifosSQLDatabaseInitializer usage
rg "MifosSQLDatabaseInitializer" --type kt -B 2 -A 2

Repository: openMF/kmp-project-template

Length of output: 97


🏁 Script executed:

# Search for SQLDelight schema and awaitCreate usage pattern
rg "awaitCreate|Schema\." --type kt -B 3 -A 3 | head -100

Repository: openMF/kmp-project-template

Length of output: 97


🏁 Script executed:

# Check if there's any migration or version handling in database initialization
rg "migrate|version|migration|schema" core/database --type kt -i | head -50

Repository: openMF/kmp-project-template

Length of output: 97


🏁 Script executed:

# Search for initialize() calls without type specification
rg "\.initialize\(\)" -B 3 -A 3

Repository: openMF/kmp-project-template

Length of output: 53


🏁 Script executed:

# Search for MifosSQLDatabaseInitializer usage
rg "MifosSQLDatabaseInitializer" -B 3 -A 3

Repository: openMF/kmp-project-template

Length of output: 1773


🏁 Script executed:

# Search for database initialization patterns
rg "awaitCreate|\.launch|SqlDriver" --glob="**/*.kt" | head -80

Repository: openMF/kmp-project-template

Length of output: 10340


🏁 Script executed:

# Check SQLDelight schema and migration patterns
find . -name "*.kt" -type f | xargs grep -l "Schema\|awaitCreate\|migrate" | head -10

Repository: openMF/kmp-project-template

Length of output: 977


🏁 Script executed:

# Look for the MifosSQLDelightDatabase definition to understand schema
find . -name "MifosSQLDelightDatabase*" -o -name "*Database.kt"

Repository: openMF/kmp-project-template

Length of output: 147


🏁 Script executed:

# Read the SQLDelightModule.kt file
cat -n core/database/src/commonMain/kotlin/org/mifos/core/database/di/SQLDelightModule.kt

Repository: openMF/kmp-project-template

Length of output: 1340


🏁 Script executed:

# Check if there's any code that waits for database initialization
rg "createdAtStart|initialize\(\)" core/database -B 5 -A 5

Repository: openMF/kmp-project-template

Length of output: 4231


🏁 Script executed:

# Check AppDatabase implementation
cat -n core/database/src/commonMain/kotlin/org/mifos/core/database/AppDatabase.kt

Repository: openMF/kmp-project-template

Length of output: 705


🏁 Script executed:

# Look for database schema definition and migration handling
find core/database -name "*.sq" -o -name "*Schema*" | head -20

Repository: openMF/kmp-project-template

Length of output: 144


🏁 Script executed:

# Check how database is used in the app
rg "MifosSQLDelightDatabase\|getDatabase\|database\.get" --glob="**/*.kt" | head -30

Repository: openMF/kmp-project-template

Length of output: 53


🏁 Script executed:

# Check SQLDelight schema API - look for migration or versioning
rg "Schema\.|awaitCreate" core/database --glob="**/*.kt" -B 2 -A 2

Repository: openMF/kmp-project-template

Length of output: 8426


🏁 Script executed:

# Look for sample.sq to understand schema structure
cat core/database/src/commonMain/sqldelight/org/mifos/core/database/Sample.sq

Repository: openMF/kmp-project-template

Length of output: 319


🏁 Script executed:

# Check if there's migration logic or schema versioning elsewhere
rg "version|migration|migrate" core/database --glob="**/*.kt" -i

Repository: openMF/kmp-project-template

Length of output: 1544


🏁 Script executed:

# Look for how MifosSQLDelightDatabase is actually used/accessed
rg "get<MifosSQLDelightDatabase|\.invoke\(\)" --glob="**/*.kt" -B 2 -A 2

Repository: openMF/kmp-project-template

Length of output: 7787


🏁 Script executed:

# Check test modules to see how they handle initialization
cat -n core/database/src/desktopTest/kotlin/org/mifos/core/database/di/TestSQLDelightModule.desktop.kt

Repository: openMF/kmp-project-template

Length of output: 1180


🏁 Script executed:

# Check if there are any docs or usage patterns for schema versioning
rg "Schema\.|version" core/database --glob="**/*.kt" -B 1 -A 1 | grep -A 2 -B 2 "awaitCreate\|synchronous"

Repository: openMF/kmp-project-template

Length of output: 3266


🏁 Script executed:

# Check if MifosSQLDelightDatabase.Schema is generated code and what methods it has
find . -name "*Database.kt" -path "*/database/*" | xargs grep -l "MifosSQLDelightDatabase" | head -5

Repository: openMF/kmp-project-template

Length of output: 53


Make schema initialization synchronous or provide a completion hook.

initialize() returns immediately after launching the coroutine, so database access after DI setup can occur before schema creation completes. On JS/WasmJs platforms this causes runtime query failures. Other platforms (Android, Native, Desktop) initialize the schema synchronously during driver setup; this should follow the same pattern or expose a suspend function that callers can await.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@core/database/src/commonMain/kotlin/org/mifos/core/database/di/MifosSQLDatabaseInitializer.kt`
around lines 22 - 25, The initialize() function currently launches schema
creation asynchronously (scope.launch {
MifosSQLDelightDatabase.Schema.awaitCreate(driver) }) which returns immediately
and can allow DB access before the schema exists; change initialize() to perform
schema creation synchronously or provide an awaitable completion: either make
initialize() a suspend function and call
MifosSQLDelightDatabase.Schema.awaitCreate(driver) directly, or have
initialize() return the launching Job/Deferred so callers can await it; update
any callers and DI wiring to call/await the new suspend initialize() (or await
the returned Job) instead of relying on scope.launch.

Comment on lines +21 to +24
JdbcSqliteDriver(
"jdbc:sqlite:$DB_FILE_NAME",
properties = Properties().apply { put("foreign_keys", "true") },
).also { MifosSQLDelightDatabase.Schema.create(it) }
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect how SQLDelight initialization is intended to happen across the codebase.
rg -n -C2 'MifosSQLDatabaseInitializer|Schema\.create\(|Schema\.migrate\(' --type=kt

Repository: openMF/kmp-project-template

Length of output: 97


🏁 Script executed:

#!/bin/bash
# Find the specific file and explore the repository structure
find . -name "SQLDelightModule.desktop.kt" -o -name "*SQLDatabaseInitializer*" | head -20

Repository: openMF/kmp-project-template

Length of output: 260


🏁 Script executed:

#!/bin/bash
# Search for SQLDelight initialization patterns without the -t flag
rg -n -C2 'MifosSQLDatabaseInitializer|Schema\.create|Schema\.migrate' | head -100

Repository: openMF/kmp-project-template

Length of output: 2004


🏁 Script executed:

#!/bin/bash
# Get the file structure to understand the layout
git ls-files | grep -E "(database|sqldelight)" | head -30

Repository: openMF/kmp-project-template

Length of output: 2313


🏁 Script executed:

#!/bin/bash
# View the desktop module file
cat -n core/database/src/desktopMain/kotlin/org/mifos/core/database/di/SQLDelightModule.desktop.kt

Repository: openMF/kmp-project-template

Length of output: 1144


🏁 Script executed:

#!/bin/bash
# View the shared initializer
cat -n core/database/src/commonMain/kotlin/org/mifos/core/database/di/MifosSQLDatabaseInitializer.kt

Repository: openMF/kmp-project-template

Length of output: 1088


🏁 Script executed:

#!/bin/bash
# View the common SQLDelightModule
cat -n core/database/src/commonMain/kotlin/org/mifos/core/database/di/SQLDelightModule.kt

Repository: openMF/kmp-project-template

Length of output: 1340


🏁 Script executed:

#!/bin/bash
# Check if Schema.create is called elsewhere and if there are other schema initialization patterns
rg -n 'Schema\.(create|awaitCreate|migrate)' core/database/

Repository: openMF/kmp-project-template

Length of output: 884


🏁 Script executed:

#!/bin/bash
# Check if Android or other platforms follow the proper pattern
find core/database -name "SQLDelightModule*.kt" -exec cat {} \;

Repository: openMF/kmp-project-template

Length of output: 4949


Remove Schema.create() call from desktop driver singleton and use Schema.synchronous() instead.

The current implementation calls Schema.create(it) on a file-backed database during driver instantiation. This is non-idempotent and will crash on the second app launch when the database file already exists. Use Schema.synchronous() (as Android and Native platforms do) to wrap the schema with proper idempotency handling, or remove schema creation from the driver and rely entirely on MifosSQLDatabaseInitializer which properly handles initialization through the coroutine-based flow.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@core/database/src/desktopMain/kotlin/org/mifos/core/database/di/SQLDelightModule.desktop.kt`
around lines 21 - 24, The desktop driver currently calls
MifosSQLDelightDatabase.Schema.create(...) during JdbcSqliteDriver instantiation
which is non-idempotent and will fail if the DB file exists; remove that call
and either wrap the schema with MifosSQLDelightDatabase.Schema.synchronous(...)
when passing it to the driver (matching Android/Native), or omit schema creation
here entirely and let MifosSQLDatabaseInitializer perform initialization via its
coroutine flow; update the code around JdbcSqliteDriver(...) to stop invoking
Schema.create and use Schema.synchronous or defer to MifosSQLDatabaseInitializer
accordingly.

Comment on lines +21 to +26
single<SqlDriver> {
createDefaultWebWorkerDriver().also { driver ->
MainScope().launch {
MifosSQLDelightDatabase.Schema.awaitCreate(driver)
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n core/database/src/jsTest/kotlin/org/mifos/core/database/di/TestSQLDelightModule.js.kt

Repository: openMF/kmp-project-template

Length of output: 1210


🏁 Script executed:

cd core/database && find . -name "*Test.kt" -o -name "*Spec.kt" | head -20

Repository: openMF/kmp-project-template

Length of output: 556


🏁 Script executed:

cat -n core/database/src/jsTest/kotlin/org/mifos/core/database/SQLDelightRepositoryJsTest.kt

Repository: openMF/kmp-project-template

Length of output: 576


🏁 Script executed:

cat -n core/database/src/commonTest/kotlin/org/mifos/core/database/SQLDelightRepositoryTest.kt

Repository: openMF/kmp-project-template

Length of output: 4147


🏁 Script executed:

find core/database -name "SampleRepository*" -type f

Repository: openMF/kmp-project-template

Length of output: 161


🏁 Script executed:

cat -n core/database/src/commonMain/kotlin/org/mifos/core/database/repository/SampleRepository.kt

Repository: openMF/kmp-project-template

Length of output: 844


🏁 Script executed:

find core/database -name "*SampleRepository*Impl*" -o -name "*Sample*RepositoryImpl*" | xargs cat -n 2>/dev/null | head -100

Repository: openMF/kmp-project-template

Length of output: 1436


🏁 Script executed:

rg "awaitCreate|MifosSQLDelightDatabase" core/database/src --type kt -A 2 -B 2

Repository: openMF/kmp-project-template

Length of output: 97


🏁 Script executed:

rg "awaitCreate|MifosSQLDelightDatabase" core/database/src --type kotlin -A 2 -B 2 | head -80

Repository: openMF/kmp-project-template

Length of output: 8924


🏁 Script executed:

cat -n core/database/src/desktopTest/kotlin/org/mifos/core/database/di/TestSQLDelightModule.desktop.kt

Repository: openMF/kmp-project-template

Length of output: 1180


The JS test driver must not be returned before schema creation completes.

Lines 22-26 return the driver immediately while schema initialization runs asynchronously in a detached MainScope().launch() block. This creates a race condition: tests that resolve SampleRepository or access the database right after @BeforeTest completes can hit an uninitialized schema.

Unlike the desktop test module (which uses runBlocking to synchronously create the schema before returning the driver), the JS implementation provides no guarantee that the schema exists when tests begin executing. Use runBlocking to await schema creation before returning the driver, similar to the desktop variant.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@core/database/src/jsTest/kotlin/org/mifos/core/database/di/TestSQLDelightModule.js.kt`
around lines 21 - 26, The JS test module returns the SqlDriver before schema
creation completes because schema setup runs asynchronously in
MainScope().launch; change the initialization inside the single<SqlDriver>
provider to perform schema creation synchronously by using runBlocking and
awaiting MifosSQLDelightDatabase.Schema.awaitCreate(driver) before returning the
driver (replace the MainScope().launch block with runBlocking around the await
call for createDefaultWebWorkerDriver()), ensuring the driver is only returned
after the schema is created.

Comment on lines +14 to +15
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

find . -name "TestDatabaseModule.native.kt" -type f

Repository: openMF/kmp-project-template

Length of output: 164


🏁 Script executed:

head -30 core/database/src/nativeTest/kotlin/org/mifos/core/database/di/TestDatabaseModule.native.kt

Repository: openMF/kmp-project-template

Length of output: 1009


🌐 Web query:

kotlinx.coroutines.IO import path Dispatchers.IO official documentation

💡 Result:

Dispatchers.IO lives in the kotlinx.coroutines package. Officially, you can import it either as:

import kotlinx.coroutines.Dispatchers
// use: Dispatchers.IO

or (as shown in Kotlin’s coroutines docs) via wildcard:

import kotlinx.coroutines.*
// use: Dispatchers.IO

Sources: Kotlin API reference for Dispatchers.IO [1] and Kotlin coroutines documentation examples [2].


Remove the invalid kotlinx.coroutines.IO import.

IO is a property of the Dispatchers object, not a top-level kotlinx.coroutines symbol. The import on line 15 cannot be resolved and will cause a compilation failure. Import Dispatchers and access Dispatchers.IO directly instead. The explicit cast to CoroutineContext is also unnecessary.

Suggested fix
 import androidx.sqlite.driver.bundled.BundledSQLiteDriver
 import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.IO
 import org.koin.core.module.Module
 import org.koin.dsl.module
 import org.mifos.core.database.AppDatabase
-import kotlin.coroutines.CoroutineContext
@@
-            .setQueryCoroutineContext(Dispatchers.IO as CoroutineContext)
+            .setQueryCoroutineContext(Dispatchers.IO)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO
import androidx.sqlite.driver.bundled.BundledSQLiteDriver
import kotlinx.coroutines.Dispatchers
import org.koin.core.module.Module
import org.koin.dsl.module
import org.mifos.core.database.AppDatabase
Suggested change
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO
.setQueryCoroutineContext(Dispatchers.IO)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@core/database/src/nativeTest/kotlin/org/mifos/core/database/di/TestDatabaseModule.native.kt`
around lines 14 - 15, Remove the invalid top-level import kotlinx.coroutines.IO
and instead reference Dispatchers.IO directly where used (keep the existing
import kotlinx.coroutines.Dispatchers), and remove the unnecessary explicit cast
to CoroutineContext so code uses Dispatchers.IO as a CoroutineContext without
casting (look for usages in TestDatabaseModule.native.kt referencing
Dispatchers, IO, and CoroutineContext).

Comment on lines +21 to +26
single<SqlDriver> {
createDefaultWebWorkerDriver().also { driver ->
MainScope().launch {
MifosSQLDelightDatabase.Schema.awaitCreate(driver)
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find and read the wasmJs test module file
fd "TestSQLDelightModule.wasmJs.kt" --type f

Repository: openMF/kmp-project-template

Length of output: 164


🏁 Script executed:

# Also search for the JS test module to compare
fd "TestSQLDelightModule" --type f | grep -E "\.(kt|kts)$"

Repository: openMF/kmp-project-template

Length of output: 627


🏁 Script executed:

# Read the wasmJs test module
cat -n "core/database/src/wasmJsTest/kotlin/org/mifos/core/database/di/TestSQLDelightModule.wasmJs.kt"

Repository: openMF/kmp-project-template

Length of output: 1210


🏁 Script executed:

# Read the JS test module to compare
cat -n "core/database/src/jsTest/kotlin/org/mifos/core/database/di/TestSQLDelightModule.js.kt"

Repository: openMF/kmp-project-template

Length of output: 1210


🏁 Script executed:

# Check other test modules for comparison
cat -n "core/database/src/commonTest/kotlin/org/mifos/core/database/di/TestSQLDelightModule.kt"

Repository: openMF/kmp-project-template

Length of output: 1123


🏁 Script executed:

# Check if there are any other implementations that handle this correctly
cat -n "core/database/src/androidUnitTest/kotlin/org/mifos/core/database/di/TestSQLDelightModule.android.kt"

Repository: openMF/kmp-project-template

Length of output: 1302


🏁 Script executed:

# Check desktop test module
cat -n "core/database/src/desktopTest/kotlin/org/mifos/core/database/di/TestSQLDelightModule.desktop.kt"

Repository: openMF/kmp-project-template

Length of output: 1180


🏁 Script executed:

# Search for how awaitCreate is used elsewhere in the codebase
rg "awaitCreate" --type kt --context 3

Repository: openMF/kmp-project-template

Length of output: 97


🏁 Script executed:

# Search for awaitCreate usage without file type restriction
rg "awaitCreate" --context 3

Repository: openMF/kmp-project-template

Length of output: 7003


Use runBlocking to wait for schema creation before returning the driver.

Lines 22–25 return the driver immediately while schema creation runs in a detached MainScope().launch coroutine. This creates a race condition where tests can access the driver before awaitCreate completes, causing intermittent startup failures.

The desktop test module correctly uses runBlocking { awaitCreate(driver) } to block until schema initialization finishes. Apply the same pattern here to ensure deterministic test initialization.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@core/database/src/wasmJsTest/kotlin/org/mifos/core/database/di/TestSQLDelightModule.wasmJs.kt`
around lines 21 - 26, The driver is returned before schema creation completes
because createDefaultWebWorkerDriver() uses MainScope().launch; replace the
detached coroutine with a blocking wait so initialization finishes before
returning: in the single<SqlDriver> provider that calls
createDefaultWebWorkerDriver(), invoke runBlocking {
MifosSQLDelightDatabase.Schema.awaitCreate(driver) } (using the returned driver
variable) instead of MainScope().launch, ensuring the test driver is fully
initialized before being returned.

Comment on lines +1 to +7
config.resolve = {
fallback: {
fs: false,
path: false,
crypto: false,
}
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "sqljs-config.js" | head -20

Repository: openMF/kmp-project-template

Length of output: 170


🏁 Script executed:

fd -name "sqljs-config.js" -type f

Repository: openMF/kmp-project-template

Length of output: 301


🌐 Web query:

webpack resolve configuration merge behavior replace vs merge

💡 Result:

When you “merge” webpack configs, the behavior depends on what tool is doing the merge:

  • webpack-cli --merge uses webpack-merge (it’s not webpack core doing it). [1]
  • webpack-merge default behavior: it merges objects and concatenates arrays (and for the same scalar key, the value “to the right” wins). So resolve (object) is merged, while resolve.extensions, resolve.modules, etc. (arrays) are concatenated by default. [2]

How “replace vs merge” works for resolve

  • resolve.alias (object): merged by key; if both configs define the same alias key, the later config’s value wins (normal “right wins” key precedence). [2]
  • resolve.extensions (array): concatenated by default in webpack-merge, so you can accidentally end up with combined/duplicate extensions unless you customize. [2]

How to force “replace” (instead of concat/merge)

Use mergeWithCustomize + customizeArray/customizeObject and set specific fields to replace (it supports append | prepend | replace, including wildcard paths). [2]

Related gotcha (not about merging, but often confused with it)

In plain webpack config (no merging), setting resolve.extensions overrides webpack’s default extensions list—to keep defaults and add more, use '...'. [3]

Sources: [1] webpack CLI docs (--merge uses webpack-merge), [2] webpack-merge README (default concat/merge + replace strategies), [3] webpack resolve.extensions docs (override defaults + '...').


🏁 Script executed:

cat -n ./core/database/src/webpack.config.d/sqljs-config.js

Repository: openMF/kmp-project-template

Length of output: 496


🏁 Script executed:

find . -name "webpack.config.js" -o -name "webpack*.config.js" | grep -v node_modules | head -20

Repository: openMF/kmp-project-template

Length of output: 53


🏁 Script executed:

rg "sqljs-config" --type js

Repository: openMF/kmp-project-template

Length of output: 53


🏁 Script executed:

find . -path "*/webpack.config.d/*" -type f

Repository: openMF/kmp-project-template

Length of output: 210


🏁 Script executed:

rg "webpack.config.d" --type js --type json

Repository: openMF/kmp-project-template

Length of output: 53


🏁 Script executed:

rg "webpack\.config\.d" --type js

Repository: openMF/kmp-project-template

Length of output: 53


🏁 Script executed:

find . -maxdepth 3 -name "webpack.config.js" -o -name "webpack.*.js" | grep -v node_modules

Repository: openMF/kmp-project-template

Length of output: 53


🏁 Script executed:

rg "require.*webpack" --type js | grep -i config | head -20

Repository: openMF/kmp-project-template

Length of output: 361


🏁 Script executed:

find . -maxdepth 2 -name "package.json" -exec grep -l "webpack" {} \;

Repository: openMF/kmp-project-template

Length of output: 53


🏁 Script executed:

cat ./package.json | head -50

Repository: openMF/kmp-project-template

Length of output: 117


🏁 Script executed:

rg "webpack" package.json

Repository: openMF/kmp-project-template

Length of output: 123


🏁 Script executed:

ls -la | head -20

Repository: openMF/kmp-project-template

Length of output: 1254


🏁 Script executed:

find . -maxdepth 2 -name "build.gradle*" -o -name "settings.gradle*"

Repository: openMF/kmp-project-template

Length of output: 298


🏁 Script executed:

find . -maxdepth 3 -name "package.json"

Repository: openMF/kmp-project-template

Length of output: 53


🏁 Script executed:

find cmp-web -name "webpack.config.js" -o -name "webpack*.js" | head -20

Repository: openMF/kmp-project-template

Length of output: 53


🏁 Script executed:

find core/database -name "webpack.config.js" -o -name "webpack*.js" | head -20

Repository: openMF/kmp-project-template

Length of output: 53


🏁 Script executed:

cat cmp-web/build.gradle.kts | head -100

Repository: openMF/kmp-project-template

Length of output: 1916


🌐 Web query:

Kotlin/JS webpack.config.d configuration merge behavior how files loaded

💡 Result:

In Kotlin/JS, webpack.config.d is not a “webpack-merge” (object/deep-merge) mechanism. The Kotlin Gradle plugin generates build/js/packages/<projectName>/webpack.config.js and then appends the contents of your extra config scripts into that generated file, where they run as plain JS that mutates a global config object. So “merge” here effectively means “concatenate and execute”. [1][2]

Which files are loaded

  • The plugin takes the configured configDirectory (default is the project-root webpack.config.d) and includes only files ending in .js. [1][2][3]

When/where they are applied

  • The appended snippets are placed near the end of the generated config (before module.exports = config), so they can override earlier defaults. [2]

Order / precedence

  • In the implementation (at least in the Kotlin Gradle plugin source shown), the directory files are iterated via confDir.listFiles() and then filtered to .jsno sorting is applied, so the effective order is filesystem-dependent and not guaranteed. If multiple files set the same field (for example config.devServer = ...), the one that runs last wins. [3]

Practical guidance

  • If ordering matters, prefer one config file, or ensure deterministic behavior by avoiding “replace” assignments and instead mutating/merging (for example pushing to arrays like config.module.rules.push(...), or doing Object.assign into an existing object). [1][2]

Sources

  1. Kotlin docs: extra .js files in webpack.config.d are “merged” into the generated webpack.config.js and should modify the global config. [1]
  2. Kotlin Gradle plugin source (KotlinWebpackConfig.appendFromConfigDir() calls appendConfigsFromDir(...) before exporting config). [2]
  3. Kotlin Gradle plugin source (appendConfigsFromDir uses confDir.listFiles() then filters .js and appends each file’s text). [3]

[1] (kotlinlang.org)
[2] (github.com)
[3] (github.com)

Citations:


🌐 Web query:

Kotlin Multiplatform JavaScript webpack config.d fragments merge

💡 Result:

In Kotlin Multiplatform/JS, webpack.config.d isn’t merged via a webpack-style deep merge. Instead, during the webpack tasks, all .js files from <projectRoot>/webpack.config.d are injected into the generated build/js/packages/<projectName>/webpack.config.js (effectively appended to the generated config file) and executed in that file’s context. [1], [2]

Practical implications:

  • Write fragments to mutate the provided config object (for example: config.module.rules.push(...), config.plugins.push(...), etc.). [1]
  • Don’t rely on changing module.exports (for example, exporting a function) inside a fragment: the Kotlin-generated config ends with module.exports = config, which can override what your fragment exports. [3]
  • If multiple fragments touch the same fields, later executed statements win (because it’s just sequential JS in one file). [2]

Sources:
[1] Kotlin docs – “Set up a Kotlin/JS project” (webpack.config.d files are “merged” into the generated webpack.config.js) (kotlinlang.org)
[2] Kotlin Slack archive – “All js files in there are copied at the bottom of generated config file” (slack-chats.kotlinlang.org)
[3] Kotlin Discussions – example showing fragment code added, then overwritten by trailing module.exports = config (discuss.kotlinlang.org)

Citations:


Merge into config.resolve; don't replace it.

The Kotlin/JS Gradle plugin generates a webpack config with default resolve settings (such as resolve.extensions and resolve.modules). Direct assignment on line 1 overwrites this entire object, discarding those defaults. Merge the fallback setting into the existing resolve object instead.

Suggested fix
-config.resolve = {
-    fallback: {
-        fs: false,
-        path: false,
-        crypto: false,
-    }
-};
+config.resolve = {
+    ...(config.resolve ?? {}),
+    fallback: {
+        ...(config.resolve?.fallback ?? {}),
+        fs: false,
+        path: false,
+        crypto: false,
+    },
+};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/database/src/webpack.config.d/sqljs-config.js` around lines 1 - 7, The
current assignment replaces the entire config.resolve object and loses defaults;
instead merge the fallback into the existing resolve object by updating
config.resolve rather than overwriting it (e.g., set config.resolve = {
...config.resolve, fallback: { fs: false, path: false, crypto: false } } or
assign to config.resolve.fallback), ensuring existing resolve properties (like
extensions and modules) are preserved while adding the fallback entries.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
build-logic/convention/src/main/kotlin/KMPSQLDelightConventionPlugin.kt (1)

5-15: Avoid org.gradle.internal in this convention plugin.

org.gradle.internal.Actions.with is not public Gradle API, so this build logic becomes unnecessarily brittle on Gradle upgrades. Kotlin's with(target) or target.run { ... } is enough here.

♻️ Proposed change
-import org.gradle.internal.Actions.with
@@
 class KMPSQLDelightConventionPlugin : Plugin<Project> {
     override fun apply(target: Project) {
-        with(target) {
+        target.run {
             pluginManager.apply("app.cash.sqldelight")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@build-logic/convention/src/main/kotlin/KMPSQLDelightConventionPlugin.kt`
around lines 5 - 15, Remove the internal Gradle import and usage of
org.gradle.internal.Actions.with in KMPSQLDelightConventionPlugin; delete the
import line and change the apply method to use Kotlin's built-in scope function
(e.g., with(target) or target.run { ... }) so the plugin uses only public
APIs—update references around the apply method and keep DATABASE_NAME and
pluginManager.apply("app.cash.sqldelight") intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@build-logic/convention/src/main/kotlin/KMPSQLDelightConventionPlugin.kt`:
- Around line 5-15: Remove the internal Gradle import and usage of
org.gradle.internal.Actions.with in KMPSQLDelightConventionPlugin; delete the
import line and change the apply method to use Kotlin's built-in scope function
(e.g., with(target) or target.run { ... }) so the plugin uses only public
APIs—update references around the apply method and keep DATABASE_NAME and
pluginManager.apply("app.cash.sqldelight") intact.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ec14f976-4332-481e-8204-2f7e011d0eca

📥 Commits

Reviewing files that changed from the base of the PR and between b48e700 and 5dc416b.

📒 Files selected for processing (1)
  • build-logic/convention/src/main/kotlin/KMPSQLDelightConventionPlugin.kt

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
core/database/build.gradle.kts (1)

1-20: ⚠️ Potential issue | 🟡 Minor

Remove duplicate license header.

The file contains two license headers: one dated 2026 (lines 1-9) and another dated 2025 (lines 12-20). Only one header should be present.

Proposed fix
 /*
  * Copyright 2026 Mifos Initiative
  *
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at https://mozilla.org/MPL/2.0/.
  *
  * See https://github.com/openMF/kmp-project-template/blob/main/LICENSE
  */
 import org.gradle.kotlin.dsl.invoke
 
-/*
- * Copyright 2025 Mifos Initiative
- *
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at https://mozilla.org/MPL/2.0/.
- *
- * See https://github.com/openMF/kmp-project-template/blob/main/LICENSE
- */
 plugins {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/database/build.gradle.kts` around lines 1 - 20, Remove the duplicated
license header by keeping only a single header block; locate the two consecutive
license comment blocks (one beginning "Copyright 2026 Mifos Initiative" and the
other "Copyright 2025 Mifos Initiative") at the top of
core/database/build.gradle.kts and delete the redundant one so only one correct
license header remains.
🧹 Nitpick comments (1)
core/database/build.gradle.kts (1)

35-40: Consider closing Process streams to avoid potential resource leaks.

Runtime.exec() returns a Process whose input/output streams should ideally be consumed or closed. In a build script context this is typically benign, but for robustness you could use ProcessBuilder with inherited I/O or explicitly close streams.

Optional improvement
 fun isInstalled(binary: String) = try {
     val command = if (isWindows) arrayOf("where", binary) else arrayOf("which", binary)
-    Runtime.getRuntime().exec(command).waitFor() == 0
+    ProcessBuilder(*command)
+        .redirectErrorStream(true)
+        .start()
+        .also { it.inputStream.bufferedReader().readText() }
+        .waitFor() == 0
 } catch (e: Exception) {
     false
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/database/build.gradle.kts` around lines 35 - 40, The isInstalled(binary:
String) function currently calls Runtime.getRuntime().exec(command) without
closing the returned Process's streams; update it to either use
ProcessBuilder(command).inheritIO().start() or capture the Process, explicitly
close its input, output and error streams (and call destroy()/waitFor()), so no
stream handles are leaked; ensure you still check the exit code
(process.waitFor() == 0) and handle exceptions as before.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@core/database/build.gradle.kts`:
- Around line 1-20: Remove the duplicated license header by keeping only a
single header block; locate the two consecutive license comment blocks (one
beginning "Copyright 2026 Mifos Initiative" and the other "Copyright 2025 Mifos
Initiative") at the top of core/database/build.gradle.kts and delete the
redundant one so only one correct license header remains.

---

Nitpick comments:
In `@core/database/build.gradle.kts`:
- Around line 35-40: The isInstalled(binary: String) function currently calls
Runtime.getRuntime().exec(command) without closing the returned Process's
streams; update it to either use ProcessBuilder(command).inheritIO().start() or
capture the Process, explicitly close its input, output and error streams (and
call destroy()/waitFor()), so no stream handles are leaked; ensure you still
check the exit code (process.waitFor() == 0) and handle exceptions as before.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2fe05f7a-ac58-4e22-8ba1-30cc2c006239

📥 Commits

Reviewing files that changed from the base of the PR and between 5dc416b and 9cec06b.

📒 Files selected for processing (1)
  • core/database/build.gradle.kts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant