Patches

A mutation selects the entities that a patch changes. A direct patch supplies entity ids without a selection query. It changes many entities in one transaction. The native database merge patch call commits every change together or commits nothing.

The document follows JSON Merge Patch semantics in the selected document format. It does not use JSON Patch operations.

Start with an empty database. Submit this merge patch. The temporary IDs #_ada, #_bob and #_cy request new entities. Bob's mentor links to Ada within the same patch.

TRON548 B
Offset (hex)Bytes (hex)Meaning
00054 52 4F 4Emagic TRON
0047C 72657469726564txt "retired"
00C01bit false
00D0F 0A 04000000 0C00 0000map leaf, size 10: @4 → @C
0173C 616765txt "age"
01B02 24000000000000 00i64 36
0240F 0A 17000000 1B00 0000map leaf, size 10: @17 → @1B
02E4C 726F6C65txt "role"
0338C 656E67696E6565 72txt "engineer"
03C0F 0A 2E000000 3300 0000map leaf, size 10: @2E → @33
0466C 616374697665txt "active"
04D09bit true
04E0F 0A 46000000 4D00 0000map leaf, size 10: @46 → @4D
05807 0E 60000000 3C00 0000 4E000000map branch, size 14, depth 1, slots 5, 6: @3C → @4E
0664C 6E616D65txt "name"
06B3C 416461txt "Ada"
06F0F 0A 66000000 6B00 0000map leaf, size 10: @66 → @6B
07907 16 01910000 0D00 0000 24000000 5800 0000 6F000000map branch, size 22, depth 0 (root), slots 0, 8, 12, 15: @D → @24 → @58 → @6F
08F5C 236C696E6Btxt "#link"
0955C 235F616461txt "#_ada"
09B0F 0A 8F000000 9500 0000map leaf, size 10: @8F → @95
0A53C 616765txt "age"
0A902 29000000000000 00i64 41
0B20F 0A A5000000 A900 0000map leaf, size 10: @A5 → @A9
0BC6C 6D656E746F72txt "mentor"
0C30F 0A BC000000 9B00 0000map leaf, size 10: @BC → @9B
0CD4C 726F6C65txt "role"
0D28C 656E67696E6565 72txt "engineer"
0DB0F 0A CD000000 D200 0000map leaf, size 10: @CD → @D2
0E56C 616374697665txt "active"
0EC01bit false
0ED0F 0A E5000000 EC00 0000map leaf, size 10: @E5 → @EC
Show all 62 nodes
Offset (hex)Bytes (hex)Meaning
0F707 0E 60000000 DB00 0000 ED000000map branch, size 14, depth 1, slots 5, 6: @DB → @ED
1054C 6E616D65txt "name"
10A3C 426F62txt "Bob"
10E0F 0A 05010000 0A01 0000map leaf, size 10: @105 → @10A
11807 16 00990000 B200 0000 C3000000 F700 0000 0E010000map branch, size 22, depth 0 (root), slots 8, 11, 12, 15: @B2 → @C3 → @F7 → @10E
12E3C 616765txt "age"
13202 1D000000000000 00i64 29
13B0F 0A 2E010000 3201 0000map leaf, size 10: @12E → @132
1454C 726F6C65txt "role"
14A8C 64657369676E65 72txt "designer"
1530F 0A 45010000 4A01 0000map leaf, size 10: @145 → @14A
15D4C 6E616D65txt "name"
1622C 4379txt "Cy"
1650F 0A 5D010000 6201 0000map leaf, size 10: @15D → @162
16F07 12 00910000 3B01 0000 53010000 6501 0000map branch, size 18, depth 0 (root), slots 8, 12, 15: @13B → @153 → @165
1816C 617574686F72txt "author"
1888C 7475746F726961 6Ctxt "tutorial"
1910F 0A 81010000 8801 0000map leaf, size 10: @181 → @188
19B6C 726561736F6Etxt "reason"
1A2DC 6372656174652D 70656F706C65txt "create-people"
1B00F 0A 9B010000 A201 0000map leaf, size 10: @19B → @1A2
1BA07 0E 40080000 9101 0000 B0010000map branch, size 14, depth 0 (root), slots 6, 11: @191 → @1B0
1C84C 235F7478txt "#_tx"
1CD0F 0A C8010000 BA01 0000map leaf, size 10: @1C8 → @1BA
1D74C 235F6379txt "#_cy"
1DC0F 0A D7010000 6F01 0000map leaf, size 10: @1D7 → @16F
1E65C 235F626F62txt "#_bob"
1EC0F 0A E6010000 1801 0000map leaf, size 10: @1E6 → @118
1F65C 235F616461txt "#_ada"
1FC0F 0A F6010000 7900 0000map leaf, size 10: @1F6 → @79
20607 16 88210000 CD01 0000 DC010000 EC01 0000 FC010000map branch, size 22, depth 0 (root), slots 3, 7, 8, 13: @1CD → @1DC → @1EC → @1FC
Offset (hex)Bytes (hex)Meaning

