mirror of
https://github.com/ZoneTree/ZoneTree.git
synced 2026-10-27 12:11:28 +08:00
Explain LSM Tree architecture in docs
This commit is contained in:
@@ -63,6 +63,10 @@ static List<string> GetMarkdownFiles(string repositoryRoot, List<string> errors)
|
||||
docsDirectory,
|
||||
"*.md",
|
||||
SearchOption.AllDirectories));
|
||||
files.AddRange(Directory.EnumerateFiles(
|
||||
docsDirectory,
|
||||
"*.mdx",
|
||||
SearchOption.AllDirectories));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -47,6 +47,9 @@ Restore those from your private copy before building in a fresh checkout.
|
||||
`analytics-spotlight.ts`, `full-text-search-spotlight.ts`, and `hero.ts`.
|
||||
- Short landing-page examples use fenced code blocks directly in
|
||||
`content/index.mdx`.
|
||||
- `content/docs/concepts/lsm-tree.mdx` explains the storage architecture with
|
||||
dedicated `lsm-architecture.ts`, `lsm-read-path.ts`, and `lsm-part-reuse.ts`
|
||||
components. `content/ui/lsm-styles.ts` registers their typed styles.
|
||||
|
||||
Keep benchmark claims tied to retained reports. The landing-page comparison
|
||||
uses completed phase times from `content/docs/benchmark/reference/2m/2m.md`,
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"usage/iteration-and-range-scans.md",
|
||||
"usage/maintenance.md",
|
||||
"concepts/",
|
||||
"concepts/lsm-tree.md",
|
||||
"concepts/lsm-tree.mdx",
|
||||
"concepts/segments.md",
|
||||
"concepts/bloom-filters.md",
|
||||
"concepts/key-ordering.md",
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
# LSM Tree
|
||||
|
||||
ZoneTree uses a Log-Structured Merge-tree model: writes are accepted in memory, protected by the WAL, then merged into sorted persistent segments by maintenance work.
|
||||
|
||||
The model is optimized for write throughput, ordered reads, and controlled background compaction.
|
||||
|
||||
## Layer Progression
|
||||
|
||||
```text
|
||||
mutable segment
|
||||
-> read-only segments
|
||||
-> DiskSegment
|
||||
-> bottom segments
|
||||
```
|
||||
|
||||
| Layer | Purpose |
|
||||
| --- | --- |
|
||||
| Mutable segment | receives current writes |
|
||||
| Read-only segments | frozen in-memory batches waiting for merge |
|
||||
| `DiskSegment` | persistent segment used by normal merge |
|
||||
| Bottom segments | persistent segments in the bottom segment queue |
|
||||
|
||||
The mutable segment is bounded by `MutableSegmentMaxItemCount`. The `DiskSegment` layer is bounded by `DiskSegmentMaxItemCount`. These are record counts, not byte limits.
|
||||
|
||||
## Visibility Order
|
||||
|
||||
Reads search newest layers first:
|
||||
|
||||
```text
|
||||
mutable segment
|
||||
-> read-only segments
|
||||
-> DiskSegment
|
||||
-> bottom segments
|
||||
```
|
||||
|
||||
That order gives ZoneTree its update semantics. A newer value hides an older value with the same key. A newer deletion marker hides older values with the same key.
|
||||
|
||||
Iterators merge the same ordered layers into a single ordered view.
|
||||
|
||||
## Merge Behavior
|
||||
|
||||
Normal merge takes read-only in-memory segments and merges them with `DiskSegment`. The output becomes the next `DiskSegment`.
|
||||
|
||||
If the new `DiskSegment` grows beyond `DiskSegmentMaxItemCount`, it moves to bottom segments and the `DiskSegment` slot is reset.
|
||||
|
||||
Bottom segment merge is separate. It compacts older sealed disk segments without changing the current mutable or read-only write path.
|
||||
|
||||
## Multipart Persistent Layer
|
||||
|
||||
ZoneTree's default disk segment is multipart. One logical disk segment can contain many ordered immutable part files:
|
||||
|
||||
```text
|
||||
multipart disk segment
|
||||
-> part A
|
||||
-> part B
|
||||
-> part C
|
||||
```
|
||||
|
||||
This horizontal shape matters during merge. If incoming records affect only a local key range, ZoneTree can rewrite the affected range and carry unrelated clean parts forward. The persistent rewrite unit is bounded by multipart part sizes instead of the entire disk segment.
|
||||
|
||||
Multipart behavior is one of the main reasons ZoneTree can keep write amplification local for workloads with key locality. The full tuning model is covered in [write amplification](../tuning/write-amplification.md).
|
||||
|
||||
## Tuning Axes
|
||||
|
||||
The main LSM controls are:
|
||||
|
||||
* mutable segment record count,
|
||||
* `DiskSegment` record count,
|
||||
* multipart part size range,
|
||||
* WAL mode,
|
||||
* compression,
|
||||
* block cache cleanup,
|
||||
* maintenance scheduling.
|
||||
|
||||
The practical decision is how much data to batch vertically, and how large each horizontal persistent rewrite unit should be.
|
||||
|
||||
See [write amplification](../tuning/write-amplification.md), [disk segment tuning](../tuning/disk-segments.md), and [read-path caching](../tuning/read-path-caching.md).
|
||||
@@ -0,0 +1,155 @@
|
||||
---
|
||||
title: LSM Tree
|
||||
description: How ZoneTree combines concurrent ordered memory, immutable multipart disk segments, and background compaction into one storage engine.
|
||||
---
|
||||
|
||||
# LSM-tree architecture
|
||||
|
||||
<div class="zt-lsm-intro">
|
||||
<p>Write into ordered memory. Compact into immutable disk segments. Read across both, while the engine keeps moving.</p>
|
||||
<p>ZoneTree is a Log-Structured Merge-tree storage engine. It separates accepting application writes from reorganizing persistent data, so parallel readers and writers can work alongside background compaction.</p>
|
||||
</div>
|
||||
|
||||
## One tree, four storage layers
|
||||
|
||||
A ZoneTree instance has one active mutable segment, a queue of read-only memory segments, a current disk-segment slot, and a queue of bottom disk segments. Together, they expose one ordered keyspace through the same read and write APIs.
|
||||
|
||||
<LsmArchitecture/>
|
||||
|
||||
The first two layers hold records in memory. The last two hold immutable persistent data. A new tree starts with an empty mutable segment; the remaining layers fill as data moves through the lifecycle.
|
||||
|
||||
<div class="zt-lsm-takeaway">
|
||||
<strong>Two dimensions shape the engine.</strong> Data moves vertically through storage layers. Within a multipart disk segment, ordered parts divide the keyspace horizontally. A part is a piece of a logical disk segment, rather than another LSM layer.
|
||||
</div>
|
||||
|
||||
## Follow a write
|
||||
|
||||
### 1. Accept it in the mutable segment
|
||||
|
||||
An `Upsert` enters the current mutable segment. Its concurrent B+Tree keeps keys in comparer order as writes arrive. Updating a key already in that segment replaces its in-memory value; updating a key that exists only in an older segment creates a newer record in the mutable segment.
|
||||
|
||||
Recent writes are also recorded through the configured write-ahead log. WAL mode determines the logging path and durability boundary. The mutable segment is where the application reads and updates current in-memory records; the WAL supports their recovery.
|
||||
|
||||
The default B+Tree lock mode uses node-level synchronization. Multiple application threads can write to the same ZoneTree, and independent reads can run concurrently. Operations that need coordinated read-modify-write behavior have separate [atomic APIs](../usage/atomic-operations.md).
|
||||
|
||||
### 2. Move a full segment to read-only memory
|
||||
|
||||
When the mutable segment reaches `MutableSegmentMaxItemCount`, the write path moves it forward: it freezes the old segment, adds it to the read-only queue, and installs a fresh mutable segment. A write that encounters a full or frozen segment retries against the active segment.
|
||||
|
||||
The frozen records remain readable in memory while new writes enter the replacement. Rollover does not wait for those records to be merged into disk storage. Several read-only segments can accumulate while a merge is running.
|
||||
|
||||
The limit counts records in the mutable segment, rather than the number of write calls or bytes allocated. Repeated updates to an existing key do not each add another record to that segment. Value sizes still determine how much memory those records consume.
|
||||
|
||||
### 3. Compact frozen batches into disk storage
|
||||
|
||||
A normal merge captures the available read-only segments and merges their ordered records with the **current disk segment**, if one exists. For duplicate keys, the newer source takes precedence. The merge writes a new disk segment and can reuse eligible parts from the previous one.
|
||||
|
||||
Once the output is ready, the engine publishes the replacement and removes the consumed read-only segments from the active layer set. Read-only segments created after that merge captured its inputs remain queued for a later merge.
|
||||
|
||||
The active mutable segment stays outside this merge. New writes and reads continue while compaction builds its output. Brief synchronization protects rollover and publication; background compaction still competes for CPU, memory bandwidth, and storage I/O.
|
||||
|
||||
## Read the newest visible value
|
||||
|
||||
A point lookup searches the mutable segment first, then read-only segments from newest to oldest, then the current disk segment, and finally bottom segments from newest to oldest. It stops at the first matching record.
|
||||
|
||||
<LsmReadPath/>
|
||||
|
||||
That order makes an update visible before older copies have been compacted away. Visibility follows the precedence of the storage layers and their segments. It does not require a lookup to collect every version of a key and compare timestamps.
|
||||
|
||||
**Deletion follows the same rule.** A deletion marker in a newer segment hides an older live value. Finding that marker ends the lookup with “not found”; falling through to an older value would incorrectly bring the record back.
|
||||
|
||||
Point reads have several accelerators. In-memory segment Bloom filters can reject absent keys. Multipart boundary keys select a candidate disk part, sparse indexes narrow the search within disk data, and caches reduce repeated reads and deserialization. See [read-path caching](../tuning/read-path-caching.md) for the individual mechanisms.
|
||||
|
||||
### Range scans combine the layers
|
||||
|
||||
An iterator merges ordered streams from its included segments into one forward or reverse sequence. For equal keys, the newer source wins, and deletion markers are hidden by default.
|
||||
|
||||
The default iterator can refresh its segment set after mutable-segment rollover. For a stable scan, `IteratorType.Snapshot` moves the mutable segment forward and scans the resulting read-only and persistent region, excluding later writes. Dispose iterators promptly: they retain the disk segments they use, which can delay physical removal of replaced files.
|
||||
|
||||
See [iteration and range scans](../usage/iteration-and-range-scans.md) for visibility options, seeking, and iterator lifetime.
|
||||
|
||||
## Multipart storage keeps merge work local
|
||||
|
||||
`MultiPartDiskSegment` is the default persistent layout. It presents one logical ordered segment backed by multiple immutable parts. Each part covers a key range, and the multipart descriptor records their order and boundary information.
|
||||
|
||||
During compaction, ZoneTree can carry an eligible clean part into the new logical segment without rewriting its records. Ranges affected by incoming records are merged into new output parts.
|
||||
|
||||
<LsmPartReuse/>
|
||||
|
||||
Reuse depends on more than the absence of updates inside a part. The merge also checks its size and whether the current output boundary allows carrying it. Small parts may be rewritten to avoid excessive fragmentation. Updates spread across most of the keyspace can affect most parts and approach a full rewrite.
|
||||
|
||||
New part targets vary between the configured minimum and maximum record counts, with boundary adjustments during merge. This avoids repeatedly emitting rigid, fully packed parts. Existing files stay immutable: extra capacity below a part's maximum is room for a future **replacement** to grow, rather than space that gets patched in place.
|
||||
|
||||
This is where vertical batching and horizontal partitioning work together. Memory segments batch incoming writes; disk parts let compaction concentrate physical rewrite work around affected ranges. See [write amplification](../tuning/write-amplification.md) for examples and sizing tradeoffs.
|
||||
|
||||
## Keep the normal disk merge layer bounded
|
||||
|
||||
After normal compaction, ZoneTree compares the new disk segment's record count with `DiskSegmentMaxItemCount`. If the result exceeds that threshold, the **whole logical disk segment** moves to the bottom queue. The current disk-segment slot becomes empty, ready for future read-only merges to create a fresh segment.
|
||||
|
||||
Bottom segments remain readable and can overlap in key range. Newer bottom segments take precedence over older ones. They can also be multipart, so the bottom queue contains logical disk segments, each with its own internal parts.
|
||||
|
||||
**Bottom compaction is a separate operation.** It merges a selected contiguous range of bottom segments and replaces that range with its merged output. It leaves the active mutable segment, read-only queue, and current disk-segment slot outside its input set.
|
||||
|
||||
Deletion markers must survive while older records outside a merge could still be visible. Normal compaction retains them when bottom segments exist. A bottom merge retains them when older bottom segments remain outside its selected range; reaching the oldest bottom segment allows deleted records to be discarded by that merge.
|
||||
|
||||
## Recovery runs alongside the storage lifecycle
|
||||
|
||||
Immutable disk segments hold data that has already been compacted. WALs hold recent updates for the in-memory segments, and the metadata WAL records changes to the active segment structure. On reopening a tree, recovery loads the persistent segments and reconstructs in-memory state from the available logs.
|
||||
|
||||
The default WAL mode is `AsyncCompressed`. Its background logging path supports high write throughput. A returned write, a completed compaction, and a hardware-level flush are different events; the configured WAL mode, flush behavior, and storage stack determine the recovery boundary. With `No WAL`, recent in-memory writes cannot be recovered after process termination.
|
||||
|
||||
Use [WAL modes](../durability/wal-modes.md) to choose that boundary, [recovery](../durability/recovery.md) to understand reopening behavior, and [live backups](../durability/backups.md) to create a restorable copy of an open tree.
|
||||
|
||||
## Give maintenance an explicit owner
|
||||
|
||||
Keep a maintainer alive with a long-running tree. It listens for storage lifecycle events, schedules normal merges according to its thresholds, and periodically releases inactive caches.
|
||||
|
||||
```csharp
|
||||
using ZoneTree;
|
||||
|
||||
using var tree = new ZoneTreeFactory<int, string>()
|
||||
.SetDataDirectory("data/catalog")
|
||||
.SetMutableSegmentMaxItemCount(100_000)
|
||||
.OpenOrCreate();
|
||||
using var maintainer = tree.CreateMaintainer();
|
||||
|
||||
tree.Upsert(42, "A new value");
|
||||
```
|
||||
|
||||
Here `100_000` is an example memory-batch size for string values. Tune it to the actual record sizes and workload. The maintainer is caller-owned, and disposing it waits for the background merge threads it tracks.
|
||||
|
||||
Bottom merging has its own explicit entry point, `maintainer.StartBottomSegmentsMerge()`. Choose when and how much bottom data to compact as part of your maintenance policy. Applications can also coordinate maintenance directly through `tree.Maintenance`.
|
||||
|
||||
Watch read-only segment accumulation alongside merge duration and write rate. A growing queue means frozen memory is arriving faster than maintenance is clearing it. Budget compaction resources and record limits together; the mutable-segment limit alone does not cap the tree's total memory use.
|
||||
|
||||
See [maintenance](../usage/maintenance.md) and [memory usage](../storage/memory-usage.md) for operational controls.
|
||||
|
||||
## The controls behind the shape
|
||||
|
||||
These defaults describe different boundaries in the same pipeline:
|
||||
|
||||
| Control | Default | What it shapes |
|
||||
| --- | --- | --- |
|
||||
| `MutableSegmentMaxItemCount` | 1,000,000 records | When the active memory segment rolls forward |
|
||||
| `DiskSegmentMaxItemCount` | 20,000,000 records | When merged disk output moves to the bottom queue |
|
||||
| `MinimumRecordCount` / `MaximumRecordCount` | 1,500,000 / 3,000,000 records | Part sizing inside a multipart disk segment |
|
||||
| WAL mode | `AsyncCompressed` | Logging behavior and recent-write recovery |
|
||||
|
||||
Record counts translate into different byte sizes for different keys and values. Smaller memory batches reduce each frozen batch's footprint; smaller disk parts reduce local rewrite units while increasing file and metadata overhead. The right balance depends on write locality, read patterns, value size, and merge throughput.
|
||||
|
||||
<Grid columns="1" columnsMd="2" class="docs-map zt-lsm-next-steps">
|
||||
<DocCard
|
||||
number="TUNE THE PIPELINE"
|
||||
title="Control write amplification."
|
||||
summary="Explore part reuse, randomized part sizes, and the effect of key locality on compaction."
|
||||
href="/docs/tuning/write-amplification/"
|
||||
icon="tabler:git-merge"
|
||||
label="Explore merge tuning"/>
|
||||
<DocCard
|
||||
number="EXPLORE THE STORAGE"
|
||||
title="Know your segments."
|
||||
summary="Connect logical segments and multipart descriptors to the physical files that hold your data."
|
||||
href="/docs/concepts/segments/"
|
||||
icon="tabler:stack-2"
|
||||
label="Read the segment guide"/>
|
||||
</Grid>
|
||||
@@ -132,4 +132,4 @@ bottom segment queue:
|
||||
| `ReadOnlySegmentsRecordCount` | records across frozen memory segments |
|
||||
| `TotalRecordCount` | physical records across mutable, read-only, disk, and bottom segment layers |
|
||||
|
||||
For the full storage flow, see [LSM Tree](lsm-tree.md). For multipart merge behavior, see [write amplification](../tuning/write-amplification.md).
|
||||
For the full storage flow, see [LSM Tree](lsm-tree.mdx). For multipart merge behavior, see [write amplification](../tuning/write-amplification.md).
|
||||
|
||||
@@ -15,7 +15,7 @@ ZoneTree is strongest when it is treated as a storage-engine foundation: an orde
|
||||
## Start Here
|
||||
|
||||
1. [Open a tree and run maintenance](getting-started.md).
|
||||
2. Learn the [LSM-tree and segment model](concepts/lsm-tree.md).
|
||||
2. Learn the [LSM-tree and segment model](concepts/lsm-tree.mdx).
|
||||
3. Choose the right [read and write APIs](usage/reads-and-writes.md).
|
||||
4. Design keys for [ordering and range scans](concepts/key-ordering.md).
|
||||
5. Select a [WAL durability mode](durability/wal-modes.md).
|
||||
|
||||
@@ -4,6 +4,9 @@ import { defineDocCard } from './doc-card'
|
||||
import { defineDistributedFoundation } from './distributed-foundation'
|
||||
import { defineAnalyticsSpotlight } from './analytics-spotlight'
|
||||
import { defineFullTextSearchSpotlight } from './full-text-search-spotlight'
|
||||
import { defineLsmArchitecture } from './lsm-architecture'
|
||||
import { defineLsmReadPath } from './lsm-read-path'
|
||||
import { defineLsmPartReuse } from './lsm-part-reuse'
|
||||
|
||||
export function defineDocsComponents() {
|
||||
return {
|
||||
@@ -13,5 +16,8 @@ export function defineDocsComponents() {
|
||||
DistributedFoundation: defineDistributedFoundation(),
|
||||
AnalyticsSpotlight: defineAnalyticsSpotlight(),
|
||||
FullTextSearchSpotlight: defineFullTextSearchSpotlight(),
|
||||
LsmArchitecture: defineLsmArchitecture(),
|
||||
LsmReadPath: defineLsmReadPath(),
|
||||
LsmPartReuse: defineLsmPartReuse(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import { defineComponent, html } from 'regor'
|
||||
|
||||
export function defineLsmArchitecture() {
|
||||
return defineComponent(html`
|
||||
<figure class="zt-lsm-figure zt-lsm-pipeline" aria-labelledby="zt-lsm-pipeline-caption">
|
||||
<div class="zt-lsm-figure-heading">
|
||||
<span class="zt-lsm-overline">THE STORAGE PIPELINE</span>
|
||||
<span class="zt-lsm-figure-note">One ZoneTree instance</span>
|
||||
</div>
|
||||
<div class="zt-lsm-writers" aria-label="Concurrent application writers">
|
||||
<span><Icon name="tabler:arrow-down"/> Writer 01</span>
|
||||
<span><Icon name="tabler:arrow-down"/> Writer 02</span>
|
||||
<span><Icon name="tabler:arrow-down"/> Writer 03</span>
|
||||
</div>
|
||||
<div class="zt-lsm-stage zt-lsm-stage--mutable">
|
||||
<div class="zt-lsm-stage-heading"><span class="zt-lsm-stage-number">01</span><strong>Mutable segment</strong><span class="zt-lsm-medium">MEMORY</span></div>
|
||||
<div class="zt-lsm-mutable-body">
|
||||
<div>
|
||||
<div class="zt-lsm-key-cells" aria-label="Keys in comparer order"><span>10</span><span>20</span><span>30</span><span>40</span><span>50</span></div>
|
||||
<p>Concurrent, ordered B+Tree</p>
|
||||
</div>
|
||||
<div class="zt-lsm-wal"><Icon name="tabler:file-text"/><div><strong>WAL</strong><span>Recent-write recovery</span></div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="zt-lsm-transfer"><Icon name="tabler:arrow-down"/><span>When full, move to read-only.<br/>New writes enter a fresh mutable segment.</span></div>
|
||||
<div class="zt-lsm-stage">
|
||||
<div class="zt-lsm-stage-heading"><span class="zt-lsm-stage-number">02</span><strong>Read-only segments</strong><span class="zt-lsm-medium">MEMORY</span></div>
|
||||
<div class="zt-lsm-batches"><span><strong>Batch 01</strong><small>Frozen</small></span><span><strong>Batch 02</strong><small>Frozen</small></span><span><strong>Batch 03</strong><small>Frozen · newest</small></span></div>
|
||||
<p>Ordered batches waiting for compaction</p>
|
||||
</div>
|
||||
<div class="zt-lsm-compaction">
|
||||
<svg viewBox="0 0 600 52" preserveAspectRatio="none" fill="none" aria-hidden="true" focusable="false">
|
||||
<path d="M100 0V12H500V0M300 12V34M100 52V34H500V52M300 34V52M96 46L100 52L104 46M296 46L300 52L304 46M496 46L500 52L504 46" stroke="currentColor" stroke-width="1.25" vector-effect="non-scaling-stroke"/>
|
||||
</svg>
|
||||
<span>Compact read-only batches + current disk segment.<br/>Publish the merged disk segment.</span>
|
||||
</div>
|
||||
<div class="zt-lsm-stage zt-lsm-stage--disk">
|
||||
<div class="zt-lsm-stage-heading"><span class="zt-lsm-stage-number">03</span><strong>Disk segment</strong><span class="zt-lsm-medium">DISK</span></div>
|
||||
<div class="zt-lsm-parts"><span><strong>Part A</strong><small>10–29</small></span><span><strong>Part B</strong><small>30–49</small></span><span><strong>Part C</strong><small>50–69</small></span></div>
|
||||
<p>One logical segment · ordered, immutable parts</p>
|
||||
</div>
|
||||
<div class="zt-lsm-transfer"><Icon name="tabler:arrow-down"/><span>When the merged record count exceeds<br/><code>DiskSegmentMaxItemCount</code></span></div>
|
||||
<div class="zt-lsm-stage">
|
||||
<div class="zt-lsm-stage-heading"><span class="zt-lsm-stage-number">04</span><strong>Bottom segments</strong><span class="zt-lsm-medium">DISK</span></div>
|
||||
<div class="zt-lsm-bottom-runs">
|
||||
<div><strong>Newer disk segment</strong><span class="zt-lsm-mini-parts"><i></i><i></i><i></i></span></div>
|
||||
<div><strong>Older disk segment</strong><span class="zt-lsm-mini-parts"><i></i><i></i><i></i></span></div>
|
||||
</div>
|
||||
<p>A queue of disk segments · compacted separately</p>
|
||||
</div>
|
||||
<div class="zt-lsm-concurrency"><Icon name="tabler:arrows-exchange"/><span>Parallel reads and writes run alongside background compaction.</span></div>
|
||||
<figcaption id="zt-lsm-pipeline-caption">Vertical layers batch and move data. Horizontal parts divide a disk segment by key range. Keys and batches shown here are illustrative.</figcaption>
|
||||
</figure>
|
||||
`)
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { defineComponent, html } from 'regor'
|
||||
|
||||
export function defineLsmPartReuse() {
|
||||
return defineComponent(html`
|
||||
<figure class="zt-lsm-figure zt-lsm-reuse" aria-labelledby="zt-lsm-reuse-caption">
|
||||
<div class="zt-lsm-figure-heading"><span class="zt-lsm-overline">COMPACTION WITH PART REUSE</span><Icon name="tabler:layers-linked"/></div>
|
||||
<p class="zt-lsm-reuse-label">Current disk segment</p>
|
||||
<div class="zt-lsm-reuse-parts"><span><strong>A</strong><small>00–24</small></span><span class="zt-lsm-part-affected"><strong>B</strong><small>25–49</small></span><span><strong>C</strong><small>50–74</small></span><span><strong>D</strong><small>75–99</small></span></div>
|
||||
<div class="zt-lsm-reuse-incoming"><Icon name="tabler:git-merge"/><span>Incoming updates overlap the range in B.</span></div>
|
||||
<div class="zt-lsm-reuse-arrows" aria-hidden="true"><span><Icon name="tabler:arrow-down"/> Reuse</span><span><Icon name="tabler:arrow-down"/> Rewrite</span><span><Icon name="tabler:arrow-down"/> Reuse</span><span><Icon name="tabler:arrow-down"/> Reuse</span></div>
|
||||
<div class="zt-lsm-reuse-parts"><span><strong>A</strong><small>Carried</small></span><span class="zt-lsm-part-new"><strong>B′</strong><small>New output</small></span><span><strong>C</strong><small>Carried</small></span><span><strong>D</strong><small>Carried</small></span></div>
|
||||
<p class="zt-lsm-reuse-label">Next disk segment · reused parts + new output</p>
|
||||
<figcaption id="zt-lsm-reuse-caption">An illustrative local update. Eligible clean parts can be carried into the next logical segment without rewriting their records. A changed range may produce one or several replacement parts.</figcaption>
|
||||
</figure>
|
||||
`)
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { defineComponent, html } from 'regor'
|
||||
|
||||
export function defineLsmReadPath() {
|
||||
return defineComponent(html`
|
||||
<figure class="zt-lsm-figure zt-lsm-lookup" aria-labelledby="zt-lsm-lookup-caption">
|
||||
<div class="zt-lsm-figure-heading"><span class="zt-lsm-overline">NEWEST VISIBLE VALUE WINS</span><code>TryGet(42)</code></div>
|
||||
<ol class="zt-lsm-lookup-steps">
|
||||
<li><span class="zt-lsm-stage-number">01</span><div><strong>Mutable segment</strong><span>Key 42 is absent</span></div><span class="zt-lsm-lookup-result">Continue <Icon name="tabler:arrow-down"/></span></li>
|
||||
<li class="zt-lsm-lookup-hit"><span class="zt-lsm-stage-number">02</span><div><strong>Newest read-only segment</strong><span>42 → "current"</span></div><span class="zt-lsm-lookup-result"><Icon name="tabler:check"/> Return value</span></li>
|
||||
<li class="zt-lsm-lookup-hidden"><span class="zt-lsm-stage-number">03</span><div><strong>Disk segment</strong><span>42 → "previous"</span></div><span class="zt-lsm-lookup-result">Not visited</span></li>
|
||||
<li class="zt-lsm-lookup-hidden"><span class="zt-lsm-stage-number">04</span><div><strong>Bottom segments</strong><span>42 → "original"</span></div><span class="zt-lsm-lookup-result">Not visited</span></li>
|
||||
</ol>
|
||||
<figcaption id="zt-lsm-lookup-caption">A single point lookup stops at its first matching record. Independent lookups can run concurrently. A matching deletion marker returns “not found” and also stops the search.</figcaption>
|
||||
</figure>
|
||||
`)
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
import type { StyleBuilder, ThemeMode } from '@purestack/ts-style'
|
||||
import { zoneTreeColors } from './skin'
|
||||
|
||||
type LsmStyleContext = ReturnType<typeof createLsmStyleContext>
|
||||
|
||||
export function registerLsmArchitectureStyles(root: ReturnType<StyleBuilder['get']>, theme: ThemeMode) {
|
||||
const context = createLsmStyleContext(root, theme)
|
||||
|
||||
registerArticleStyles(context)
|
||||
registerFigureStyles(context)
|
||||
registerPipelineStyles(context)
|
||||
registerLookupStyles(context)
|
||||
registerPartReuseStyles(context)
|
||||
registerResponsiveStyles(context)
|
||||
}
|
||||
|
||||
function createLsmStyleContext(root: ReturnType<StyleBuilder['get']>, theme: ThemeMode) {
|
||||
return {
|
||||
root,
|
||||
colors: zoneTreeColors[theme],
|
||||
merge: theme === 'dark' ? '#dfba70' : '#805b1e',
|
||||
mergeSurface: theme === 'dark' ? '#30291c' : '#f6eddb',
|
||||
}
|
||||
}
|
||||
|
||||
function registerArticleStyles({ root, colors }: LsmStyleContext) {
|
||||
root.select('.doc-content:has(.zt-lsm-intro) > h1')
|
||||
.borderBottom('none').paddingBottom('0').marginBottom('20px')
|
||||
root.select('.doc-content .zt-lsm-intro')
|
||||
.marginBottom('40px')
|
||||
root.select('.doc-content .zt-lsm-intro > p:first-child')
|
||||
.fontSize('21px').lineHeight('1.65').letterSpacing('-.3px').color(colors.ink)
|
||||
root.select('.doc-content .zt-lsm-intro > p:last-child')
|
||||
.color(colors.muted).marginBottom('0')
|
||||
root.select('.doc-content .zt-lsm-takeaway')
|
||||
.borderLeft(`3px solid ${colors.signal}`).padding('16px 20px')
|
||||
.background(colors.accentSurface).borderRadius('0 10px 10px 0')
|
||||
.marginBlock('28px').fontSize('15px').lineHeight('1.8')
|
||||
root.select('.zt-lsm-next-steps').marginTop('28px')
|
||||
}
|
||||
|
||||
function registerFigureStyles({ root, colors }: LsmStyleContext) {
|
||||
root.select('.doc-content .zt-lsm-figure')
|
||||
.margin('32px 0').padding('24px').border(`1px solid ${colors.border}`)
|
||||
.borderRadius('16px').background(colors.surface).color(colors.ink)
|
||||
.boxShadow('0 12px 32px #00000008')
|
||||
root.select('.zt-lsm-figure *, .zt-lsm-figure *::before, .zt-lsm-figure *::after')
|
||||
.boxSizing('border-box')
|
||||
root.select('.zt-lsm-figure .icon')
|
||||
.width('18px').height('18px').flexShrink('0')
|
||||
root.select('.zt-lsm-figure-heading')
|
||||
.display('flex').justifyContent('space-between').alignItems('center')
|
||||
.flexWrap('wrap').gap('8px 16px').marginBottom('20px')
|
||||
root.select('.zt-lsm-overline')
|
||||
.fontSize('10px').fontWeight('650').letterSpacing('1.2px').color(colors.accentInk)
|
||||
root.select('.zt-lsm-figure-note')
|
||||
.fontSize('11px').color(colors.muted)
|
||||
root.select('.zt-lsm-figure p')
|
||||
.margin('10px 0 0').fontSize('12px').lineHeight('1.65').color(colors.muted)
|
||||
root.select('.zt-lsm-figure figcaption')
|
||||
.marginTop('20px').paddingTop('16px').borderTop(`1px solid ${colors.subtle}`)
|
||||
.fontSize('12px').lineHeight('1.75').textAlign('left').color(colors.muted)
|
||||
root.select('.zt-lsm-figure code')
|
||||
.fontSize('11px').overflowWrap('anywhere')
|
||||
root.select('.zt-lsm-stage-number')
|
||||
.display('inline-flex').alignItems('center').justifyContent('center')
|
||||
.width('26px').height('26px').flexShrink('0').borderRadius('7px')
|
||||
.background(colors.surfaceAlt).color(colors.accentInk)
|
||||
.fontSize('10px').fontWeight('650').fontVariantNumeric('tabular-nums')
|
||||
}
|
||||
|
||||
function registerPipelineStyles({ root, colors, merge, mergeSurface }: LsmStyleContext) {
|
||||
root.select('.doc-content .zt-lsm-pipeline')
|
||||
.background(`linear-gradient(155deg, ${colors.accentSurface}80, ${colors.surface} 35%)`)
|
||||
root.select('.zt-lsm-writers')
|
||||
.display('grid').gridTemplateColumns('repeat(3, minmax(0, 1fr))')
|
||||
.gap('10px').marginBottom('12px')
|
||||
root.select('.zt-lsm-writers > span')
|
||||
.display('flex').alignItems('center').justifyContent('center')
|
||||
.gap('6px').fontSize('11px').fontWeight('600').color(colors.accentInk)
|
||||
root.select('.zt-lsm-stage')
|
||||
.padding('16px 18px').border(`1px solid ${colors.subtle}`)
|
||||
.borderRadius('12px').background(colors.surface)
|
||||
root.select('.zt-lsm-stage--mutable')
|
||||
.borderColor(colors.accentBorder).background(colors.accentSurface)
|
||||
root.select('.zt-lsm-stage--disk').borderColor(merge)
|
||||
root.select('.zt-lsm-stage-heading')
|
||||
.display('flex').alignItems('center').gap('10px').marginBottom('12px')
|
||||
root.select('.zt-lsm-stage-heading > strong')
|
||||
.fontSize('15px').fontWeight('650').letterSpacing('-.3px')
|
||||
root.select('.zt-lsm-medium')
|
||||
.marginLeft('auto').fontSize('8px').fontWeight('600')
|
||||
.letterSpacing('1px').color(colors.muted)
|
||||
root.select('.zt-lsm-mutable-body')
|
||||
.display('grid').gridTemplateColumns('1fr auto').alignItems('center').gap('18px')
|
||||
root.select('.zt-lsm-key-cells')
|
||||
.display('grid').gridTemplateColumns('repeat(5, minmax(0, 1fr))').gap('5px')
|
||||
root.select('.zt-lsm-key-cells > span')
|
||||
.paddingBlock('5px').textAlign('center').background(colors.surface)
|
||||
.border(`1px solid ${colors.accentBorder}`).borderRadius('5px')
|
||||
.fontSize('11px').fontWeight('600').color(colors.accentInk)
|
||||
root.select('.zt-lsm-wal')
|
||||
.display('flex').gap('8px').alignItems('center').padding('10px')
|
||||
.border(`1px dashed ${colors.accentBorder}`).borderRadius('7px').color(colors.accentInk)
|
||||
root.select('.zt-lsm-wal strong, .zt-lsm-wal span').display('block')
|
||||
root.select('.zt-lsm-wal strong').fontSize('11px').fontWeight('600')
|
||||
root.select('.zt-lsm-wal span').fontSize('9px').color(colors.muted)
|
||||
root.select('.zt-lsm-transfer')
|
||||
.display('flex').gap('10px').alignItems('center').padding('12px 18px')
|
||||
.color(colors.muted).fontSize('11px').lineHeight('1.7')
|
||||
root.select('.zt-lsm-transfer > .icon').height('28px').color(colors.accentInk)
|
||||
root.select('.zt-lsm-compaction')
|
||||
.padding('12px 18px').color(merge).fontSize('11px').lineHeight('1.7')
|
||||
.textAlign('center')
|
||||
root.select('.zt-lsm-compaction > svg')
|
||||
.display('block').width('100%').height('40px').marginBottom('8px')
|
||||
root.select('.zt-lsm-batches, .zt-lsm-parts, .zt-lsm-bottom-runs')
|
||||
.display('grid').gridTemplateColumns('repeat(3, minmax(0, 1fr))').gap('8px')
|
||||
root.select('.zt-lsm-batches > span, .zt-lsm-parts > span')
|
||||
.display('flex').flexDirection('column').gap('2px').padding('9px 10px')
|
||||
.border(`1px solid ${colors.subtle}`).borderRadius('6px').background(colors.surfaceAlt)
|
||||
root.select('.zt-lsm-batches strong, .zt-lsm-parts strong')
|
||||
.fontSize('12px').fontWeight('600')
|
||||
root.select('.zt-lsm-batches small, .zt-lsm-parts small')
|
||||
.fontSize('10px').color(colors.muted)
|
||||
root.select('.zt-lsm-parts > span').background(mergeSurface)
|
||||
root.select('.zt-lsm-parts strong').color(merge)
|
||||
root.select('.zt-lsm-bottom-runs').gridTemplateColumns('repeat(2, minmax(0, 1fr))')
|
||||
root.select('.zt-lsm-bottom-runs > div')
|
||||
.padding('10px').border(`1px solid ${colors.subtle}`).borderRadius('6px')
|
||||
root.select('.zt-lsm-bottom-runs strong').display('block').fontSize('10px').fontWeight('500')
|
||||
root.select('.zt-lsm-mini-parts').display('flex').gap('4px').marginTop('8px')
|
||||
root.select('.zt-lsm-mini-parts > i')
|
||||
.flex('1').height('12px').borderRadius('2px').background(colors.border)
|
||||
root.select('.zt-lsm-concurrency')
|
||||
.display('flex').alignItems('center').gap('10px').marginTop('18px')
|
||||
.fontSize('12px').lineHeight('1.6').color(colors.accentInk)
|
||||
}
|
||||
|
||||
function registerLookupStyles({ root, colors }: LsmStyleContext) {
|
||||
root.select('.zt-lsm-lookup-steps')
|
||||
.listStyle('none').padding('0').margin('0').display('grid').gap('8px')
|
||||
root.select('.zt-lsm-lookup-steps > li')
|
||||
.display('grid').gridTemplateColumns('26px minmax(0, 1fr) auto')
|
||||
.gap('12px').alignItems('center').padding('14px')
|
||||
.border(`1px solid ${colors.subtle}`).borderRadius('9px').margin('0')
|
||||
root.select('.zt-lsm-lookup-steps strong, .zt-lsm-lookup-steps li > div > span').display('block')
|
||||
root.select('.zt-lsm-lookup-steps strong').fontSize('13px').fontWeight('600')
|
||||
root.select('.zt-lsm-lookup-steps li > div > span')
|
||||
.fontSize('11px').color(colors.muted).marginTop('2px')
|
||||
root.select('.zt-lsm-lookup-result')
|
||||
.display('flex').alignItems('center').gap('6px').fontSize('10px').color(colors.muted)
|
||||
root.select('.zt-lsm-lookup-steps > .zt-lsm-lookup-hit')
|
||||
.borderColor(colors.accentBorder).background(colors.accentSurface)
|
||||
root.select('.zt-lsm-lookup-hit .zt-lsm-lookup-result').color(colors.accentInk).fontWeight('600')
|
||||
root.select('.zt-lsm-lookup-hidden').color(colors.muted).borderStyle('dashed')
|
||||
}
|
||||
|
||||
function registerPartReuseStyles({ root, colors, merge, mergeSurface }: LsmStyleContext) {
|
||||
root.select('.zt-lsm-reuse .zt-lsm-reuse-label')
|
||||
.fontSize('11px').margin('14px 0').color(colors.muted)
|
||||
root.select('.zt-lsm-reuse-parts, .zt-lsm-reuse-arrows')
|
||||
.display('grid').gridTemplateColumns('repeat(4, minmax(0, 1fr))').gap('8px')
|
||||
root.select('.zt-lsm-reuse-parts > span')
|
||||
.display('flex').flexDirection('column').gap('3px').textAlign('center')
|
||||
.padding('14px 6px').border(`1px solid ${colors.accentBorder}`)
|
||||
.borderRadius('8px').background(colors.accentSurface)
|
||||
root.select('.zt-lsm-reuse-parts strong')
|
||||
.fontSize('21px').lineHeight('1.3').fontWeight('600').color(colors.accentInk)
|
||||
root.select('.zt-lsm-reuse-parts small').fontSize('10px').color(colors.muted)
|
||||
root.select('.zt-lsm-reuse-parts > .zt-lsm-part-affected')
|
||||
.borderColor(merge).borderStyle('dashed').background(mergeSurface)
|
||||
root.select('.zt-lsm-reuse-parts > .zt-lsm-part-new')
|
||||
.borderColor(merge).background(mergeSurface)
|
||||
root.select('.zt-lsm-part-affected strong, .zt-lsm-part-new strong').color(merge)
|
||||
root.select('.zt-lsm-reuse-incoming')
|
||||
.display('flex').gap('8px').alignItems('center').justifyContent('center')
|
||||
.paddingBlock('18px 8px').fontSize('11px').lineHeight('1.6').color(merge)
|
||||
root.select('.zt-lsm-reuse-arrows').paddingBlock('8px 16px')
|
||||
root.select('.zt-lsm-reuse-arrows > span')
|
||||
.display('flex').flexDirection('column').alignItems('center').gap('4px')
|
||||
.fontSize('10px').color(colors.accentInk)
|
||||
root.select('.zt-lsm-reuse-arrows > span:nth-child(2)').color(merge)
|
||||
}
|
||||
|
||||
function registerResponsiveStyles({ root }: LsmStyleContext) {
|
||||
const compact = root.media('max-width: 650px')
|
||||
compact.select('.doc-content .zt-lsm-figure').padding('16px').borderRadius('12px')
|
||||
compact.select('.zt-lsm-stage').padding('12px')
|
||||
compact.select('.zt-lsm-stage-heading').gap('7px')
|
||||
compact.select('.zt-lsm-stage-heading > strong').fontSize('13px')
|
||||
compact.select('.zt-lsm-mutable-body').gridTemplateColumns('1fr').gap('10px')
|
||||
compact.select('.zt-lsm-wal').padding('8px 10px')
|
||||
compact.select('.zt-lsm-wal > div').display('flex').gap('10px').alignItems('center')
|
||||
compact.select('.zt-lsm-transfer').paddingInline('12px')
|
||||
compact.select('.zt-lsm-batches > span, .zt-lsm-parts > span').padding('8px 6px')
|
||||
compact.select('.zt-lsm-batches strong, .zt-lsm-parts strong').fontSize('11px')
|
||||
compact.select('.zt-lsm-batches small').fontSize('9px')
|
||||
compact.select('.zt-lsm-lookup-steps > li')
|
||||
.gridTemplateColumns('26px minmax(0, 1fr)').gap('8px 10px').padding('12px')
|
||||
compact.select('.zt-lsm-lookup-result').gridColumn('2')
|
||||
compact.select('.zt-lsm-reuse-parts, .zt-lsm-reuse-arrows').gap('5px')
|
||||
compact.select('.zt-lsm-reuse-parts small').fontSize('9px')
|
||||
compact.select('.zt-lsm-figure-heading').gap('6px 12px')
|
||||
compact.select('.zt-lsm-medium').fontSize('7px').letterSpacing('.5px')
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { docLayoutVars, styleBuilder, themes, type ThemeMode, type ThemePalette } from '@purestack/ts-style'
|
||||
import { zoneTreeColors } from './skin'
|
||||
import { registerHeroMotionStyles } from './hero-motion'
|
||||
import { registerLsmArchitectureStyles } from './lsm-styles'
|
||||
|
||||
export function registerDocsStyles() {
|
||||
themes.forEach((theme, palette) => {
|
||||
@@ -20,6 +21,7 @@ export function registerDocsStyles() {
|
||||
registerLightThemeStyles(context)
|
||||
registerResponsiveStyles(context)
|
||||
registerHeroMotionStyles(context.root, theme)
|
||||
registerLsmArchitectureStyles(context.root, theme)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -11,9 +11,9 @@
|
||||
"deploy": "pgdeploy deploy --host https://zonetree.dev --package ./dist/web --allow-path 'pagefind/*,.ts-ssg/manifest.json'"
|
||||
},
|
||||
"dependencies": {
|
||||
"@purestack/ts-style": "^1.1.17",
|
||||
"@purestack/ts-util": "^1.1.17",
|
||||
"purestack": "^1.1.17",
|
||||
"@purestack/ts-style": "^1.1.19",
|
||||
"@purestack/ts-util": "^1.1.19",
|
||||
"purestack": "^1.1.19",
|
||||
"regor": "^1.7.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
+117
-117
@@ -306,134 +306,134 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@purestack/ts-common@npm:^1.1.17":
|
||||
version: 1.1.17
|
||||
resolution: "@purestack/ts-common@npm:1.1.17"
|
||||
"@purestack/ts-common@npm:^1.1.19":
|
||||
version: 1.1.19
|
||||
resolution: "@purestack/ts-common@npm:1.1.19"
|
||||
dependencies:
|
||||
"@purestack/ts-html": "npm:^1.1.17"
|
||||
"@purestack/ts-style": "npm:^1.1.17"
|
||||
"@purestack/ts-util": "npm:^1.1.17"
|
||||
checksum: 10c0/cc47d4fd5c46ebde9f8d67f2073b3842d55d47fb8cbd8d6de570e82f7b760b2ee254e21172dacb49bbe1a72d995a7bce96b7a2d4863518ed4391f439b268778f
|
||||
"@purestack/ts-html": "npm:^1.1.19"
|
||||
"@purestack/ts-style": "npm:^1.1.19"
|
||||
"@purestack/ts-util": "npm:^1.1.19"
|
||||
checksum: 10c0/866ec8525d18ef3f2e1631641ff57a95b52a42fb2d96b828f5db1b04738576fcf66aaf3fa870b5ed772eff361111cd3f3ae92374e307b98966c966f36f07b28e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@purestack/ts-components@npm:^1.1.17":
|
||||
version: 1.1.17
|
||||
resolution: "@purestack/ts-components@npm:1.1.17"
|
||||
"@purestack/ts-components@npm:^1.1.19":
|
||||
version: 1.1.19
|
||||
resolution: "@purestack/ts-components@npm:1.1.19"
|
||||
dependencies:
|
||||
"@purestack/ts-common": "npm:^1.1.17"
|
||||
"@purestack/ts-css": "npm:^1.1.17"
|
||||
"@purestack/ts-page-scripts": "npm:^1.1.17"
|
||||
"@purestack/ts-style": "npm:^1.1.17"
|
||||
"@purestack/ts-svg-icons": "npm:^1.1.17"
|
||||
"@purestack/ts-util": "npm:^1.1.17"
|
||||
"@purestack/ts-common": "npm:^1.1.19"
|
||||
"@purestack/ts-css": "npm:^1.1.19"
|
||||
"@purestack/ts-page-scripts": "npm:^1.1.19"
|
||||
"@purestack/ts-style": "npm:^1.1.19"
|
||||
"@purestack/ts-svg-icons": "npm:^1.1.19"
|
||||
"@purestack/ts-util": "npm:^1.1.19"
|
||||
regor: "npm:^1.7.6"
|
||||
checksum: 10c0/e93f3bfc1a57bdfa30aa241fd5b116cfbc5d8edd1341ab8f84fcbd1bdb5b1323da60a51dffbbd17a4c8065100ad9f731cdb2092b2e9e7d52b5645768de9793b0
|
||||
checksum: 10c0/248dc0dfb985ce612d8d436cd8b01e1b96286cefcbdd0cae654c05dae920a5f85e50d50ae79c4aa44e99c39da46e95b0509d7798a70320c53f3e4d6c3902cff7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@purestack/ts-css@npm:^1.1.17":
|
||||
version: 1.1.17
|
||||
resolution: "@purestack/ts-css@npm:1.1.17"
|
||||
"@purestack/ts-css@npm:^1.1.19":
|
||||
version: 1.1.19
|
||||
resolution: "@purestack/ts-css@npm:1.1.19"
|
||||
dependencies:
|
||||
"@purestack/ts-util": "npm:^1.1.17"
|
||||
"@purestack/ts-util": "npm:^1.1.19"
|
||||
prettier: "npm:^3.8.1"
|
||||
checksum: 10c0/d249219799ad60d5cfde117d99496ef29411d505e11a374f5cfd2f6ab3a98df20e6b87c204ed2b48201f648ad1e975d91d00ff6ce33c76afcbe5621ce397f0f6
|
||||
checksum: 10c0/2e18d4d2401e4c530063e85f675ec5e35ac8b2866d8a5c58de237a76eaedcd91d39b33b8f4a06eae6499ae59fa869b17aefa393009a9942bd8212036a8626b67
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@purestack/ts-highlight@npm:^1.1.17":
|
||||
version: 1.1.17
|
||||
resolution: "@purestack/ts-highlight@npm:1.1.17"
|
||||
"@purestack/ts-highlight@npm:^1.1.19":
|
||||
version: 1.1.19
|
||||
resolution: "@purestack/ts-highlight@npm:1.1.19"
|
||||
dependencies:
|
||||
"@purestack/ts-common": "npm:^1.1.17"
|
||||
"@purestack/ts-util-node": "npm:^1.1.17"
|
||||
"@purestack/ts-common": "npm:^1.1.19"
|
||||
"@purestack/ts-util-node": "npm:^1.1.19"
|
||||
highlight.js: "npm:^11.11.1"
|
||||
shiki: "npm:^3.22.0"
|
||||
checksum: 10c0/b5f528510ba884c2ca62458619926d199b0ac8db92d07937a2f1fe335b0d53436db8beb7b145271095a791286ea42b8caff9df416fb32c9b645a384a52e096fe
|
||||
checksum: 10c0/a5ff6a371123dd132cca56fb7581d82f36ab414b87f65281880702a8fb14807ab47b80c5525fd267635d825b3e24ecc39f2fb6c184500e619400663402a739fa
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@purestack/ts-html@npm:^1.1.17":
|
||||
version: 1.1.17
|
||||
resolution: "@purestack/ts-html@npm:1.1.17"
|
||||
"@purestack/ts-html@npm:^1.1.19":
|
||||
version: 1.1.19
|
||||
resolution: "@purestack/ts-html@npm:1.1.19"
|
||||
dependencies:
|
||||
"@purestack/ts-util": "npm:^1.1.17"
|
||||
"@purestack/ts-util": "npm:^1.1.19"
|
||||
prettier: "npm:^3.8.1"
|
||||
checksum: 10c0/680bde7cebf26c0f85120bc0b7c5da477cc151b12e2d5f19e670d9c0ad3051741b883f7b7c1651d96dbfa774e2d9be262916b17339b9c8c32c7078c14913f239
|
||||
checksum: 10c0/5dfe61453df3757384dd78c1304502ae221ff52413d5eadaa46198cf15557409ce5cf101d43c5b4035943c2cfe093163a2f3b7406b7043bb538f00518a767312
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@purestack/ts-minidom@npm:^1.1.17":
|
||||
version: 1.1.17
|
||||
resolution: "@purestack/ts-minidom@npm:1.1.17"
|
||||
checksum: 10c0/5dca506c331f962616d81746d50a894aa646b0e0e03d0c2f9728d870625e10d0985b9b3e9b621249edeea47cee0f4e082a892da9a13ba416d5a3ad297e08763f
|
||||
"@purestack/ts-minidom@npm:^1.1.19":
|
||||
version: 1.1.19
|
||||
resolution: "@purestack/ts-minidom@npm:1.1.19"
|
||||
checksum: 10c0/24a23121daf3ec38384be6de565065393a02be12085d01c9719fe0263d4334dbf6265742a83251a73e408d87bae9937025a0ad5c9ad3b6d1f78332c6f18de28f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@purestack/ts-page-scripts@npm:^1.1.17":
|
||||
version: 1.1.17
|
||||
resolution: "@purestack/ts-page-scripts@npm:1.1.17"
|
||||
"@purestack/ts-page-scripts@npm:^1.1.19":
|
||||
version: 1.1.19
|
||||
resolution: "@purestack/ts-page-scripts@npm:1.1.19"
|
||||
dependencies:
|
||||
"@purestack/ts-common": "npm:^1.1.17"
|
||||
"@purestack/ts-html": "npm:^1.1.17"
|
||||
checksum: 10c0/18277eba0b84d8936e7bd5f2ec0aad3c3387e68f00b43f99e4b9f3d8498ad9fad5eedb4c98f238af5d7514043ffe19b683cb4ad69a3122f7348b2b9f86c7062f
|
||||
"@purestack/ts-common": "npm:^1.1.19"
|
||||
"@purestack/ts-html": "npm:^1.1.19"
|
||||
checksum: 10c0/b7b5c16ae86a0cdc6b9c5fd90dbbc2f92fde2d2567fc1d8c1bde6fe80e0abaf38cf81bb83d66bb7a0b8479aec03956669e7d51a4f76dd9922e3ae4cff88546e0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@purestack/ts-pagefind@npm:^1.1.17":
|
||||
version: 1.1.17
|
||||
resolution: "@purestack/ts-pagefind@npm:1.1.17"
|
||||
"@purestack/ts-pagefind@npm:^1.1.19":
|
||||
version: 1.1.19
|
||||
resolution: "@purestack/ts-pagefind@npm:1.1.19"
|
||||
dependencies:
|
||||
"@purestack/ts-common": "npm:^1.1.17"
|
||||
"@purestack/ts-util": "npm:^1.1.17"
|
||||
"@purestack/ts-common": "npm:^1.1.19"
|
||||
"@purestack/ts-util": "npm:^1.1.19"
|
||||
logpot: "npm:^1.0.7"
|
||||
pagefind: "npm:^1.4.0"
|
||||
checksum: 10c0/294012411be571498cc18be59d4f4175aa973835a7f3d987cfd18c73828994ef5935156036aef529de5723d1cf096c83910925f2c82448dfd50c24419bdd5d57
|
||||
checksum: 10c0/fc118cd54a73b8822cc33978f40f741c764aa1bc71fe3652f71e6027a5a5e6d8f33317af3b262cc82d5672c847aee17860744c83aec99a1fa77807186faf0cf7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@purestack/ts-render@npm:^1.1.17":
|
||||
version: 1.1.17
|
||||
resolution: "@purestack/ts-render@npm:1.1.17"
|
||||
"@purestack/ts-render@npm:^1.1.19":
|
||||
version: 1.1.19
|
||||
resolution: "@purestack/ts-render@npm:1.1.19"
|
||||
dependencies:
|
||||
"@purestack/ts-common": "npm:^1.1.17"
|
||||
"@purestack/ts-minidom": "npm:^1.1.17"
|
||||
"@purestack/ts-util": "npm:^1.1.17"
|
||||
"@purestack/ts-common": "npm:^1.1.19"
|
||||
"@purestack/ts-minidom": "npm:^1.1.19"
|
||||
"@purestack/ts-util": "npm:^1.1.19"
|
||||
regor: "npm:^1.7.6"
|
||||
checksum: 10c0/80b590f83654b2a578a1da5e99f51726d5ab171f100570de502f16adfb5e5524d986d13cb18b86e68131784e9df10b152430bf2c8d5ee1b30d0a9e79c7d44db4
|
||||
checksum: 10c0/9308f6aea7986342a9bb74de80e1720d6dae39999544234fd1deb1b45fefc26519224897746cc86d3563db67022626f26492c4aa6c576cff838635b5b641b40d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@purestack/ts-seo@npm:^1.1.17":
|
||||
version: 1.1.17
|
||||
resolution: "@purestack/ts-seo@npm:1.1.17"
|
||||
"@purestack/ts-seo@npm:^1.1.19":
|
||||
version: 1.1.19
|
||||
resolution: "@purestack/ts-seo@npm:1.1.19"
|
||||
dependencies:
|
||||
"@purestack/ts-common": "npm:^1.1.17"
|
||||
"@purestack/ts-html": "npm:^1.1.17"
|
||||
"@purestack/ts-util": "npm:^1.1.17"
|
||||
checksum: 10c0/0edca49969600e66d800f38e7c171b839b5fda1fcf239f4efbcb63b4503da94c87c225b2852fe2315750da77c653c1044e7ae5794b1c226e58b59a6b4ae5047f
|
||||
"@purestack/ts-common": "npm:^1.1.19"
|
||||
"@purestack/ts-html": "npm:^1.1.19"
|
||||
"@purestack/ts-util": "npm:^1.1.19"
|
||||
checksum: 10c0/c10d3e42790963c02a1b278031f34ddc620e099909f45b74c6d05e1981a66e44d8079fc47b77a5c9cef33d4991a1adfba9a4910c9d0eef31fc962d2a56c0cc04
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@purestack/ts-ssg@npm:^1.1.17":
|
||||
version: 1.1.17
|
||||
resolution: "@purestack/ts-ssg@npm:1.1.17"
|
||||
"@purestack/ts-ssg@npm:^1.1.19":
|
||||
version: 1.1.19
|
||||
resolution: "@purestack/ts-ssg@npm:1.1.19"
|
||||
dependencies:
|
||||
"@11ty/gray-matter": "npm:^3.0.0"
|
||||
"@purestack/ts-common": "npm:^1.1.17"
|
||||
"@purestack/ts-components": "npm:^1.1.17"
|
||||
"@purestack/ts-highlight": "npm:^1.1.17"
|
||||
"@purestack/ts-html": "npm:^1.1.17"
|
||||
"@purestack/ts-minidom": "npm:^1.1.17"
|
||||
"@purestack/ts-page-scripts": "npm:^1.1.17"
|
||||
"@purestack/ts-pagefind": "npm:^1.1.17"
|
||||
"@purestack/ts-render": "npm:^1.1.17"
|
||||
"@purestack/ts-seo": "npm:^1.1.17"
|
||||
"@purestack/ts-style": "npm:^1.1.17"
|
||||
"@purestack/ts-theme": "npm:^1.1.17"
|
||||
"@purestack/ts-util": "npm:^1.1.17"
|
||||
"@purestack/ts-util-node": "npm:^1.1.17"
|
||||
"@purestack/ts-common": "npm:^1.1.19"
|
||||
"@purestack/ts-components": "npm:^1.1.19"
|
||||
"@purestack/ts-highlight": "npm:^1.1.19"
|
||||
"@purestack/ts-html": "npm:^1.1.19"
|
||||
"@purestack/ts-minidom": "npm:^1.1.19"
|
||||
"@purestack/ts-page-scripts": "npm:^1.1.19"
|
||||
"@purestack/ts-pagefind": "npm:^1.1.19"
|
||||
"@purestack/ts-render": "npm:^1.1.19"
|
||||
"@purestack/ts-seo": "npm:^1.1.19"
|
||||
"@purestack/ts-style": "npm:^1.1.19"
|
||||
"@purestack/ts-theme": "npm:^1.1.19"
|
||||
"@purestack/ts-util": "npm:^1.1.19"
|
||||
"@purestack/ts-util-node": "npm:^1.1.19"
|
||||
esbuild: "npm:^0.28.0"
|
||||
hast-util-from-html: "npm:^2.0.3"
|
||||
hast-util-to-html: "npm:^9.0.5"
|
||||
@@ -449,58 +449,58 @@ __metadata:
|
||||
unified: "npm:^11.0.5"
|
||||
bin:
|
||||
ts-ssg: dist/cli.mjs
|
||||
checksum: 10c0/be6ac63ceb62b965129c77e87b6765677fb9bef98a3fd39f308877ae1882c08e1e6e213010866db6a63cbe069cdb2c607bc5fd2a22477160a0c97f8a635e8723
|
||||
checksum: 10c0/53242967070d38502a09b252a91174128419296714171823926ce3362bb65987174aca1b5dffc4336b706c962d2d024403c31d9ea1679ac8512910c4edc65da2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@purestack/ts-style@npm:^1.1.17":
|
||||
version: 1.1.17
|
||||
resolution: "@purestack/ts-style@npm:1.1.17"
|
||||
"@purestack/ts-style@npm:^1.1.19":
|
||||
version: 1.1.19
|
||||
resolution: "@purestack/ts-style@npm:1.1.19"
|
||||
dependencies:
|
||||
"@purestack/ts-css": "npm:^1.1.17"
|
||||
"@purestack/ts-util": "npm:^1.1.17"
|
||||
checksum: 10c0/0dcfe156eaed8213cc58dece014c48027ab382605ef519176ba69528b032ba3cd4b80e5f1b8f4dfd2cd98c26ba5670b7a0da062decc37c297e7f7050dd7d3e68
|
||||
"@purestack/ts-css": "npm:^1.1.19"
|
||||
"@purestack/ts-util": "npm:^1.1.19"
|
||||
checksum: 10c0/1f55610594b4cef262fcf4c4cc2e6e9f963d318123f74eed48b9015540ae5622ac048413e48149b740b3ef25e8f190f77cb49e566b8d27719d8c70b5f2f073ad
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@purestack/ts-svg-icons@npm:^1.1.17":
|
||||
version: 1.1.17
|
||||
resolution: "@purestack/ts-svg-icons@npm:1.1.17"
|
||||
checksum: 10c0/47bee0e42c53f1582ff99219c42f486dc40f20a40592f82c7080c0491e942b802af724ac23770bb978703dbd86a691bf42a072a06de93272e9b64ccd03bfd86f
|
||||
"@purestack/ts-svg-icons@npm:^1.1.19":
|
||||
version: 1.1.19
|
||||
resolution: "@purestack/ts-svg-icons@npm:1.1.19"
|
||||
checksum: 10c0/70a3b6a873b589263cbc0b9e79f68625a7b2610f82a2dc0eddb3905671b60b3aca0738b8b8201138ac1a63f83f1505387980eb3e2ea492b940ef8bfbff3eb0a5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@purestack/ts-theme@npm:^1.1.17":
|
||||
version: 1.1.17
|
||||
resolution: "@purestack/ts-theme@npm:1.1.17"
|
||||
"@purestack/ts-theme@npm:^1.1.19":
|
||||
version: 1.1.19
|
||||
resolution: "@purestack/ts-theme@npm:1.1.19"
|
||||
dependencies:
|
||||
"@purestack/ts-common": "npm:^1.1.17"
|
||||
"@purestack/ts-html": "npm:^1.1.17"
|
||||
"@purestack/ts-page-scripts": "npm:^1.1.17"
|
||||
"@purestack/ts-style": "npm:^1.1.17"
|
||||
"@purestack/ts-svg-icons": "npm:^1.1.17"
|
||||
"@purestack/ts-util-node": "npm:^1.1.17"
|
||||
"@purestack/ts-common": "npm:^1.1.19"
|
||||
"@purestack/ts-html": "npm:^1.1.19"
|
||||
"@purestack/ts-page-scripts": "npm:^1.1.19"
|
||||
"@purestack/ts-style": "npm:^1.1.19"
|
||||
"@purestack/ts-svg-icons": "npm:^1.1.19"
|
||||
"@purestack/ts-util-node": "npm:^1.1.19"
|
||||
"@types/hast": "npm:^3.0.4"
|
||||
highlight.js: "npm:^11.11.1"
|
||||
logpot: "npm:^1.0.7"
|
||||
checksum: 10c0/d7dcd3b1087ebb605907affe939dafbdf590b7722d9a27eb5938571544ffab2db049f61d2c9d60d0fa4ec3b7fe856405c457fab92cddcff22ab4ad9dc90e5085
|
||||
checksum: 10c0/f262b7590f1719ee7365ce419e7cf59e1759a40cd1b661be69c4b0eef4bc5a6b4236880d795342c822bf9322632f0af1e16e0ff3b333753df0c8a5079dd8515d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@purestack/ts-util-node@npm:^1.1.17":
|
||||
version: 1.1.17
|
||||
resolution: "@purestack/ts-util-node@npm:1.1.17"
|
||||
"@purestack/ts-util-node@npm:^1.1.19":
|
||||
version: 1.1.19
|
||||
resolution: "@purestack/ts-util-node@npm:1.1.19"
|
||||
dependencies:
|
||||
"@purestack/ts-util": "npm:^1.1.17"
|
||||
"@purestack/ts-util": "npm:^1.1.19"
|
||||
proper-lockfile: "npm:^4.1.2"
|
||||
checksum: 10c0/c8359fc60d26342e4ec3176514ea9bde1f1ea270cff1ce8251feeb63fc34fb178b8ea518577592e7e78f215127bf963a73749474493018f931ba11e1b4796c95
|
||||
checksum: 10c0/8c439e0cd94efb9c6d51a40366a3684cdc5ce3c972523059f55bc995c364760c6f297ec574aa4009d814ee294cee0caa08a91fd14a15199981fe5354603b81a9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@purestack/ts-util@npm:^1.1.17":
|
||||
version: 1.1.17
|
||||
resolution: "@purestack/ts-util@npm:1.1.17"
|
||||
checksum: 10c0/c0e5c60a20fe5161fcaf0a77074c78aed62a8e1448c32bb567f113d35886fb79e6c4d4c3ad52ee07bef6052180b837ceccaabc0ec1030151762b5342e4bfc72f
|
||||
"@purestack/ts-util@npm:^1.1.19":
|
||||
version: 1.1.19
|
||||
resolution: "@purestack/ts-util@npm:1.1.19"
|
||||
checksum: 10c0/62bc89f7e3777088fed2ca066a10798134f141d228e204f518eb97a5c718b624493eef9bf2e7ce9c9f0eb7af21a9499d93a7149f81a5f35af4d636e6d50006dd
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -2069,14 +2069,14 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"purestack@npm:^1.1.17":
|
||||
version: 1.1.17
|
||||
resolution: "purestack@npm:1.1.17"
|
||||
"purestack@npm:^1.1.19":
|
||||
version: 1.1.19
|
||||
resolution: "purestack@npm:1.1.19"
|
||||
dependencies:
|
||||
"@purestack/ts-ssg": "npm:^1.1.17"
|
||||
"@purestack/ts-ssg": "npm:^1.1.19"
|
||||
bin:
|
||||
purestack: dist/cli.mjs
|
||||
checksum: 10c0/c48a758adc9df669e3181e330aff4abfbaaa5b773dc20c3b18e64f49ce431580d0591a285c56a5a9dd85e74da4bdcd4cf87357071e076da2fb257465d0aeab47
|
||||
checksum: 10c0/6a19eca7ac7543e27a519151ae2ab70744212885ee77e6226cc7f3f23731604eb34798c802b1b5248615cd90e5c71005fa41c313848fee340088a1c427f7056e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -2585,9 +2585,9 @@ __metadata:
|
||||
version: 0.0.0-use.local
|
||||
resolution: "zonetree.dev@workspace:."
|
||||
dependencies:
|
||||
"@purestack/ts-style": "npm:^1.1.17"
|
||||
"@purestack/ts-util": "npm:^1.1.17"
|
||||
purestack: "npm:^1.1.17"
|
||||
"@purestack/ts-style": "npm:^1.1.19"
|
||||
"@purestack/ts-util": "npm:^1.1.19"
|
||||
purestack: "npm:^1.1.19"
|
||||
regor: "npm:^1.7.6"
|
||||
typescript: "npm:^7.0.2"
|
||||
languageName: unknown
|
||||
|
||||
Reference in New Issue
Block a user