Point0by 1gr14
  • Меню
    • GitHub
    • Блог
  • Introduction
    • Overview
    • Getting Started
    • Full Overview
    • Benchmarks
    • Points
  • Points
    • Page
    • Layout
    • Component
    • Provider
    • Mountable
    • Query
    • Infinite Query
    • Mutation
    • Action
    • Root
    • Base
    • Plugin
  • Methods
    • Validation
    • Loader
    • Context
    • Middleware
    • Loading & error
    • .with
    • Mapper
    • Transformer
    • Stage Methods
  • Core
    • Navigation
    • SSR
    • RSC
    • Request
    • Response
    • Error handling
    • Env
    • Head
    • MDX
    • Assets
    • File upload
    • OpenAPI
    • Query client
    • Events
    • Infer
  • Engine
    • Engine Config
    • Engine Runtime
    • CLI
    • Dev
    • Build
    • Compiler
    • Generator
    • Project MCP
    • Docs MCP
    • Importer
    • Public dir
    • Testing
    • Deploy
    • Bun or Vite
  • Extra
    • SsrStore
    • CookieStore
    • Basic Auth
    • CORS
    • Cache-Control
    • Compress
  • Examples
    • Basic
    • Vite
    • Better Auth
    • Capacitor
    • Expo
  • 1gr14
    • Главная
    • Start0
    • Поддержать
    • Обучение
    • Группа
    • Блог
    • Автор
  • Сообщество
    • Discord
    • Telegram
  • Опенсорс
    • Point0
    • Route0
    • Error0
    • Flat
  • Аккаунт
    • Войти
    • Регистрация
Point0by 1gr14
Создаю опенсорс во славу Господа Иисуса Христа ☦️
С любовью ко всем разработчикам на свете ❤️
Условия использованияПолитика конфиденциальностиСергей Дмитриев 2026 😎

Generator

  • Раздел: Engine

On this page

  1. The generated files
  2. points.server.ts — server points
  3. points.client.ts — client points
  4. routes.ts — the typed route table
  5. meta.ts — point metadata for tooling
  6. assets.d.ts — ambient asset typings
  7. When it regenerates
  8. Gitignored output
  9. Reference
  10. CLI
  11. Where each output is configured
  12. Per-output options
  13. Custom outputs
  14. Importing points with emitPointsImports

The generator scans your source for points, then writes a small set of files the app imports at boot: a server points aggregator, a client one, a typed route table, a metadata file, and ambient asset typings. You don't hand-write any of them — you point each output at a path in the engine config, and point0 generate fills it in.

// engine.ts — every generated file is opt-in
export const engine = Engine.create({
  file: import.meta.url,
  pointsGlob: '**/*.{ts,tsx,mdx}',
  generate: {
    meta: './generated/point0/meta.ts',
    assetsTypes: './generated/point0/assets.d.ts',
  },
  server: {
    points: async () => await import('./generated/point0/points.server'),
    generate: { points: './generated/point0/points.server.ts' },
  },
  client: {
    points: async () => await import('./generated/point0/points.client'),
    generate: {
      points: './generated/point0/points.client.ts',
      routes: {
        outfile: './generated/point0/routes.ts',
        origin: 'process.env.CLIENT_URL',
      },
    },
  },
})

Run it with the CLI:

point0 generate       # one-shot: scan points, write the files
point0 generate -w    # ...and keep watching for changes

dev and build run it for you — point0 dev generates once at startup and watches; point0 build always generates first.

The generated files

The generator writes a separate file per output, and only the outputs you name in generate — there are no implicit defaults. The example apps put them all under src/generated/point0/, but the path is yours.

points.server.ts — server points

The server's aggregator: a flat array of every server-side point, static-imported so it all lands in the server bundle.

// generated points.server.ts
import type { PointsDefinition } from '@point0/core'
import { root as root_0, page as page_1, layout as layout_2 } from './file0.js'
export default [root_0, page_1, layout_2] as PointsDefinition<
  (typeof root_0)['Infer']['RequiredCtx'],
  (typeof root_0)['Infer']['Error']
>

A point lands here when it's the root of the scope or has an HTTP endpoint — a query, mutation, or action, or any other point (page, layout, component, provider) that has a .loader. The app feeds it back to the engine:

