Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
138 commits
Select commit Hold shift + click to select a range
2cb1359
First draft of new Naksha Data Model with modified/optimized JBON.
xeus2001 May 4, 2026
3aab8c3
Updated JBON and LIB_DATA, ensure downward compatiblity with current …
xeus2001 May 5, 2026
5ebfc56
Update documentation.
xeus2001 May 11, 2026
60012f2
Improved documentation about JBON2, LIB_DATA
xeus2001 May 13, 2026
6f839a6
Fix bitmask in partitioning
xeus2001 May 13, 2026
afe311a
Added back JBON1
xeus2001 May 13, 2026
1191b8f
Add ai directory and plan remove_uid
xeus2001 May 13, 2026
02dc735
Remove uid, encode action in the lower two bit of the version/txn.
xeus2001 May 13, 2026
6112906
Fix broken tests.
xeus2001 May 13, 2026
d62ee67
Add user-defined members and indices to NakshaCollection
kkin-here May 18, 2026
88547c1
Added proxy skill
kkin-here May 18, 2026
42cded5
Add user-defined members and indices to NakshaCollection
kkin-here May 18, 2026
58b6fe2
Improve JBON2, more to come.
xeus2001 May 18, 2026
1bb3f28
Finish JBON2 specification, to be reviewed.
xeus2001 May 19, 2026
f40f31d
Adjust JBON2 and LIB_DATA documentation.
xeus2001 May 20, 2026
53f6147
Cleanup of prev_tn field (#584)
kkin-here May 21, 2026
d37fa8e
Minor changes to JBON2, improve LIB_DATA
xeus2001 May 21, 2026
35649d6
Improve JBON2 and LIB_DATA, adding object support, maps are not enough.
xeus2001 May 21, 2026
6789de6
Lib data next tn (#585)
kkin-here May 22, 2026
2cd6b74
Switch to using version, fn. Drop tn
kkin-here May 22, 2026
722ce26
Merge pull request #586 from heremaps/lib_data_version_fn
kkin-here May 22, 2026
1488c72
Add TODO list for LIB_DATA (#587)
kkin-here May 26, 2026
f3780a5
Remove Operation, we do not need it any longer, it is replaced replac…
xeus2001 May 26, 2026
1b4476d
Remove deletion table. (#589)
xeus2001 May 26, 2026
12b46b3
Remove `flags` column (#591)
kkin-here May 27, 2026
d8fb6bf
Replace two array iterations in query builder with a single pass (#594)
kkin-here May 29, 2026
f110dfc
Clean up tuples and next version stripping
kkin-here May 27, 2026
f219b03
Merge pull request #595 from heremaps/lib_data_pgindex_improvements
kkin-here May 29, 2026
4a58197
Update TODO_LIB_DATA
kkin-here May 29, 2026
4cab0c2
Improve JBON2 spec. Needs review and more work, but getting close.
xeus2001 May 26, 2026
7e16715
Polish and improve JBON2 specification; except for the Java code part…
xeus2001 May 27, 2026
e0abbe4
Store tags as raw JSONB (#597)
kkin-here Jun 1, 2026
3c3e088
V3 lib data jbon2 (#598)
xeus2001 Jun 1, 2026
db8300e
Update TODO_LIB_DATA 1/06/2026
kkin-here Jun 1, 2026
9392e77
Ensure that all collections treated the same, Add TAGS, TAGS_FROM_ARR…
xeus2001 Jun 2, 2026
fc3db27
Merge pull request #599 from heremaps/v3_lib_data_jbon2
kkin-here Jun 2, 2026
454282d
Fix the broken tests.
xeus2001 Jun 2, 2026
1419a28
Fix version.
xeus2001 Jun 2, 2026
74346f3
Improve JBON2 encoder, add support for member replacement.
xeus2001 Jun 2, 2026
0088107
Minor fixes, remove undefined from primitives, make set to be an orde…
xeus2001 Jun 2, 2026
3f03ac0
Improve JbonEncoder2 to support geometry, remove special handlings, a…
xeus2001 Jun 2, 2026
2ff5131
Implement support for TWKB and member references.
xeus2001 Jun 2, 2026
9ef7f5b
Extend the definition of the members book, so that member names are n…
xeus2001 Jun 3, 2026
2bd7969
Adjust to new JBON2 specification.
xeus2001 Jun 3, 2026
1a3e924
Fix a bug for structs greater than 65535 byte in JBON2 encoder.
xeus2001 Jun 3, 2026
b75d937
Fix random GUID bits, requires higher 8 bit being 0.
xeus2001 Jun 3, 2026
ead9042
Fix tests by adjusting expected order. However, eventually we need to…
xeus2001 Jun 3, 2026
865c783
Fix that version is now only a decimal number, it no longer is guratn…
xeus2001 Jun 3, 2026
1cc4316
Add helper getByName
xeus2001 Jun 3, 2026
477a517
Autodetect encoding in naksha_feature (#600)
kkin-here Jun 9, 2026
c7fb635
Update JBON2 specification, introduce support for a simple byte-array
xeus2001 Jun 9, 2026
a9728a5
Rename IDict into IBook, add a new default implementation HeapBook.
xeus2001 Jun 9, 2026
f652087
Update Tuple and members, so that a tuple is only the mandatory, the …
xeus2001 Jun 9, 2026
35636b3
fix tests
gunplar Jun 9, 2026
eb7bea7
fix test
gunplar Jun 9, 2026
900c84a
SET implementation for tags
kkin-here Jun 10, 2026
e32dd85
fix test
gunplar Jun 10, 2026
1cbe4dc
Auto-detection for Naksha decoding
kkin-here Jun 9, 2026
48329cd
Merge pull request #601 from heremaps/lib_data_collection_decoding
kkin-here Jun 10, 2026
d82a6c7
Merge pull request #603 from heremaps/lib_data_set_tags
kkin-here Jun 10, 2026
dd14ccb
fix test
gunplar Jun 10, 2026
85a1820
fix test
gunplar Jun 10, 2026
2f7a462
Add member path test (#605)
kkin-here Jun 12, 2026
77970cf
Add skill for AI to run tests.
xeus2001 Jun 9, 2026
7a0451f
Minor fix in delete and update feature tests.
xeus2001 Jun 9, 2026
d5ac124
Add JSON path to standard members.
xeus2001 Jun 9, 2026
7432781
Replaced hardcoded strings with StandardMembers references.
xeus2001 Jun 10, 2026
6501f6b
Import StandardMembers
xeus2001 Jun 10, 2026
efab25b
Renamed map into path in members, because thats what it actually is.
xeus2001 Jun 10, 2026
a763efb
Improve the member handling, partially removed the hardcoded workarou…
xeus2001 Jun 10, 2026
a0b1d0b
Little architectural overview generated by AI.
xeus2001 Jun 10, 2026
69df3e3
Fix errors left over from conflicts after rebasing.
xeus2001 Jun 10, 2026
06e3fa3
Improve member handling, so that the path is always used and features…
xeus2001 Jun 11, 2026
3ebe8e3
Add support for BookType, delete metadata and repalce with members. F…
xeus2001 Jun 12, 2026
64abbf4
Fix compilation errors that were the result of the previous modificat…
xeus2001 Jun 12, 2026
7879c57
Latest state, work in progress.
xeus2001 Jun 12, 2026
2cac66d
Fix minor issue in JBON2 spec.
xeus2001 Jun 15, 2026
c512529
Next bunch of fixes about the members not being hardcoded.
xeus2001 Jun 15, 2026
cdfc307
Handle Xyz members (#606)
kkin-here Jun 15, 2026
e5fdf06
Next round of AI code cleanup, now members are as they should be, sti…
xeus2001 Jun 18, 2026
6b7cb81
Fix more issues in PgRows and related.
xeus2001 Jun 18, 2026
b006742
Some more fixes.
xeus2001 Jun 18, 2026
5c56227
Fix catalog, add some comments where needed, deprecate usage of encod…
xeus2001 Jun 18, 2026
4db5ad6
More fixes in PgCatalog, session, transaction, aso.
xeus2001 Jun 18, 2026
5ae2aa7
Fix more compiler issues.
xeus2001 Jun 19, 2026
739d1cc
More minor fixes, mainly naming and comments.
xeus2001 Jun 19, 2026
998e890
Rename more maps into catalogs
xeus2001 Jun 19, 2026
426d3c6
Start fixing query builder.
xeus2001 Jun 19, 2026
c63ae99
fix Write classes
gunplar Jun 19, 2026
2d0c875
Add dedicated members as helper, specifically for query convertion.
xeus2001 Jun 19, 2026
e2ac769
Add support for member queries.
xeus2001 Jun 22, 2026
c864b30
Ensure that we consistently talk about TagList and TagMap.
xeus2001 Jun 22, 2026
808f637
Add missing operations.
xeus2001 Jun 22, 2026
be9e9e1
Update JBON2 examples.
xeus2001 Jun 23, 2026
c5e3559
Add queryMembers to ReadFeatures as new member query, add converter f…
xeus2001 Jun 23, 2026
95cc887
Moved code to correct placed.
xeus2001 Jun 23, 2026
71a9d4b
Fixed PgQueryBuilder
xeus2001 Jun 23, 2026
e9ca517
Fix PgQueryBuilder and lots of small issues, like JS annotations.
xeus2001 Jun 23, 2026
f079cb3
Fix more issues.
xeus2001 Jun 23, 2026
bab6ac2
Fix PgWriteDelete.
xeus2001 Jun 24, 2026
eee40c6
Improve ID verification.
xeus2001 Jun 25, 2026
48c8275
Fix PgWriter.
xeus2001 Jun 25, 2026
7ac9ab3
Add query converter methods for new members (#607)
kkin-here Jun 25, 2026
4e9de95
implement TagList ops
gunplar Jun 25, 2026
192293c
Fix id usage in delete, fix insert
xeus2001 Jun 25, 2026
7c53c4c
Fix parts of upsert, minor improvements to members and heap book.
xeus2001 Jun 25, 2026
5929508
implement TagList ops
gunplar Jun 25, 2026
1b2ca10
Fixed WriterUpsert
xeus2001 Jun 25, 2026
2f56218
Fix issues in WriterUpdate
xeus2001 Jun 25, 2026
0ee5df5
Final fixes for update.
xeus2001 Jun 25, 2026
12a50a5
Add missing documentation to members.
xeus2001 Jun 26, 2026
8125be5
Add equals infix operator to member and allow TupleNumber.fromByteArr…
xeus2001 Jun 26, 2026
0c01548
Make default XYZ member being typed members.
xeus2001 Jun 26, 2026
96f9e16
Fix compilation errors of test.
xeus2001 Jun 26, 2026
e4b89ee
Update TagList to be a string-list.
xeus2001 Jun 26, 2026
1284acf
AIs fixes of tests compilation.
xeus2001 Jun 26, 2026
4df4225
Fix indices
xeus2001 Jun 26, 2026
c510861
Fix compilation errors.
xeus2001 Jun 26, 2026
5bd995f
Add support in the get methods to read from Tuple, next to read from …
xeus2001 Jun 26, 2026
ff27444
fix some activitylog tests
gunplar Jun 29, 2026
74fc43f
Cleanup member queries
kkin-here Jun 26, 2026
6e6ade5
Fix test targets
kkin-here Jun 30, 2026
ff1d911
Address review comments
kkin-here Jul 1, 2026
c2d6b2a
Merge pull request #608 from heremaps/lib_data_members_cleanup
kkin-here Jul 1, 2026
2cd3bd5
temp guid ReadFeatures support and small bugfix
gunplar Jul 1, 2026
f374a57
bugfix
gunplar Jul 1, 2026
229f511
make ActivityLogHandler move away from ReadFeatures guids query and u…
gunplar Jul 3, 2026
b598850
use backward guid in ReadFeatures for ActivityLogHandler, fix second …
gunplar Jul 6, 2026
9c019d8
fix old Property of PQuery
gunplar Jul 6, 2026
f3b5dac
fix columns name in postgres indices
gunplar Jul 6, 2026
7298a96
temp bypass closed loop error
gunplar Jul 6, 2026
d72f89b
comment correction
gunplar Jul 6, 2026
001e254
Lib data fix activity log and standard indices (#617)
gunplar Jul 10, 2026
82d3162
Lib data members fix1 libsql fix (#615)
kkin-here Jul 10, 2026
fdf2bad
Update PartitioningTest TODO comment
kkin-here Jul 13, 2026
6734a65
fix tuple-number tests, seed admin-catalog history partitions (#619)
kkin-here Jul 14, 2026
d5e85ac
Upgrade libraries, JVM and Kotlin. (#620)
xeus2001 Jul 15, 2026
7e8770c
Immediate fixes after versioning bump
kkin-here Jul 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
269 changes: 269 additions & 0 deletions .claude/skills/naksha-proxy/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
---
name: naksha-proxy
description: Generate a Naksha proxy (ObjectProxy / ListProxy / MapProxy) or a JsEnum class that follows the conventions used throughout `here-naksha-lib-*`. Use when the user asks to create a new proxy, model class, request type, or enumeration that wraps `PlatformObject` / `PlatformList` data, or asks to add typed accessors over an existing one.
---

# Naksha proxy / JsEnum skill

This skill encodes the conventions for writing typed wrappers over `PlatformObject` / `PlatformList` in the Naksha codebase, plus the conventions for typed enumerations.

## Core concepts

- The JSON parser returns raw `PlatformObject` (map-like, exposed in lib-base as `AnyObject` which extends `MapProxy<String, Any>`) and `PlatformList` (list-like, exposed as `ListProxy<T>`).
- A **proxy** is a typed view over one of those. The runtime data is the raw platform value; the proxy is a thin facade that exposes typed getters and setters via delegated properties. Apply a proxy at runtime via `anyObject.proxy(MyProxy::class)`.
- Proxies are **cached** by the underlying map: `obj.proxy(Foo::class) === obj.proxy(Foo::class)`. They are bound to the same map once and never unlinked.
- Proxies are **not thread-safe**; only one thread accesses a given instance at a time.
- An **enumeration** extends `naksha.base.JsEnum`. Values are registered statically through `def(...)` or `defIgnoreCase(...)`. They serialize as plain strings/ints in JSON.
- Both flavors are `@JsExport`-ed because the same code runs on JVM and JS.
- For every property, the canonical surface a caller can rely on is four methods: `has<Name>()`, `get<Name>()`, `set<Name>(value)`, `remove<Name>()`. Kotlin's `var name by DELEGATE` compiles to `getName`/`setName` automatically; `hasName`/`removeName` must be written explicitly. Generate all four for every property unless the user explicitly asks for a slimmer surface.

## When NOT to use

- Plain Kotlin data classes that never round-trip through JSON parsing. Use `data class` instead.
- Static configuration that doesn't need to be exported to JS — use `companion object` / `enum class`.

## Workflow

Before generating anything, do these in order:

1. **Confirm the kind**. Ask which is wanted if unclear:
- **Object proxy** — typed view over a JSON object (most common)
- **List proxy** — typed view over a JSON array of homogeneous elements
- **Map proxy** — typed view over a JSON map with non-string keys (rare)
- **JsEnum** — closed set of named string/int values
2. **Confirm the property list** (for proxies): name, kotlin type, nullable, default value, optional JSON key override.
3. **Locate the target module/package**. Naksha's convention is one file per public type, package mirrors directory, package name is lowercase `naksha.<module>.<sub>`. Use `grep -R "<NeighborClass>" here-naksha-lib-*/src/commonMain/kotlin` to find the right neighbor.
4. **Generate the file** using the matching template below.
5. **Validate** with the checklist at the end.

## Object proxy template

A typed view over an `AnyObject` (= `MapProxy<String, Any>`). Use this 90% of the time.

```kotlin
@file:Suppress("OPT_IN_USAGE")

package <package>

import naksha.base.*
import kotlin.js.JsExport
import kotlin.jvm.JvmField

/**
* One short sentence describing what this models.
* @since 3.0
*/
@JsExport
open class <ClassName> : AnyObject() {

/**
* Doc for [propName]. Mention units, range, defaults, and {Create-Only} if relevant.
* @since 3.0
*/
var <propName>: <Type> by <DELEGATE>

/** True iff the underlying map has an entry for [propName]. */
fun has<PropName>(): Boolean = hasRaw("<jsonKey>")

/** Remove [propName] from the underlying map; returns this for chaining. */
fun remove<PropName>(): <ClassName> {
removeRaw("<jsonKey>")
return this
}

/** Fluent setter for [propName]; returns this for chaining. */
fun with<PropName>(value: <Type>): <ClassName> {
<propName> = value
return this
}

// ...repeat for each property

companion object <ClassName>_C {
// One private delegate per property; placing them in companion lets the Kotlin
// compiler inline the getter/setter calls.
private val <DELEGATE> = NotNullProperty<<ClassName>, <Type>>(<Type>::class) { _, _ -> <default> }
// For nullable string property with no default:
// private val STRING_NULL = NullableProperty<<ClassName>, String>(String::class)
// For nullable property that should be REMOVED from the underlying map when set to null:
// private val PROP_NULL = NullableProperty<<ClassName>, Foo>(Foo::class, autoRemove = true)
// For nullable property that should be auto-created on first read:
// private val PROP_NULL = NullableProperty<<ClassName>, NakshaList>(NakshaList::class, autoCreate = true)
// For an enum-typed property:
// private val MODE = NotNullEnum<<ClassName>, StoreMode>(StoreMode::class) { _, _ -> StoreMode.ON }
// private val MODE_NULL = NullableEnum<<ClassName>, StoreMode>(StoreMode::class)
}
}
```

`<jsonKey>` is the same string the underlying map uses. It defaults to the Kotlin property name; only differs when the delegate is constructed with `name = "<jsonKey>"` (see *Renaming the JSON key* below). `hasRaw` / `removeRaw` bypass the delegate, so they need the wire key directly.

### Delegate choice cheat sheet

| Property declared as | Delegate to use |
|---|---|
| `var x: String by D` | `NotNullProperty<Self, String>(String::class) { _, _ -> "" }` |
| `var x: String? by D` | `NullableProperty<Self, String>(String::class)` |
| `var x: Int by D` | `NotNullProperty<Self, Int>(Int::class) { _, _ -> 0 }` |
| `var x: Int64 by D` | `NotNullProperty<Self, Int64>(Int64::class) { _, _ -> Int64(0) }` |
| `var x: Boolean by D` | `NotNullProperty<Self, Boolean>(Boolean::class) { _, _ -> false }` |
| `var x: MyProxy by D` (auto-created on first read) | `NotNullProperty<Self, MyProxy>(MyProxy::class)` — no `init` lambda; `MyProxy`'s default constructor is invoked the first time the property is read while absent. This is the only way to get auto-create on a non-null proxy-typed property. |
| `var x: MyProxy? by D` | `NullableProperty<Self, MyProxy>(MyProxy::class)` |
| `var x: MyEnum by D` | `NotNullEnum<Self, MyEnum>(MyEnum::class) { _, _ -> MyEnum.DEFAULT }` |
| `var x: MyEnum? by D` | `NullableEnum<Self, MyEnum>(MyEnum::class)` |

### "Required, no default"

`NotNullProperty` always returns a value, so it always needs an initializer. When the user says a property is "required, no default", they usually mean one of three things — pick deliberately:

1. **Sentinel default + downstream validation.** Use `{ _, _ -> "" }` (or `0`, `Int64(0)`, `false`, ...) and rely on a `validate()` method or caller-side check to reject the sentinel before serializing. This is the codebase's prevailing pattern. Use it unless told otherwise.
2. **Treat as nullable internally.** Declare `var x: String? by NullableProperty(...)` and let callers handle absence. Lose type strength but get true "missing".
3. **Throw from the init lambda.** `{ _, name -> throw illegalArg("required property '$name' missing") }`. Use only when reading a missing required property is an actual programmer error rather than user input.

### Renaming the JSON key

The delegate uses the Kotlin property name as the map key by default. **Only pass `name = "<wireKey>"` when the JSON key differs from the Kotlin property name.** Passing `name=` redundantly is noise:

```kotlin
// Correct — Kotlin name `mapId`, wire key "map_id" (snake_case)
private val MAP_ID = NullableProperty<NakshaCollection, String>(String::class, name = "map_id")

// Incorrect — Kotlin name `indexed`, wire key "indexed". The `name =` is redundant; omit it.
private val INDEXED = NotNullProperty<Foo, Boolean>(Boolean::class, name = "indexed") { _, _ -> true }
```

If you do override the wire key, use the same string for both the `name =` argument *and* every `hasRaw("...")` / `removeRaw("...")` call — those bypass the delegate and need the raw map key.

## List proxy template

Typed view over a `PlatformList` of homogeneous elements.

```kotlin
@file:Suppress("OPT_IN_USAGE")

package <package>

import naksha.base.ListProxy
import kotlin.js.JsExport
import kotlin.js.JsName

/**
* A list of [ElementType].
* @since 3.0
*/
@JsExport
open class <ClassName>() : ListProxy<<ElementType>>(<ElementType>::class) {

/** Initializer accepting a vararg of elements. */
@JsName("fromElements")
constructor(vararg elements: <ElementType>) : this() {
addAll(elements.toList())
}
}
```

The `ListProxy` base already provides `size`, `add`, `addAll`, `set`, `removeAt`, `iterator`, etc.

## Map proxy template

Only when the JSON key is not a `String` (rare). Use:

```kotlin
@JsExport
open class <ClassName> : MapProxy<<KeyType>, <ValueType>>(<KeyType>::class, <ValueType>::class)
```

For `Map<String, Any>` use `AnyObject` (defined as exactly that) and treat it as an object proxy.

## JsEnum template

Closed set of named values that serialize as strings (most common) or ints. Use `defIgnoreCase` if the wire form should be matched without case sensitivity.

```kotlin
@file:Suppress("OPT_IN_USAGE")

package <package>

import naksha.base.JsEnum
import kotlin.js.JsExport
import kotlin.jvm.JvmField
import kotlin.reflect.KClass

/**
* One short sentence describing the enumeration's purpose.
* - [ON] description of ON
* - [OFF] description of OFF
* @since 3.0
*/
@JsExport
class <ClassName> : JsEnum() {

@Suppress("NON_EXPORTABLE_TYPE")
override fun namespace(): KClass<out JsEnum> = <ClassName>::class

override fun initClass() {}

companion object <ClassName>_C {
/** Doc for [ON]. */
@JvmField
val ON = defIgnoreCase(<ClassName>::class, "on")

/** Doc for [OFF]. */
@JvmField
val OFF = defIgnoreCase(<ClassName>::class, "off")
}
}
```

### `def` vs `defIgnoreCase`

- `def(Klass, "Value")` — case-sensitive. Use when the wire form is fixed (e.g. uppercase constants, ints).
- `defIgnoreCase(Klass, "value")` — case-insensitive on input but emits exactly what's passed on output. Default choice for human-typed config values.

### Integer-valued enums

Use `def(Klass, intValue)` instead of a string. The companion still uses `@JvmField val FOO = ...`.

### Subclass per value (legacy)

A few legacy enums use one Kotlin subclass per constant (see `here-naksha-lib-base/src/commonMain/kotlin/naksha/base/JsEnum.kt` doc block). Do **not** use this pattern for new code — single-class with `companion object` constants is the current convention.

## File placement

- Package follows the directory: `here-naksha-lib-<module>/src/commonMain/kotlin/naksha/<module>/.../<ClassName>.kt`
- One public class per file.
- Filename matches the class name.
- New types that downstream code already references go next to their nearest neighbor in the existing directory tree.

## Conventions checklist (run before declaring done)

1. **Header**: `@file:Suppress("OPT_IN_USAGE")` is present. Add `"LeakingThis"` if `init {}` writes to `this` properties.
2. **Class is `@JsExport`** and `open class` (proxies must allow subclassing for `proxy()` to work).
3. **Companion** is named `<ClassName>_C` (matches the rest of the codebase).
4. **Companion delegates are `private val`** — never `internal` or public.
5. **One delegate per property**. Sharing a single `STRING_NULL` delegate across multiple properties is fine *only* if their JSON key matches their Kotlin name and they share an initializer.
6. **All public properties have KDoc** ending in `@since 3.0`. Use exactly `3.0` (two components) — that's what `NakshaCollection.kt` / `NakshaFeature.kt` and the rest of the model module use. A few older files use `3.0.0`; ignore them.
7. **No constructor logic that reads properties** — proxies are usually instantiated by `<obj>.proxy(MyProxy::class)`, and the constructor runs against an empty `AnyObject`. Use delegate `init = { _, _ -> ... }` for defaults instead.
8. **`with<Prop>` returns `<ClassName>`**, not the base class. If the proxy extends another proxy that already has a `with<Prop>`, override with `as <ClassName>` cast (see `NakshaCollection.withId`).
9. **Enums use `@JvmField`** on companion constants. Add `@JsStatic` only if JS callers need direct access (most don't — they go through `JsEnum.get`).
10. **Validate the package compiles**: `nix develop --command bash -c 'cd naksha && gradle :here-naksha-lib-<module>:compileKotlinJvm 2>&1 | tail -30'` (the build script ignores `kotlin.compiler.execution.strategy` warnings).

## Examples in the codebase

Use these as exact references for the conventions, not as code to copy:

- Object proxy: `here-naksha-lib-model/src/commonMain/kotlin/naksha/model/objects/NakshaCollection.kt`
- Object proxy with enum + auto-create: `here-naksha-lib-model/src/commonMain/kotlin/naksha/model/objects/NakshaFeature.kt`
- List proxy: `here-naksha-lib-base/src/commonMain/kotlin/naksha/base/StringList.kt`
- JsEnum (small): `here-naksha-lib-model/src/commonMain/kotlin/naksha/model/objects/StoreMode.kt`
- JsEnum (with intValue and aliases): `here-naksha-lib-model/src/commonMain/kotlin/naksha/model/Action.kt`
- Delegate definitions: `here-naksha-lib-base/src/commonMain/kotlin/naksha/base/NotNullProperty.kt`, `NullableProperty.kt`, `NotNullEnum.kt`, `NullableEnum.kt`

## Common pitfalls

- **Forgetting `@JsExport`** — JS callers can't see the class, but JVM still works, so this slips through unless you actually run the JS target.
- **Putting initializer code in the constructor body** — for proxies, prefer the `init = { _, _ -> default }` lambda on the delegate. The constructor often runs in contexts (deserialization, `proxy()` cast) where reading other properties would yield defaults.
- **Sharing one delegate across properties with different defaults** — if the values diverge later, the shared delegate produces wrong defaults silently. Keep one delegate per property unless they truly are identical.
- **Using `kotlin.enum`** — the project uses `JsEnum`. A Kotlin `enum class` does not survive JSON round-tripping the way `JsEnum` does.
- **Adding `has<Name>()` / `remove<Name>()` for every property** — only add them when the property is genuinely optional in the wire format. Required properties don't need them; the delegate's default handles presence.
- **Renaming a property without updating the JSON key** — if the property name was the JSON key, renaming silently breaks serialization. Either keep the old name as `name=` on the delegate, or include both in a deprecation cycle.
70 changes: 70 additions & 0 deletions .claude/skills/naksha-test/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
---
name: naksha-test
description: Use ONLY when asked to run tests for the Naksha project. Do NOT use for other projects or general testing questions.
---

# General
Most tests require a database. If the tests are executed without environment variables, they will start docker containers. When unclear, ask the user if they want to run the tests using automatically created Docker containers, or if they prefer to run the tests against their own, possibly local, PostgresQL test database.

# Environment Variables
All environment variables contain some placeholders that need to be replaced:

- `{host}`: The host of the PostgresQL cluster. If not given any other instructions, assume `localhost`.
- `{port}`: Needs to be replaced by you with the port at which the database is listening. If not given any other instructions, assume `5432`.
- `{user}`: Needs to be replaced by you with the user. If not given any other instructions, assume `postgres`.
- `{password}`: Needs to be replaced by you with the password. If not given any other instructions, assume `password`.

You can test the connection to the database. If you detect that the connection to the database fails due to wrong credentials or hostname, ask the user for host, port, user, and password _(whatever is needed)_. Use defaults for any value not provided. Tell the user the defaults.

## Library tests (here-naksha-lib-psql)
Only needs one variable. If not set, Docker auto-starts:

```bash
export NAKSHA_TEST_PSQL_DB_URL="jdbc:postgresql://{host}:{port}/postgres?user={user}&password={password}&ssl=false"
```

## Server tests (here-naksha-app-service)
Needs all variables. These tests require a running Naksha server and will fail without one. Skip unless explicitly asked:

```bash
export NAKSHA_APP_SERVICE_TEST_CONTEXT=LOCAL_STANDALONE
export NAKSHA_TEST_STORAGE_ID=local_psql_test_storage
export HUB_ADMIN_STORAGE_ID=local_psql_test_storage
export NAKSHA_TEST_PSQL_DB_URL="jdbc:postgresql://{host}:{port}/postgres?user={user}&password={password}&ssl=false"
export NAKSHA_TEST_ADMIN_DB_URL="jdbc:postgresql://{host}:{port}/postgres?user={user}&password={password}&ssl=false"
export NAKSHA_TEST_DATA_DB_URL="jdbc:postgresql://{host}:{port}/postgres?user={user}&password={password}&ssl=false"
```

# Commands

## All library tests (JVM):
Docker auto-starts if no env vars are set:

```bash
./gradlew :here-naksha-lib-model:jvmTest :here-naksha-lib-psql:jvmTest :here-naksha-lib-jbon:jvmTest :here-naksha-lib-geo:jvmTest
```

## All JVM tests (includes server tests that will fail without a running server):
Docker auto-starts if no env vars are set. This includes `here-naksha-app-service:jvmTest` which requires a running Naksha server and will fail with `ConnectException` if no server is available:

```bash
./gradlew jvmTest
```

## All library tests (JS):
```bash
./gradlew :here-naksha-lib-model:jsTest :here-naksha-lib-jbon:jsTest :here-naksha-lib-geo:jsTest
```

## Server tests
Only run if user explicitly asks. Requires a running Naksha server:

```bash
./gradlew :here-naksha-app-service:jvmTest
```

# Common Issues
- Kotlintest discovery errors: If `here-naksha-lib-psql:jvmTest` fails with test discovery errors, try `./gradlew clean` first
- Docker not available: The psql tests require Docker. If Docker isn't running, set `NAKSHA_TEST_PSQL_DB_URL` to an external Postgres instance
- Port conflicts: The Docker container uses host port 15432. If this port is in use, the container will fail to start
- Server tests fail with ConnectException: This is expected when no Naksha server is running. Skip these tests unless the server is available
Loading