The commit result includes the transaction ID and an ids mapping from each temporary ID to its allocated entity ID. In this fresh database, these fields are:

TRON163 B
Offset (hex)Bytes (hex)Meaning
0054 52 4F 4Emagic TRON
044C 235F6379txt "#_cy"
0902 03000000000000 00i64 3
120F 0A 04000000 0900 0000map leaf, size 10: @4 → @9
1C5C 235F626F62txt "#_bob"
2202 02000000000000 00i64 2
2B0F 0A 1C000000 2200 0000map leaf, size 10: @1C → @22
355C 235F616461txt "#_ada"
3B02 01000000000000 00i64 1
440F 0A 35000000 3B00 0000map leaf, size 10: @35 → @3B
4E07 12 80210000 1200 0000 2B000000 4400 0000map branch, size 18, depth 0 (root), slots 7, 8, 13: @12 → @2B → @44
603C 696473txt "ids"
640F 0A 60000000 4E00 0000map leaf, size 10: @60 → @4E
6EBC 7472616E736163 74696F6Etxt "transaction"
7A02 04000000000000 00i64 4
830F 0A 6E000000 7A00 0000map leaf, size 10: @6E → @7A
8D07 0E 01020000 6400 0000 83000000map branch, size 14, depth 0 (root), slots 0, 9: @64 → @83

Keep the IDs from your actual result. An existing database can allocate different numbers. The examples below use the returned IDs 1 for Ada, 2 for Bob and 3 for Cy. The special temporary ID #_tx attaches its author and reason metadata to transaction 4.

Merge patch keyed by id

A patch is a document whose keys are entity ids. Each key is the id of a data entity. Its value contains the fields to change.

TRON205 B
Offset (hex)Bytes (hex)Meaning
0054 52 4F 4Emagic TRON
047C 72657469726564txt "retired"
0C00nil
0D0F 0A 04000000 0C00 0000map leaf, size 10: @4 → @C
174C 726F6C65txt "role"
1C8C 656E67696E6565 72txt "engineer"
250F 0A 17000000 1C00 0000map leaf, size 10: @17 → @1C
2F6C 616374697665txt "active"
3601bit false
370F 0A 2F000000 3600 0000map leaf, size 10: @2F → @36
4107 0E 60000000 2500 0000 37000000map branch, size 14, depth 1, slots 5, 6: @25 → @37
4F07 0E 01100000 0D00 0000 41000000map branch, size 14, depth 0 (root), slots 0, 12: @D → @41
5D6C 616374697665txt "active"
6409bit true
650F 0A 5D000000 6400 0000map leaf, size 10: @5D → @64
6F4C 6E6F7465txt "note"
748C 72657669657765 64txt "reviewed"
7D0F 0A 6F000000 7400 0000map leaf, size 10: @6F → @74
8707 0E C0000000 6500 0000 7D000000map branch, size 14, depth 1, slots 6, 7: @65 → @7D
9507 0A 00100000 8700 0000map branch, size 10, depth 0 (root), slots 12: @87
9F1C 31txt "1"
A10F 0A 9F000000 4F00 0000map leaf, size 10: @9F → @4F
AB1C 32txt "2"
AD0F 0A AB000000 9500 0000map leaf, size 10: @AB → @95
B707 0E 14000000 A100 0000 AD000000map branch, size 14, depth 0 (root), slots 2, 4: @A1 → @AD

This patch gives:

