pastoria 0.0.1 → 1.0.2

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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ # pastoria
2
+
3
+ ## 1.0.2
4
+
5
+ ### Patch Changes
6
+
7
+ - Remove usage of experimentalStripTypes
8
+
9
+ ## 1.0.1
10
+
11
+ ### Patch Changes
12
+
13
+ - Pastoria alpha version for testing
package/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Ryan Delaney
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,39 @@
1
+ import { type BuildEnvironmentOptions, type Plugin } from 'vite';
2
+ export declare const CLIENT_BUILD: BuildEnvironmentOptions;
3
+ export declare const SERVER_BUILD: BuildEnvironmentOptions;
4
+ export declare function createBuildConfig(buildEnv: BuildEnvironmentOptions): {
5
+ appType: "custom";
6
+ build: {
7
+ assetsInlineLimit: number;
8
+ manifest: boolean;
9
+ ssrManifest: boolean;
10
+ target?: "baseline-widely-available" | import("vite").EsbuildTransformOptions["target"] | false;
11
+ polyfillModulePreload?: boolean;
12
+ modulePreload?: boolean | import("vite").ModulePreloadOptions;
13
+ outDir?: string;
14
+ assetsDir?: string;
15
+ cssCodeSplit?: boolean;
16
+ cssTarget?: import("vite").EsbuildTransformOptions["target"] | false;
17
+ cssMinify?: boolean | "esbuild" | "lightningcss";
18
+ sourcemap?: boolean | "inline" | "hidden";
19
+ minify?: boolean | "terser" | "esbuild";
20
+ terserOptions?: import("vite").TerserOptions;
21
+ rollupOptions?: import("rollup").RollupOptions;
22
+ commonjsOptions?: import("vite").RollupCommonJSOptions;
23
+ dynamicImportVarsOptions?: import("vite").RollupDynamicImportVarsOptions;
24
+ write?: boolean;
25
+ emptyOutDir?: boolean | null;
26
+ copyPublicDir?: boolean;
27
+ lib?: import("vite").LibraryOptions | false;
28
+ ssr?: boolean | string;
29
+ ssrEmitAssets?: boolean;
30
+ emitAssets?: boolean;
31
+ reportCompressedSize?: boolean;
32
+ chunkSizeWarningLimit?: number;
33
+ watch?: import("rollup").WatcherOptions | null;
34
+ createEnvironment?: (name: string, config: import("vite").ResolvedConfig) => Promise<import("vite").BuildEnvironment> | import("vite").BuildEnvironment;
35
+ };
36
+ plugins: (Plugin<any> | Plugin<any>[])[];
37
+ };
38
+ export declare function createBuild(): Promise<void>;
39
+ //# sourceMappingURL=build.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"AAGA,OAAO,EAAQ,KAAK,uBAAuB,EAAE,KAAK,MAAM,EAAC,MAAM,MAAM,CAAC;AA8EtE,eAAO,MAAM,YAAY,EAAE,uBAK1B,CAAC;AAEF,eAAO,MAAM,YAAY,EAAE,uBAM1B,CAAC;AAEF,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAkBlE;AAED,wBAAsB,WAAW,kBAUhC"}
package/dist/build.js ADDED
@@ -0,0 +1,121 @@
1
+ import react from '@vitejs/plugin-react';
2
+ import tailwindcss from '@tailwindcss/vite';
3
+ import { cjsInterop } from 'vite-plugin-cjs-interop';
4
+ import { build } from 'vite';
5
+ // TODO: Only emit `App` code if _app exits.
6
+ const PASTORIA_CLIENT_ENTRY = `// Generated by Pastoria.
7
+ import {createRouterApp} from '#genfiles/router/router';
8
+ import {App} from '#src/pages/_app';
9
+ import {hydrateRoot} from 'react-dom/client';
10
+
11
+ async function main() {
12
+ const RouterApp = await createRouterApp();
13
+ hydrateRoot(document, <RouterApp App={App} />);
14
+ }
15
+
16
+ main();
17
+ `;
18
+ // TODO: Remove hard-coded context import here.
19
+ const PASTORIA_ENTRY_SERVER = `// Generated by Pastoria.
20
+ import {JSResource} from '#genfiles/router/js_resource';
21
+ import {
22
+ listRoutes,
23
+ router__createAppFromEntryPoint,
24
+ router__loadEntryPoint,
25
+ } from '#genfiles/router/router';
26
+ import {getSchema} from '#genfiles/schema/schema';
27
+ import {Context} from '#src/lib/server/context';
28
+ import {App} from '#src/pages/_app';
29
+ import {GraphQLSchema, specifiedDirectives} from 'graphql';
30
+ import {createRouterHandler} from 'pastoria-runtime/server';
31
+ import type {Manifest} from 'vite';
32
+
33
+ const schemaConfig = getSchema().toConfig();
34
+ const schema = new GraphQLSchema({
35
+ ...schemaConfig,
36
+ directives: [...specifiedDirectives, ...schemaConfig.directives],
37
+ });
38
+
39
+ export function createHandler(
40
+ persistedQueries: Record<string, string>,
41
+ manifest?: Manifest,
42
+ ) {
43
+ return createRouterHandler(
44
+ listRoutes(),
45
+ JSResource.srcOfModuleId,
46
+ router__loadEntryPoint,
47
+ router__createAppFromEntryPoint,
48
+ App,
49
+ schema,
50
+ () => new Context(),
51
+ persistedQueries,
52
+ manifest,
53
+ );
54
+ }
55
+ `;
56
+ function pastoriaEntryPlugin() {
57
+ const clientEntryModuleId = 'virtual:pastoria-entry-client.tsx';
58
+ const serverEntryModuleId = 'virtual:pastoria-entry-server.tsx';
59
+ return {
60
+ name: 'pastoria-entry',
61
+ resolveId(id) {
62
+ if (id === clientEntryModuleId) {
63
+ return clientEntryModuleId; // Return without \0 prefix so React plugin can see .tsx extension
64
+ }
65
+ else if (id === serverEntryModuleId) {
66
+ return serverEntryModuleId;
67
+ }
68
+ },
69
+ load(id) {
70
+ if (id === clientEntryModuleId) {
71
+ return PASTORIA_CLIENT_ENTRY;
72
+ }
73
+ else if (id === serverEntryModuleId) {
74
+ return PASTORIA_ENTRY_SERVER;
75
+ }
76
+ },
77
+ };
78
+ }
79
+ export const CLIENT_BUILD = {
80
+ outDir: 'dist/client',
81
+ rollupOptions: {
82
+ input: 'virtual:pastoria-entry-client.tsx',
83
+ },
84
+ };
85
+ export const SERVER_BUILD = {
86
+ outDir: 'dist/server',
87
+ ssr: true,
88
+ rollupOptions: {
89
+ input: 'virtual:pastoria-entry-server.tsx',
90
+ },
91
+ };
92
+ export function createBuildConfig(buildEnv) {
93
+ return {
94
+ appType: 'custom',
95
+ build: {
96
+ ...buildEnv,
97
+ assetsInlineLimit: 0,
98
+ manifest: true,
99
+ ssrManifest: true,
100
+ },
101
+ plugins: [
102
+ pastoriaEntryPlugin(),
103
+ tailwindcss(),
104
+ react({ babel: { plugins: ['relay'] } }),
105
+ cjsInterop({
106
+ dependencies: ['react-relay', 'react-relay/hooks', 'relay-runtime'],
107
+ }),
108
+ ],
109
+ };
110
+ }
111
+ export async function createBuild() {
112
+ const clientBuild = await build({
113
+ ...createBuildConfig(CLIENT_BUILD),
114
+ configFile: false,
115
+ });
116
+ const serverBuild = await build({
117
+ ...createBuildConfig(SERVER_BUILD),
118
+ configFile: false,
119
+ });
120
+ }
121
+ //# sourceMappingURL=build.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build.js","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,sBAAsB,CAAC;AACzC,OAAO,WAAW,MAAM,mBAAmB,CAAC;AAC5C,OAAO,EAAC,UAAU,EAAC,MAAM,yBAAyB,CAAC;AACnD,OAAO,EAAC,KAAK,EAA4C,MAAM,MAAM,CAAC;AAEtE,4CAA4C;AAC5C,MAAM,qBAAqB,GAAG;;;;;;;;;;;CAW7B,CAAC;AAEF,+CAA+C;AAC/C,MAAM,qBAAqB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoC7B,CAAC;AAEF,SAAS,mBAAmB;IAC1B,MAAM,mBAAmB,GAAG,mCAAmC,CAAC;IAChE,MAAM,mBAAmB,GAAG,mCAAmC,CAAC;IAEhE,OAAO;QACL,IAAI,EAAE,gBAAgB;QACtB,SAAS,CAAC,EAAE;YACV,IAAI,EAAE,KAAK,mBAAmB,EAAE,CAAC;gBAC/B,OAAO,mBAAmB,CAAC,CAAC,kEAAkE;YAChG,CAAC;iBAAM,IAAI,EAAE,KAAK,mBAAmB,EAAE,CAAC;gBACtC,OAAO,mBAAmB,CAAC;YAC7B,CAAC;QACH,CAAC;QACD,IAAI,CAAC,EAAE;YACL,IAAI,EAAE,KAAK,mBAAmB,EAAE,CAAC;gBAC/B,OAAO,qBAAqB,CAAC;YAC/B,CAAC;iBAAM,IAAI,EAAE,KAAK,mBAAmB,EAAE,CAAC;gBACtC,OAAO,qBAAqB,CAAC;YAC/B,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,YAAY,GAA4B;IACnD,MAAM,EAAE,aAAa;IACrB,aAAa,EAAE;QACb,KAAK,EAAE,mCAAmC;KAC3C;CACF,CAAC;AAEF,MAAM,CAAC,MAAM,YAAY,GAA4B;IACnD,MAAM,EAAE,aAAa;IACrB,GAAG,EAAE,IAAI;IACT,aAAa,EAAE;QACb,KAAK,EAAE,mCAAmC;KAC3C;CACF,CAAC;AAEF,MAAM,UAAU,iBAAiB,CAAC,QAAiC;IACjE,OAAO;QACL,OAAO,EAAE,QAAiB;QAC1B,KAAK,EAAE;YACL,GAAG,QAAQ;YACX,iBAAiB,EAAE,CAAC;YACpB,QAAQ,EAAE,IAAI;YACd,WAAW,EAAE,IAAI;SAClB;QACD,OAAO,EAAE;YACP,mBAAmB,EAAE;YACrB,WAAW,EAAE;YACb,KAAK,CAAC,EAAC,KAAK,EAAE,EAAC,OAAO,EAAE,CAAC,OAAO,CAAC,EAAC,EAAC,CAAC;YACpC,UAAU,CAAC;gBACT,YAAY,EAAE,CAAC,aAAa,EAAE,mBAAmB,EAAE,eAAe,CAAC;aACpE,CAAC;SACH;KACF,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW;IAC/B,MAAM,WAAW,GAAG,MAAM,KAAK,CAAC;QAC9B,GAAG,iBAAiB,CAAC,YAAY,CAAC;QAClC,UAAU,EAAE,KAAK;KAClB,CAAC,CAAC;IAEH,MAAM,WAAW,GAAG,MAAM,KAAK,CAAC;QAC9B,GAAG,iBAAiB,CAAC,YAAY,CAAC;QAClC,UAAU,EAAE,KAAK;KAClB,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,4 @@
1
+ export declare function startDevserver(opts: {
2
+ port: string;
3
+ }): Promise<void>;
4
+ //# sourceMappingURL=devserver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"devserver.d.ts","sourceRoot":"","sources":["../src/devserver.ts"],"names":[],"mappings":"AAmBA,wBAAsB,cAAc,CAAC,IAAI,EAAE;IAAC,IAAI,EAAE,MAAM,CAAA;CAAC,iBAiCxD"}
@@ -0,0 +1,34 @@
1
+ import cookieParser from 'cookie-parser';
2
+ import dotenv from 'dotenv';
3
+ import express from 'express';
4
+ import { readFile } from 'node:fs/promises';
5
+ import pc from 'picocolors';
6
+ import { createServer as createViteServer } from 'vite';
7
+ import { CLIENT_BUILD, createBuildConfig } from './build';
8
+ export async function startDevserver(opts) {
9
+ dotenv.config();
10
+ const buildConfig = createBuildConfig(CLIENT_BUILD);
11
+ const vite = await createViteServer({
12
+ ...buildConfig,
13
+ configFile: false,
14
+ server: { middlewareMode: true },
15
+ });
16
+ const app = express();
17
+ app.use(cookieParser());
18
+ app.use(vite.middlewares);
19
+ app.use(async (req, res, next) => {
20
+ const persistedQueries = JSON.parse(await readFile('__generated__/persisted_queries.json', 'utf-8'));
21
+ const { createHandler } = (await vite.ssrLoadModule('virtual:pastoria-entry-server.tsx'));
22
+ const handler = createHandler(persistedQueries);
23
+ handler(req, res, next);
24
+ });
25
+ app.listen(Number(opts.port), (err) => {
26
+ if (err) {
27
+ console.error(err);
28
+ }
29
+ else {
30
+ console.log(pc.cyan(`Listening on port ${opts.port}!`));
31
+ }
32
+ });
33
+ }
34
+ //# sourceMappingURL=devserver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"devserver.js","sourceRoot":"","sources":["../src/devserver.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,eAAe,CAAC;AACzC,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,OAAO,MAAM,SAAS,CAAC;AAC9B,OAAO,EAAC,QAAQ,EAAC,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EAAE,MAAM,YAAY,CAAC;AAC5B,OAAO,EAAC,YAAY,IAAI,gBAAgB,EAAgB,MAAM,MAAM,CAAC;AACrE,OAAO,EAAC,YAAY,EAAE,iBAAiB,EAAC,MAAM,SAAS,CAAC;AAaxD,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,IAAoB;IACvD,MAAM,CAAC,MAAM,EAAE,CAAC;IAEhB,MAAM,WAAW,GAAG,iBAAiB,CAAC,YAAY,CAAC,CAAC;IACpD,MAAM,IAAI,GAAG,MAAM,gBAAgB,CAAC;QAClC,GAAG,WAAW;QACd,UAAU,EAAE,KAAK;QACjB,MAAM,EAAE,EAAC,cAAc,EAAE,IAAI,EAAC;KAC/B,CAAC,CAAC;IAEH,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;IACtB,GAAG,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC,CAAC;IACxB,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAC1B,GAAG,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;QAC/B,MAAM,gBAAgB,GAAG,IAAI,CAAC,KAAK,CACjC,MAAM,QAAQ,CAAC,sCAAsC,EAAE,OAAO,CAAC,CAChE,CAAC;QAEF,MAAM,EAAC,aAAa,EAAC,GAAG,CAAC,MAAM,IAAI,CAAC,aAAa,CAC/C,mCAAmC,CACpC,CAAgB,CAAC;QAElB,MAAM,OAAO,GAAG,aAAa,CAAC,gBAAgB,CAAC,CAAC;QAChD,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;IAC1B,CAAC,CAAC,CAAC;IAEH,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE;QACpC,IAAI,GAAG,EAAE,CAAC;YACR,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACrB,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,qBAAqB,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC;QAC1D,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * @fileoverview Router Code Generator
3
+ *
4
+ * This script generates type-safe router configuration files by scanning TypeScript
5
+ * source code for JSDoc annotations. It's part of the "Pastoria" routing framework.
6
+ *
7
+ * How it works:
8
+ * 1. Scans all TypeScript files in the project for exported functions/classes
9
+ * 2. Looks for JSDoc tags: @route, @resource, and @param
10
+ * 3. Generates three files from templates:
11
+ * - js_resource.ts: Resource configuration for lazy loading
12
+ * - router.tsx: Client-side router with type-safe routes
13
+ * - server_router.ts: Server-side router configuration
14
+ *
15
+ * Usage:
16
+ * - Add @route <route-name> to functions to create routes
17
+ * - Add @param <name> <type> to document route parameters
18
+ * - Add @resource <resource-name> to exports for lazy loading
19
+ *
20
+ * The generator automatically creates Zod schemas for route parameters based on
21
+ * TypeScript types, enabling runtime validation and type safety.
22
+ */
23
+ export declare function generatePastoriaArtifacts(): Promise<void>;
24
+ //# sourceMappingURL=generate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../src/generate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAoJH,wBAAsB,yBAAyB,kBA2G9C"}
@@ -0,0 +1,209 @@
1
+ /**
2
+ * @fileoverview Router Code Generator
3
+ *
4
+ * This script generates type-safe router configuration files by scanning TypeScript
5
+ * source code for JSDoc annotations. It's part of the "Pastoria" routing framework.
6
+ *
7
+ * How it works:
8
+ * 1. Scans all TypeScript files in the project for exported functions/classes
9
+ * 2. Looks for JSDoc tags: @route, @resource, and @param
10
+ * 3. Generates three files from templates:
11
+ * - js_resource.ts: Resource configuration for lazy loading
12
+ * - router.tsx: Client-side router with type-safe routes
13
+ * - server_router.ts: Server-side router configuration
14
+ *
15
+ * Usage:
16
+ * - Add @route <route-name> to functions to create routes
17
+ * - Add @param <name> <type> to document route parameters
18
+ * - Add @resource <resource-name> to exports for lazy loading
19
+ *
20
+ * The generator automatically creates Zod schemas for route parameters based on
21
+ * TypeScript types, enabling runtime validation and type safety.
22
+ */
23
+ import { readFile } from 'node:fs/promises';
24
+ import * as path from 'node:path';
25
+ import { default as pc } from 'picocolors';
26
+ import { Project, SyntaxKind, ts, TypeFlags } from 'ts-morph';
27
+ const JS_RESOURCE_FILENAME = '__generated__/router/js_resource.ts';
28
+ const JS_RESOURCE_TEMPLATE = path.join(import.meta.dirname, '../templates/js_resource.ts');
29
+ const ROUTER_FILENAME = '__generated__/router/router.tsx';
30
+ const ROUTER_TEMPLATE = path.join(import.meta.dirname, '../templates/router.tsx');
31
+ async function loadRouterFiles(project) {
32
+ async function loadSourceFile(fileName, templateFileName) {
33
+ const template = await readFile(templateFileName, 'utf-8');
34
+ const warningComment = `/*
35
+ * This file was generated by \`pastoria\`.
36
+ * Do not modify this file directly. Instead, edit the template at ${path.basename(templateFileName)}.
37
+ */
38
+
39
+ `;
40
+ return project.createSourceFile(fileName, warningComment + template, {
41
+ overwrite: true,
42
+ });
43
+ }
44
+ const [jsResource, router] = await Promise.all([
45
+ loadSourceFile(JS_RESOURCE_FILENAME, JS_RESOURCE_TEMPLATE),
46
+ loadSourceFile(ROUTER_FILENAME, ROUTER_TEMPLATE),
47
+ ]);
48
+ return { jsResource, router };
49
+ }
50
+ function collectRouterNodes(project) {
51
+ const resources = [];
52
+ const routes = [];
53
+ function visitRouterNodes(sourceFile) {
54
+ // TODO: Skip sourceFile if a pastora JSDoc tag isn't used at all.
55
+ sourceFile.getExportSymbols().forEach((symbol) => {
56
+ let routerResource = null;
57
+ let routerRoute = null;
58
+ const routeParams = new Map();
59
+ function visitJSDocTags(tag) {
60
+ if (ts.isJSDoc(tag)) {
61
+ tag.tags?.forEach(visitJSDocTags);
62
+ }
63
+ else if (ts.isJSDocParameterTag(tag)) {
64
+ const typeNode = tag.typeExpression?.type;
65
+ const tc = project.getTypeChecker().compilerObject;
66
+ const type = typeNode == null
67
+ ? tc.getUnknownType()
68
+ : tc.getTypeFromTypeNode(typeNode);
69
+ routeParams.set(tag.name.getText(), type);
70
+ }
71
+ else if (typeof tag.comment === 'string') {
72
+ switch (tag.tagName.getText()) {
73
+ case 'route': {
74
+ routerRoute = {
75
+ routeName: tag.comment,
76
+ sourceFile,
77
+ symbol,
78
+ params: routeParams,
79
+ };
80
+ break;
81
+ }
82
+ case 'resource': {
83
+ routerResource = {
84
+ resourceName: tag.comment,
85
+ sourceFile,
86
+ symbol,
87
+ };
88
+ break;
89
+ }
90
+ }
91
+ }
92
+ }
93
+ symbol
94
+ .getDeclarations()
95
+ .flatMap((decl) => ts.getJSDocCommentsAndTags(decl.compilerNode))
96
+ .forEach(visitJSDocTags);
97
+ if (routerRoute != null)
98
+ routes.push(routerRoute);
99
+ if (routerResource != null)
100
+ resources.push(routerResource);
101
+ });
102
+ }
103
+ project.getSourceFiles().forEach(visitRouterNodes);
104
+ return { resources, routes };
105
+ }
106
+ function zodSchemaOfType(tc, t) {
107
+ if (t.getFlags() & TypeFlags.String) {
108
+ return `z.pipe(z.string(), z.transform(decodeURIComponent))`;
109
+ }
110
+ else if (t.getFlags() & TypeFlags.Number) {
111
+ return `z.coerce.number<number>()`;
112
+ }
113
+ else if (t.getFlags() & TypeFlags.Null) {
114
+ return `z.preprocess(s => s == null ? undefined : s, z.undefined())`;
115
+ }
116
+ else if (t.isUnion()) {
117
+ const isRepresentingOptional = t.types.length === 2 &&
118
+ t.types.some((s) => s.getFlags() & TypeFlags.Null);
119
+ if (isRepresentingOptional) {
120
+ const nonOptionalType = t.types.find((s) => !(s.getFlags() & TypeFlags.Null));
121
+ return `z.pipe(z.nullish(${zodSchemaOfType(tc, nonOptionalType)}), z.transform(s => s == null ? undefined : s))`;
122
+ }
123
+ else {
124
+ return `z.union([${t.types.map((it) => zodSchemaOfType(tc, it)).join(', ')}])`;
125
+ }
126
+ }
127
+ else if (tc.isArrayLikeType(t)) {
128
+ const typeArg = tc.getTypeArguments(t)[0];
129
+ const argZodSchema = typeArg == null ? `z.any()` : zodSchemaOfType(tc, typeArg);
130
+ return `z.array(${argZodSchema})`;
131
+ }
132
+ else {
133
+ console.log('Could not handle type:', tc.typeToString(t));
134
+ return `z.any()`;
135
+ }
136
+ }
137
+ export async function generatePastoriaArtifacts() {
138
+ const targetDir = process.cwd();
139
+ const project = new Project({
140
+ tsConfigFilePath: path.join(targetDir, 'tsconfig.json'),
141
+ });
142
+ const tc = project.getTypeChecker().compilerObject;
143
+ const routerFiles = await loadRouterFiles(project);
144
+ const routerNodes = collectRouterNodes(project);
145
+ const resourceConf = routerFiles.jsResource
146
+ .getVariableDeclarationOrThrow('RESOURCE_CONF')
147
+ .getInitializerIfKindOrThrow(SyntaxKind.AsExpression)
148
+ .getExpressionIfKindOrThrow(SyntaxKind.ObjectLiteralExpression);
149
+ resourceConf.getPropertyOrThrow('noop').remove();
150
+ for (const { resourceName, sourceFile, symbol } of routerNodes.resources) {
151
+ const filePath = path.relative(process.cwd(), sourceFile.getFilePath());
152
+ const moduleSpecifier = routerFiles.jsResource.getRelativePathAsModuleSpecifierTo(sourceFile.getFilePath());
153
+ resourceConf.addPropertyAssignment({
154
+ name: `"${resourceName}"`,
155
+ initializer: (writer) => {
156
+ writer.block(() => {
157
+ writer
158
+ .writeLine(`src: "${filePath}",`)
159
+ .writeLine(`loader: () => import("${moduleSpecifier}").then(m => m.${symbol.getName()})`);
160
+ });
161
+ },
162
+ });
163
+ console.log('Created resource', pc.cyan(resourceName), 'for', pc.green(symbol.getName()), 'exported from', pc.yellow(filePath));
164
+ }
165
+ const routerConf = routerFiles.router
166
+ .getVariableDeclarationOrThrow('ROUTER_CONF')
167
+ .getInitializerIfKindOrThrow(SyntaxKind.AsExpression)
168
+ .getExpressionIfKindOrThrow(SyntaxKind.ObjectLiteralExpression);
169
+ routerConf.getPropertyOrThrow('noop').remove();
170
+ let entryPointImportIndex = 0;
171
+ for (const { routeName, sourceFile, symbol, params } of routerNodes.routes) {
172
+ const importAlias = `e${entryPointImportIndex++}`;
173
+ const filePath = path.relative(process.cwd(), sourceFile.getFilePath());
174
+ const moduleSpecifier = routerFiles.router.getRelativePathAsModuleSpecifierTo(sourceFile.getFilePath());
175
+ routerFiles.router.addImportDeclaration({
176
+ moduleSpecifier,
177
+ namedImports: [
178
+ {
179
+ name: symbol.getName(),
180
+ alias: importAlias,
181
+ },
182
+ ],
183
+ });
184
+ routerConf.addPropertyAssignment({
185
+ name: `"${routeName}"`,
186
+ initializer: (writer) => {
187
+ writer
188
+ .write('{')
189
+ .indent(() => {
190
+ writer.writeLine(`entrypoint: ${importAlias},`);
191
+ if (params.size === 0) {
192
+ writer.writeLine(`schema: z.object({})`);
193
+ }
194
+ else {
195
+ writer.writeLine(`schema: z.object({`);
196
+ for (const [paramName, paramType] of Array.from(params)) {
197
+ writer.writeLine(` ${paramName}: ${zodSchemaOfType(tc, paramType)},`);
198
+ }
199
+ writer.writeLine('})');
200
+ }
201
+ })
202
+ .write('} as const');
203
+ },
204
+ });
205
+ console.log('Created route', pc.cyan(routeName), 'for', pc.green(symbol.getName()), 'exported from', pc.yellow(filePath));
206
+ }
207
+ await Promise.all([routerFiles.jsResource.save(), routerFiles.router.save()]);
208
+ }
209
+ //# sourceMappingURL=generate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate.js","sourceRoot":"","sources":["../src/generate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env node
2
+ import { program } from 'commander';
3
+ import { readFile } from 'node:fs/promises';
4
+ import * as path from 'node:path';
5
+ import { createBuild } from './build';
6
+ import { startDevserver } from './devserver';
7
+ import { generatePastoriaArtifacts } from './generate';
8
+ async function main() {
9
+ const packageData = JSON.parse(await readFile(path.join(import.meta.dirname, '../package.json'), 'utf-8'));
10
+ program
11
+ .name('pastoria')
12
+ .description(packageData.description)
13
+ .version(packageData.version);
14
+ program
15
+ .command('gen')
16
+ .description('Run Pastoria code generation')
17
+ .action(generatePastoriaArtifacts);
18
+ program
19
+ .command('dev')
20
+ .description('Start the pastoria devserver')
21
+ .option('--port <port>', 'Port the devserver will listen on', '3000')
22
+ .action(startDevserver);
23
+ program
24
+ .command('build')
25
+ .description('Creates a production build of the project')
26
+ .action(createBuild);
27
+ program.parseAsync();
28
+ }
29
+ main().catch(console.error);
30
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAC,OAAO,EAAC,MAAM,WAAW,CAAC;AAClC,OAAO,EAAC,QAAQ,EAAC,MAAM,kBAAkB,CAAC;AAC1C,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAC,WAAW,EAAC,MAAM,SAAS,CAAC;AACpC,OAAO,EAAC,cAAc,EAAC,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAC,yBAAyB,EAAC,MAAM,YAAY,CAAC;AAErD,KAAK,UAAU,IAAI;IACjB,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAC5B,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,iBAAiB,CAAC,EAAE,OAAO,CAAC,CAC3E,CAAC;IAEF,OAAO;SACJ,IAAI,CAAC,UAAU,CAAC;SAChB,WAAW,CAAC,WAAW,CAAC,WAAW,CAAC;SACpC,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;IAEhC,OAAO;SACJ,OAAO,CAAC,KAAK,CAAC;SACd,WAAW,CAAC,8BAA8B,CAAC;SAC3C,MAAM,CAAC,yBAAyB,CAAC,CAAC;IAErC,OAAO;SACJ,OAAO,CAAC,KAAK,CAAC;SACd,WAAW,CAAC,8BAA8B,CAAC;SAC3C,MAAM,CAAC,eAAe,EAAE,mCAAmC,EAAE,MAAM,CAAC;SACpE,MAAM,CAAC,cAAc,CAAC,CAAC;IAE1B,OAAO;SACJ,OAAO,CAAC,OAAO,CAAC;SAChB,WAAW,CAAC,2CAA2C,CAAC;SACxD,MAAM,CAAC,WAAW,CAAC,CAAC;IAEvB,OAAO,CAAC,UAAU,EAAE,CAAC;AACvB,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,14 +1,35 @@
1
1
  {
2
2
  "name": "pastoria",
3
- "version": "0.0.1",
3
+ "version": "1.0.2",
4
+ "description": "Pastoria Development CLI",
5
+ "license": "MIT",
4
6
  "type": "module",
5
- "bin": "./src/index.mts",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "bin": {
10
+ "pastoria": "dist/index.js"
11
+ },
6
12
  "dependencies": {
13
+ "@tailwindcss/vite": "^4.1.14",
14
+ "@vitejs/plugin-react": "^5.0.4",
15
+ "commander": "^14.0.1",
16
+ "cookie-parser": "^1.4.7",
17
+ "dotenv": "^16.6.1",
18
+ "express": "^5.1.0",
7
19
  "picocolors": "^1.1.1",
8
- "ts-morph": "^26.0.0"
20
+ "ts-morph": "^26.0.0",
21
+ "vite": "^7.1.9",
22
+ "vite-plugin-cjs-interop": "^2.3.0"
9
23
  },
10
24
  "devDependencies": {
11
- "@types/node": "20.3.1",
12
- "typescript": "^5.9.2"
25
+ "@types/cookie-parser": "^1.4.9",
26
+ "@types/express": "^5.0.3",
27
+ "@types/node": "^22.12.0",
28
+ "typescript": "^5.9.2",
29
+ "pastoria-runtime": "1.0.2"
30
+ },
31
+ "scripts": {
32
+ "build": "tsc",
33
+ "check:types": "tsc --noEmit"
13
34
  }
14
- }
35
+ }
package/src/build.ts ADDED
@@ -0,0 +1,127 @@
1
+ import react from '@vitejs/plugin-react';
2
+ import tailwindcss from '@tailwindcss/vite';
3
+ import {cjsInterop} from 'vite-plugin-cjs-interop';
4
+ import {build, type BuildEnvironmentOptions, type Plugin} from 'vite';
5
+
6
+ // TODO: Only emit `App` code if _app exits.
7
+ const PASTORIA_CLIENT_ENTRY = `// Generated by Pastoria.
8
+ import {createRouterApp} from '#genfiles/router/router';
9
+ import {App} from '#src/pages/_app';
10
+ import {hydrateRoot} from 'react-dom/client';
11
+
12
+ async function main() {
13
+ const RouterApp = await createRouterApp();
14
+ hydrateRoot(document, <RouterApp App={App} />);
15
+ }
16
+
17
+ main();
18
+ `;
19
+
20
+ // TODO: Remove hard-coded context import here.
21
+ const PASTORIA_ENTRY_SERVER = `// Generated by Pastoria.
22
+ import {JSResource} from '#genfiles/router/js_resource';
23
+ import {
24
+ listRoutes,
25
+ router__createAppFromEntryPoint,
26
+ router__loadEntryPoint,
27
+ } from '#genfiles/router/router';
28
+ import {getSchema} from '#genfiles/schema/schema';
29
+ import {Context} from '#src/lib/server/context';
30
+ import {App} from '#src/pages/_app';
31
+ import {GraphQLSchema, specifiedDirectives} from 'graphql';
32
+ import {createRouterHandler} from 'pastoria-runtime/server';
33
+ import type {Manifest} from 'vite';
34
+
35
+ const schemaConfig = getSchema().toConfig();
36
+ const schema = new GraphQLSchema({
37
+ ...schemaConfig,
38
+ directives: [...specifiedDirectives, ...schemaConfig.directives],
39
+ });
40
+
41
+ export function createHandler(
42
+ persistedQueries: Record<string, string>,
43
+ manifest?: Manifest,
44
+ ) {
45
+ return createRouterHandler(
46
+ listRoutes(),
47
+ JSResource.srcOfModuleId,
48
+ router__loadEntryPoint,
49
+ router__createAppFromEntryPoint,
50
+ App,
51
+ schema,
52
+ () => new Context(),
53
+ persistedQueries,
54
+ manifest,
55
+ );
56
+ }
57
+ `;
58
+
59
+ function pastoriaEntryPlugin(): Plugin {
60
+ const clientEntryModuleId = 'virtual:pastoria-entry-client.tsx';
61
+ const serverEntryModuleId = 'virtual:pastoria-entry-server.tsx';
62
+
63
+ return {
64
+ name: 'pastoria-entry',
65
+ resolveId(id) {
66
+ if (id === clientEntryModuleId) {
67
+ return clientEntryModuleId; // Return without \0 prefix so React plugin can see .tsx extension
68
+ } else if (id === serverEntryModuleId) {
69
+ return serverEntryModuleId;
70
+ }
71
+ },
72
+ load(id) {
73
+ if (id === clientEntryModuleId) {
74
+ return PASTORIA_CLIENT_ENTRY;
75
+ } else if (id === serverEntryModuleId) {
76
+ return PASTORIA_ENTRY_SERVER;
77
+ }
78
+ },
79
+ };
80
+ }
81
+
82
+ export const CLIENT_BUILD: BuildEnvironmentOptions = {
83
+ outDir: 'dist/client',
84
+ rollupOptions: {
85
+ input: 'virtual:pastoria-entry-client.tsx',
86
+ },
87
+ };
88
+
89
+ export const SERVER_BUILD: BuildEnvironmentOptions = {
90
+ outDir: 'dist/server',
91
+ ssr: true,
92
+ rollupOptions: {
93
+ input: 'virtual:pastoria-entry-server.tsx',
94
+ },
95
+ };
96
+
97
+ export function createBuildConfig(buildEnv: BuildEnvironmentOptions) {
98
+ return {
99
+ appType: 'custom' as const,
100
+ build: {
101
+ ...buildEnv,
102
+ assetsInlineLimit: 0,
103
+ manifest: true,
104
+ ssrManifest: true,
105
+ },
106
+ plugins: [
107
+ pastoriaEntryPlugin(),
108
+ tailwindcss(),
109
+ react({babel: {plugins: ['relay']}}),
110
+ cjsInterop({
111
+ dependencies: ['react-relay', 'react-relay/hooks', 'relay-runtime'],
112
+ }),
113
+ ],
114
+ };
115
+ }
116
+
117
+ export async function createBuild() {
118
+ const clientBuild = await build({
119
+ ...createBuildConfig(CLIENT_BUILD),
120
+ configFile: false,
121
+ });
122
+
123
+ const serverBuild = await build({
124
+ ...createBuildConfig(SERVER_BUILD),
125
+ configFile: false,
126
+ });
127
+ }
@@ -0,0 +1,53 @@
1
+ import cookieParser from 'cookie-parser';
2
+ import dotenv from 'dotenv';
3
+ import express from 'express';
4
+ import {readFile} from 'node:fs/promises';
5
+ import pc from 'picocolors';
6
+ import {createServer as createViteServer, type Manifest} from 'vite';
7
+ import {CLIENT_BUILD, createBuildConfig} from './build';
8
+
9
+ interface PersistedQueries {
10
+ [hash: string]: string;
11
+ }
12
+
13
+ interface ServerEntry {
14
+ createHandler(
15
+ persistedQueries: PersistedQueries,
16
+ manifest?: Manifest,
17
+ ): express.Router;
18
+ }
19
+
20
+ export async function startDevserver(opts: {port: string}) {
21
+ dotenv.config();
22
+
23
+ const buildConfig = createBuildConfig(CLIENT_BUILD);
24
+ const vite = await createViteServer({
25
+ ...buildConfig,
26
+ configFile: false,
27
+ server: {middlewareMode: true},
28
+ });
29
+
30
+ const app = express();
31
+ app.use(cookieParser());
32
+ app.use(vite.middlewares);
33
+ app.use(async (req, res, next) => {
34
+ const persistedQueries = JSON.parse(
35
+ await readFile('__generated__/persisted_queries.json', 'utf-8'),
36
+ );
37
+
38
+ const {createHandler} = (await vite.ssrLoadModule(
39
+ 'virtual:pastoria-entry-server.tsx',
40
+ )) as ServerEntry;
41
+
42
+ const handler = createHandler(persistedQueries);
43
+ handler(req, res, next);
44
+ });
45
+
46
+ app.listen(Number(opts.port), (err) => {
47
+ if (err) {
48
+ console.error(err);
49
+ } else {
50
+ console.log(pc.cyan(`Listening on port ${opts.port}!`));
51
+ }
52
+ });
53
+ }
@@ -1,4 +1,3 @@
1
- #!/usr/bin/env node --experimental-strip-types
2
1
  /**
3
2
  * @fileoverview Router Code Generator
4
3
  *
@@ -20,12 +19,6 @@
20
19
  *
21
20
  * The generator automatically creates Zod schemas for route parameters based on
22
21
  * TypeScript types, enabling runtime validation and type safety.
23
- *
24
- * Roadmap:
25
- * 1. [DONE] Type-safe router APIs - Generate strongly typed navigation functions
26
- * 2. Support for useTransition during routing - React 19 concurrent features
27
- * 3. Support for metadata management in <head>
28
- * 4. HTML manual generator suitable to be read by LLMs - Auto-generated docs
29
22
  */
