MishkaCmsCore. Runtime. States. WebRunTimeMetaDataState
(Mishka CMS Core v0.0.1)
Copy Markdown
View Source
High-performance ETS-based state manager for web page metadata with multi-index support.
Manages runtime metadata for web pages, posts, and content with optimized storage and retrieval using multiple ETS tables for different access patterns. Designed for high-concurrency web applications requiring fast page lookups, pagination, and content organization.
Architecture
Uses a multi-table ETS architecture with specialized indexes:
- Main Table: Primary storage with full page data
- Path Index: O(1) lookups by path and site combination
- Site Index: Efficient site-based filtering
- Section Index: Content organization by site and section
- Sort Index: Ordered set for priority-based pagination
Data Structure
Each page record contains:
id: Unique page identifiersite: Site identifier for multi-tenant supportpath: URL path for routingtitle: Page title for displaycreated_at: Timestamp for orderingsection: Content category (Page, Blog, etc.)priority: Sort priority (higher = more important)
Key Features
- O(1) Path Lookups: Instant page resolution by path/site
- Optimized Pagination: Priority-based sorting with efficient offset/limit
- Memory Streaming: Process large datasets without memory overhead
- Conflict Prevention: Automatic path uniqueness validation
- Batch Operations: High-performance bulk data operations
- Multi-tenant Support: Site-based data isolation
- Concurrent Access: Read/write concurrency optimization
Performance Characteristics
- Path lookups: O(1)
- Pagination queries: O(n log n) where n = matching records
- Memory usage: ~200-300 bytes per page record
- Concurrent reads: Fully optimized
- Batch inserts: ~10x faster than individual saves
Usage Examples
# Save a page
WebRunTimeMetaDataState.save(%{
id: "blog_post_123",
site: "myblog",
path: "/posts/hello-world",
title: "Hello World",
created_at: DateTime.utc_now(),
section: "Blog",
priority: 90
})
# Fast path lookup
{:ok, page} = WebRunTimeMetaDataState.find_by_path_and_site("/posts/hello-world", "myblog")
# Paginated results
%{data: pages, total: count} = WebRunTimeMetaDataState.paginate(%{
site: "myblog",
section: "Blog",
page: 1,
limit: 10
})
# Memory-efficient streaming
WebRunTimeMetaDataState.stream_paginated(%{site: "myblog"})
|> Stream.each(&process_page/1)
|> Stream.run()Integration Patterns
- Router Integration: Fast route resolution for dynamic pages
- SEO Management: Title and metadata organization
- Content Management: Section-based content organization
- Analytics: Priority-based content ranking
- Multi-site CMSs: Tenant-isolated page management
Production Considerations
- Monitor memory usage with
stats/0function - Use batch operations for initial data loading
- Consider streaming for large dataset processing
- Implement cleanup strategies for unused pages
- Path conflicts are prevented automatically
Summary
Functions
Returns a specification to start this module under a supervisor.
Clears all data from all tables.
Returns the number of pages in the registry.
Returns the number of pages for a specific site.
Returns the number of pages for a specific site and section.
Deletes a page by ID and removes from all indexes.
Deletes every routing entry for a site and returns the count removed.
Whether a stored path is a pattern rather than a literal.
Fast check if a page exists.
The page whose stored path PATTERN matches this request, with the segments it captured.
The record STORED at this path, for a caller naming one it already knows.
Gets page data by ID using direct lookup (O(1)).
Gets page data by ID, returns nil if not found.
High-performance paginated query with sorting by priority DESC, created_at ASC.
Fast check if a path exists for a site.
Rebuilds all indexes from the main table (useful for maintenance).
The page for a REQUEST path, and whatever its pattern captured.
find_by_path_and_site!/3 with the captured segments beside the page.
Saves page data to the ETS tables with all fields.
Batch save multiple pages for better performance.
Starts the MishkaCmsCore.Runtime.States.WebRunTimeMetaDataState GenServer and creates the ETS tables.
Returns detailed statistics about the registry.
Stream paginated results for memory-efficient processing.
Functions
Returns a specification to start this module under a supervisor.
See Supervisor.
Clears all data from all tables.
Returns the number of pages in the registry.
Returns the number of pages for a specific site.
Returns the number of pages for a specific site and section.
Deletes a page by ID and removes from all indexes.
Deletes every routing entry for a site and returns the count removed.
Whether a stored path is a pattern rather than a literal.
Fast check if a page exists.
The page whose stored path PATTERN matches this request, with the segments it captured.
A page stored at /post/:category/:slug answers /post/elixir/hello-world and captures
%{"category" => "elixir", "slug" => "hello-world"}. *rest captures everything left, joined,
which is how one page serves a whole subtree.
Why this is ours and not Phoenix's
Phoenix.Router compiles its routes: the match is generated at BUILD time from macros, so it
cannot know a path an admin typed into the editor this morning. That is exactly why this app
routes live "/*path" and resolves the rest itself. There is no Phoenix or Plug function that
matches a runtime pattern STRING against runtime segments — Plug.Router.Utils.build_path_match/1
returns AST for the compiler — so the matcher below is the part that genuinely has to be ours.
MishkaCmsCore.Runtime.Resources.Validations.PathFormat has always allowed these paths, naming
"route params like :id, and wildcards like *rest". Until now nothing matched them.
Precedence
An exact path always wins: find_by_path_and_site/2 only reaches this after the literal ETS
lookup misses, so no URL that resolves today resolves differently.
Between two patterns that both match, the one with FEWER captures wins — /post/new beats
/post/:slug, and /post/:slug beats /*rest. Ties break on the page's own priority, which is
the field Page's :by_priority read already exists to order by, and then on id so the answer is
stable rather than dependent on ETS order.
A capture named lang
Matches only one of the site's languages, in whichever segment it sits — see
MishkaCmsCore.Runtime.Languages. /:lang/about does not answer /xyz/about: that pattern is
passed over as though it had not matched, so the request goes to the next pattern or to the 404.
The site's languages are read only when some pattern of the site captures lang.
The record STORED at this path, for a caller naming one it already knows.
A layout is reached by the path the page names it with and an error page by the path its row
carries; neither is a URL a visitor typed. So this looks the path up as written — a pattern path
included — which is what separates it from resolve/2.
Gets page data by ID using direct lookup (O(1)).
Gets page data by ID, returns nil if not found.
High-performance paginated query with sorting by priority DESC, created_at ASC.
Examples
MishkaCmsCore.Runtime.States.WebRunTimeMetaDataState.paginate(%{
site: "example",
section: "blog",
page: 1,
limit: 20
})
Fast check if a path exists for a site.
Rebuilds all indexes from the main table (useful for maintenance).
The page for a REQUEST path, and whatever its pattern captured.
%{} for a literal page, which is almost all of them.
A REQUEST NEVER CARRIES PATTERN SYNTAX, and refusing it here is the whole of this clause. A
pattern page is stored under its pattern — /:lang/blog — and the path index is keyed by the
stored string, so typing that pattern into the address bar matched the index EXACTLY and was
served as though it were a literal page: /:lang/blog and /:lang/blog/tags answered 200, in
no language, from a row nobody could reach that way on purpose. The dynamic matcher was never
consulted, which is why Languages.admits?/3 did not stop it — /xyz/blog was refused all
along, and only the pattern's own text got through.
Every capture is refused, not just lang: /post/:slug would otherwise answer with :slug as
the slug, and *rest the same. dynamic_path?/1 is the same predicate that decides what earns a
dynamic row, so a path is a pattern here exactly when it is one there.
An address that names no language
On a site whose primary language has no prefix (MishkaCmsCore.Runtime.Languages.url_mode/1), a
request whose first segment is not one of its languages is in the primary one
(Languages.implied/2), and is asked three questions in turn:
- the literal page stored at the path as typed —
/blog; - the literal page stored under the primary language —
/en/blog; - the best pattern for either form, by the precedence
find_by_dynamic_path/2keeps, and the prefixed form when the two tie —/:lang/bloganswers/blogas/en/blog.
A match through the prefixed form captures lang as the primary language, as /en/blog would
have, so a page's editions and the language of the response read it the same way. A site that
prefixes every language, and a request that names one, are asked about the path as typed alone.
find_by_path_and_site!/3 with the captured segments beside the page.
Raises the same NotFoundError, so a caller that wants the captures does not have to handle a
different failure than one that does not.
Saves page data to the ETS tables with all fields.
Examples
MishkaCmsCore.Runtime.States.WebRunTimeMetaDataState.save(%{
id: "page_123",
site: "example",
path: "/about",
title: "About Us",
created_at: ~U[2024-01-01 10:00:00Z],
section: "Page",
priority: 80
})
Batch save multiple pages for better performance.
Starts the MishkaCmsCore.Runtime.States.WebRunTimeMetaDataState GenServer and creates the ETS tables.
Returns detailed statistics about the registry.
Stream paginated results for memory-efficient processing.