TRON356 B
Offset (hex)Bytes (hex)Meaning
00054 52 4F 4Emagic TRON
0043C 616765txt "age"
00802 24000000000000 00i64 36
0110F 0A 04000000 0800 0000map leaf, size 10: @4 → @8
01B4C 726F6C65txt "role"
0208C 656E67696E6565 72txt "engineer"
0290F 0A 1B000000 2000 0000map leaf, size 10: @1B → @20
0336C 616374697665txt "active"
03A01bit false
03B0F 0A 33000000 3A00 0000map leaf, size 10: @33 → @3A
04507 0E 60000000 2900 0000 3B000000map branch, size 14, depth 1, slots 5, 6: @29 → @3B
0534C 6E616D65txt "name"
0583C 416461txt "Ada"
05C0F 0A 53000000 5800 0000map leaf, size 10: @53 → @58
06607 12 00910000 1100 0000 45000000 5C00 0000map branch, size 18, depth 0 (root), slots 8, 12, 15: @11 → @45 → @5C
0785C 236C696E6Btxt "#link"
07E02 01000000000000 00i64 1
0870F 0A 78000000 7E00 0000map leaf, size 10: @78 → @7E
0913C 616765txt "age"
09502 29000000000000 00i64 41
09E0F 0A 91000000 9500 0000map leaf, size 10: @91 → @95
0A86C 6D656E746F72txt "mentor"
0AF0F 0A A8000000 8700 0000map leaf, size 10: @A8 → @87
0B94C 726F6C65txt "role"
0BE8C 656E67696E6565 72txt "engineer"
0C70F 0A B9000000 BE00 0000map leaf, size 10: @B9 → @BE
0D16C 616374697665txt "active"
0D809bit true
0D90F 0A D1000000 D800 0000map leaf, size 10: @D1 → @D8
0E34C 6E6F7465txt "note"
0E88C 72657669657765 64txt "reviewed"
0F10F 0A E3000000 E800 0000map leaf, size 10: @E3 → @E8
0FB07 12 E0000000 C700 0000 D9000000 F100 0000map branch, size 18, depth 1, slots 5, 6, 7: @C7 → @D9 → @F1
10D4C 6E616D65txt "name"
1123C 426F62txt "Bob"
1160F 0A 0D010000 1201 0000map leaf, size 10: @10D → @112
12007 16 00990000 9E00 0000 AF000000 FB00 0000 16010000map branch, size 22, depth 0 (root), slots 8, 11, 12, 15: @9E → @AF → @FB → @116
1361C 31txt "1"
1380F 0A 36010000 6600 0000map leaf, size 10: @136 → @66
1421C 32txt "2"
1440F 0A 42010000 2001 0000map leaf, size 10: @142 → @120
14E07 0E 14000000 3801 0000 44010000map branch, size 14, depth 0 (root), slots 2, 4: @138 → @144

The merge patch rules produce that result:

  • An omitted member stays as it is.
  • The patch removes a member set to null.
  • Each other value replaces the current value.

Stardust applies those rules to each entity that the patch names. The members of an entity object are its fields. That is why bob keeps role engineer, ada loses retired, and each other supplied field gets its new value. A transaction document uses the same decimal ID keys.

Compare-and-set per entity

Put per-entity conditions in #_tx.stardust/checkAndSet. Each map key is a decimal entity ID. Its value is the expected transaction.

For a separate example, submit only the initial patch in a fresh database. It changes Ada and Bob in transaction 4. Then submit:

TRON164 B
Offset (hex)Bytes (hex)Meaning
0054 52 4F 4Emagic TRON
043C 616765txt "age"
0802 25000000000000 00i64 37
110F 0A 04000000 0800 0000map leaf, size 10: @4 → @8
1B1C 31txt "1"
1D02 04000000000000 00i64 4
260F 0A 1B000000 1D00 0000map leaf, size 10: @1B → @1D
301C 32txt "2"
3202 04000000000000 00i64 4
3B0F 0A 30000000 3200 0000map leaf, size 10: @30 → @32
4507 0E 14000000 2600 0000 3B000000map branch, size 14, depth 0 (root), slots 2, 4: @26 → @3B
5314 14 737461726475 73742F636865636B 416E64536574txt "stardust/checkAndSet"
690F 0A 53000000 4500 0000map leaf, size 10: @53 → @45
731C 31txt "1"
750F 0A 73000000 1100 0000map leaf, size 10: @73 → @11
7F4C 235F7478txt "#_tx"
840F 0A 7F000000 6900 0000map leaf, size 10: @7F → @69
8E07 0E 0C000000 7500 0000 84000000map branch, size 14, depth 0 (root), slots 2, 3: @75 → @84

