npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@postxl/generator

v1.3.6

Published

Core package that orchestrates the code generation of a PXL project

Readme

PXL Generator

The core package that orchestrates the code generation of a PXL project

Overview & context

Each PXL project is initially generated using two components:

  • The ProjectSchema defines the overall structure of the project, including the models, enums, generators, etc.
  • The generate program that uses the ProjectSchema to generate the code for the project.

The ProjectSchema is defined and explained in the Schema package.

This package provides the tooling for the generate program.

Composable generators

The generate program is composed of multiple generators.

Here is a simplified example program:

import { Generator } from '@postxl/generator'
import { generateTypes } from '@postxl/generators/types'
import { registerApiContext, generateApi}* from '@postxl/generators/nestjs-backend'
import { generateRepositories } from '@postxl/generators/prisma-repositories'

import { zProjectSchema } from '@postxl/schema'

import { projectSchemaJSON } from './project-schema.json'

async function generateProject() {

  const projectSchema = zProjectSchema.parse(projectSchemaJSON)

  const generator = new Generator(projectSchema)
  await generator
    // some global generators (API, E2E, CmdK, ?) register some "collectors" in the context,
    // so other generator can store data in these collectors.
    // Upon generation, these global generators use this data to generator the code.
    // Example: E2E Selector collector: frontend generators provide selectors that will be
    //           used by the E2E generators to generate the selectors object
    .register(registerApiContext)

    // these generators will create the relevant files in the virtual file system and provide more context
    // to subsequent generators
    .generate(generateTypes)
    .generate(generateRepositories)

    // this will generate the API code using the registered collector
    .generate(generateApi)

    // prettify files, add disclaimer, calculate checksums and write files to disk
    // that have not been ejected
    .flush()
}

Running the generator and updating the result on disk

Running the generator will create the code of the project, given the ProjectSchema and the generator configs.

From there, two things will happen over time:

  • The models (or generator configs) will change,
  • The (initially generated) code will be manually changed (formerly known as "ejected")

In case the file was not manually changed but the model was changed, the generator will automatically update the file on disk. In case the file was manually changed and the model was not changed, nothing will happen. Only in case the file was manually changed and the model was changed, we will need to decide how to handle the conflict. For this, the generator will update the existing file in a way that is compatible with a git merge conflict. This way, the developer must decide how to resolve the conflict.

Under the hood, the above logic is implemented leveraging the "postxl-lock.json" file. This file contains the hash values for each generated file. With this hash value, we can determine:

  • If the file was manually changed: in this case the hash of the current file will be different from the hash in the lock file
  • If the file was changed by the latest generator run: in this case the hash of the newly generated file will be different from the hash in the lock file

File Sync Algorithm

The generator uses a 3-way sync algorithm to intelligently handle file changes. The three sources are:

  1. Virtual File System (VFS) - The newly generated content
  2. Lock File - Hash values from the previous generation run (postxl-lock.json)
  3. Disk - The actual files on the filesystem

State Matrix

| Generated | Lock File | Disk | Action | Description | | --------- | --------- | -------- | ------------------ | -------------------------------------------------------- | | ✓ Changed | Same | Modified | Merge Conflict | File was ejected AND generator template changed | | ✓ Changed | Same | Same | Write | Template changed, file not ejected → auto-update | | ✓ Same | Same | Modified | No Action | File ejected, but template unchanged → keep your changes | | ✓ Same | Same | Same | No Action | Nothing changed | | ✓ New | - | Exists | Merge Conflict | New generated file conflicts with existing file | | ✓ New | - | - | Write | Brand new file | | - Removed | Exists | Modified | Delete | Generator no longer produces this file |

Ejected Files

A file is considered "ejected" when you manually modify it. Once ejected:

  • The generator will not overwrite your changes automatically
  • If the generator template changes, you'll get a merge conflict to resolve
  • The file remains tracked in postxl-lock.json so the generator knows it exists

Merge Conflicts

When both you and the generator have made changes to the same file, the generator creates Git-style merge conflict markers:

// Unchanged code stays clean
import { Injectable } from '@nestjs/common'

@Injectable()
export class UserService {
<<<<<<< Manual
  // Your manual changes appear here
  findAll() {
    return this.customLogic()
  }
=======
  // Generated version appears here
  findAll() {
    return this.repository.findAll()
  }
>>>>>>> Generated
}

Resolving Merge Conflicts

  1. Open the file in your editor
  2. Decide which version to keep (or combine both)
  3. Remove the conflict markers (<<<<<<<, =======, >>>>>>>)
  4. Run the generator again to verify

Note: The generator will refuse to run if there are unresolved merge conflicts in your project (unless --force flag is set). Resolve all conflicts before regenerating.

Force Regeneration

If you want to discard your changes and reset to the generated version:

# Force regenerate a specific file
pnpm run generate -f -p 'backend/libs/types/**/*.ts'

# Force regenerate everything (careful!)
pnpm run generate -f

Custom Block Preservation

When you extend generated files with custom code, you can mark your additions with special comment markers. This prevents unnecessary merge conflicts when the generator updates other parts of the file.

Basic Usage

