### Install Packages Source: https://github.com/djgrant/piq/blob/main/docs/packages/index.md Use this command to install the core piqit package and its resolvers. ```bash npm install piqit @piqit/resolvers ``` -------------------------------- ### Install piq and dependencies Source: https://github.com/djgrant/piq/blob/main/docs/guide/index.md Install the core piqit library and necessary resolvers using npm. ```bash npm install piqit @piqit/resolvers zod ``` -------------------------------- ### Install piqit Package Source: https://github.com/djgrant/piq/blob/main/docs/packages/piqit.md Install the piqit package using npm. ```bash npm install piqit ``` -------------------------------- ### Install @piqit/resolvers Source: https://github.com/djgrant/piq/blob/main/docs/packages/resolvers.md Install the package using npm. This command is for the Node.js package manager. ```bash npm install @piqit/resolvers ``` -------------------------------- ### Import piq Library Source: https://github.com/djgrant/piq/blob/main/docs/reference/api.md Import necessary functions from the piqit library to start building queries. ```APIDOC ## Import piq Library ### Description Import the `piq` and `from` functions from the `piqit` library. ### Code Example ```typescript import { piq, from } from 'piqit' ``` ``` -------------------------------- ### Import piq Functions Source: https://github.com/djgrant/piq/blob/main/docs/reference/api.md Import the necessary functions 'piq' and 'from' from the 'piqit' library to start building queries. ```typescript import { piq, from } from 'piqit' ``` -------------------------------- ### Primary API Usage Example Source: https://github.com/djgrant/piq/blob/main/docs/packages/piqit.md Demonstrates the primary API usage for building and executing a query. Ensure `select()` is called before `exec()`. ```typescript import { piq } from 'piqit' const rows = await piq .from(postsResolver) .scan({ year: '2024' }) .filter({ status: 'published' }) .select('params.slug', 'frontmatter.title') .exec() ``` -------------------------------- ### piq.from - Create a Query from a Resolver Source: https://context7.com/djgrant/piq/llms.txt The `piq.from()` function initializes a query builder instance using a provided resolver. This is the starting point for all queries, returning a chainable QueryBuilder. ```APIDOC ## piq.from - Create a Query from a Resolver ### Description The `piq.from()` function creates a new query builder instance from a resolver, which is the entry point for all queries. It returns a chainable QueryBuilder that supports scan, filter, select, and execution methods. ### Request Example ```typescript import { piq } from 'piqit' import { fileMarkdown } from '@piqit/resolvers' import { z } from 'zod' // Create a resolver for markdown posts const posts = fileMarkdown({ base: 'content/posts', path: '{year}/{slug}.md', frontmatter: z.object({ title: z.string(), status: z.enum(['draft', 'published']), tags: z.array(z.string()).default([]), }), body: { html: true, headings: true } }) // Create and execute a query const results = await piq.from(posts) .scan({ year: '2024' }) .filter({ status: 'published' }) .select('params.slug', 'frontmatter.title', 'body.html') .exec() // Results are FLAT with field names from last path segment: // [{ slug: 'hello-world', title: 'Hello World', html: '

Content...

' }] ``` ``` -------------------------------- ### Quick Start: Querying Published Posts Source: https://github.com/djgrant/piq/blob/main/README.md Set up a resolver for markdown posts and query for published posts from a specific year, selecting specific fields. Results are flattened. ```typescript import { piq } from 'piqit' import { fileMarkdown } from '@piqit/resolvers' import { z } from 'zod' // Create a resolver for markdown posts const posts = fileMarkdown({ base: 'content/posts', path: '{year}/{slug}.md', frontmatter: z.object({ title: z.string(), status: z.enum(['draft', 'published']), tags: z.array(z.string()), }), body: { html: true, headings: true } }) // Query for published posts from 2024 const results = await piq.from(posts) .scan({ year: '2024' }) .filter({ status: 'published' }) .select('params.slug', 'frontmatter.title', 'body.html') .exec() // Results are FLAT - field names from the last path segment: // [{ slug: 'hello-world', title: 'Hello World', html: '