This patch changes Ada only if both entities still have revision 4. A change to another entity does not invalidate these checks. A check can guard an entity that the patch does not change.

Stardust checks every revision in the atomic write transaction. A mismatch rejects the complete patch with conflict. The diagnostic identifies the entity and gives the expected and actual revisions. No changes or temporary IDs are committed. The engine consumes stardust/checkAndSet; it does not store these conditions as transaction metadata.

A revision includes field removals and entity deletion. Changing a value and then restoring it does not restore the old revision. To retry after a conflict, read the current state and obtain its revision before computing a new patch.

Positive expectations require an exact last-touch transaction. Zero instead requires that the entity has no current facts, including after deletion:

TRON80 B
Offset (hex)Bytes (hex)Meaning
0054 52 4F 4Emagic TRON
041C 31txt "1"
0602 00000000000000 00i64 0
0F0F 0A 04000000 0600 0000map leaf, size 10: @4 → @6
1914 14 737461726475 73742F636865636B 416E64536574txt "stardust/checkAndSet"
2F0F 0A 19000000 0F00 0000map leaf, size 10: @19 → @F
394C 235F7478txt "#_tx"
3E0F 0A 39000000 2F00 0000map leaf, size 10: @39 → @2F

A request with only conditions checks them without creating a transaction. It returns no_change on success. The same checks apply when supplied field values already match. Other transaction metadata accompanies data changes.

The condition must be an object. Checked IDs must be decimal user entity IDs and appear only once. Expected transactions must be nonnegative integers. An empty condition map imposes no entity conditions. Temporary IDs, deletions and other transaction metadata work as in an ordinary patch.

Reading an entity for a conditional write

Read entity returns one document from one store snapshot. Read Bob:

TRON38 B
Offset (hex)Bytes (hex)Meaning
0054 52 4F 4Emagic TRON
046C 656E74697479txt "entity"
0B02 02000000000000 00i64 2
140F 0A 04000000 0B00 0000map leaf, size 10: @4 → @B

The result is:

TRON301 B
Offset (hex)Bytes (hex)Meaning
00054 52 4F 4Emagic TRON
0045C 236C696E6Btxt "#link"
00A02 01000000000000 00i64 1
0130F 0A 04000000 0A00 0000map leaf, size 10: @4 → @A
01D3C 616765txt "age"
02102 29000000000000 00i64 41
02A0F 0A 1D000000 2100 0000map leaf, size 10: @1D → @21
0346C 6D656E746F72txt "mentor"
03B0F 0A 34000000 1300 0000map leaf, size 10: @34 → @13
0454C 726F6C65txt "role"
04A8C 656E67696E6565 72txt "engineer"
0530F 0A 45000000 4A00 0000map leaf, size 10: @45 → @4A
05D6C 616374697665txt "active"
06409bit true
0650F 0A 5D000000 6400 0000map leaf, size 10: @5D → @64
06F4C 6E6F7465txt "note"
0748C 72657669657765 64txt "reviewed"
07D0F 0A 6F000000 7400 0000map leaf, size 10: @6F → @74
08707 12 E0000000 5300 0000 65000000 7D00 0000map branch, size 18, depth 1, slots 5, 6, 7: @53 → @65 → @7D
0994C 6E616D65txt "name"
09E3C 426F62txt "Bob"
0A20F 0A 99000000 9E00 0000map leaf, size 10: @99 → @9E
0AC07 16 00990000 2A00 0000 3B000000 8700 0000 A2000000map branch, size 22, depth 0 (root), slots 8, 11, 12, 15: @2A → @3B → @87 → @A2
0C21C 32txt "2"
0C402 05000000000000 00i64 5
0CD0F 0A C2000000 C400 0000map leaf, size 10: @C2 → @C4
0D79C 7472756E636174 6564txt "truncated"
0E101bit false
0E20F 0A D7000000 E100 0000map leaf, size 10: @D7 → @E1
0EC8C 646F63756D656E 74txt "document"
0F50F 0A EC000000 AC00 0000map leaf, size 10: @EC → @AC
0FF9C 7265766973696F 6E73txt "revisions"
1090F 0A FF000000 CD00 0000map leaf, size 10: @FF → @CD
11307 12 82200000 E200 0000 F5000000 0901 0000map branch, size 18, depth 0 (root), slots 1, 7, 13: @E2 → @F5 → @109

