image
Akan.js
Docs
DocsConventionsReferencesCheatsheet
English
image
Akan.js
Akan.js v2 docs are now available.View the v1 docs
DocsConventionsReferencesCheatsheet
Released under the MIT License
Official Akan.js Consulting onAkansoft
Copyright © 2026 Akan.js All rights reserved.
System managed bybassman
General
• Authorization
• Schema Design
• Edge Computing
• File Management
• Single Sign-On
• DataList & Enum
Interface
• CRUD
• Endpoint
• Form
Observability
• Logging
• Dependency Injection
• Error Handling
• Metrics
Performance
• Caching
• Image Optimization
• Lazy Loading
• Querying
• Mutating
• Queueing
• Realtime
Development
• Documentation
• Script
• Console
• Mobile
• Docker
• Kubernetes
• PWA
General
• Authorization
• Schema Design
• Edge Computing
• File Management
• Single Sign-On
• DataList & Enum
Interface
• CRUD
• Endpoint
• Form
Observability
• Logging
• Dependency Injection
• Error Handling
• Metrics
Performance
• Caching
• Image Optimization
• Lazy Loading
• Querying
• Mutating
• Queueing
• Realtime
Development
• Documentation
• Script
• Console
• Mobile
• Docker
• Kubernetes
• PWA
Previous
Querying
Next
Queueing

Mutating

Akan has two ways to change stored data. Use document methods when you edit one loaded document, and use query updates when you change matching rows directly in the database.
  • Document methods (`.set().save()`, `Model.update`, `Model.remove`) load a document, run save/update/remove hooks, then persist it.
  • Query updates (`updateOne`, `updateMany`, `deleteMany`, `bulkWrite`) compile to a single atomic SQL statement and do not load documents.
  • Use the `u` update helper for operators, the same way `q` is used for query conditions.

Two Write Styles

Pass a plain object for simple value assignments, or a builder function to reach the operator helpers scoped to that call. A bare value is shorthand for `set`.
Object form (bare value = set)
Builder form (operators)
The builder runs synchronously. Compute awaited values (like a password hash) before the call and reference them inside the builder.

Counters And Sets

Numeric operators (`inc`, `mul`, `min`, `max`) and array operators (`push`, `addToSet`, `pull`) run inside the database, so concurrent writers do not lose each other's changes.
Atomic counters
`addToSet` and `pull` match array elements by value and are reliable for scalar sets (ids, strings, numbers).

Upsert

With `{ upsert: true }`, a missing match inserts a new row. Values from the filter seed the document, operators apply from empty defaults, and `setOnInsert` only applies on that insert.
Insert or increment

How It Becomes SQL

The adaptor folds every operator into one nested JSON expression on the `_doc` column and always stamps `updatedAt`. The whole update is a single statement the database applies atomically.
Update helperDocument updateSQL fragment
plain value (set)
{ status: "done" }
json_set(_doc, '$.status', json(?))
u.set
({ set }) => ({ status: set("done") })
json_set(_doc, '$.status', json(?))
u.unset
({ unset }) => ({ draft: unset() })
json_remove(_doc, '$.draft')
u.inc
({ inc }) => ({ views: inc(1) })
json_set(_doc, '$.views', COALESCE(json_extract(_doc, '$.views'), 0) + ?)
u.mul
({ mul }) => ({ price: mul(1.1) })
json_set(_doc, '$.price', COALESCE(json_extract(_doc, '$.price'), 0) * ?)
u.min
({ min }) => ({ lowest: min(10) })
json_set(_doc, '$.lowest', MIN(COALESCE(json_extract(_doc, '$.lowest'), ?), ?))
u.max
({ max }) => ({ highest: max(90) })
json_set(_doc, '$.highest', MAX(COALESCE(json_extract(_doc, '$.highest'), ?), ?))
u.push
({ push }) => ({ logs: push(entry) })
json_set(_doc, '$.logs', json_insert(COALESCE(json_extract(_doc, '$.logs'), json('[]')), '$[#]', json(?)))
u.addToSet
({ addToSet }) => ({ tags: addToSet("urgent") })
json_set(_doc, '$.tags', CASE WHEN EXISTS (SELECT 1 FROM json_each(...) WHERE value = ?) THEN ... ELSE json_insert(..., '$[#]', json(?)) END)
u.pull
({ pull }) => ({ tags: pull("urgent") })
json_set(_doc, '$.tags', (SELECT json_group_array(value) FROM json_each(...) WHERE value <> ?))
u.setOnInsert
({ setOnInsert }) => ({ status: setOnInsert("new") })
applied only when upsert inserts a new row
nested path
({ set }) => ({ "profile.city": set("Seoul") })
json_set(_doc, '$.profile.city', json(?))
combined
({ inc, addToSet }) => ({ views: inc(1), tags: addToSet("hot") })
json_set(json_set(_doc, '$.views', ... + ?), '$.tags', ...)
These SQL snippets are simplified to show the idea, and reflect the SQLite/libsql dialect. Postgres uses the equivalent jsonb functions. Every operator reads the pre-update document, so all changes in one call see the same original values.
Query updates do not run document hooks.
  • `updateOne`, `updateMany`, `deleteMany`, and `bulkWrite` write directly in the database and do not fire save/update/remove hooks.
  • When a per-document rule must always run, use a document path: `Model.update(id, patch)`, `Model.remove(id)`, or `doc.set(...).save()`.

Tips

  • Prefer query updates for counters and bulk state changes; prefer document methods when hooks or rich domain logic must run.
  • Use the builder form instead of importing update helpers at module scope.
  • Check `modifiedCount` from the result when a mutation must have matched a row.
Mutating
Two Write Styles
Counters And Sets
Upsert
How It Becomes SQL
Tips