...' }] ``` -------------------------------- ### Fluent API: Query Construction Source: https://github.com/djgrant/piq/blob/main/README.md Demonstrates the method chaining for constructing a query, starting with 'from' and progressing through 'scan', 'filter', 'select', and 'exec'. ```typescript piq.from(posts) .scan({ year: '2024' }) // enumerate by pattern (cheap) .filter({ status: 'published' }) // narrow by criteria (costs I/O) .select('params.slug', 'frontmatter.title') // what to read .exec() // execute ``` -------------------------------- ### scan() Method: Finding Items by Pattern Source: https://github.com/djgrant/piq/blob/main/README.md Examples of using the scan() method to find items by pattern, including finding all posts from a specific year, a specific post, or all posts. ```typescript // Find all posts from 2024 piq.from(posts).scan({ year: '2024' }) // Find a specific post piq.from(posts).scan({ year: '2024', slug: 'hello-world' }) // Find all posts (empty constraints) piq.from(posts).scan({}) ``` -------------------------------- ### Creating a QueryBuilder Source: https://github.com/djgrant/piq/blob/main/docs/reference/api.md Demonstrates how to initialize a QueryBuilder instance using `piq.from()` or the helper export `from()`. ```APIDOC ## Creating a QueryBuilder ### Description Initialize a `QueryBuilder` instance using a resolver function. This can be done via `piq.from(resolver)` or the equivalent helper export `from(resolver)`. ### Entry Points #### `piq.from(resolver)` Creates a `QueryBuilder` from a resolver. ```typescript const query = piq.from(postsResolver) ``` #### `from(resolver)` Equivalent helper export. ```typescript const query = from(postsResolver) ``` ``` -------------------------------- ### Get Single QueryBuilder with single() Source: https://github.com/djgrant/piq/blob/main/docs/reference/api.md Obtain a 'SingleQueryBuilder' instance using the 'single()' method, which is used for retrieving a single result. ```typescript const single = piq.from(postsResolver) .scan({ year: '2024', slug: 'hello-world' }) .select('params.slug') .single() ``` -------------------------------- ### Build and check @piqit/resolvers Source: https://github.com/djgrant/piq/blob/main/docs/packages/resolvers.md Commands to build and check the @piqit/resolvers package within a workspace environment using Bun. ```bash bun run --filter @piqit/resolvers build bun run --filter @piqit/resolvers check ``` -------------------------------- ### Build and Check piqit Workspace Source: https://github.com/djgrant/piq/blob/main/docs/packages/piqit.md Commands to build and check the piqit package within a workspace environment using bun. ```bash bun run --filter piqit build ``` ```bash bun run --filter piqit check ``` -------------------------------- ### filter() Method: Narrowing Results by Criteria Source: https://github.com/djgrant/piq/blob/main/README.md Example of using the filter() method to narrow down results based on document-level criteria, typically used after scan(). ```typescript // Find published posts from 2024 piq.from(posts) .scan({ year: '2024' }) .filter({ status: 'published' }) ``` -------------------------------- ### Build and Deploy Commands Source: https://github.com/djgrant/piq/blob/main/DEPLOY.md These bash commands are used to build the project's static assets and API worker, and to deploy the site to Cloudflare Pages. Development servers for VitePress and Astro can be run separately. ```bash # Build both sites and merge pok site build # Build and deploy to Cloudflare Pages pok deploy # Development (run separately) pok docs dev # VitePress dev server at localhost:5173 pok website dev # Astro dev server at localhost:4321 ``` -------------------------------- ### Flatten Select Output Source: https://github.com/djgrant/piq/blob/main/docs/guide/concepts.md Select paths are namespaced, but the output is flattened by the final segment. This example shows selecting 'params.slug' and 'frontmatter.title' into a flattened object. ```typescript .select('params.slug', 'frontmatter.title') // { slug, title } ``` -------------------------------- ### Create Cloudflare Pages Project Source: https://github.com/djgrant/piq/blob/main/DEPLOY.md This command initializes a new Cloudflare Pages project. You will need to configure the custom domain and DNS settings in the Cloudflare dashboard afterward. ```bash npx wrangler pages project create piq ``` -------------------------------- ### Retrieve Single Result with single() Source: https://github.com/djgrant/piq/blob/main/README.md Use single() to get the first result or undefined. For cases where exactly one result is expected, use execOrThrow() to throw an error if no results are found. ```typescript // Returns first result or undefined const post = await piq.from(posts) .scan({ year: '2024', slug: 'hello-world' }) .select('params.slug', 'frontmatter.title') .single() .exec() // Throws if no results const post = await piq.from(posts) .scan({ year: '2024', slug: 'hello-world' }) .select('params.slug') .single() .execOrThrow() ``` -------------------------------- ### Configure fileMarkdown resolver Source: https://github.com/djgrant/piq/blob/main/docs/packages/resolvers.md Set up the fileMarkdown resolver with a base directory, path pattern, frontmatter schema, and body processing options. Bun runtime is required. ```typescript const posts = fileMarkdown({ base: 'content/posts', path: '{year}/{slug}.md', frontmatter: z.object({ title: z.string(), status: z.enum(['draft', 'published']), }), body: { html: true, headings: true }, }) ``` -------------------------------- ### Parse Markdown Body Content Source: https://context7.com/djgrant/piq/llms.txt Parses markdown body to extract raw text, HTML, and headings. `extractHeadings` gets heading metadata, `markdownToHtml` converts markdown to HTML, and `slugify` creates URL-safe slugs. ```typescript import { parseMarkdownBody, extractHeadings, markdownToHtml, slugify, readMarkdownBody, readParsedBody } from '@piqit/resolvers' const content = `--- title: Test --- # Welcome This is **bold** and _italic_ text. ## Getting Started Some content here. ### Installation \`\`\`bash npm install piqit \`\`\` ` // Parse body with options const body = parseMarkdownBody(content, { raw: true, html: true, headings: true }) // { // raw: '# Welcome\n\nThis is **bold**...', // html: '

Welcome

\n

This is bold...

', // headings: [ // { depth: 1, text: 'Welcome', slug: 'welcome' }, // { depth: 2, text: 'Getting Started', slug: 'getting-started' }, // { depth: 3, text: 'Installation', slug: 'installation' } // ] // } // Extract just headings const headings = extractHeadings('# Title\n## Section\n### Subsection') // [{ depth: 1, text: 'Title', slug: 'title' }, ...] // Convert markdown to HTML const html = markdownToHtml('**bold** and _italic_') // '

