diff --git a/scripts/validate-docs/Program.cs b/scripts/validate-docs/Program.cs index 5884bda..12baca2 100644 --- a/scripts/validate-docs/Program.cs +++ b/scripts/validate-docs/Program.cs @@ -63,6 +63,10 @@ static List GetMarkdownFiles(string repositoryRoot, List errors) docsDirectory, "*.md", SearchOption.AllDirectories)); + files.AddRange(Directory.EnumerateFiles( + docsDirectory, + "*.mdx", + SearchOption.AllDirectories)); } else { diff --git a/zonetree-dev/README.md b/zonetree-dev/README.md index d713fde..4ffca58 100644 --- a/zonetree-dev/README.md +++ b/zonetree-dev/README.md @@ -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`, diff --git a/zonetree-dev/content/docs/_nav.json b/zonetree-dev/content/docs/_nav.json index a321c35..e7d421c 100644 --- a/zonetree-dev/content/docs/_nav.json +++ b/zonetree-dev/content/docs/_nav.json @@ -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", diff --git a/zonetree-dev/content/docs/concepts/lsm-tree.md b/zonetree-dev/content/docs/concepts/lsm-tree.md deleted file mode 100644 index ba05099..0000000 --- a/zonetree-dev/content/docs/concepts/lsm-tree.md +++ /dev/null @@ -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). diff --git a/zonetree-dev/content/docs/concepts/lsm-tree.mdx b/zonetree-dev/content/docs/concepts/lsm-tree.mdx new file mode 100644 index 0000000..1b8be86 --- /dev/null +++ b/zonetree-dev/content/docs/concepts/lsm-tree.mdx @@ -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 + +
+

Write into ordered memory. Compact into immutable disk segments. Read across both, while the engine keeps moving.

+

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.

+
+ +## 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. + + + +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. + +
+ Two dimensions shape the engine. 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. +
+ +## 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. + + + +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. + + + +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() + .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. + + + + + diff --git a/zonetree-dev/content/docs/concepts/segments.md b/zonetree-dev/content/docs/concepts/segments.md index 4fc22cc..d0cae16 100644 --- a/zonetree-dev/content/docs/concepts/segments.md +++ b/zonetree-dev/content/docs/concepts/segments.md @@ -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). diff --git a/zonetree-dev/content/docs/index.md b/zonetree-dev/content/docs/index.md index 460f070..90e1fc1 100644 --- a/zonetree-dev/content/docs/index.md +++ b/zonetree-dev/content/docs/index.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). diff --git a/zonetree-dev/content/ui/components/index.ts b/zonetree-dev/content/ui/components/index.ts index 6f31ad7..5c6cb7f 100644 --- a/zonetree-dev/content/ui/components/index.ts +++ b/zonetree-dev/content/ui/components/index.ts @@ -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(), } } diff --git a/zonetree-dev/content/ui/components/lsm-architecture.ts b/zonetree-dev/content/ui/components/lsm-architecture.ts new file mode 100644 index 0000000..4378f4d --- /dev/null +++ b/zonetree-dev/content/ui/components/lsm-architecture.ts @@ -0,0 +1,55 @@ +import { defineComponent, html } from 'regor' + +export function defineLsmArchitecture() { + return defineComponent(html` +
+
+ THE STORAGE PIPELINE + One ZoneTree instance +
+
+ Writer 01 + Writer 02 + Writer 03 +
+
+
01Mutable segmentMEMORY
+
+
+
1020304050
+

Concurrent, ordered B+Tree

+
+
WALRecent-write recovery
+
+
+
When full, move to read-only.
New writes enter a fresh mutable segment.
+
+
02Read-only segmentsMEMORY
+
Batch 01FrozenBatch 02FrozenBatch 03Frozen · newest
+

Ordered batches waiting for compaction

+
+
+ + Compact read-only batches + current disk segment.
Publish the merged disk segment.
+
+
+
03Disk segmentDISK
+
Part A10–29Part B30–49Part C50–69
+

One logical segment · ordered, immutable parts

+
+
When the merged record count exceeds
DiskSegmentMaxItemCount
+
+
04Bottom segmentsDISK
+
+
Newer disk segment
+
Older disk segment
+
+

A queue of disk segments · compacted separately

+
+
Parallel reads and writes run alongside background compaction.
+
Vertical layers batch and move data. Horizontal parts divide a disk segment by key range. Keys and batches shown here are illustrative.
+
+ `) +} diff --git a/zonetree-dev/content/ui/components/lsm-part-reuse.ts b/zonetree-dev/content/ui/components/lsm-part-reuse.ts new file mode 100644 index 0000000..161c767 --- /dev/null +++ b/zonetree-dev/content/ui/components/lsm-part-reuse.ts @@ -0,0 +1,16 @@ +import { defineComponent, html } from 'regor' + +export function defineLsmPartReuse() { + return defineComponent(html` +
+
COMPACTION WITH PART REUSE
+

Current disk segment

+
A00–24B25–49C50–74D75–99
+
Incoming updates overlap the range in B.
+ +
ACarriedB′New outputCCarriedDCarried
+

Next disk segment · reused parts + new output

+
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.
+
+ `) +} diff --git a/zonetree-dev/content/ui/components/lsm-read-path.ts b/zonetree-dev/content/ui/components/lsm-read-path.ts new file mode 100644 index 0000000..86ee186 --- /dev/null +++ b/zonetree-dev/content/ui/components/lsm-read-path.ts @@ -0,0 +1,16 @@ +import { defineComponent, html } from 'regor' + +export function defineLsmReadPath() { + return defineComponent(html` +
+
NEWEST VISIBLE VALUE WINSTryGet(42)
+
    +
  1. 01
    Mutable segmentKey 42 is absent
    Continue
  2. +
  3. 02
    Newest read-only segment42 → "current"
    Return value
  4. +
  5. 03
    Disk segment42 → "previous"
    Not visited
  6. +
  7. 04
    Bottom segments42 → "original"
    Not visited
  8. +
+
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.
+
+ `) +} diff --git a/zonetree-dev/content/ui/lsm-styles.ts b/zonetree-dev/content/ui/lsm-styles.ts new file mode 100644 index 0000000..52f5673 --- /dev/null +++ b/zonetree-dev/content/ui/lsm-styles.ts @@ -0,0 +1,206 @@ +import type { StyleBuilder, ThemeMode } from '@purestack/ts-style' +import { zoneTreeColors } from './skin' + +type LsmStyleContext = ReturnType + +export function registerLsmArchitectureStyles(root: ReturnType, theme: ThemeMode) { + const context = createLsmStyleContext(root, theme) + + registerArticleStyles(context) + registerFigureStyles(context) + registerPipelineStyles(context) + registerLookupStyles(context) + registerPartReuseStyles(context) + registerResponsiveStyles(context) +} + +function createLsmStyleContext(root: ReturnType, 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') +} diff --git a/zonetree-dev/content/ui/styles.ts b/zonetree-dev/content/ui/styles.ts index 8486623..4071f78 100644 --- a/zonetree-dev/content/ui/styles.ts +++ b/zonetree-dev/content/ui/styles.ts @@ -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) }) } diff --git a/zonetree-dev/package.json b/zonetree-dev/package.json index 3727147..2e6714f 100644 --- a/zonetree-dev/package.json +++ b/zonetree-dev/package.json @@ -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": { diff --git a/zonetree-dev/yarn.lock b/zonetree-dev/yarn.lock index 30877e5..675ad89 100644 --- a/zonetree-dev/yarn.lock +++ b/zonetree-dev/yarn.lock @@ -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