30
23
 
31
24
  import {readFile} from 'node:fs/promises';
@@ -35,22 +28,16 @@ import {Project, SourceFile, Symbol, SyntaxKind, ts, TypeFlags} from 'ts-morph';
35
28
 
36
29
  const JS_RESOURCE_FILENAME = '__generated__/router/js_resource.ts';
37
30
  const JS_RESOURCE_TEMPLATE = path.join(
38
- path.dirname(new URL(import.meta.url).pathname),
31
+ import.meta.dirname,
39
32
  '../templates/js_resource.ts',
40
33
  );
41
34
 
42
35
  const ROUTER_FILENAME = '__generated__/router/router.tsx';
43
36
  const ROUTER_TEMPLATE = path.join(
44
- path.dirname(new URL(import.meta.url).pathname),
37
+ import.meta.dirname,
45
38
  '../templates/router.tsx',
46
39
  );
47
40
 
48
- const SERVER_ROUTER_FILENAME = '__generated__/router/server_router.ts';
49
- const SERVER_ROUTER_TEMPLATE = path.join(
50
- path.dirname(new URL(import.meta.url).pathname),
51
- '../templates/server_router.ts',
52
- );
53
-
54
41
  async function loadRouterFiles(project: Project) {
55
42
  async function loadSourceFile(fileName: string, templateFileName: string) {
56
43
  const template = await readFile(templateFileName, 'utf-8');
@@ -65,13 +52,12 @@ async function loadRouterFiles(project: Project) {
65
52
  });
66
53
  }
