create-vitnode-app 2.0.0-canary.3 → 2.0.0-canary.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/copy-of-vitnode-app/api/src/vitnode.api.config.ts +1 -19
  2. package/copy-of-vitnode-app/api-single-app/src/routes/api/$.ts +1 -22
  3. package/copy-of-vitnode-app/api-single-app/src/server/api-bridge.ts +1 -12
  4. package/copy-of-vitnode-app/api-single-app/src/vitnode.api.config.ts +1 -9
  5. package/copy-of-vitnode-app/docker/docker-compose.yml +6 -2
  6. package/copy-of-vitnode-app/monorepo/apps/api/.env.example +1 -1
  7. package/copy-of-vitnode-app/monorepo/apps/web/.env.example +1 -12
  8. package/copy-of-vitnode-app/monorepo/turbo.json +2 -2
  9. package/copy-of-vitnode-app/root/.env.example +1 -8
  10. package/copy-of-vitnode-app/root/global.d.ts +1 -19
  11. package/copy-of-vitnode-app/root/src/components/admin-shell.tsx +1 -44
  12. package/copy-of-vitnode-app/root/src/components/main-header.tsx +1 -30
  13. package/copy-of-vitnode-app/root/src/lib/admin-nav.ts +1 -38
  14. package/copy-of-vitnode-app/root/src/lib/admin-search.ts +1 -26
  15. package/copy-of-vitnode-app/root/src/lib/content-registry.ts +1 -42
  16. package/copy-of-vitnode-app/root/src/lib/i18n/runtime.ts +4 -53
  17. package/copy-of-vitnode-app/root/src/lib/i18n/shared.ts +1 -9
  18. package/copy-of-vitnode-app/root/src/lib/navigation.ts +1 -17
  19. package/copy-of-vitnode-app/root/src/lib/page-head.ts +1 -14
  20. package/copy-of-vitnode-app/root/src/locales/app.ts +1 -25
  21. package/copy-of-vitnode-app/root/src/locales/packages.ts +1 -34
  22. package/copy-of-vitnode-app/root/src/router.tsx +6 -160
  23. package/copy-of-vitnode-app/root/src/routes/__root.tsx +6 -110
  24. package/copy-of-vitnode-app/root/src/routes/_admin/admin.core.index.tsx +2 -51
  25. package/copy-of-vitnode-app/root/src/routes/_admin.tsx +2 -195
  26. package/copy-of-vitnode-app/root/src/routes/_main/index.tsx +0 -30
  27. package/copy-of-vitnode-app/root/src/routes/_main.tsx +1 -43
  28. package/copy-of-vitnode-app/root/src/server/messages.server.ts +0 -13
  29. package/copy-of-vitnode-app/root/src/start.ts +0 -19
  30. package/copy-of-vitnode-app/root/src/styles.css +2 -1
  31. package/copy-of-vitnode-app/root/src/vitnode.config.ts +0 -56
  32. package/copy-of-vitnode-app/root/src/vitnode.server.config.ts +0 -11
  33. package/copy-of-vitnode-app/root/vite.config.ts +0 -84
  34. package/copy-of-vitnode-plugin/root/global.d.ts +6 -6
  35. package/dist/src/plugin/create/route-templates.js +3 -40
  36. package/dist/tsconfig.build.tsbuildinfo +1 -1
  37. package/package.json +2 -2
@@ -12,25 +12,7 @@ export const POSTGRES_URL =
12
12
 