bold and italic

' // Create URL-safe slug const slug = slugify('Hello World!') // 'hello-world' // Read body from file const rawBody = await readMarkdownBody('content/post.md') const parsedBody = await readParsedBody('content/post.md', { html: true }) ``` -------------------------------- ### Create Query from Resolver with piq.from Source: https://context7.com/djgrant/piq/llms.txt Use `piq.from()` to create a query builder instance from a resolver. This is the entry point for all queries and returns a chainable QueryBuilder. ```typescript import { piq } from 'piqit' import { fileMarkdown } from '@piqit/resolvers' import { z } from 'zod' // Create a resolver for markdown posts const posts = fileMarkdown({ base: 'content/posts', path: '{year}/{slug}.md', frontmatter: z.object({ title: z.string(), status: z.enum(['draft', 'published']), tags: z.array(z.string()).default([]), }), body: { html: true, headings: true } }) // Create and execute a query const results = await piq.from(posts) .scan({ year: '2024' }) .filter({ status: 'published' }) .select('params.slug', 'frontmatter.title', 'body.html') .exec() // Results are FLAT with field names from last path segment: // [{ slug: 'hello-world', title: 'Hello World', html: '

Content...

' }] ``` -------------------------------- ### Execute a basic piq query Source: https://github.com/djgrant/piq/blob/main/docs/guide/index.md Run a query against the configured posts resolver, scanning for a specific year, filtering by status, and selecting desired fields. Results are flattened with the last path segment as the key. ```typescript import { piq } from 'piqit' import { posts } from './posts-resolver' const results = await piq .from(posts) .scan({ year: '2024' }) .filter({ status: 'published' }) .select('params.slug', 'frontmatter.title', 'body.html') .exec() // [{ slug: 'hello-world', title: 'Hello World', html: '

...