67
54
 
68
- const [jsResource, router, serverRouter] = await Promise.all([
55
+ const [jsResource, router] = await Promise.all([
69
56
  loadSourceFile(JS_RESOURCE_FILENAME, JS_RESOURCE_TEMPLATE),
70
57
  loadSourceFile(ROUTER_FILENAME, ROUTER_TEMPLATE),
71
- loadSourceFile(SERVER_ROUTER_FILENAME, SERVER_ROUTER_TEMPLATE),
72
58
  ]);
73
59
 
74
- return {jsResource, router, serverRouter} as const;
60
+ return {jsResource, router} as const;
75
61
  }
76
62
 
77
63
  type RouterResource = {
@@ -92,6 +78,7 @@ function collectRouterNodes(project: Project) {
92
78
  const routes: RouterRoute[] = [];
93
79
 
94
80
  function visitRouterNodes(sourceFile: SourceFile) {
81
+ // TODO: Skip sourceFile if a pastora JSDoc tag isn't used at all.
95
82
  sourceFile.getExportSymbols().forEach((symbol) => {
96
83
  let routerResource = null as RouterResource | null;
97
84
  let routerRoute = null as RouterRoute | null;
@@ -180,14 +167,13 @@ function zodSchemaOfType(tc: ts.TypeChecker, t: ts.Type): string {
180
167
  }
181
168
  }
182
169
 
183
- async function main() {
184
- const targetDir = path.resolve(process.argv[2] || process.cwd());
185
- process.chdir(targetDir);
170
+ export async function generatePastoriaArtifacts() {
171
+ const targetDir = process.cwd();
186
172
  const project = new Project({
187
173
  tsConfigFilePath: path.join(targetDir, 'tsconfig.json'),
188
174
  });
189
- const tc = project.getTypeChecker().compilerObject;
190
175
 
176
+ const tc = project.getTypeChecker().compilerObject;
191
177
  const routerFiles = await loadRouterFiles(project);
192
178
  const routerNodes = collectRouterNodes(project);
193
179
 
@@ -287,11 +273,5 @@ async function main() {
287
273
  );
288
274
  }
289
275
 
290
- await Promise.all([
291
- routerFiles.jsResource.save(),
292
- routerFiles.router.save(),
293
- routerFiles.serverRouter.save(),
294
- ]);
276
+ await Promise.all([routerFiles.jsResource.save(), routerFiles.router.save()]);
295
277
  }
296
-
297
- main().catch(console.error);
package/src/index.ts ADDED
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env node
2
+
3
+ import {program} from 'commander';
4
+ import {readFile} from 'node:fs/promises';
5
+ import * as path from 'node:path';
6
+ import {createBuild} from './build';
7
+ import {startDevserver} from './devserver';
8
+ import {generatePastoriaArtifacts} from './generate';
9
+
10
+ async function main() {
11
+ const packageData = JSON.parse(
12
+ await readFile(path.join(import.meta.dirname, '../package.json'), 'utf-8'),
13
+ );
14
+
15
+ program
16
+ .name('pastoria')
17
+ .description(packageData.description)
18
+ .version(packageData.version);
19
+
20
+ program
21
+ .command('gen')
22
+ .description('Run Pastoria code generation')
23
+ .action(generatePastoriaArtifacts);
24
+
25
+ program
26
+ .command('dev')
27
+ .description('Start the pastoria devserver')
28
+ .option('--port <port>', 'Port the devserver will listen on', '3000')
29
+ .action(startDevserver);
30
+
31
+ program
32
+ .command('build')
33
+ .description('Creates a production build of the project')
34
+ .action(createBuild);
35
+
36
+ program.parseAsync();
37
+ }
38
+
39
+ main().catch(console.error);
@@ -1,8 +1,15 @@
1
+ import {
2
+ AnyPreloadedEntryPoint,
3
+ EnvironmentProvider,
4
+ relayClientEnvironment,
5
+ RouterOps,
6
+ } from 'pastoria-runtime';
1
7
  import {createRouter} from 'radix3';
2
8
  import {
3
9
  AnchorHTMLAttributes,
4
10
  createContext,
5
11
  PropsWithChildren,
12
+ StrictMode,
6
13
  Suspense,
7
14
  useCallback,
8
15
  useContext,
@@ -10,22 +17,16 @@ import {
10
17
  useMemo,
11
18
  useState,
12
19
  } from 'react';
20
+ import {preinit, preloadModule} from 'react-dom';
13
21
  import {
14
22
  EntryPoint,
15
23
  EntryPointContainer,
16
- EnvironmentProviderOptions,
17
- IEnvironmentProvider,
18
24
  loadEntryPoint,
19
- PreloadedEntryPoint,
25
+ RelayEnvironmentProvider,
20
26
  useEntryPointLoader,
21
27
  } from 'react-relay/hooks';
22
- import {OperationDescriptor, PayloadData} from 'relay-runtime';
23
- import type {Manifest} from 'vite';
24
28
  import * as z from 'zod/v4-mini';
25
29
 
26
- export type AnyPreloadedEntryPoint = PreloadedEntryPoint<any>;
27
- export type RouterOps = [OperationDescriptor, PayloadData][];
28
-
29
30
  type RouterConf = typeof ROUTER_CONF;
30
31
  const ROUTER_CONF = {
31
32
  noop: {
@@ -121,9 +122,7 @@ function useLocation(initialPath?: string) {
121
122
  return [location, setLocation] as const;
122
123
  }
123
124
 
124
- export function router__hydrateStore(
125
- provider: IEnvironmentProvider<EnvironmentProviderOptions>,
126
- ) {
125
+ export function router__hydrateStore(provider: EnvironmentProvider) {
127
126
  const env = provider.getEnvironment(null);
128
127
  if ('__router_ops' in window) {
129
128
  const ops = (window as any).__router_ops as RouterOps;
@@ -134,7 +133,7 @@ export function router__hydrateStore(
134
133
  }
135
134
 
136
135
  export async function router__loadEntryPoint(
137
- provider: IEnvironmentProvider<EnvironmentProviderOptions>,
136
+ provider: EnvironmentProvider,
138
137
  initialPath?: string,
139
138
  ) {
140
139
  if (!initialPath) initialPath = window.location.href;
@@ -159,12 +158,63 @@ const RouterContext = createContext<RouterContextValue>({
159
158
  setLocation: () => {},
160
159
  });
161
160
 
161
+ const REACT_REFRESH_SCRIPT = `
162
+ import RefreshRuntime from 'http://localhost:3000/@react-refresh'
163
+ RefreshRuntime.injectIntoGlobalHook(window)
164
+ window.$RefreshReg$ = () => {}
165
+ window.$RefreshSig$ = () => (type) => type
166
+ window.__vite_plugin_react_preamble_installed__ = true`;
167
+
162
168
  export function router__createAppFromEntryPoint(
163
- provider: IEnvironmentProvider<EnvironmentProviderOptions>,
164
169
  initialEntryPoint: AnyPreloadedEntryPoint | null,
170
+ provider: EnvironmentProvider,
165
171
  initialPath?: string,
166
172
  ) {
167
- function RouterApp() {
173
+ const env = provider.getEnvironment(null);
174
+
175
+ function RouterShell({
176
+ preloadModules,
177
+ preloadStylesheets,
178
+ children,
179
+ }: PropsWithChildren<{
180
+ preloadModules?: string[];
181
+ preloadStylesheets?: string[];
182
+ }>) {
183
+ for (const m of preloadModules ?? []) {
184
+ preloadModule(m, {as: 'script'});
185
+ }
186
+
187
+ for (const s of preloadStylesheets ?? []) {
188
+ preinit(s, {as: 'style'});
189
+ }
190
+
191
+ return (
192
+ <StrictMode>
193
+ <RelayEnvironmentProvider environment={env}>
194
+ <html>
195
+ <head>
196
+ <meta charSet="utf-8" />
197
+ <meta
198
+ name="viewport"
199
+ content="width=device-width, initial-scale=1"
200
+ />
201
+
202
+ {process.env.NODE_ENV !== 'production' && (
203
+ <script
204
+ type="module"
205
+ dangerouslySetInnerHTML={{__html: REACT_REFRESH_SCRIPT}}
206
+ />
207
+ )}
208
+ </head>
209
+
210
+ <body>{children}</body>
211
+ </html>
212
+ </RelayEnvironmentProvider>
213
+ </StrictMode>
214
+ );
215
+ }
216
+
217
+ function RouterCore() {
168
218
  const [location, setLocation] = useLocation(initialPath);
169
219
  const routerContextValue = useMemo(
170
220
  (): RouterContextValue => ({
@@ -213,16 +263,34 @@ export function router__createAppFromEntryPoint(
213
263
  );
214
264
  }
215
265
 
216
- RouterApp.bootstrap = (manifest?: Manifest): string | null => null;
266
+ function RouterApp(props: {
267
+ preloadModules?: string[];
268
+ preloadStylesheets?: string[];
269
+ App?: React.ComponentType<PropsWithChildren<{}>> | null;
270
+ }) {
271
+ return (
272
+ <RouterShell
273
+ preloadModules={props.preloadModules}
274
+ preloadStylesheets={props.preloadStylesheets}
275
+ >
276
+ {props.App == null ? (
277
+ <RouterCore />
278
+ ) : (
279
+ <props.App children={<RouterCore />} />
280
+ )}
281
+ </RouterShell>
282
+ );
283
+ }
284
+
217
285
  return RouterApp;
218
286
  }
219
287
 
220
- export async function createRouterApp(
221
- provider: IEnvironmentProvider<EnvironmentProviderOptions>,
222
- ) {
288
+ export async function createRouterApp() {
289
+ const provider = relayClientEnvironment;
290
+
223
291
  router__hydrateStore(provider);
224
292
  const ep = await router__loadEntryPoint(provider);
225
- return router__createAppFromEntryPoint(provider, ep);
293
+ return router__createAppFromEntryPoint(ep, provider);
226
294
  }
227
295
 
228
296
  export function usePath() {
package/tsconfig.json CHANGED
@@ -1,17 +1,19 @@
1
1
  {
2
2
  "compilerOptions": {
3
3
  "target": "ES2022",
4
- "module": "NodeNext",
5
- "moduleResolution": "NodeNext",
6
- "allowSyntheticDefaultImports": true,
4
+ "module": "ES2022",
5
+ "lib": ["ES2022", "DOM"],
6
+ "moduleResolution": "bundler",
7
+ "outDir": "./dist",
8
+ "rootDir": "./src",
9
+ "declaration": true,
10
+ "declarationMap": true,
11
+ "sourceMap": true,
12
+ "strict": true,
7
13
  "esModuleInterop": true,
8
- "strict": false,
9
14
  "skipLibCheck": true,
10
15
  "forceConsistentCasingInFileNames": true,
11
- "downlevelIteration": true,
12
- "types": ["node"],
13
- "lib": ["ES2022"],
14
- "noEmit": true
16
+ "jsx": "react-jsx"
15
17
  },
16
18
  "include": ["src/**/*"],
17
19
  "exclude": ["node_modules", "dist"]
@@ -1,112 +0,0 @@
1
- import {
2
- EnvironmentProviderOptions,
3
- IEnvironmentProvider,
4
- OperationDescriptor,
5
- PreloadedQuery,
6
- } from 'react-relay/hooks';
7
- import {
8
- createOperationDescriptor,
9
- GraphQLResponse,
10
- GraphQLSingularResponse,
11
- OperationType,
12
- PayloadData,
13
- PreloadableQueryRegistry,
14
- } from 'relay-runtime';
15
- import serialize from 'serialize-javascript';
16
- import type {Manifest} from 'vite';
17
- import {JSResource} from './js_resource';
18
- import {
19
- AnyPreloadedEntryPoint,
20
- router__createAppFromEntryPoint,
21
- router__loadEntryPoint,
22
- RouterOps,
23
- } from './router';
24
-
25
- type AnyPreloadedQuery = PreloadedQuery<OperationType>;
26
-
27
- function router__bootstrapScripts(
28
- entryPoint: AnyPreloadedEntryPoint,
29
- ops: RouterOps,
30
- manifest?: Manifest,
31
- ) {
32
- let bootstrap = `
33
- <script type="text/javascript">
34
- window.__router_ops = ${serialize(ops)};
35
- </script>`;
36
-
37
- const rootModuleSrc = JSResource.srcOfModuleId(entryPoint.rootModuleID);
38
- if (rootModuleSrc == null) return bootstrap;
39
-
40
- function crawlImports(moduleName: string) {
41
- const chunk = manifest?.[moduleName];
42
- if (!chunk) return;
43
-
44
- chunk.imports?.forEach(crawlImports);
45
- bootstrap =
46
- `<link rel="modulepreload" href="${chunk.file}" />\n` + bootstrap;
47
- }
48
-
49
- crawlImports(rootModuleSrc);
50
- return bootstrap;
51
- }
52
-
53
- async function router__ensureQueryFlushed(
54
- query: AnyPreloadedQuery,
55
- ): Promise<GraphQLResponse> {
56
- return new Promise((resolve, reject) => {
57
- if (query.source == null) {
58
- resolve({data: {}});
59
- } else {
60
- query.source.subscribe({
61
- next: resolve,
62
- error: reject,
63
- });
64
- }
65
- });
66
- }
67
-
68
- async function router__loadQueries(entryPoint: AnyPreloadedEntryPoint) {
69
- const preloadedQueryOps: [OperationDescriptor, PayloadData][] = [];
70
- for (const query of Object.values(
71
- entryPoint?.queries ?? {},
72
- ) as PreloadedQuery<OperationType>[]) {
73
- try {
74
- const payload = await router__ensureQueryFlushed(query);
75
- const concreteRequest =
76
- query.id == null ? null : PreloadableQueryRegistry.get(query.id);
77
-
78
- if (concreteRequest != null) {
79
- const desc = createOperationDescriptor(
80
- concreteRequest,
81
- query.variables,
82
- );
83
-
84
- preloadedQueryOps.push([
85
- desc,
86
- (payload as GraphQLSingularResponse).data!,
87
- ]);
88
- }
89
- } catch (e) {
90
- console.error(e);
91
- throw e;
92
- }
93
- }
94
-
95
- return preloadedQueryOps;
96
- }
97
-
98
- export async function createRouterServerApp(
99
- provider: IEnvironmentProvider<EnvironmentProviderOptions>,
100
- initialPath: string,
101
- ) {
102
- const ep = await router__loadEntryPoint(provider, initialPath);
103
- const ops = ep != null ? await router__loadQueries(ep) : [];
104
- const RouterApp = router__createAppFromEntryPoint(provider, ep, initialPath);
105
-
106
- if (ep != null) {
107
- RouterApp.bootstrap = (manifest) =>
108
- router__bootstrapScripts(ep, ops, manifest);
109
- }
110
-
111
- return RouterApp;
112
- }