13
13
  export const vitNodeApiConfig = buildApiConfig({
14
14
  plugins: [],
15
- /**
16
- * The languages this installation serves.
17
- *
18
- * The API half of a split deployment. `apps/web/src/vitnode.config.ts`
19
- * declares the same list, and the two are one declaration in two places by
20
- * necessity rather than by design: they are separate packages, so neither can
21
- * import the other's. Nothing walks the filesystem looking for the web app's
22
- * config either - a bootstrap that guessed at a sibling application is exactly
23
- * what that replaced, and it guessed wrong the moment the two were not laid
24
- * out the way it expected.
25
- *
26
- * They have to agree, and this is the copy that matters most: this app owns
27
- * the schema, so `vitnode db:prepare` seeds `core_languages` from *this* list.
28
- * A language that is here and not in the web app's renders nowhere; one that
29
- * is in the web app's and not here has no row in the database.
30
- *
31
- * Packages ship their own translations, so a new locale needs no `messages`
32
- * entry - anything untranslated falls back to `defaultLocale` key by key.
33
- */
15
+
34
16
  i18n: {
35
17
  defaultLocale: "en",
36
18
  locales: [{ code: "en", name: "English" }],
@@ -2,28 +2,7 @@ import { createFileRoute } from '@tanstack/react-router'
2
2
 
3
3
  import { apiBridge } from '#/server/vitnode-api.server'
4
4
 
5
- /**
6
- * `/api/*` - the existing VitNode Hono application, mounted.
7
- *
8
- * `server` is the only option on this route on purpose. TanStack Start prunes a
9
- * route file whose sole option is `server` out of the client route tree
10
- * entirely (and its client code-splitter deletes the `server` node on top of
11
- * that), so none of the API - Hono, Drizzle, the plugins - can reach the browser
12
- * bundle. The `.server.ts` import is refused by import protection if that ever
13
- * stops being true.
14
- *
15
- * `ANY` rather than a handler per method: routing, OpenAPI, middleware, auth,
16
- * plugin mounting and error handling all stay inside Hono, exactly as they are
17
- * when the same app runs standalone in `apps/api` or under the Next.js catch-all
18
- * in `apps/docs`. `ANY` is part of the framework's `RouteMethod` union and is
19
- * what Start falls back to for any method it was given no handler for - `HEAD`
20
- * included, where it calls this handler and strips the response body itself.
21
- * So the API keeps answering for methods this file has never heard of, which is
22
- * the point: there is nothing here to keep in sync with the API's routes.
23
- *
24
- * `src/tests/api-server-route.test.ts` drives the real request handler over this
25
- * route to hold that to every method the API needs.
26
- */
5
+
27
6
  export const Route = createFileRoute('/api/$')({
28
7
  server: {
29
8
  handlers: ({ createHandlers }) =>
@@ -1,15 +1,4 @@
1
- /**
2
- * The seam between the web runtime and the Hono API.
3
- *
4
- * A bridge is handed the `Request` the browser (or an SSR loader) made to
5
- * `/api/*` on this origin and answers it with whatever Hono answers. It is one
6
- * line, and that is the point: status, body, every `Set-Cookie`, the cookie and
7
- * `x-forwarded-*` headers the API reads are all already correct on the request
8
- * the platform built, and stay correct exactly as long as nobody rebuilds them.
9
- *
10
- * `src/tests/api-bridge-contract.ts` holds this to that behaviour, including the
11
- * ways a rebuilt request loses it.
12
- */
1
+
13
2
  export type ApiBridge = (request: Request) => Promise<Response> | Response
14
3
 
15
4
  interface FetchableApp {
@@ -13,15 +13,7 @@ export const vitNodeApiConfig = buildApiConfig({
13
13
  shortTitle: "VitNode",
14
14
  },
15
15
  plugins: [],
16
- /**
17
- * The site's own locale declaration, because this app is both: `vitnode.config.ts`
18
- * is the one statement of which languages exist, and both configs read it.
19
- *
20
- * It is also what `vitnode db:prepare` seeds `core_languages` from - this app
21
- * owns the schema - so adding a language there and re-running `dev` inserts
22
- * its row. Leave it out and the seed falls back to `en` alone, whatever the
23
- * site serves.
24
- */
16
+
25
17
  i18n: vitNodeConfig.i18n,
26
18
  dbProvider: drizzle({
27
19
  connection: POSTGRES_URL,
@@ -12,7 +12,11 @@ services:
12
12
  volumes:
13
13
  - ./docker/dev:/var/lib/postgresql/data
14
14
  ports:
15
- - '5432:5432'
15
+ # Loopback only. These are development containers with a default password
16
+ # of `root`, and `'5432:5432'` publishes them on every interface the host
17
+ # has - which on a laptop is whatever café or office network it is joined
18
+ # to, and on a VPS is the internet.
19
+ - '127.0.0.1:5432:5432'
16
20
  networks:
17
21
  - vitnode_dev
18
22
 
@@ -22,7 +26,7 @@ services:
22
26
  restart: unless-stopped
23
27
  command: redis-server --requirepass ${REDIS_PASSWORD-root}
24
28
  ports:
25
- - '6379:6379'
29
+ - '127.0.0.1:6379:6379'
26
30
  networks:
27
31
  - vitnode_dev
28
32
 
@@ -1,7 +1,7 @@
1
1
  POSTGRES_URL=postgresql://root:root@localhost:5432/vitnode
2
2
  REDIS_URL=redis://localhost:6379
3
3
 
4
- NEXT_PUBLIC_WEB_URL=http://localhost:3000
4
+ VITNODE_WEB_URL=http://localhost:3000
5
5
 
6
6
  # === Docker Database Postgres ===
7
7
  POSTGRES_USER=root
@@ -1,12 +1 @@
1
- NEXT_PUBLIC_API_URL=http://localhost:8000
2
-
3
- # Optional. Set these to back the Next.js caches with Redis, so cached pages and
4
- # `use cache` entries are shared between instances and a tag revalidation on one
5
- # instance is seen by all of them.
6
- #
7
- # The web process needs its own copy: the cache handlers are loaded by Next.js
8
- # itself and read the environment directly - they cannot see the API's
9
- # `vitnode.api.config.ts`. Point them at the same Redis the API uses.
10
- # https://vitnode.com/docs/dev/advanced/redis#caching-nextjs-output
11
- # REDIS_URL=redis://localhost:6379
12
- # REDIS_PASSWORD=root
1
+ VITNODE_API_URL=http://localhost:8000
@@ -17,13 +17,13 @@
17
17
  "dependsOn": ["^build"],
18
18
  "inputs": ["$TURBO_DEFAULT$", ".env*"],
19
19
  "outputs": [".output/**", "dist/**"],
20
- "env": ["POSTGRES_URL", "NEXT_PUBLIC_API_URL", "NEXT_PUBLIC_WEB_URL"]
20
+ "env": ["POSTGRES_URL", "VITNODE_API_URL", "VITNODE_WEB_URL"]
21
21
  },
22
22
  "dev": {
23
23
  "cache": false,
24
24
  "persistent": true,
25
25
  "inputs": ["$TURBO_DEFAULT$", ".env*"],
26
- "env": ["POSTGRES_URL", "NEXT_PUBLIC_API_URL", "NEXT_PUBLIC_WEB_URL"]
26
+ "env": ["POSTGRES_URL", "VITNODE_API_URL", "VITNODE_WEB_URL"]
27
27
  },
28
28
  "start": {
29
29
  "dependsOn": ["^start"]
@@ -1,14 +1,7 @@
1
1
  POSTGRES_URL=postgresql://root:root@localhost:5432/vitnode
2
2
  REDIS_URL=redis://localhost:6379
3
3
 
4
- # Both name this app's own origin, and it has to be the port `dev` actually
5
- # serves. Neither is strictly required: on the server the API origin is taken
6
- # off the request being rendered, and in the browser it falls back to the origin
7
- # the page was served from - so a preview deployment on a generated hostname
8
- # needs no config at all. Set `NEXT_PUBLIC_API_URL` only to point at a separate
9
- # API server; `NEXT_PUBLIC_WEB_URL` is what the API stamps cookies with.
10
- NEXT_PUBLIC_WEB_URL=http://localhost:3000
11
- NEXT_PUBLIC_API_URL=http://localhost:3000
4
+ VITNODE_WEB_URL=http://localhost:3000
12
5
 
13
6
  # === CRON Secret for Internal API Calls ===
14
7
  CRON_SECRET=your-secure-cron-secret-key
@@ -2,25 +2,7 @@
2
2
 
3
3
  import core from '@vitnode/core/locales/en.json' with { type: 'json' }
4
4
 
5
- /**
6
- * What `useTranslations` is allowed to be asked for.
7
- *
8
- * Augmenting **`use-intl`** rather than `next-intl`. `AppConfig` is declared by
9
- * `use-intl`, which is what VitNode renders every string through on every host;
10
- * `next-intl` only ever re-exported it. This is a type-level dependency and so
11
- * it survives a grep for imports - which is exactly why it is worth naming.
12
- *
13
- * `use-intl` has to be a *direct* dependency of this app for the reference above
14
- * to resolve. Under pnpm's strict `node_modules` layout, reaching it through
15
- * `@vitnode/core` is not enough.
16
- *
17
- * Typed against core's English tree, which is the default locale: every other
18
- * language falls back to it key by key, so it is the one that defines which keys
19
- * exist. Add a plugin's tree to the intersection to get its keys checked too:
20
- *
21
- * import blog from '@acme/blog/locales/en.json' with { type: 'json' }
22
- * Messages: typeof core & typeof blog
23
- */
5
+
24
6
  declare module 'use-intl' {
25
7
  interface AppConfig {
26
8
  Messages: typeof core
@@ -7,50 +7,7 @@ import { adminNav } from "#/lib/admin-nav";
7
7
  import { adminUserSearchFn } from "#/lib/admin-search";
8
8
  import { useAppNavigate } from "#/lib/navigation";
9
9
 
10
- /**
11
- * The AdminCP shell, as this app mounts it.
12
- *
13
- * Everything the panel *is* - the sidebar, the header, the palette, the user
14
- * menu, the breadcrumb area and the one `<main>` - is `AdminShellContent`'s.
15
- * What is bound here is only what a package cannot answer for an application:
16
- *
17
- * onNavigate useAppNavigate the palette's Enter key
18
- * searchUsers adminUserSearchFn this app's own server function
19
- * languageSwitcher <LanguageSwitcher/> the router's
20
- * nav adminNav the plugins *this* app configured
21
- *
22
- * No `LinkComponent`: every sidebar destination is a route in this application's
23
- * own tree, so the shell's own default - `RouterLink` - is the right one. The
24
- * exception is handled a layer down rather than here: a plugin's `admin.nav`
25
- * entry may point at a docs site or a status page, and `adminLinkFor` renders
26
- * those as plain anchors, so an absolute URL is never handed to a router that
27
- * would try to match it. The sidebar and the command palette both go through it,
28
- * so an entry cannot behave one way when clicked and another when searched.
29
- *
30
- * `onNavigate` is still passed, because the palette moves the router *without a
31
- * link*: Enter on a highlighted entry is a navigation nobody clicked, and it has
32
- * to be handed the same de-localized destination a `<Link>` would build. See
33
- * `#/lib/navigation`.
34
- *
35
- * ## `nav` is a projection, not the plugin registry
36
- *
37
- * `src/vitnode.config.ts` is server-side and carries message loaders, which a
38
- * browser bundle has no business holding. So the sidebar is built from
39
- * `#/lib/admin-nav`, which reads the generated browser-safe projection: ids,
40
- * hrefs, permissions, icons and content type definitions, and nothing that
41
- * renders a screen. The screens have a projection of their own - see
42
- * `#/lib/content-registry`, which the content route imports so that a plugin's
43
- * editor fields and form layouts land in that route's chunk rather than in the
44
- * shell's.
45
- *
46
- * It carries the message namespaces with it, because a plugin group's headings
47
- * live under that plugin's own id and the shell would otherwise render them as
48
- * dotted identifiers. `_admin`'s loader warms the same list.
49
- *
50
- * No navigation is derived from a plugin's route tree, in either direction: the
51
- * navigation model is complete regardless of which screen a click lands on, and
52
- * a nav entry is a product decision rather than a consequence of the route tree.
53
- */
10
+
54
11
  const searchUsers: AdminUserSearch = async search =>
55
12
  await adminUserSearchFn({ data: { search } });
56
13
 
@@ -1,33 +1,4 @@
1
1
  import { MainHeader as MainHeaderContent } from "@vitnode/core/tanstack/layout";
2
2
 
3
- /**
4
- * The site header, as the main shell's slot for it.
5
- *
6
- * One line of application. The bar, the nav, the language and theme switchers,
7
- * the user area and both cache entries they read are
8
- * `@vitnode/core/tanstack/layout`'s `MainHeader`.
9
- *
10
- * **Your mark goes here.** Left alone the header renders VitNode's own, which is
11
- * the right default and the wrong answer for a real site. Pass your own:
12
- *
13
- * <MainHeaderContent logo={<YourLogo />} />
14
- *
15
- * This file exists so that choosing a mark is a sentence a site says out loud
16
- * rather than something it inherits without noticing.
17
- *
18
- * No `LinkComponent`. Every header destination is a route in this application's
19
- * own tree, so the header's own default - `RouterLink`, the router's `Link` in
20
- * the shape the shared views ask for - is the right one, and the prop stays
21
- * available for a host that needs to answer differently.
22
- *
23
- * ## What the shell owes it
24
- *
25
- * Two warm cache entries, both ensured by `_main`'s loader:
26
- *
27
- * headerIntlQueryOptions -> a `useSuspenseQuery`, so this is required
28
- * prefetchSession -> the first paint shows the visitor, not a gap
29
- *
30
- * See the loader in `routes/_main.tsx`, which states why one is `ensure` and the
31
- * other `prefetch`.
32
- */
3
+
33
4
  export const MainHeader = () => <MainHeaderContent />;
@@ -2,42 +2,5 @@ import { adminNavBundle } from "@vitnode/core/tanstack/admin";
2
2
 
3
3
  import { pluginAdminNav } from "#/admin-nav.gen";
4
4
 
5
- /**
6
- * This installation's AdminCP navigation - core's own, plus whatever the plugins
7
- * it configured contribute.
8
- *
9
- * Two lines, and both of them are host-only work by necessity. Which plugins are
10
- * installed is a property of this application, and `src/admin-nav.gen.ts` is
11
- * where the build writes the answer: one literal import per configured plugin
12
- * that exports an `admin/nav` module. Every *rule* about what a sidebar contains
13
- * - which entries a content type earns, how a hand-declared entry is titled,
14
- * which permission hides one, which message namespaces the result needs - is
15
- * `@vitnode/core`'s, in `adminNavBundle`. Nothing about navigation is decided
16
- * here.
17
- *
18
- * ## Why it is not read from `vitnode.config.ts`
19
- *
20
- * The config does carry each plugin's registration, and a Next.js host walks it
21
- * in its render pass. Reading it here would make every configured plugin's
22
- * editing screens reachable from the module the document shell imports, which is
23
- * the one graph that is never lazy. The generated projection carries exactly
24
- * what a sidebar needs instead - ids, hrefs, permissions, icons and content type
25
- * definitions, all plain data - and the Content Engine's UI arrives separately,
26
- * when a content screen actually renders, through `src/lib/content-registry.ts`.
27
- *
28
- * ## Module scope, and why that matters twice
29
- *
30
- * Evaluated once per bundle rather than per render. `AdminShellContent` memoises
31
- * its namespace list on this object's identity, and `_admin`'s loader warms the
32
- * messages from the same `namespaces` array the shell then reads - so a stable
33
- * value here is what makes those one cache entry rather than two.
34
- *
35
- * ## Content entries were never rewritten, and that is the point
36
- *
37
- * A content type's entry is `/admin/content/…`, and it said exactly that
38
- * through every change in which framework rendered those screens. Only *how a
39
- * click travels* ever changed, and nothing here was edited for it. Routes and
40
- * navigation stay separate concepts: nothing derives a nav entry from a route
41
- * file, and nothing makes one conditional on a route existing.
42
- */
5
+
43
6
  export const adminNav = adminNavBundle({ plugins: pluginAdminNav });
@@ -2,32 +2,7 @@ import { createServerFn } from "@tanstack/react-start";
2
2
  import { readAdminUserSearchOnApi } from "@vitnode/core/tanstack/admin/server";
3
3
  import { z } from "zod";
4
4
 
5
- /**
6
- * The AdminCP command palette's user lookup, as this app's server function.
7
- *
8
- * One handler, one line, exactly like `lib/auth.ts` - and here for the same
9
- * reason. `createServerFn` needs the module it sits in to be transformed by the
10
- * Start compiler on both sides of the render, and `@vitnode/core` reaches the
11
- * server un-compiled (see the note in `lib/auth.ts`), so the declaration lives
12
- * in the host and the behaviour lives in the package.
13
- *
14
- * ## Why it has a validator
15
- *
16
- * A server function is a public same-origin endpoint: its input is whatever a
17
- * caller posts, not whatever the dialog typed. This value reaches an API query
18
- * string, so it is parsed rather than trusted - trimmed, length-bounded, and a
19
- * string at all.
20
- *
21
- * The bound is not a security control; the API authorizes the read from the
22
- * admin cookie and pages it itself. It is a cheap refusal of the requests that
23
- * could only ever be abuse - a megabyte of "search term" that the API would
24
- * otherwise have to hand to Postgres.
25
- *
26
- * `POST` puts it behind `createCsrfMiddleware` in `src/start.ts`, which matters
27
- * more here than it looks: this reads other people's names and email addresses,
28
- * and a `GET` would be reachable from another origin's page with the
29
- * administrator's cookies attached.
30
- */
5
+
31
6
  const adminUserSearchInput = z.object({
32
7
  search: z.string().trim().min(1).max(128),
33
8
  });
@@ -5,48 +5,7 @@ import {
5
5
 
6
6
  import { pluginContentTypes } from "#/content-registry.gen";
7
7
 
8
- /**
9
- * This installation's Content Engine registry - every content type its
10
- * configured plugins register, with their editing screens attached.
11
- *
12
- * Two lines, and both of them are host-only work by necessity. Which plugins are
13
- * installed is a property of this application, and `src/content-registry.gen.ts`
14
- * is where the build writes the answer: one literal import per configured plugin
15
- * that exports an `admin/content` module. Every *rule* about what a registration
16
- * is - which paths are legal, which two of them collide, how they are ordered,
17
- * how one is found by id or by `admin.path` - is `@vitnode/core`'s, in
18
- * `buildContentFrontendRegistry`. Nothing about the Content Engine is decided
19
- * here.
20
- *
21
- * ## Why it is not read from `vitnode.config.ts`
22
- *
23
- * The config does carry each plugin's registration, and a Next.js host reads it
24
- * from there. What it cannot give is *when*: it is imported by the document
25
- * shell, so anything reachable from it is reachable eagerly. This module is
26
- * loaded behind a dynamic `import()` in `src/router.tsx` instead, so the
27
- * definitions, the icons and the override components arrive with the content
28
- * route - the same arrangement `src/lib/admin-nav.ts` uses for the sidebar, one
29
- * layer deeper.
30
- *
31
- * ## Registration, and where it belongs in the import graph
32
- *
33
- * `setContentFrontendRegistry` fills a module-scope slot in `@vitnode/core`, so
34
- * the package's own content code finds the registry without being handed it as
35
- * a prop - the same shape as `setAdminTransport`. Module scope means *per
36
- * bundle*: the browser has one instance and the server has one, and each
37
- * registers its own.
38
- *
39
- * This module is reached through a `() => import(...)` that `/admin/content`'s
40
- * loader awaits, never through a static import, and that is deliberate.
41
- * Registration only has to happen before a content screen runs, and deferring it
42
- * to the route is what lets Rollup put this whole graph - every plugin's field
43
- * components, table cells and form layouts, plus `zod` and the Content Engine
44
- * itself - in that route's chunk instead of in the bundle every page of the site
45
- * loads first. `router.tsx` holds the thunk because that is where the route tree
46
- * is composed; what it does *not* hold is the value. A plugin's own heavier
47
- * parts stay lazier still: `@vitnode/blog` draws a `React.lazy` boundary around
48
- * its Tiptap editor, so even opening the article list does not fetch it.
49
- */
8
+
50
9
  export const contentRegistry = buildContentFrontendRegistry(pluginContentTypes);
51
10
 
52
11
  setContentFrontendRegistry(contentRegistry);
@@ -5,71 +5,22 @@ import { IntlProvider } from "use-intl";
5
5
  import { loadIntlMessages } from "#/server/messages.server";
6
6
  import { vitNodeConfig } from "#/vitnode.config";
7
7
 
8
- /**
9
- * One language's messages for one set of namespaces, fetched on the server.
10
- *
11
- * The one piece of the i18n runtime that cannot live in `@vitnode/core`, and the
12
- * reason is the compiler rather than the code. A server function has to be
13
- * transformed by the Start plugin in *both* bundles; the package is externalised
14
- * from this app's SSR pass, so its modules reach the server un-compiled and a
15
- * `createServerFn` declared there resolves to `undefined` during SSR with no
16
- * error. See `packages/vitnode/src/tanstack/boundary.test.ts`.
17
- *
18
- * So the wrapper is here and the body is not: `validateIntlInput` is core's, and
19
- * `loadIntlMessages` delegates to core's loading engine. Start strips the
20
- * handler - and `#/server/messages.server` with it - out of the client build.
21
- */
8
+
22
9
  export const getIntlMessages = createServerFn()
23
10
  .validator(validateIntlInput)
24
11
  .handler(async ({ data }) => await loadIntlMessages(data));
25
12
 
26
- /**
27
- * This app's languages, handed to the package once.
28
- *
29
- * Everything in `@vitnode/core/tanstack/i18n` reads what this registers, so a
30
- * route file imports `RouteMessages` and `intlQueryOptions` straight from the
31
- * package. What must not happen is a route running before this module has been
32
- * evaluated - so `src/router.tsx`, which owns the route tree, imports from
33
- * here. The request pipeline needs no such guarantee: `createVitNodeStart`
34
- * derives its own locale routing from the config it is handed.
35
- *
36
- * The registration is at module scope but reads `getIntlMessages` above only by
37
- * reference, so the order within this file does not matter: the validator and
38
- * the fetcher are both called per request, long after it has finished
39
- * evaluating.
40
- */
13
+
41
14
  export const {
42
15
  defaultLocale,
43
16
  isLocale: isSupportedLocale,
44
17
  localeRouting,
45
18
  } = configureIntl({
46
19
  fetchMessages: async input => await getIntlMessages({ data: input }),
47
- /**
48
- * This app's own `use-intl`, handed over so `RouteMessages` can provide it.
49
- *
50
- * `@vitnode/core` is external to this app's SSR pass (`vite.config.ts`), so
51
- * under `vite dev` Node loads the package and Vite loads this app, and the two
52
- * resolve `use-intl` to two different files - two `createContext` calls, two
53
- * React contexts. Everything the package renders reads its own; anything this
54
- * app renders with its own `useTranslations` - `routes/_main/index.tsx` does -
55
- * reads this one, and nothing inside the package can import it.
56
- *
57
- * So it is registered rather than imported, and `RouteMessages` mounts it
58
- * outermost. A production build resolves `use-intl` once and the providers
59
- * collapse into one, which is why leaving this out is a dev-only failure: the
60
- * route's server render throws "No intl context found", React quietly falls
61
- * back to client rendering, and the page still appears.
62
- */
20
+
63
21
  hostIntlProvider: IntlProvider,
64
22
  i18n: vitNodeConfig.i18n,
65
23
  });
66
24
 
67
- /**
68
- * The router's half of locale routing, bound to this app's languages.
69
- *
70
- * Re-exported from here rather than imported straight from the package by
71
- * `src/router.tsx`: it is the router entry's only i18n import, and routing it
72
- * through this module is what makes `configureIntl` above run before the router
73
- * - and therefore before any route, loader or component - exists.
74
- */
25
+
75
26
  export { createLocaleRewrite } from "@vitnode/core/tanstack/i18n";
@@ -2,15 +2,7 @@ import type { vitNodeConfig } from "#/vitnode.config";
2
2
 
3
3
  import { localeRouting } from "#/lib/i18n/runtime";
4
4
 
5
- /**
6
- * A language this app serves, as a type. `"en"`, or `"en" | "de"` once a second
7
- * one is declared - derived from the config rather than written twice.
8
- *
9
- * The one i18n thing this app still owns, and it has to: `@vitnode/core` is
10
- * installed by apps with different language lists, so it types a locale as
11
- * `string` and takes this union as a type argument where the value originates
12
- * (`useLocale<Locale>()`, `resolveLocale<Locale>()`).
13
- */
5
+
14
6
  export type Locale = (typeof vitNodeConfig.i18n.locales)[number]["code"];
15
7
 
16
8
  export { defaultLocale, localeRouting } from "#/lib/i18n/runtime";
@@ -2,23 +2,7 @@ import { createAuthNavigation } from "@vitnode/core/tanstack/auth";
2
2
 
3
3
  import { localeRouting } from "#/lib/i18n/shared";
4
4
 
5
- /**
6
- * Going somewhere in this application from code, bound to this app's languages.
7
- *
8
- * One line of application, and everything else is
9
- * `@vitnode/core/tanstack/auth`: the two questions a user-supplied target has to
10
- * answer (may we send a browser there, and what does the router want to be
11
- * handed), the reason a redirect carries `to` rather than `href`, and the fact
12
- * that the same decision is made on a server and in a browser.
13
- *
14
- * What is left here is the only thing a package cannot answer - which languages
15
- * this installation serves, which is what decides whether `/pl/discover` is a
16
- * Polish page or a route called `pl`.
17
- *
18
- * `@vitnode/core/tanstack/routes` builds its own from the same factory, handed
19
- * the same `localeRouting`, so core's auth screens and this app's AdminCP command
20
- * palette navigate by one rule rather than two.
21
- */
5
+
22
6
  export const { internalDestination, useAppNavigate } = createAuthNavigation({
23
7
  localeRouting,
24
8
  });
@@ -2,18 +2,5 @@ import { createRouteHead } from "@vitnode/core/tanstack/metadata";
2
2
 
3
3
  import { vitNodeConfig } from "#/vitnode.config";
4
4
 
5
- /**
6
- * A route's `head`, bound to this app's name.
7
- *
8
- * Two lines of application, and everything else is
9
- * `@vitnode/core/tanstack/metadata`: the `"<page> - <site>"` title rule Next.js
10
- * applies through `title.template`, the decision that a robots directive is
11
- * stated rather than assumed, and the handling of a `loaderData` that is
12
- * `undefined` on a route's first pass.
13
- *
14
- * What is left here is the only thing a package cannot answer - this site's own
15
- * name, which is what every tab title ends with.
16
- *
17
- * head: ({ loaderData }) => pageHead({ robots: 'index, follow', ...loaderData })
18
- */
5
+
19
6
  export const pageHead = createRouteHead(vitNodeConfig.metadata);
@@ -1,28 +1,4 @@
1
1
  import type { AppMessagesMap } from "@vitnode/core/lib/i18n/types";
2
2
 
3
- /**
4
- * Translations this app owns, on top of whatever the packages ship.
5
- *
6
- * Empty, and usually stays that way. Every VitNode package ships its own
7
- * translations and `locales/packages.ts` is what registers them - carrying a
8
- * second copy of a string a package already owns is how one product comes to
9
- * spell the same settings tab two different ways.
10
- *
11
- * What belongs here is a string this app *changes*. Add a locale, then the key
12
- * you are rewording:
13
- *
14
- * export const appMessages: AppMessagesMap = {
15
- * en: {
16
- * '@vitnode/core': async () => await import('./en.json'),
17
- * },
18
- * }
19
- *
20
- * Deep-merged last, so a file here only needs the keys it actually changes:
21
- * everything it leaves out falls back to the package's, and then to the default
22
- * locale, key by key.
23
- *
24
- * Server-side only, and kept out of `src/vitnode.config.ts` on purpose: these
25
- * are functions, and the shared config crosses to the browser and has to stay
26
- * serializable. `src/vitnode.server.config.ts` is what registers this map.
27
- */
3
+
28
4
  export const appMessages: AppMessagesMap = {};
@@ -2,40 +2,7 @@ import type { LocaleMessagesMap } from "@vitnode/core/lib/i18n/types";
2
2
 
3
3
  import { CONFIG_PLUGIN as CORE } from "@vitnode/core/config";
4
4
 
5
- /**
6
- * Where this app reads each installed package's translations from.
7
- *
8
- * Every VitNode package ships a locale barrel - `@vitnode/core/locales/index` -
9
- * that loads its own files with a runtime
10
- * `import("./en.json", { with: { type: "json" } })`. Under Node that is exactly
11
- * right, and it is how an API app reads them.
12
- *
13
- * It cannot work here, and the reason is the import attribute rather than
14
- * anything about VitNode. Vite and Nitro inline `@vitnode/core`'s build output
15
- * into this app's server chunks - `ssr.external` applies to the SSR pass, not to
16
- * Nitro's own bundling - but Rollup will not follow a dynamic import that
17
- * carries `with: { type: "json" }`, so it neither emits the JSON nor rewrites
18
- * the specifier. What ships is a relative import pointing next to a chunk that
19
- * the JSON was never copied to, and every string on the page renders as its own
20
- * key.
21
- *
22
- * So the loaders are declared here instead, with static specifiers a bundler can
23
- * follow. Each resolves through the package's `./locales/*.json` export to the
24
- * real file and lands in the build as a chunk fetched on demand, which is the
25
- * same laziness the barrels wanted.
26
- *
27
- * **Add a line here for every plugin you install.** A plugin registered in
28
- * `vitnode.config.ts` with no entry in this map renders its own strings as keys:
29
- *
30
- * import { CONFIG_PLUGIN as BLOG } from '@acme/blog/const'
31
- *
32
- * [BLOG.pluginId]: {
33
- * en: async () => await import('@acme/blog/locales/en.json'),
34
- * },
35
- *
36
- * This is the app's only copy of that list - `vitnode.server.config.ts` reads it
37
- * from here.
38
- */
5
+
39
6
  export const packageMessages: Record<string, LocaleMessagesMap> = {
40
7
  [CORE.pluginId]: {
41
8
  en: async () => await import("@vitnode/core/locales/en.json"),