import { Injectable } from '@nestjs/common'

// @custom-start:imports
import { CustomLogger } from './logger'
import { MetricsService } from './metrics'
// @custom-end:imports

@Injectable()
export class UserService {
  constructor(
    private readonly repository: UserRepository,
    // @custom-start:dependencies
    private readonly logger: CustomLogger,
    private readonly metrics: MetricsService,
    // @custom-end:dependencies
  ) {}

  // Generated methods...
  findAll() {
    return this.repository.findAll()
  }

  // @custom-start:customMethods
  async findAllWithMetrics() {
    this.metrics.increment('user.findAll')
    return this.findAll()
  }

  async customBusinessLogic() {
    this.logger.log('Custom logic executed')
    return 'custom result'
  }
  // @custom-end:customMethods
}

How It Works

When the generator runs and detects custom block markers in an ejected file:

  1. Extract: Custom blocks are identified and extracted from your modified file
  2. Compare: The remaining code (minus custom blocks) is compared to the new generated output
  3. Reinsert: Custom blocks are automatically inserted back into the generated output at the same relative position
  4. Conflict only if needed: Only actual changes outside your custom blocks will show merge conflict markers

Marker Syntax

// Line comment style (recommended)
// @custom-start:blockName
// ... your custom code ...
// @custom-end:blockName

// Unnamed blocks (works, but names help with clarity)
// @custom-start
// ... your custom code ...
// @custom-end

// Block comment style (for languages that prefer it)
/* @custom-start:blockName */
/* ... your custom code ... */
/* @custom-end:blockName */

Block Names

Names are optional but recommended when you have multiple custom blocks:

  • Must be alphanumeric with hyphens/underscores: [a-zA-Z0-9_-]+
  • Help identify blocks in warnings
  • Opening and closing names should match

Anchor-Based Positioning

Custom blocks are repositioned based on anchor context - the significant code lines immediately before and after your block. For best results:

  • Place custom blocks after stable, identifiable lines (method signatures, class declarations, import statements)
  • Avoid placing blocks in areas that frequently change
  • The more unique the surrounding context, the more reliable the repositioning

When Blocks Cannot Be Placed

If the generator cannot find a suitable position for a custom block (e.g., the surrounding code changed significantly), it will:

  1. Append the block at the end of the file
  2. Add a warning comment so you know to move it manually
// ... rest of file ...

// ⚠️ WARNING: The following custom blocks could not be automatically placed.
// Please manually move them to the appropriate location.

// --- Unplaced custom block: orphanedFeature ---
// @custom-start:orphanedFeature
// This code needs to be moved manually
// @custom-end:orphanedFeature

Best Practices

  1. Use descriptive names: // @custom-start:authMiddleware is better than // @custom-start
  2. Keep blocks focused: One feature per block makes them easier to manage
  3. Place strategically: Put blocks after stable anchor points
  4. Don't nest blocks: Nested custom blocks are not supported
  5. Match names: Ensure @custom-start:foo has a matching @custom-end:foo

Example: Adding Custom Routes

// Generated router file
import { Router } from 'express'
import { getUsers, getUserById, createUser } from './handlers'

const router = Router()

// Generated routes
router.get('/users', getUsers)
router.get('/users/:id', getUserById)
router.post('/users', createUser)

// @custom-start:customRoutes
// Custom export endpoint
router.get('/users/export', async (req, res) => {
  const users = await exportUsersToCSV()
  res.attachment('users.csv').send(users)
})

// Custom bulk operations
router.post('/users/bulk', bulkCreateUsers)
router.delete('/users/bulk', bulkDeleteUsers)
// @custom-end:customRoutes

export default router

When the generator adds new routes, your custom routes will be preserved without conflict markers (assuming the anchor context—the generated routes above—remains recognizable).

CLI Options

# Standard generation
pnpm run generate

# Force regenerate all files (overwrites ejected files)
pnpm run generate -f

# Force regenerate specific files (glob pattern)
pnpm run generate -f -p 'backend/libs/types/**/*.ts'

# Show ejected files after generation
pnpm run generate -e

# Show diff between ejected and generated versions
pnpm run generate -d

# Watch mode - regenerate on schema changes
pnpm run generate:watch

# Skip linting and formatting
pnpm run generate -t

Troubleshooting

"Unresolved merge conflicts detected"

The generator found files with conflict markers (<<<<<<<, =======, >>>>>>>). Resolve these manually before running the generator again.

Custom blocks appearing at end of file

The generator couldn't find the anchor context for your block. This happens when:

  • The code before/after your block changed significantly
  • The block was placed in a frequently-changing area

Solution: Move the block back to its correct position and ensure it has stable anchor lines nearby.

Unexpected merge conflicts in custom block areas

If you're seeing conflicts around custom blocks, check:

  • Block markers are properly formatted (@custom-start/@custom-end)
  • Names match between start and end markers
  • No nested custom blocks

Lock file out of sync

If postxl-lock.json gets out of sync with your files:

# Regenerate everything (preserves ejected files unless they conflict)
pnpm run generate

# Or force regenerate to reset lock file
pnpm run generate -f