// engine.ts → server
points: async () => await import('./generated/point0/points.server'),
generate: { points: './generated/point0/points.server.ts' },

SSR changes the contents. With ssr: true every page comes to the server (it gets server-rendered). With ssr: false only pages that have a .loader come — they have an endpoint to fetch; loader-less pages are client-only and stay out.

points.client.ts — client points

The client's aggregator: pages, layouts, and the root for the SPA. By default each point is a lazy dynamic import, so it becomes its own chunk loaded on navigation:

// generated points.client.ts (lazy — the default)
import type { PointsDefinition } from '@point0/core'
import { root as root_0 } from './file0.js'
export default [
  root_0,
  {
    type: 'page',
    name: 'mypage',
    route: '/news',
    polh: false, // prefetch-on-link-hover (from .prefetchPageOnLinkHover)
    point: async () => (await import('./file0.js')).page,
  },
  {
    type: 'layout',
    name: 'mylayout',
    route: '/layout',
    point: async () => (await import('./file0.js')).layout,
  },
] as PointsDefinition<
  (typeof root_0)['Infer']['RequiredCtx'],
  (typeof root_0)['Infer']['Error']
>

Each page record carries its route, its polh flag (the hover-prefetch setting, boolean or a debounce in ms), and any layouts it sits under. The root is always imported statically — it has to be present from the first render.

Set lazy: false and the client file becomes the same static-import shape as the server one — every point in a single bundle, no per-page chunks:

// engine.ts → client
generate: { points: { outfile: './generated/point0/points.client.ts', lazy: false } },

There is no per-page .lazy() method; lazy vs static is a whole-file switch in the codegen config. See the page authoring notes for the trade-off.

routes.ts — the typed route table

Every page route, collected into a Route0 table you import for <Link>s and navigate:

// generated routes.ts
import { Routes } from '@1gr14/route0'

export const routes = Routes.create({
  mypage: '/news',
})
import { routes } from '@/generated/point0/routes'

Only pages with a route are included. A page's .basePath prefix is already folded into its route string. Set an origin and it rides along on the table — a raw expression when it starts with process.env. / import.meta.env. / a backtick, otherwise a quoted string:

generate: { routes: { outfile: './generated/point0/routes.ts', origin: 'process.env.CLIENT_URL' } }
// => Routes.create({ mypage: '/news' }, { origin: process.env.CLIENT_URL })

Route keys are quoted only when they aren't a valid JS identifier (mypage: bare, 'my-page': quoted) — matching Prettier's quoteProps: 'as-needed', so diffs stay clean.

NOTE — routes are emitted as bare path strings, untyped. Typed-search routes are intentionally disabled to avoid a routes.ts → page → Link → routes.ts type cycle, so a page that declares .search() still emits a plain string here.

meta.ts — point metadata for tooling

A full description of every point — including invalid ones — for tools that need to reason about your app without importing it.

// generated meta.ts (engine block)
export default {
  engine: {
    file: '<file>',
    import: async () =>
      (await Engine.findAndImportSelf({ engineFile: '<file>' })).engine,
    server: { scope: 'myroot' },
    clients: [{ scope: 'myroot' }],
  },
  points: [
    {
      scope: 'myroot',
      type: 'page',
      name: 'mypage',
      id: 'myroot:page:mypage',
      tags: ['ideas'],
      description: `...`,
      route: undefined, // or Route0.create(...) when the point has a route
      endpoint: undefined, // or { method, route } for query/mutation/action
      pos: { file: '<file>', line: 5, column: 20 }, // source position
      import: async () => (await import('./file0.js')).page,
      valid: true,
      errors: [],
      ssr: false,
      parents: [],
      layouts: [],
    },
    // ...one entry per point, invalid and plugin points included
  ],
}

The project MCP bin consumes it:

point0-project-mcp --meta ./src/generated/point0/meta.ts

The MCP re-reads the file on every call, so it never serves stale points after a point0 generate.

assets.d.ts — ambient asset typings

Ambient declarations that type imported static assets, so import logo from './logo.png' and its ?url / ?file / ?text / ?raw / ?react forms type correctly:

generate: {
  assetsTypes: './generated/point0/assets.d.ts'
}
// generated assets.d.ts (excerpt)
declare module '*.png' {
  const src: string
  export default src
}
declare module '*.png?url' {
  const src: string
  export default src
}
declare module '*.svg?react' {
  import type { FC, SVGProps } from 'react'
  const ReactComponent: FC<SVGProps<SVGSVGElement>>
  export default ReactComponent
}

The extension list and the bare-import type both default from the general compiler.assets config (defaultMode: 'url' out of the box) — one source of truth — and you can override them per output:

generate: { assetsTypes: { outfile: './generated/point0/assets.d.ts', extensions: ['png', 'svg', 'pdf'] } }

Reference the file from your tsconfig types or include, or with a /// <reference path="..." />. The asset pipeline as a whole is on Assets.

When it regenerates

point0 generate       # one-shot
point0 generate -w    # watch and regenerate on change
  • point0 dev generates once at startup, then runs a watcher in parallel. -G / --no-generate skips generation; -W / --no-watch disables watching.
  • point0 build always generates first — there is no "build without generate" (skipping it would leave a stale aggregator on disk).
  • The watcher follows pointsGlob. Add, edit, or delete a point file and it logs add: page.mypage / remove: page.mypage and rewrites only the affected files.

Two things keep the output stable:

  • Idempotent writes. A file is rewritten only when its content actually changes, and points are sorted deterministically — so unrelated edits produce no diff. Writes are atomic (temp file under node_modules/.cache, then renamed).
  • Safe on errors. A point with parse or collection errors is logged but doesn't break generation; the previous good point set is kept, so a broken edit never blows away your aggregators.

Hot reload does not need a regenerate — the dev server resolves points from the engine's source, not from the generated file. Generation matters when a point is added or removed, which is exactly what the watcher catches.

NOTE: the generator's watcher is pointsGlob, separate from the server's devWatchGlob (restart trigger) and buildWatchGlob. Vite/expo exclude meta.ts from devWatchGlob because meta changes on every point edit and would otherwise restart the server needlessly.

Gitignored output

Generated code is gitignored — the example apps ignore src/generated wholesale. So a fresh checkout, worktree, or CI run must generate before typechecking, or imports like ./generated/point0/points.client won't resolve. That's what bun run setup does at the repo level (it runs point0 generate per app); per-app, the generate script is wired to point0 generate.

examples/expo is the exception: it has no client generate and commits its points.server.ts, ignoring only meta.ts and the Prisma client.

NOTE: setup is not a CLI command. bun run setup is repo/app package.json orchestration (Prisma generate + point0 generate); the only generator CLI verb is point0 generate.

Reference

CLI

CommandFlagEffect
point0 generate—scan points, write the configured files once
point0 generate-w, --watchregenerate on every change
point0 generate--engine <path>engine file (absolute or relative to cwd)
point0 dev-G, --no-generateskip generation in dev
point0 dev-W, --no-watchdon't restart/regenerate on change

point0 build always generates; it has no skip flag.

Where each output is configured

generate lives in three places, each carrying different outputs:

OutputConfig locationKey
points.server.tsserver.generatepoints
points.client.tsclient.generatepoints (lazy?)
routes.tsclient.generateroutes (origin?)
meta.tsgenerate (general)meta
assets.d.tsgenerate (general)assetsTypes (extensions?)

Each value is either a string (the outfile path) or an object with outfile plus extra keys. Omit generate and nothing is generated — it defaults to [].

Per-output options

OptionTypeDefaultEffect
points.outfile (server/client)string—aggregator path
points.bannerstring | nullnonetext prepended to the file
points.lazy (client)booleantruelazy per-page chunks vs one static bundle
routes.outfilestring—route table path
routes.originstring | nullnoneroute origin; raw if process.env. / import.meta.env. / backtick prefix
routes.bannerstring | nullnoneprepended text
meta.outfilestring—metadata path
meta.bannerstring | nullnoneprepended text
assetsTypes.outfilestring—.d.ts path
assetsTypes.extensionsstring[]compiler.assets.extensions, else point0's built-in asset extension listmanaged extensions
assetsTypes.bannerstring | nullnoneprepended text

