@calimero-network/agent-skills 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/README.md +137 -17
  2. package/SKILL.md +31 -23
  3. package/package.json +2 -2
  4. package/scripts/install.js +3 -3
  5. package/scripts/test.js +6 -15
  6. package/skills/calimero-abi-codegen/SKILL.md +121 -22
  7. package/skills/calimero-abi-codegen/references/abi-format.md +3 -5
  8. package/skills/calimero-abi-codegen/references/generated-output.md +12 -4
  9. package/skills/calimero-abi-codegen/rules/schema-version.md +11 -4
  10. package/skills/calimero-abi-codegen/rules/unique-names.md +2 -6
  11. package/skills/calimero-client-js/SKILL.md +127 -22
  12. package/skills/calimero-client-js/references/auth.md +18 -10
  13. package/skills/calimero-client-js/references/rpc-calls.md +15 -21
  14. package/skills/calimero-client-js/references/sso.md +9 -9
  15. package/skills/calimero-client-js/references/websocket-events.md +73 -59
  16. package/skills/calimero-client-js/rules/camelcase-api.md +10 -7
  17. package/skills/calimero-client-js/rules/token-refresh.md +59 -21
  18. package/skills/calimero-client-py/SKILL.md +26 -10
  19. package/skills/calimero-client-py/references/api.md +41 -43
  20. package/skills/calimero-client-py/references/auth.md +7 -7
  21. package/skills/calimero-client-py/rules/async-usage.md +27 -31
  22. package/skills/calimero-client-py/rules/stable-node-name.md +7 -7
  23. package/skills/calimero-core/SKILL.md +135 -0
  24. package/skills/calimero-core/references/architecture.md +101 -0
  25. package/skills/calimero-core/references/jsonrpc-protocol.md +192 -0
  26. package/skills/calimero-core/references/namespaces-groups.md +94 -0
  27. package/skills/calimero-core/references/storage-types.md +118 -0
  28. package/skills/calimero-core/references/websocket-events.md +142 -0
  29. package/skills/calimero-core/rules/context-is-not-app.md +35 -0
  30. package/skills/calimero-core/rules/crdt-types-only.md +55 -0
  31. package/skills/calimero-desktop/SKILL.md +25 -14
  32. package/skills/calimero-desktop/references/sso-integration.md +49 -22
  33. package/skills/calimero-desktop/rules/sso-fallback.md +3 -2
  34. package/skills/calimero-merobox/SKILL.md +255 -28
  35. package/skills/calimero-merobox/references/ci-integration.md +3 -2
  36. package/skills/calimero-merobox/references/workflow-files.md +7 -5
  37. package/skills/calimero-merobox/rules/docker-required.md +7 -6
  38. package/skills/calimero-meroctl/SKILL.md +68 -0
  39. package/skills/calimero-meroctl/references/commands.md +177 -0
  40. package/skills/calimero-meroctl/references/scripting.md +80 -0
  41. package/skills/calimero-meroctl/rules/call-view-flag.md +28 -0
  42. package/skills/calimero-meroctl/rules/register-node-once.md +34 -0
  43. package/skills/calimero-merod/SKILL.md +49 -0
  44. package/skills/calimero-merod/references/health-endpoints.md +90 -0
  45. package/skills/calimero-merod/references/init-flags.md +84 -0
  46. package/skills/calimero-merod/rules/init-before-run.md +40 -0
  47. package/skills/calimero-merod/rules/port-assignments.md +33 -0
  48. package/skills/calimero-node/SKILL.md +52 -35
  49. package/skills/calimero-node/references/context-lifecycle.md +34 -17
  50. package/skills/calimero-node/references/meroctl-commands.md +89 -99
  51. package/skills/calimero-node/rules/app-vs-context.md +4 -4
  52. package/skills/calimero-registry/SKILL.md +110 -31
  53. package/skills/calimero-registry/references/bundle-and-push.md +99 -34
  54. package/skills/calimero-registry/references/manifest-format.md +56 -35
  55. package/skills/calimero-registry/references/mero-sign.md +10 -9
  56. package/skills/calimero-registry/rules/key-security.md +3 -2
  57. package/skills/calimero-registry/rules/sign-before-pack.md +5 -5
  58. package/skills/calimero-rust-sdk/SKILL.md +154 -44
  59. package/skills/calimero-rust-sdk/references/blob-api.md +119 -0
  60. package/skills/calimero-rust-sdk/references/event-handlers.md +122 -0
  61. package/skills/calimero-rust-sdk/references/events.md +2 -1
  62. package/skills/calimero-rust-sdk/references/examples.md +81 -29
  63. package/skills/calimero-rust-sdk/references/migrations.md +123 -0
  64. package/skills/calimero-rust-sdk/references/nested-crdts.md +113 -0
  65. package/skills/calimero-rust-sdk/references/private-storage.md +76 -34
  66. package/skills/calimero-rust-sdk/references/state-collections.md +106 -21
  67. package/skills/calimero-rust-sdk/references/user-and-frozen-storage.md +169 -0
  68. package/skills/calimero-rust-sdk/rules/app-macro-placement.md +5 -2
  69. package/skills/calimero-rust-sdk/rules/no-std-collections.md +5 -2
  70. package/skills/calimero-rust-sdk/rules/state-derives.md +45 -0
  71. package/skills/calimero-rust-sdk/rules/wasm-constraints.md +12 -10
  72. package/skills/calimero-sdk-js/SKILL.md +145 -0
  73. package/skills/calimero-sdk-js/references/build-pipeline.md +98 -0
  74. package/skills/calimero-sdk-js/references/collections.md +132 -0
  75. package/skills/calimero-sdk-js/references/events.md +63 -0
  76. package/skills/calimero-sdk-js/rules/crdt-only-state.md +47 -0
  77. package/skills/calimero-sdk-js/rules/no-console-log.md +38 -0
  78. package/skills/calimero-sdk-js/rules/view-decorator.md +48 -0