document holds the entity's current fields. revisions maps each entity the result reflects to its last-touch transaction, so it serves directly as the stardust/checkAndSet map of a patch computed from that document. The returned document is independent of the database. Close it when finished.

Read results omit object fields whose values are empty objects or arrays, including objects that become empty after nested fields are omitted. This applies to entity data and metadata: links is absent when empty. Null, empty strings, false, zero, and array positions stay intact. document is absent if all its fields are omitted.

An entity with no current facts has a null document. Its revision is the deletion's transaction after a deletion, and zero for a never-touched entity. Thus, a positive condition can require an exact deletion revision, while zero checks only the absence of current facts.

With max_depth 1, the read also expands Bob's mentor in place. links preserves the original mentor reference, and revisions adds Ada:

TRON81 B
Offset (hex)Bytes (hex)Meaning
0054 52 4F 4Emagic TRON
046C 656E74697479txt "entity"
0B02 02000000000000 00i64 2
140F 0A 04000000 0B00 0000map leaf, size 10: @4 → @B
1E9C 6D61785F646570 7468txt "max_depth"
2802 01000000000000 00i64 1
310F 0A 1E000000 2800 0000map leaf, size 10: @1E → @28
3B07 0E 10020000 1400 0000 31000000map branch, size 14, depth 0 (root), slots 4, 9: @14 → @31

The result is:

TRON500 B
Offset (hex)Bytes (hex)Meaning
00054 52 4F 4Emagic TRON
0043C 616765txt "age"
00802 24000000000000 00i64 36
0110F 0A 04000000 0800 0000map leaf, size 10: @4 → @8
01B4C 726F6C65txt "role"
0208C 656E67696E6565 72txt "engineer"
0290F 0A 1B000000 2000 0000map leaf, size 10: @1B → @20
0336C 616374697665txt "active"
03A01bit false
03B0F 0A 33000000 3A00 0000map leaf, size 10: @33 → @3A
04507 0E 60000000 2900 0000 3B000000map branch, size 14, depth 1, slots 5, 6: @29 → @3B
0534C 6E616D65txt "name"
0583C 416461txt "Ada"
05C0F 0A 53000000 5800 0000map leaf, size 10: @53 → @58
06607 12 00910000 1100 0000 45000000 5C00 0000map branch, size 18, depth 0 (root), slots 8, 12, 15: @11 → @45 → @5C
0783C 616765txt "age"
07C02 29000000000000 00i64 41
0850F 0A 78000000 7C00 0000map leaf, size 10: @78 → @7C
08F6C 6D656E746F72txt "mentor"
0960F 0A 8F000000 6600 0000map leaf, size 10: @8F → @66
0A04C 726F6C65txt "role"
0A58C 656E67696E6565 72txt "engineer"
0AE0F 0A A0000000 A500 0000map leaf, size 10: @A0 → @A5
0B86C 616374697665txt "active"
0BF09bit true
0C00F 0A B8000000 BF00 0000map leaf, size 10: @B8 → @BF
0CA4C 6E6F7465txt "note"
0CF8C 72657669657765 64txt "reviewed"
0D80F 0A CA000000 CF00 0000map leaf, size 10: @CA → @CF
0E207 12 E0000000 AE00 0000 C0000000 D800 0000map branch, size 18, depth 1, slots 5, 6, 7: @AE → @C0 → @D8
0F44C 6E616D65txt "name"
0F93C 426F62txt "Bob"
0FD0F 0A F4000000 F900 0000map leaf, size 10: @F4 → @F9
10707 16 00990000 8500 0000 96000000 E200 0000 FD000000map branch, size 22, depth 0 (root), slots 8, 11, 12, 15: @85 → @96 → @E2 → @FD
11D5C 236C696E6Btxt "#link"
12302 01000000000000 00i64 1
12C0F 0A 1D010000 2301 0000map leaf, size 10: @11D → @123
1367C 2F6D656E746F72txt "/mentor"
13E0F 0A 36010000 2C01 0000map leaf, size 10: @136 → @12C
1481C 31txt "1"
14A02 05000000000000 00i64 5
1530F 0A 48010000 4A01 0000map leaf, size 10: @148 → @14A
15D1C 32txt "2"
15F02 05000000000000 00i64 5
1680F 0A 5D010000 5F01 0000map leaf, size 10: @15D → @15F
17207 0E 14000000 5301 0000 68010000map branch, size 14, depth 0 (root), slots 2, 4: @153 → @168
1809C 7472756E636174 6564txt "truncated"
18A01bit false
18B0F 0A 80010000 8A01 0000map leaf, size 10: @180 → @18A
1958C 646F63756D656E 74txt "document"
19E0F 0A 95010000 0701 0000map leaf, size 10: @195 → @107
1A85C 6C696E6B73txt "links"
1AE0F 0A A8010000 3E01 0000map leaf, size 10: @1A8 → @13E
1B807 0E 20100000 9E01 0000 AE010000map branch, size 14, depth 1, slots 5, 12: @19E → @1AE
1C69C 7265766973696F 6E73txt "revisions"
1D00F 0A C6010000 7201 0000map leaf, size 10: @1C6 → @172
1DA07 12 82200000 8B01 0000 B8010000 D001 0000map branch, size 18, depth 0 (root), slots 1, 7, 13: @18B → @1B8 → @1D0