The bare-import type (defaultMode — 'url' | 'file' | 'text' | 'react' | false, where false omits the bare declaration) is not a key on the simple object config; in that form it always comes from compiler.assets ('url' out of the box). To set it per output you need the raw array form: generate: [{ what: 'assetsTypes', outfile, defaultMode: 'file' }].

A page record needs a root in the scope; the points and routes outputs throw Root point not found for scope <scope> without one.

Custom outputs

Beyond the named outputs, generate accepts custom tasks for generating your own files from the point set:

  • { what: 'customFile', handler, outfile } — handler returns the file content and the generator writes it to outfile atomically, just like the built-ins.
  • { what: 'customControlled', handler } — handler writes its own files (no outfile); use it when one task emits several files or a path you compute yourself.

Both handlers receive the same options object and run on every (re)generation:

type Handler = (options: {
  points: CompilerPoint[] // the valid points, after collection
  cwd: string // engine cwd — resolve your output paths against it
  log: LogFn // the generator's logger
  tempDir: string // scratch dir for atomic temp files
  emitPointsImports: (options: {
    points: CompilerPoint[]
    // customControlled also passes the target `outfile` so imports resolve relative to it
  }) => EmitNamedImportsResult // ready-to-write import lines for the given points
}) => /* customFile */ string | Promise<string>
// /* customControlled */ | void | Promise<void>

Each point is a CompilerPoint (exported from @point0/compiler). The fields a handler reads to describe or import a point: id (scope:type:name), scope, type, name, route (or undefined), endpoint ({ method, route } for any point reachable over HTTP — an SSR page always (GET), plus any point with a .loader(): layouts (GET), queries / mutations (POST), infinite-queries, and actions (their declared method); undefined otherwise), tags, description, ssr, layouts, parents, valid (always true here — points holds only valid points), pos ({ file, line, column }), and file (with file.abs, the absolute source path).

// engine.ts → generate
generate: {
  custom: [
    {
      what: 'customFile',
      outfile: './generated/point0/point-ids.ts',
      handler: ({ points }) =>
        `export const pointIds = ${JSON.stringify(points.map((p) => p.id))}`,
    },
  ],
},

Supply them either as the full raw array form (generate: [{ what: 'customFile', … }]) or via a custom array inside the simple object config (generate: { custom: [...] }, and likewise under server.generate / client.generate).

Importing points with emitPointsImports

To emit a file that re-exports or wires up the points, call emitPointsImports instead of writing import lines by hand — it resolves each point's source path relative to your outfile and gives every point a unique local binding. It returns an EmitNamedImportsResult:

FieldTypeWhat it is
importLinesstring[]the import { x as x_0 } from './file.js' statements, one per source file
importedPointsArray<{ point, index, renamedExportName }>each imported point with its unique local binding (renamedExportName) and its position
rootSingleImportLinestring | nulla standalone import line for just the scope root, or null if no root was in points
hasNotRootPointsbooleanwhether points held anything beyond the root

Push importLines into the file, then reference each point through its renamedExportName:

// engine.ts → generate
generate: {
  custom: [
    {
      what: 'customFile',
      outfile: './generated/point0/my-points.ts',
      handler: ({ points, emitPointsImports }) => {
        const { importLines, importedPoints } = emitPointsImports({ points })
        const lines = [...importLines]
        for (const { point, renamedExportName } of importedPoints) {
          lines.push(`export const ${point.name}_${point.type} = ${renamedExportName}`)
        }
        return lines.join('\n') + '\n'
      },
    },
  ],
},

Under customControlled the handler also passes the target outfile to emitPointsImports so import paths resolve relative to the file you write.

НазадCompilerДалееProject MCP

Enjoying Point0?

Give it a star on GitHub, ask questions, and follow what's new

Star Point0

A star on GitHub helps more developers find Point0
Star on GitHub

Start0

Point0 integrated into the Start0 SaaS boilerplate — the best way to use it
Explore Start0

YouTube

Tutorials, walkthroughs, and updates
YouTube

Discord

Questions and chat in English
Join Discord

Telegram

News channel and chat in Russian
ChannelChat

Twitter

Posts and updates
Twitter