@@ -0,0 +1,135 @@
1
+ # calimero-core — Agent Instructions
2
+
3
+ You are helping a developer understand the **core Calimero runtime** — the mental model, protocols,
4
+ and primitives that every application, client library, and node operator needs to know.
5
+
6
+ > Use this skill alongside a language-specific skill (`calimero-rust-sdk`, `calimero-sdk-js`,
7
+ > `calimero-client-js`, etc.) to give the AI full-stack context.
8
+
9
+ ## The three-layer model
10
+
11
+ ```text
12
+ ┌─────────────────────────────────────────────────────┐
13
+ │ Application logic (WASM) │ calimero-rust-sdk / calimero-sdk-js
14
+ │ @State / #[app::state], CRDT collections │
15
+ └────────────────────────┬────────────────────────────┘
16
+ │ JSON-RPC calls + WebSocket events
17
+ ┌────────────────────────▼────────────────────────────┐
18
+ │ merod node runtime │ calimero-merod
19
+ │ Hosts WASM, manages storage, exposes HTTP/WS API │
20
+ └────────────────────────┬────────────────────────────┘
21
+ │ meroctl CLI / HTTP clients / Python client
22
+ ┌────────────────────────▼────────────────────────────┐
23
+ │ Clients & tooling │ calimero-client-js / calimero-client-py
24
+ │ mero-js, mero-react, calimero-client-py │ calimero-meroctl
25
+ └─────────────────────────────────────────────────────┘
26
+ ```
27
+
28
+ ## Core concepts
29
+
30
+ ### Application vs Context
31
+
32
+ | Concept | What it is | Analogy |
33
+ | --------------- | ------------------------------------------------------------------ | -------------------- |
34
+ | **Application** | WASM binary + manifest; the code | A class / template |
35
+ | **Context** | A running instance of an app; has isolated state, members, storage | An object / instance |
36
+
37
+ One application can power many independent contexts. State is never shared across contexts.
38
+
39
+ ### Identity
40
+
41
+ An **identity** is an Ed25519 keypair. Each context member has an identity on the node. The
42
+ identity's public key is used:
43
+
44
+ - As `executorPublicKey` in RPC calls (who is calling)
45
+ - For signing mutations in the CRDT sync protocol
46
+
47
+ ### Namespace and Group
48
+
49
+ | Concept | Role |
50
+ | ------------- | ------------------------------------------------------------------------------------------ |
51
+ | **Namespace** | A root trust anchor shared across nodes; created by one node, others join via invite token |
52
+ | **Group** | Sub-grouping within a namespace; contexts are joined via group membership |
53
+
54
+ Multi-node participation requires: create namespace → invite peer → peer joins namespace → peer
55
+ joins context via group.
56
+
57
+ ## How calls work (JSON-RPC)
58
+
59
+ All application method calls go through the node's JSON-RPC endpoint:
60
+
61
+ ```text
62
+ POST http://localhost:2428/api/v0/context/{contextId}/execute
63
+ Authorization: Bearer <access_token>
64
+
65
+ {
66
+ "method": "get_posts",
67
+ "argsJson": "{}",
68
+ "executorPublicKey": "<base58-pubkey>"
69
+ }
70
+ ```
71
+
72
+ - **Mutations** (methods taking `&mut self`) change shared CRDT state; changes are synced to all
73
+ context members automatically.
74
+ - **Views** (methods taking `&self`, annotated `@View` or returning read-only) do NOT persist state.
75
+ - There is no separate "query" endpoint — mutations and views use the same call path. The `--view`
76
+ flag in `meroctl call` tells the client to skip state persistence.
77
+
78
+ ## How events work (WebSocket)
79
+
80
+ Applications emit events with `app::emit!()` (Rust) or `env.emit()` (JS). Clients subscribe via
81
+ WebSocket to receive them in real time.
82
+
83
+ Two event types the node sends to subscribers:
84
+
85
+ | Type | When | Payload |
86
+ | ---------------- | ----------------------------- | ------------------------------ |
87
+ | `ExecutionEvent` | App called `app::emit!()` | `{ events: [{ kind, data }] }` |
88
+ | `StateMutation` | A member mutated shared state | `{ newRoot: string }` |
89
+
90
+ WebSocket endpoint: `ws://localhost:2428/ws`
91
+
92
+ ## CRDT storage types
93
+
94
+ All shared application state uses conflict-free replicated data types from
95
+ `calimero_storage::collections`. Plain `HashMap`, `Vec`, or `HashSet` must never be used for shared
96
+ state.
97
+
98
+ | Type | Use for |
99
+ | --------------------------- | --------------------------------------- |
100
+ | `UnorderedMap<K, V>` | Key-value store (most common) |
101
+ | `Vector<T>` | Append-only ordered log |
102
+ | `UnorderedSet<T>` | Unique value set |
103
+ | `LwwRegister<T>` | Single last-write-wins scalar |
104
+ | `Counter` / `Counter<true>` | Grow-only / PN counter |
105
+ | `FrozenStorage<T>` | Immutable content-addressed blobs |
106
+ | `UserStorage<T>` | Per-member private storage (not synced) |
107
+ | `ReplicatedGrowableArray` | Collaborative text / ordered sequence |
108
+
109
+ ## Authentication
110
+
111
+ The node uses short-lived JWT access tokens + long-lived refresh tokens:
112
+
113
+ ```bash
114
+ # Get tokens
115
+ POST /api/v0/identity/login
116
+ { "username": "admin", "password": "..." }
117
+ → { "accessToken": "...", "refreshToken": "..." }
118
+
119
+ # Refresh
120
+ POST /api/v0/identity/refresh
121
+ { "refreshToken": "..." }
122
+ → { "accessToken": "..." }
123
+ ```
124
+
125
+ Tokens are passed as `Authorization: Bearer <accessToken>` on all API calls.
126
+
127
+ ## References
128
+
129
+ See `references/` for:
130
+
131
+ - Full JSON-RPC protocol and endpoint list
132
+ - WebSocket event schemas with decoding examples
133
+ - CRDT storage type guide
134
+ - Namespace and group model detail
135
+ - Architecture overview
@@ -0,0 +1,101 @@
1
+ # Calimero Architecture
2
+
3
+ ## What merod is
4
+
5
+ `merod` is the Calimero node daemon. It:
6
+
7
+ - Hosts and executes WASM applications inside isolated contexts
8
+ - Manages persistent CRDT storage per context
9
+ - Exposes a JSON-RPC + WebSocket API on a configurable HTTP port
10
+ - Participates in a P2P network on a separate swarm port for state sync
11
+ - Issues and validates JWT tokens for client auth
12
+
13
+ One `merod` process = one node. Multiple nodes can be run on the same machine with different
14
+ `--node` names and different ports.
15
+
16
+ ## What a Context is
17
+
18
+ A **context** is a sandboxed instance of a WASM application with:
19
+
20
+ - Its own isolated CRDT state (not shared with other contexts, even of the same app)
21
+ - Its own set of **members** (identities that can call methods and receive events)
22
+ - Its own blob storage
23
+ - A unique `context-id` (hex string)
24
+
25
+ When a context is created, the app's `init()` / `@Init` method is called once to seed the initial
26
+ state.
27
+
28
+ ## What an Application is
29
+
30
+ An **application** is a compiled WASM binary + optional manifest. It has:
31
+
32
+ - A unique `application-id` assigned at install time
33
+ - No state of its own — state lives in contexts that instantiate it
34
+ - One app can power many independent contexts
35
+
36
+ Installing an app does NOT create a context. These are always two separate steps.
37
+
38
+ ## What an Identity is
39
+
40
+ An **identity** is an Ed25519 keypair managed by the node:
41
+
42
+ - `identityId` — the base58-encoded public key, used as `executorPublicKey` in RPC calls
43
+ - Identities are created per node (`meroctl identity create`)
44
+ - An identity participates in a context as a member
45
+
46
+ ## Call path for an app method
47
+
48
+ ```text
49
+ Client
50
+ │ POST /api/v0/context/{contextId}/execute
51
+ │ { method, argsJson, executorPublicKey }
52
+
53
+ merod HTTP handler
54
+ │ validates JWT, resolves context
55
+
56
+ WASM executor
57
+ │ deserializes state from CRDT storage
58
+ │ calls the method
59
+ │ serializes mutations back to storage
60
+
61
+ CRDT sync engine
62
+ │ broadcasts mutation to all context members (P2P)
63
+
64
+ WebSocket subscribers
65
+ receive ExecutionEvent / StateMutation
66
+ ```
67
+
68
+ ## Sync model
69
+
70
+ State sync is automatic and happens over the P2P network:
71
+
72
+ - All context members receive all mutations from all other members
73
+ - Conflicts are resolved deterministically by the CRDT merge rules
74
+ - Sync works offline — mutations queue and merge on reconnect
75
+ - Manual sync trigger: `meroctl context sync <context-id>`
76
+
77
+ ## Multi-node participation
78
+
79
+ Nodes join contexts through the namespace + group model:
80
+
81
+ ```text
82
+ Node A creates a namespace (trust root)
83
+ → Node A creates a context
84
+ → Node A generates namespace invite token
85
+ → Node B accepts the invite (joins namespace)
86
+ → Node B joins the context via group membership
87
+ → Both nodes now sync CRDT state for that context
88
+ ```
89
+
90
+ Once joined, state sync is fully automatic. Node B receives all future mutations from Node A (and
91
+ vice versa) without any further configuration.
92
+
93
+ ## Port assignment
94
+
95
+ | Port | Purpose |
96
+ | ------------------------------ | -------------------------------------------------- |
97
+ | `--server-port` (default 2428) | HTTP/WS API — clients and meroctl connect here |
98
+ | `--swarm-port` (default 2528) | P2P swarm — inter-node state sync, invite protocol |
99
+
100
+ These ports must be accessible to meroctl and app clients (server port), and to other nodes for sync
101
+ (swarm port).
@@ -0,0 +1,192 @@
1
+ # JSON-RPC Protocol
2
+
3
+ The Calimero node exposes its application and admin APIs over HTTP/REST at
4
+ `http://localhost:2428/api/v0/` by default.
5
+
6
+ ## Base URL
7
+
8
+ ```text
9
+ http://<node-host>:<server-port>/api/v0
10
+ ```
11
+
12
+ Default: `http://localhost:2428/api/v0`
13
+
14
+ All endpoints require `Authorization: Bearer <accessToken>` except `/identity/login`.
15
+
16
+ ---
17
+
18
+ ## Authentication endpoints
19
+
20
+ ### Login
21
+
22
+ ```text
23
+ POST /api/v0/identity/login
24
+
25
+ Body: { "username": "admin", "password": "..." }
26
+
27
+ Response: {
28
+ "accessToken": "eyJ...",
29
+ "refreshToken": "eyJ..."
30
+ }
31
+ ```
32
+
33
+ ### Refresh
34
+
35
+ ```text
36
+ POST /api/v0/identity/refresh
37
+
38
+ Body: { "refreshToken": "eyJ..." }
39
+
40
+ Response: { "accessToken": "eyJ..." }
41
+ ```
42
+
43
+ ---
44
+
45
+ ## Application method execution
46
+
47
+ This is how frontends and clients invoke WASM app logic.
48
+
49
+ ### Execute a method (mutation or view)
50
+
51
+ ```text
52
+ POST /api/v0/context/{contextId}/execute
53
+
54
+ Headers:
55
+ Authorization: Bearer <accessToken>
56
+
57
+ Body: {
58
+ "method": "method_name",
59
+ "argsJson": "{\"key\":\"hello\"}",
60
+ "executorPublicKey": "<base58-identity-pubkey>"
61
+ }
62
+
63
+ Response (success): {
64
+ "output": <json-value> // method return value, null for void
65
+ }
66
+
67
+ Response (error): {
68
+ "error": {
69
+ "cause": {
70
+ "info": { "message": "..." }
71
+ }
72
+ }
73
+ }
74
+ ```
75
+
76
+ - `argsJson` is a **JSON string** (double-encoded) — not an inline object.
77
+ - `executorPublicKey` is the base58 public key of the identity making the call.
78
+ - Mutations and views use the same endpoint; views skip state persistence. The `mero-js` /
79
+ `mero-react` SDK handles this transparently.
80
+
81
+ ---
82
+
83
+ ## Context management endpoints
84
+
85
+ ### List contexts
86
+
87
+ ```text
88
+ GET /api/v0/contexts
89
+
90
+ Response: [
91
+ { "id": "<context-id>", "applicationId": "<app-id>", ... },
92
+ ...
93
+ ]
94
+ ```
95
+
96
+ ### Get context details
97
+
98
+ ```text
99
+ GET /api/v0/contexts/{contextId}
100
+ ```
101
+
102
+ ### Create context
103
+
104
+ ```text
105
+ POST /api/v0/contexts
106
+
107
+ Body: { "applicationId": "<app-id>" }
108
+
109
+ Response: { "id": "<context-id>" }
110
+ ```
111
+
112
+ ### Delete context
113
+
114
+ ```text
115
+ DELETE /api/v0/contexts/{contextId}
116
+ ```
117
+
118
+ ---
119
+
120
+ ## Application management endpoints
121
+
122
+ ### List installed applications
123
+
124
+ ```text
125
+ GET /api/v0/applications
126
+ ```
127
+
128
+ ### Install application from local file
129
+
130
+ ```text
131
+ POST /api/v0/applications
132
+ Content-Type: multipart/form-data
133
+
134
+ file: <wasm-binary>
135
+
136
+ Response: { "applicationId": "<id>" }
137
+ ```
138
+
139
+ ### Get application details
140
+
141
+ ```text
142
+ GET /api/v0/applications/{applicationId}
143
+ ```
144
+
145
+ ---
146
+
147
+ ## Identity endpoints
148
+
149
+ ### List identities
150
+
151
+ ```text
152
+ GET /api/v0/identities
153
+ ```
154
+
155
+ ### Create identity
156
+
157
+ ```text
158
+ POST /api/v0/identities
159
+
160
+ Response: { "publicKey": "<base58>", "privateKey": "<base58>" }
161
+ ```
162
+
163
+ ---
164
+
165
+ ## WebSocket endpoint
166
+
167
+ ```text
168
+ ws://localhost:2428/ws
169
+ ```
170
+
171
+ After connecting, send a subscribe message:
172
+
173
+ ```json
174
+ { "action": "subscribe", "contextIds": ["<context-id>"] }
175
+ ```
176
+
177
+ The node will push `ExecutionEvent` and `StateMutation` messages. See `websocket-events.md` for the
178
+ full schema.
179
+
180
+ ---
181
+
182
+ ## Error codes
183
+
184
+ | HTTP status | Meaning |
185
+ | ----------- | ------------------------------------------------------------- |
186
+ | `400` | Bad request — malformed JSON, missing fields |
187
+ | `401` | Unauthorized — missing or expired access token |
188
+ | `403` | Forbidden — identity not a context member |
189
+ | `404` | Not found — unknown context or application ID |
190
+ | `500` | Internal server error — WASM execution panic or storage error |
191
+
192
+ On `401`, refresh the access token and retry.
@@ -0,0 +1,94 @@
1
+ # Namespaces and Groups
2
+
3
+ Namespaces and groups are the multi-node participation primitives in Calimero. They control which
4
+ nodes can join which contexts and how trust is established between nodes.
5
+
6
+ ## Concepts
7
+
8
+ | Concept | Role |
9
+ | ------------- | ---------------------------------------------------------------------------------------------------------------- |
10
+ | **Namespace** | A root trust anchor created by one node. Other nodes join by accepting an invite token generated by the creator. |
11
+ | **Group** | A sub-grouping within a namespace. Contexts are joined via group membership, not directly. |
12
+
13
+ A node must be in the correct namespace/group before it can participate in a context.
14
+
15
+ ---
16
+
17
+ ## Full multi-node setup
18
+
19
+ ### Node A: Create and invite
20
+
21
+ ```bash
22
+ # 1. Create a namespace (trust root)
23
+ meroctl --node node1 namespace create
24
+ # → <namespace-id>
25
+
26
+ # 2. Create a context from an installed app
27
+ meroctl --node node1 context create --application-id <app-id>
28
+ # → <context-id>
29
+
30
+ # 3. Generate an invite for Node B
31
+ meroctl --node node1 namespace invite <namespace-id>
32
+ # → prints invitation JSON payload — share this with Node B out-of-band
33
+ ```
34
+
35
+ ### Node B: Accept and join
36
+
37
+ ```bash
38
+ # 4. Accept the namespace invite
39
+ meroctl --node node2 namespace join <namespace-id> '<invitation-json>'
40
+
41
+ # 5. Join the context via group membership
42
+ meroctl --node node2 group join-context <context-id>
43
+ # Node B is now a context member and will sync CRDT state from Node A
44
+ ```
45
+
46
+ After step 5, state sync is fully automatic. Both nodes send and receive mutations through the P2P
47
+ swarm.
48
+
49
+ ---
50
+
51
+ ## Namespace commands
52
+
53
+ ```bash
54
+ # Create a new namespace
55
+ meroctl --node node1 namespace create
56
+
57
+ # List namespaces on this node
58
+ meroctl --node node1 namespace ls
59
+
60
+ # Generate an invite token for a namespace
61
+ meroctl --node node1 namespace invite <namespace-id>
62
+
63
+ # Join a namespace (Node B)
64
+ meroctl --node node2 namespace join <namespace-id> '<invitation-json>'
65
+ ```
66
+
67
+ ---
68
+
69
+ ## Group commands
70
+
71
+ ```bash
72
+ # List groups
73
+ meroctl --node node1 group ls
74
+
75
+ # Join a context via group
76
+ meroctl --node node2 group join-context <context-id>
77
+ ```
78
+
79
+ ---
80
+
81
+ ## What happens after a node joins
82
+
83
+ 1. The new member receives the full CRDT state from existing members.
84
+ 2. Future mutations from any member are broadcast to all other members.
85
+ 3. Offline mutations are queued and merged on reconnect — no data is lost.
86
+ 4. The new member's identity can execute methods and its mutations are accepted by other members.
87
+
88
+ ---
89
+
90
+ ## Single-node vs multi-node
91
+
92
+ For local development on a single node you do not need namespaces or groups — create a context
93
+ directly and call methods. Namespaces/groups are only required when two or more `merod` instances
94
+ need to share a context.
@@ -0,0 +1,118 @@
1
+ # CRDT Storage Types
2
+
3
+ All shared application state must use conflict-free replicated data types (CRDTs). These types
4
+ handle concurrent writes from multiple context members automatically.
5
+
6
+ ## Why CRDTs
7
+
8
+ When two nodes both mutate state while offline and then reconnect, the CRDT engine merges both sets
9
+ of changes deterministically — no manual conflict resolution needed. Standard `HashMap`, `Vec`, or
10
+ `HashSet` do not have this property and must never be used for state that is shared across context
11
+ members.
12
+
13
+ ---
14
+
15
+ ## Type reference
16
+
17
+ | Type | Use for | Key rule |
18
+ | ------------------------- | ------------------------------------- | ------------------------------------------------- |
19
+ | `UnorderedMap<K, V>` | Key-value store | Wrap values in `LwwRegister<V>` for scalar values |
20
+ | `Vector<T>` | Append-only ordered log | Cannot remove items |
21
+ | `UnorderedSet<T>` | Unique value collection | Grow-only by default |
22
+ | `LwwRegister<T>` | Single mutable scalar | Last write wins — safe for simple values |
23
+ | `Counter` | Grow-only integer counter | `.increment()`, `.value()` |
24
+ | `Counter<true>` | PN counter (supports decrement) | `.increment()`, `.decrement()`, `.value()` |
25
+ | `FrozenStorage<T>` | Immutable content-addressed entries | Write once; identified by hash |
26
+ | `UserStorage<T>` | Per-member private storage | NOT synced to other members |
27
+ | `ReplicatedGrowableArray` | Collaborative text / ordered sequence | For collaborative editing |
28
+
29
+ ---
30
+
31
+ ## Rust import
32
+
33
+ ```rust
34
+ use calimero_storage::collections::{
35
+ Counter, FrozenStorage, LwwRegister, ReplicatedGrowableArray,
36
+ UnorderedMap, UnorderedSet, UserStorage, Vector,
37
+ };
38
+ ```
39
+
40
+ > **Critical:** Do NOT use `calimero_sdk::state::*` — that path no longer exists.
41
+
42
+ ---
43
+
44
+ ## Common patterns
45
+
46
+ ### Key-value store
47
+
48
+ ```rust
49
+ items: UnorderedMap<String, LwwRegister<String>>,
50
+
51
+ // Write
52
+ self.items.insert(key, LwwRegister::new(value))?;
53
+
54
+ // In-place update (no clone needed)
55
+ if let Some(mut guard) = self.items.get_mut(&key)? {
56
+ guard.set(new_value);
57
+ }
58
+
59
+ // Read
60
+ let val = self.items.get(&key)?.map(|v| v.get().clone());
61
+ ```
62
+
63
+ ### Append-only log
64
+
65
+ ```rust
66
+ log: Vector<String>,
67
+
68
+ self.log.push(entry)?;
69
+ let all: Vec<String> = self.log.iter()?.collect();
70
+ ```
71
+
72
+ ### Counter
73
+
74
+ ```rust
75
+ counter: Counter,
76
+
77
+ self.counter.increment()?;
78
+ let n = self.counter.value()?;
79
+ ```
80
+
81
+ ### Per-member private data (not synced)
82
+
83
+ ```rust
84
+ notes: UserStorage<String>,
85
+
86
+ self.notes.set("my private note".to_string())?;
87
+ let note = self.notes.get()?;
88
+ ```
89
+
90
+ ---
91
+
92
+ ## TypeScript (SDK JS) equivalents
93
+
94
+ The JS SDK provides equivalent CRDT types:
95
+
96
+ ```typescript
97
+ import { UnorderedMap, Vector, Counter } from '@calimero-network/calimero-sdk-js/collections';
98
+
99
+ @State
100
+ class AppState {
101
+ items: UnorderedMap<string, string> = new UnorderedMap();
102
+ log: Vector<string> = new Vector();
103
+ counter: Counter = new Counter();
104
+ }
105
+ ```
106
+
107
+ JS state fields must also be CRDT types — no plain `Map`, `Set`, or `Array`.
108
+
109
+ ---
110
+
111
+ ## Rules
112
+
113
+ 1. **Never use `std::collections` for shared state.** `HashMap`, `BTreeMap`, `Vec`, `HashSet` are
114
+ not CRDTs and will cause data loss on concurrent writes.
115
+ 2. **All collection operations are fallible in Rust.** Always use `?` to propagate errors.
116
+ 3. **`FrozenStorage` is write-once.** Use it for immutable content (images, blobs) that are
117
+ identified by hash.
118
+ 4. **`UserStorage` is per-member.** Only the local member can read/write it — it is never synced.