links maps JSON Pointer paths within document to their original references. /employer/city selects a nested field; /friends/0 selects an array element. In a field name, ~ becomes ~0 and / becomes ~1, so /a~1b selects the field a/b. References replaced by null or an omitted empty object keep their entries in links.

max_depth counts link hops along each path, up to 32. A link to an entity with no current fields becomes null, and the field stays; revisions still records that entity. Links inside stardust/ fields stay references. An entity reached on several paths, or on a cycle, is expanded again on each path until max_depth. Every link one hop away is expanded before any two hops away.

max_entities (default 1000) bounds the entity documents in the result, and the result counts against the database memory budget. When either limit refuses an expansion, that link and every later one stay {#link id} references and truncated is true. The conditions in revisions then cover exactly the entities in the document.

A link in a patch can name an ID that no entity has yet. An ID of 0, a negative ID or an ID at or above 2^40 is refused with invalid_argument and the code invalid_link, which names the key and field.

A no-change commit does not give a new entity revision. Do not infer the last touch from the newest remaining fact: a field removal can be more recent.

Creating with temporary names

A key that starts with #_ is a temporary name for an entity the patch creates. Stardust allocates an id for the entity when the patch commits. {#link #_name} refers to that id anywhere in the same patch, including in a new or existing entity. The temporary name applies only within the patch. After the commit, use the entity ID to address the entity. The engine rejects a repeated name, an undeclared temporary link target, and null on a temporary name.

TRON270 B
Offset (hex)Bytes (hex)Meaning
00054 52 4F 4Emagic TRON
0045C 236C696E6Btxt "#link"
00A02 01000000000000 00i64 1
0130F 0A 04000000 0A00 0000map leaf, size 10: @4 → @A
01D6C 6D656E746F72txt "mentor"
0240F 0A 1D000000 1300 0000map leaf, size 10: @1D → @13
02E4C 6E616D65txt "name"
0335C 4361726F6Ctxt "Carol"
0390F 0A 2E000000 3300 0000map leaf, size 10: @2E → @33
04307 0E 00880000 2400 0000 39000000map branch, size 14, depth 0 (root), slots 11, 15: @24 → @39
0515C 236C696E6Btxt "#link"
0577C 235F6361726F6Ctxt "#_carol"
05F0F 0A 51000000 5700 0000map leaf, size 10: @51 → @57
0695C 6275646479txt "buddy"
06F0F 0A 69000000 5F00 0000map leaf, size 10: @69 → @5F
0794C 6E616D65txt "name"
07E4C 44617665txt "Dave"
0830F 0A 79000000 7E00 0000map leaf, size 10: @79 → @7E
08D07 0E 04800000 6F00 0000 83000000map branch, size 14, depth 0 (root), slots 2, 15: @6F → @83
09B5C 236C696E6Btxt "#link"
0A17C 235F6361726F6Ctxt "#_carol"
0A90F 0A 9B000000 A100 0000map leaf, size 10: @9B → @A1
0B37C 70726F74656765txt "protege"
0BB0F 0A B3000000 A900 0000map leaf, size 10: @B3 → @A9
0C51C 31txt "1"
0C70F 0A C5000000 BB00 0000map leaf, size 10: @C5 → @BB
0D17C 235F6361726F6Ctxt "#_carol"
0D90F 0A D1000000 4300 0000map leaf, size 10: @D1 → @43
0E36C 235F64617665txt "#_dave"
0EA0F 0A E3000000 8D00 0000map leaf, size 10: @E3 → @8D
0F407 12 04180000 C700 0000 D9000000 EA00 0000map branch, size 18, depth 0 (root), slots 2, 11, 12: @C7 → @D9 → @EA

This commits two new entities and one change to Ada, all in one transaction. After the preceding examples, the commit returns these fields:

TRON138 B
Offset (hex)Bytes (hex)Meaning
0054 52 4F 4Emagic TRON
047C 235F6361726F6Ctxt "#_carol"
0C02 06000000000000 00i64 6
150F 0A 04000000 0C00 0000map leaf, size 10: @4 → @C
1F6C 235F64617665txt "#_dave"
2602 07000000000000 00i64 7
2F0F 0A 1F000000 2600 0000map leaf, size 10: @1F → @26
3907 0E 00180000 1500 0000 2F000000map branch, size 14, depth 0 (root), slots 11, 12: @15 → @2F
473C 696473txt "ids"
4B0F 0A 47000000 3900 0000map leaf, size 10: @47 → @39
55BC 7472616E736163 74696F6Etxt "transaction"
6102 08000000000000 00i64 8
6A0F 0A 55000000 6100 0000map leaf, size 10: @55 → @61
7407 0E 01020000 4B00 0000 6A000000map branch, size 14, depth 0 (root), slots 0, 9: @4B → @6A

Use the returned IDs to refer to Carol and Dave in later calls.

#_tx is a special temporary ID for the transaction that commits the patch. Its object supplies metadata, such as author and reason, on that transaction. It does not create an ordinary data entity. {#link #_tx} links to the same transaction from elsewhere in the patch. This metadata accompanies data changes. Stardust rejects a patch that contains only #_tx.

One value at a time

A list or a map stored in a field is one Stardust value. The patch gives the complete intended value for that field. It does not give editing operations inside the value. An object inside an entity is a map in that field, never a second entity: a data entity has no children.

Stardust compares each field with the current facts before it writes. Stardust skips a field that already matches. It also skips null for a field that is absent. A patch with no effect commits no transaction.

Deleting

An entity id set to null deletes that entity.

TRON25 B
Offset (hex)Bytes (hex)Meaning
0054 52 4F 4Emagic TRON
041C 32txt "2"
0600nil
070F 0A 04000000 0600 0000map leaf, size 10: @4 → @6

After this patch, bob has no facts:

TRON25 B
Offset (hex)Bytes (hex)Meaning
0054 52 4F 4Emagic TRON
041C 32txt "2"
0600nil
070F 0A 04000000 0600 0000map leaf, size 10: @4 → @6

Entities elsewhere that link to a deleted entity survive, and their links keep the deleted target's stable numeric id.

Invalid patch forms

The keys of a patch are ids, so a field at the root is an error.

TRON31 B
Offset (hex)Bytes (hex)Meaning
0054 52 4F 4Emagic TRON
047C 6D697373696E67txt "missing"
0C09bit true
0D0F 0A 04000000 0C00 0000map leaf, size 10: @4 → @C

Error

Rejected

An invalid id is an error, and the patch changes nothing:

TRON58 B
Offset (hex)Bytes (hex)Meaning
0054 52 4F 4Emagic TRON
044C 726F6C65txt "role"
097C 76697369746F72txt "visitor"
110F 0A 04000000 0900 0000map leaf, size 10: @4 → @9
1BCC 66666666666666 6666666666txt "ffffffffffff"
280F 0A 1B000000 1100 0000map leaf, size 10: @1B → @11

Error

Rejected

A #_name key creates a data entity. References to that temporary name resolve within the patch. The native commit result gives the resolved ids.

Stardust also rejects text that is not a document. No rejected patch changes a stored fact.

A schema-checked patch

The core patch operation can validate each staged entity against a schema before it commits. A deleted entity has no document to validate. The current public merge-patch call does not expose that schema scope.

Reading the committed transaction

A commit result identifies its transaction. Reading that transaction document gives the patch for the commit, keyed by entity id. Each value holds asserted fields and null for retractions. Unchanged fields are absent.

The transaction entity appears under its own ID with stardust/transaction and stardust/committedAt. There are no entities or transaction wrappers. Definition and object changes include their structural facts and numeric references. The recorded patch stays the same after a later rename or deletion. Native documents and DUST contain the same changes.

Run it

database merge patch.