' }] ``` -------------------------------- ### Query Execution Source: https://github.com/djgrant/piq/blob/main/docs/reference/api.md Explains how to execute queries using `exec()`, `single()`, and `stream()` methods. ```APIDOC ## Query Execution ### `exec()` Runs the query and returns all resulting rows. - **Return type**: `Promise` - **Throws**: If `select()` was never called. ```typescript const rows = await piq.from(postsResolver) .scan({ year: '2024' }) .select('params.slug') .exec() ``` ### `single()` Returns a `SingleQueryBuilder`. ```typescript const single = piq.from(postsResolver) .scan({ year: '2024', slug: 'hello-world' }) .select('params.slug') .single() ``` #### `single().exec()` Returns the first row or `undefined` if no rows are found. ```typescript const maybeRow = await query.single().exec() ``` #### `single().execOrThrow()` Returns the first row, throws an error if the result set is empty. ```typescript const row = await query.single().execOrThrow() ``` ### `stream()` Returns an `AsyncGenerator` for streaming results. ```typescript for await (const row of piq.from(postsResolver).scan({}).select('params.slug').stream()) { console.log(row.slug) } ``` **Note**: The current implementation calls `exec()` internally and then yields rows one by one. ``` -------------------------------- ### Monorepo Scripts Source: https://github.com/djgrant/piq/blob/main/docs/packages/index.md Run these commands from the repository root to manage the monorepo. ```bash bun run build ``` ```bash bun run test ``` ```bash bun run check ``` -------------------------------- ### Import piqit Core Exports Source: https://github.com/djgrant/piq/blob/main/docs/packages/piqit.md Import core components from the piqit package for query building and utilities. ```typescript import { piq, from, QueryBuilder, SingleQueryBuilder, undot, undotWithAliases, undotAll, undotAllWithAliases, expandWildcards, } from 'piqit' ``` -------------------------------- ### Edge / Worker Environments Source: https://github.com/djgrant/piq/blob/main/README.md Information on using PIQ in environments without filesystem access. ```APIDOC ## Edge / Worker Environments For environments without filesystem access (Cloudflare Workers, etc.), use the `staticContent` resolver with pre-compiled content. ### Entry Points - `@piqit/resolvers` — Full package (includes `fileMarkdown` and `staticContent`) - `@piqit/resolvers/edge` — Edge-only (only `staticContent`, no Node.js dependencies) ``` -------------------------------- ### Edge Entrypoint for Resolvers Source: https://github.com/djgrant/piq/blob/main/docs/reference/resolvers.md Import edge-safe APIs like staticContent and staticResolver from the edge entrypoint of the piq resolvers package. ```typescript import { staticContent } from '@piqit/resolvers/edge' ``` -------------------------------- ### Create QueryBuilder with piq.from() Source: https://github.com/djgrant/piq/blob/main/docs/reference/api.md Use 'piq.from()' to create a QueryBuilder instance, initializing it with a resolver function. ```typescript const query = piq.from(postsResolver) ``` -------------------------------- ### Select Type Utilities Source: https://github.com/djgrant/piq/blob/main/docs/reference/types.md Lists the type helper utilities exported by `piqit` for compile-time validation and result inference related to selecting fields. ```APIDOC ## Select Type Utilities ### Description Lists the type helper utilities exported by `piqit` for compile-time validation and result inference related to selecting fields. ### Utilities - `SelectablePaths` - `GetFieldName` - `GetPathValue` - `ExpandWildcard` - `HasCollision` - `Undot` - `UndotWithAliases` These power compile-time validation and result inference. ``` -------------------------------- ### Configure File System Markdown Resolver Source: https://github.com/djgrant/piq/blob/main/docs/reference/resolvers.md Set up a file system resolver for markdown documents with YAML frontmatter. Specify the base directory, path pattern, frontmatter schema, and body parsing options. ```typescript import { fileMarkdown } from '@piqit/resolvers' import { z } from 'zod' const posts = fileMarkdown({ base: 'content/posts', path: '{year}/{slug}.md', frontmatter: z.object({ title: z.string(), status: z.enum(['draft', 'published']), tags: z.array(z.string()), }), body: { raw: true, html: true, headings: true }, }) ``` -------------------------------- ### Define Path Patterns Source: https://github.com/djgrant/piq/blob/main/README.md Use {param} syntax in path patterns to define URL-style segments for matching files. Parameters extracted from the path are available as `params.*` in select. ```plaintext '{year}/{slug}.md' // Matches: 2024/hello-world.md '{category}/{year}/{slug}.md' // Matches: tech/2024/my-post.md 'TASK-{num}-{title}.md' // Matches: TASK-123-title-which-may-include-hyphens.md ``` -------------------------------- ### Create QueryBuilder with from() Source: https://github.com/djgrant/piq/blob/main/docs/reference/api.md An equivalent helper export, 'from()', can also be used to create a QueryBuilder instance with a resolver. ```typescript const query = from(postsResolver) ``` -------------------------------- ### Build and Serve Static Content on Edge Source: https://context7.com/djgrant/piq/llms.txt Build script compiles markdown content into a static module for edge environments. The worker code then uses this static content to resolve requests. ```typescript // === Build script (runs at build time) === import { fileMarkdown } from '@piqit/resolvers' import { piq } from 'piqit' import { z } from 'zod' const posts = fileMarkdown({ base: 'content/posts', path: '{year}/{slug}.md', frontmatter: z.object({ title: z.string(), status: z.string() }), body: { html: true, headings: true } }) // Compile all content at build time const allPosts = await piq.from(posts) .scan({}) .select('params.*', 'frontmatter.*', 'body.*') .exec() // Write to generated module await Bun.write( 'src/generated/content.ts', `export const posts = ${JSON.stringify(allPosts)};` ) ``` ```typescript // === Worker code (runs at edge) === import { posts } from './generated/content' import { staticContent } from '@piqit/resolvers/edge' import { piq } from 'piqit' const postsResolver = staticContent(posts) export default { async fetch(request: Request) { const results = await piq.from(postsResolver) .scan({ year: '2024' }) .filter({ status: 'published' }) .select('params.slug', 'frontmatter.title') .exec() return Response.json(results) } } ``` -------------------------------- ### Scan Returns Metadata for Free Source: https://github.com/djgrant/piq/blob/main/README.md Design sources so enumeration carries summary data. The `params` namespace, derived from the path, provides metadata at no extra cost. ```typescript // Path: {status}/{date}/{slug}.md .scan({ status: 'published' }) .select('params.date', 'params.slug') // Free data from path ``` -------------------------------- ### Compile Content at Build Time Source: https://github.com/djgrant/piq/blob/main/README.md Use this to compile markdown content into a TypeScript file during the build process. Ensure the base path and file structure match your project. ```typescript import { fileMarkdown } from '@piqit/resolvers' import { piq } from 'piqit' const posts = fileMarkdown({ base: 'content/posts', path: '{year}/{slug}.md', ... }) const allPosts = await piq.from(posts) .scan({}) .select('params.*', 'frontmatter.*', 'body.*') .exec() await Bun.write( 'src/generated/content.ts', `export const posts = ${JSON.stringify(allPosts)};` ) ``` -------------------------------- ### Build Static Dataset for Edge Source: https://github.com/djgrant/piq/blob/main/docs/guide/recipes.md Generates a static dataset of all posts by selecting all fields from 'params', 'frontmatter', and 'body' namespaces, then serializes it to a TypeScript file. ```typescript // build-content.ts (Bun) const allPosts = await piq .from(postsResolver) .scan({}) .select('params.*', 'frontmatter.*', 'body.*') .exec() await Bun.write('src/generated/posts.ts', `export const posts = ${JSON.stringify(allPosts)} as const`) ``` ```typescript // worker.ts import { piq } from 'piqit' import { staticContent } from '@piqit/resolvers/edge' import { posts } from './generated/posts' const postsResolver = staticContent([...posts]) const rows = await piq .from(postsResolver) .scan({ year: '2024' }) .select('params.slug', 'frontmatter.title') .exec() ``` -------------------------------- ### Resolver Factories Source: https://github.com/djgrant/piq/blob/main/docs/packages/resolvers.md APIs for creating content resolvers. `fileMarkdown` is for filesystem-based markdown files, while `staticContent` is for precompiled data, suitable for edge environments. ```APIDOC ## Resolver Factories ### `fileMarkdown(options)` Creates a resolver for markdown files from the filesystem. **Parameters** - **options** (object) - Required - Configuration for the resolver. - **base** (string) - Required - The base directory for content files. - **path** (string) - Required - A pattern to match file paths (e.g., `{year}/{slug}.md`). - **frontmatter** (ZodSchema) - Optional - A Zod schema for frontmatter validation. - **body** (object) - Optional - Configuration for body processing. - **html** (boolean) - Optional - Whether to convert markdown body to HTML. - **headings** (boolean) - Optional - Whether to extract headings from the body. **Notes** - Requires Bun runtime. - Scan values are derived from path placeholders. - Filter checks frontmatter equality. ### `staticContent(data)` Creates a resolver for precompiled content data. Ideal for runtime-safe execution in edge environments. **Parameters** - **data** (any) - Required - The precompiled content data. ### `staticResolver` An alias for `staticContent`. ``` -------------------------------- ### Defining Resolvers Source: https://github.com/djgrant/piq/blob/main/README.md How to define custom resolvers, focusing on the `fileMarkdown` resolver. ```APIDOC ## Defining Resolvers ### fileMarkdown The primary resolver for markdown content with YAML frontmatter. ```typescript import { fileMarkdown } from '@piqit/resolvers' import { z } from 'zod' const posts = fileMarkdown({ // Base directory for finding files base: 'content/posts', // Path pattern with {param} placeholders path: '{year}/{slug}.md', // Schema for frontmatter (any StandardSchema-compatible library) frontmatter: z.object({ title: z.string(), status: z.enum(['draft', 'published']), tags: z.array(z.string()).default([]), }), // Body parsing options body: { raw: true, // Include raw markdown html: true, // Include HTML conversion headings: true // Extract headings with slugs } }) ``` ### Path Patterns Path patterns use `{param}` syntax to define URL-style segments: ```typescript '{year}/{slug}.md' // Matches: 2024/hello-world.md '{category}/{year}/{slug}.md' // Matches: tech/2024/my-post.md 'TASK-{num}-{title}.md' // Matches: TASK-123-title-which-may-include-hyphens.md ``` Parameters extracted from the path are available as `params.*` in select. When multiple params share a segment, matching is left-to-right, so trailing params can include additional separators (like `-`). ### StandardSchema Frontmatter schemas use [StandardSchema](https://github.com/standard-schema/standard-schema), compatible with: - Zod - Valibot - ArkType - Any library implementing the standard ### Body Options Control what gets parsed from the markdown body: ```typescript body: { raw: true, // string - original markdown html: true, // string - converted to HTML headings: true // Heading[] - extracted heading structure } ``` The `Heading` type: ```typescript interface Heading { depth: number // 1-6 text: string // Heading text slug: string // URL-safe slug } ``` ``` -------------------------------- ### Scan by Path Parameters with piq Source: https://context7.com/djgrant/piq/llms.txt The `scan()` method filters items by pattern-based path parameters. This is a cheap operation as it works at the collection level using glob patterns without reading file contents. Parameters extracted from the path pattern are available as `params.*` in select. ```typescript import { piq } from 'piqit' import { posts } from './resolvers' // Scan by year only - uses glob pattern '2024/*.md' const postsFrom2024 = await piq.from(posts) .scan({ year: '2024' }) .select('params.slug', 'params.year') .exec() // [{ slug: 'hello-world', year: '2024' }, { slug: 'my-post', year: '2024' }] // Scan by multiple params - finds specific file const specificPost = await piq.from(posts) .scan({ year: '2024', slug: 'hello-world' }) .select('params.slug', 'frontmatter.title') .exec() // [{ slug: 'hello-world', title: 'Hello World' }] // Empty scan returns all files const allPosts = await piq.from(posts) .scan({}) .select('params.slug') .exec() // Returns all posts across all years ``` -------------------------------- ### select() Method: Declaring Fields for Results Source: https://github.com/djgrant/piq/blob/main/README.md Demonstrates the select() method for specifying which fields to include in the query results. It uses dotted paths and produces flat results. ```typescript .select('params.slug', 'frontmatter.title', 'body.html') // Result: { slug: 'hello-world', title: 'Hello World', html: '

...' } ``` -------------------------------- ### Select All Fields with Wildcards Source: https://github.com/djgrant/piq/blob/main/README.md Use the wildcard '*' to select all fields from a specific namespace. This is convenient for retrieving all parameters or frontmatter data at once. ```typescript .select('params.*') // Result: { slug: string; year: string } (all params fields) .select('params.*', 'frontmatter.*') // Result: { slug, year, title, status, tags, ... } ``` -------------------------------- ### Query with Namespace Wildcards Source: https://github.com/djgrant/piq/blob/main/docs/guide/recipes.md Retrieves all fields from 'params' and 'frontmatter' namespaces for posts in a specific year. Prefer explicit fields for stable production contracts. ```typescript const rows = await piq .from(postsResolver) .scan({ year: '2024' }) .select('params.*', 'frontmatter.*') .exec() ``` -------------------------------- ### Utility APIs Source: https://github.com/djgrant/piq/blob/main/docs/packages/resolvers.md Helper functions for various tasks including path pattern compilation, frontmatter parsing, and markdown processing. ```APIDOC ## Utility APIs ### Path Patterns - `compilePattern(pattern)`: Compiles a path pattern string. - `createParamsSchema(pattern)`: Creates a Zod schema for path parameters. ### Frontmatter - `parseFrontmatter(content)`: Parses frontmatter from a string. - `readFrontmatter(filePath)`: Reads and parses frontmatter from a file. - `readFrontmatterWithOffset(filePath, offset)`: Reads and parses frontmatter with a specified offset. ### Markdown Processing - `parseMarkdownBody(markdown)`: Parses markdown content. - `extractHeadings(markdown)`: Extracts headings from markdown. - `slugify(text)`: Converts text to a URL-friendly slug. - `markdownToHtml(markdown)`: Converts markdown to HTML. ``` -------------------------------- ### Configure Static Content Resolver Source: https://github.com/djgrant/piq/blob/main/docs/reference/resolvers.md Create a static-data resolver for edge runtimes using an array of data. This resolver filters parameters and frontmatter by equality and returns only selected namespaces. ```typescript import { staticContent } from '@piqit/resolvers/edge' const postsResolver = staticContent(postsArray) ``` -------------------------------- ### Create Filesystem Markdown Resolver with fileMarkdown() Source: https://context7.com/djgrant/piq/llms.txt The fileMarkdown() function generates a resolver for querying markdown files from the filesystem. It supports path patterns with placeholders, frontmatter validation using StandardSchema-compatible libraries like Zod, and configurable body parsing options for raw markdown, HTML, and heading extraction. ```typescript import { fileMarkdown } from '@piqit/resolvers' import { z } from 'zod' // Create resolver with all options const posts = fileMarkdown({ // Base directory for finding files (absolute or relative to cwd) base: 'content/posts', // Path pattern with {param} placeholders - params available as params.* path: '{year}/{slug}.md', // Frontmatter schema using any StandardSchema-compatible library frontmatter: z.object({ title: z.string(), status: z.enum(['draft', 'published']), tags: z.array(z.string()).default([]), date: z.string(), }), // Body parsing options body: { raw: true, // Include raw markdown string html: true, // Include HTML conversion headings: true // Extract headings array with depth, text, slug } }) // Path pattern examples: // '{year}/{slug}.md' -> matches 2024/hello-world.md // '{category}/{year}/{slug}.md' -> matches tech/2024/my-post.md // 'TASK-{num}-{title}.md' -> matches TASK-123-build-feature.md // Query the resolver const results = await piq.from(posts) .scan({ year: '2024' }) .select('params.slug', 'frontmatter.title', 'body.headings') .exec() // [{ slug: 'hello-world', title: '...', headings: [{ depth: 1, text: '...', slug: '...' }] }] ``` -------------------------------- ### Create staticContent resolver Source: https://github.com/djgrant/piq/blob/main/docs/packages/resolvers.md Instantiate a staticContent resolver with precompiled data. This is intended for edge or runtime-safe execution without filesystem dependencies. ```typescript const postsResolver = staticContent(precompiledPosts) ``` -------------------------------- ### Execute Query with exec() Source: https://github.com/djgrant/piq/blob/main/README.md Use exec() to retrieve all results from a query as an array. This is suitable for smaller result sets. ```typescript const results = await piq.from(posts) .scan({ year: '2024' }) .select('params.slug') .exec() // results: Array<{ slug: string }> ``` -------------------------------- ### Query Detail Page Post Source: https://github.com/djgrant/piq/blob/main/docs/guide/recipes.md Retrieves a single post by year and slug, selecting its title, HTML body, and headings. Throws an error if the post is not found. ```typescript const post = await piq .from(postsResolver) .scan({ year: '2024', slug: 'hello-world' }) .select('frontmatter.title', 'body.html', 'body.headings') .single() .execOrThrow() ``` -------------------------------- ### Define a fileMarkdown resolver Source: https://github.com/djgrant/piq/blob/main/docs/guide/index.md Configure a resolver to read markdown files from a specified directory, extracting frontmatter and HTML body content. Ensure Zod is used for schema validation. ```typescript import { fileMarkdown } from '@piqit/resolvers' import { z } from 'zod' export const posts = fileMarkdown({ base: 'content/posts', path: '{year}/{slug}.md', frontmatter: z.object({ title: z.string(), status: z.enum(['draft', 'published']), tags: z.array(z.string()).default([]), }), body: { html: true, headings: true }, }) ``` -------------------------------- ### The Select API Source: https://github.com/djgrant/piq/blob/main/README.md Defines how to select fields from query results using dotted-string paths. ```APIDOC ## The Select API Select uses dotted-string paths that map to the resolver's namespaced output. Results are **flat**—the last segment of each path becomes the property name. ### Namespaces For the `fileMarkdown` resolver: - `params` — extracted from path patterns (free, from scan) - `frontmatter` — YAML metadata (light I/O) - `body` — parsed content (heavy I/O) ### Variadic Strings The simplest form—list the fields you want: ```typescript .select('params.slug', 'params.year', 'frontmatter.title', 'body.html') // Result type inferred as: // { slug: string; year: string; title: string; html: string } ``` The field name is the last segment of each path. ### Object Form (Aliasing) When you need custom property names or have naming collisions: ```typescript .select({ postSlug: 'params.slug', postTitle: 'frontmatter.title', content: 'body.html' }) // Result: { postSlug: string; postTitle: string; content: string } ``` ### Wildcards Select all fields from a namespace: ```typescript .select('params.*') // Result: { slug: string; year: string } (all params fields) .select('params.*', 'frontmatter.*') // Result: { slug, year, title, status, tags, ... } ``` ### Collision Detection If two paths have the same final segment, TypeScript reports a compile-time error: ```typescript // ERROR: 'title' appears in both paths .select('params.title', 'frontmatter.title') // FIX: use object form to alias .select({ paramTitle: 'params.title', frontmatterTitle: 'frontmatter.title' }) ``` This prevents runtime surprises where one field overwrites another. ``` -------------------------------- ### Execute Query with exec() Source: https://github.com/djgrant/piq/blob/main/docs/reference/api.md Run the query and retrieve all rows using the 'exec()' method. This method throws an error if 'select()' has not been called. ```typescript const rows = await piq.from(postsResolver) .scan({ year: '2024' }) .select('params.slug') .exec() ``` -------------------------------- ### fileMarkdown Resolver Source: https://github.com/djgrant/piq/blob/main/docs/reference/resolvers.md Filesystem resolver for markdown documents with YAML frontmatter. It allows fetching and parsing markdown files based on a specified path structure and frontmatter schema. ```APIDOC ## fileMarkdown(options) ### Description Filesystem resolver for markdown documents with YAML frontmatter. ### Method `fileMarkdown` (Function) ### Parameters #### Options - **base** (string) - The base directory for resolving files. - **path** (string) - The path pattern for files, supporting `{param}` placeholders. Example: `{year}/{slug}.md`. - **frontmatter** (StandardSchema) - Zod schema for validating YAML frontmatter. - **body** (object) - Optional configuration for body parsing. - **raw** (boolean) - Whether to include the raw body content. - **html** (boolean) - Whether to parse the body into HTML. - **headings** (boolean) - Whether to extract headings from the body. ### Query Behavior - `scan` maps to path parameters derived from the `path` pattern. - `filter` checks frontmatter values by strict equality. - Resolvers read only the data needed for selected namespaces. - `params.*` does not require file content reads. - `frontmatter.*` reads and parses frontmatter. - `body.*` parses the body according to requested fields. - For multi-param segments, matching is left-to-right, allowing trailing params to include additional separators. ### Runtime This resolver uses Bun APIs (`Bun.Glob`, `Bun.file`) and is intended for Bun environments. ### Request Example ```typescript import { fileMarkdown } from '@piqit/resolvers' import { z } from 'zod' const posts = fileMarkdown({ base: 'content/posts', path: '{year}/{slug}.md', frontmatter: z.object({ title: z.string(), status: z.enum(['draft', 'published']), tags: z.array(z.string()), }), body: { raw: true, html: true, headings: true }, }) ``` ``` -------------------------------- ### Type Utilities for Path Validation Source: https://context7.com/djgrant/piq/llms.txt Leverage piqit's type utilities for compile-time validation of select paths, collision detection, and result type inference. These enhance TypeScript integration. ```typescript import type { SelectablePaths, GetFieldName, GetPathValue, ExpandWildcard, HasCollision, Undot, UndotWithAliases, Resolver, QuerySpec, StandardSchema, Infer } from 'piqit' // SelectablePaths extracts valid dot-paths from a result type type Result = { params: { slug: string; year: string }; frontmatter: { title: string } } type Paths = SelectablePaths // 'params.slug' | 'params.year' | 'frontmatter.title' | 'params.*' | 'frontmatter.*' // HasCollision detects when two paths would produce the same key type Collision = HasCollision<['params.title', 'frontmatter.title']> // true type NoCollision = HasCollision<['params.slug', 'frontmatter.title']> // false // Undot transforms selected paths to flat result type type Selected = Undot // { slug: string; title: string } // UndotWithAliases for object-form select type Aliased = UndotWithAliases // { mySlug: string; myTitle: string } // Infer extracts the type from a StandardSchema type MySchema = StandardSchema<{ title: string }> type Extracted = Infer // { title: string } ``` -------------------------------- ### Astro Prerendering Configuration Source: https://github.com/djgrant/piq/blob/main/DEPLOY.md In Astro, setting `export const prerender = true` for a page ensures it is built as static HTML. For API routes, `export const prerender = false` should be used to enable SSR. ```javascript export const prerender = true ``` ```javascript export const prerender = false ``` -------------------------------- ### Query Execution Methods Source: https://github.com/djgrant/piq/blob/main/README.md Methods for executing queries and retrieving results. ```APIDOC ## exec() ### Description Executes the query and returns all results as an array. ### Method Implicit (used after .select() or other query builders) ### Endpoint N/A (Client-side library method) ### Request Body N/A ### Request Example ```typescript const results = await piq.from(posts) .scan({ year: '2024' }) .select('params.slug') .exec() // results: Array<{ slug: string }> ``` ### Response #### Success Response (200) - **results** (Array) - An array of objects, where each object contains the selected fields. #### Response Example ```json [ { "slug": "post-1" }, { "slug": "post-2" } ] ``` ## single() ### Description Returns a builder for single-result queries. Can be used to fetch the first result or throw an error if no results are found. ### Method Implicit (used after .select() or other query builders) ### Endpoint N/A (Client-side library method) ### Request Body N/A ### Request Example ```typescript // Returns first result or undefined const post = await piq.from(posts) .scan({ year: '2024', slug: 'hello-world' }) .select('params.slug', 'frontmatter.title') .single() .exec() // Throws if no results const post = await piq.from(posts) .scan({ year: '2024', slug: 'hello-world' }) .select('params.slug') .single() .execOrThrow() ``` ### Response #### Success Response (200) - **post** (Object | undefined) - An object containing the selected fields for a single result, or undefined if no result is found (when using `.exec()`). #### Response Example ```json { "slug": "hello-world", "title": "Hello World" } ``` ## stream() ### Description For large result sets, stream results instead of loading all into memory. ### Method Implicit (used after .select() or other query builders) ### Endpoint N/A (Client-side library method) ### Request Body N/A ### Request Example ```typescript for await (const post of piq.from(posts).scan({}).select('params.slug').stream()) { console.log(post.slug) } ``` ### Response #### Success Response (200) - **post** (Object) - An object representing a single result from the stream. #### Response Example ```json { "slug": "post-1" } ``` ``` -------------------------------- ### Utility Exports Source: https://github.com/djgrant/piq/blob/main/docs/reference/resolvers.md Provides various utility functions for path pattern manipulation, frontmatter processing, and markdown parsing. ```APIDOC ## Utility Exports `@piqit/resolvers` also exports utility helpers: - **Path pattern**: `compilePattern`, `createParamsSchema` - **Frontmatter**: `parseFrontmatter`, `readFrontmatter`, `readFrontmatterWithOffset` - **Markdown**: `parseMarkdownBody`, `extractHeadings`, `slugify`, `markdownToHtml ``` -------------------------------- ### Query Published Posts by Year Source: https://github.com/djgrant/piq/blob/main/docs/guide/recipes.md Fetches published posts for a specific year, selecting only the slug, title, and tags. ```typescript const posts = await piq .from(postsResolver) .scan({ year: '2024' }) .filter({ status: 'published' }) .select('params.slug', 'frontmatter.title', 'frontmatter.tags') .exec() ``` -------------------------------- ### Compile and Use Path Patterns Source: https://context7.com/djgrant/piq/llms.txt Use compilePattern to create objects for generating glob patterns, matching paths, and building paths. Supports inline regex constraints for parameters. ```typescript import { compilePattern, createParamsSchema } from '@piqit/resolvers' // Compile a path pattern const pattern = compilePattern('{year}/{slug}.md') // Pattern properties pattern.pattern // '{year}/{slug}.md' pattern.paramNames // ['year', 'slug'] // Generate glob patterns for file scanning pattern.toGlob() // '*/*.md' pattern.toGlob({ year: '2024' }) // '2024/*.md' pattern.toGlob({ year: '2024', slug: 'hello' }) // '2024/hello.md' // Match paths and extract parameters pattern.match('2024/hello-world.md') // { year: '2024', slug: 'hello-world' } pattern.match('2023/old-post.md') // { year: '2023', slug: 'old-post' } pattern.match('invalid') // null // Build paths from parameters pattern.build({ year: '2024', slug: 'new-post' }) // '2024/new-post.md' // Pattern with inline constraints (regex) const taskPattern = compilePattern('TASK-{num:\d+}-{title}.md') taskPattern.match('TASK-123-build-feature.md') // { num: '123', title: 'build-feature' } // Create StandardSchema for params validation const paramsSchema = createParamsSchema(pattern) // Used internally by resolvers for type-safe scan parameters ``` -------------------------------- ### Execute Queries with exec(), single(), and stream() Source: https://context7.com/djgrant/piq/llms.txt Use exec() to retrieve all query results as an array. For single results, use single().exec() which returns the first result or undefined, or single().execOrThrow() which throws an error if no results are found. stream() provides an async generator to iterate over results one by one. ```typescript import { piq } from 'piqit' import { posts } from './resolvers' // exec() returns all results as array const allResults = await piq.from(posts) .scan({ year: '2024' }) .select('params.slug') .exec() // Array<{ slug: string }> ``` ```typescript // single().exec() returns first result or undefined const maybePost = await piq.from(posts) .scan({ year: '2024', slug: 'hello-world' }) .select('params.slug', 'frontmatter.title') .single() .exec() // { slug: 'hello-world', title: 'Hello World' } | undefined ``` ```typescript // single().execOrThrow() throws if no results try { const post = await piq.from(posts) .scan({ year: '9999', slug: 'nonexistent' }) .select('params.slug') .single() .execOrThrow() } catch (error) { // Error: "Query returned no results" } ``` ```typescript // stream() yields results one at a time for await (const post of piq.from(posts).scan({}).select('params.slug').stream()) { console.log(post.slug) } ``` -------------------------------- ### Select Fields with select(...paths) Source: https://github.com/djgrant/piq/blob/main/docs/reference/api.md Specify output fields using dotted path strings with the 'select(...paths)' method. This returns a new QueryBuilder. ```typescript .select('params.slug', 'frontmatter.title', 'body.html') ```