@taujs/create-taujs 0.3.2 → 0.5.0

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 (2) hide show
  1. package/dist/index.js +275 -55
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -45,11 +45,15 @@ var PACKAGE_MANAGERS = {
45
45
  pnpm: "pnpm install",
46
46
  yarn: "yarn install"
47
47
  };
48
- var FRAMEWORKS = ["react", "vue"];
48
+ var FRAMEWORKS = ["react", "vue", "solid"];
49
+ var PACKAGE_MANAGER_NAMES = Object.keys(PACKAGE_MANAGERS);
49
50
  function parseArgs() {
50
51
  const rawArgs = process.argv.slice(2);
51
52
  let projectName;
52
53
  let framework;
54
+ let packageManager;
55
+ let sawInstall = false;
56
+ let sawNoInstall = false;
53
57
  for (let i = 0; i < rawArgs.length; i++) {
54
58
  const arg = rawArgs[i];
55
59
  if (arg === "--framework") {
@@ -60,12 +64,37 @@ function parseArgs() {
60
64
  framework = arg.slice("--framework=".length);
61
65
  continue;
62
66
  }
67
+ if (arg === "--package-manager") {
68
+ packageManager = rawArgs[++i];
69
+ continue;
70
+ }
71
+ if (arg.startsWith("--package-manager=")) {
72
+ packageManager = arg.slice("--package-manager=".length);
73
+ continue;
74
+ }
75
+ if (arg === "--install") {
76
+ sawInstall = true;
77
+ continue;
78
+ }
79
+ if (arg === "--no-install") {
80
+ sawNoInstall = true;
81
+ continue;
82
+ }
63
83
  if (!arg.startsWith("-") && !projectName) {
64
84
  projectName = arg;
65
85
  continue;
66
86
  }
67
87
  }
68
- return { projectName, framework };
88
+ return {
89
+ projectName,
90
+ framework,
91
+ packageManager,
92
+ install: sawInstall && !sawNoInstall ? true : sawNoInstall && !sawInstall ? false : void 0,
93
+ installConflict: sawInstall && sawNoInstall
94
+ };
95
+ }
96
+ function validatePackageManager(value) {
97
+ return PACKAGE_MANAGER_NAMES.includes(value) ? true : `Package manager must be one of: ${PACKAGE_MANAGER_NAMES.join(", ")}`;
69
98
  }
70
99
  function validateProjectName(value) {
71
100
  if (!value) return "Project name is required";
@@ -79,7 +108,19 @@ function validateFramework(value) {
79
108
  }
80
109
  async function main() {
81
110
  console.log(pc.cyan("\nWelcome to \u03C4js (taujs)\n"));
82
- const { projectName: argName, framework: argFramework } = parseArgs();
111
+ const { projectName: argName, framework: argFramework, packageManager: argPackageManager, install: argInstall, installConflict } = parseArgs();
112
+ if (installConflict) {
113
+ console.log(pc.red("\n\u2716 --install and --no-install are mutually exclusive"));
114
+ process.exit(1);
115
+ }
116
+ if (argPackageManager) {
117
+ const res = validatePackageManager(argPackageManager);
118
+ if (res !== true) {
119
+ console.log(pc.red(`
120
+ \u2716 Invalid package manager "${argPackageManager}": ${res}`));
121
+ process.exit(1);
122
+ }
123
+ }
83
124
  if (argName) {
84
125
  const res = validateProjectName(argName);
85
126
  if (res !== true) {
@@ -110,12 +151,13 @@ async function main() {
110
151
  message: "Framework:",
111
152
  choices: [
112
153
  { title: "React", value: "react" },
113
- { title: "Vue", value: "vue" }
154
+ { title: "Vue", value: "vue" },
155
+ { title: "Solid", value: "solid" }
114
156
  ],
115
157
  initial: 0
116
158
  },
117
159
  {
118
- type: "select",
160
+ type: argPackageManager ? null : "select",
119
161
  name: "packageManager",
120
162
  message: "Package manager:",
121
163
  choices: [
@@ -126,7 +168,7 @@ async function main() {
126
168
  initial: 0
127
169
  },
128
170
  {
129
- type: "confirm",
171
+ type: argInstall === void 0 ? "confirm" : null,
130
172
  name: "installDeps",
131
173
  message: "Install dependencies now?",
132
174
  initial: true
@@ -158,8 +200,8 @@ async function main() {
158
200
  }
159
201
  const config = {
160
202
  projectName,
161
- packageManager: answers.packageManager,
162
- installDeps: answers.installDeps,
203
+ packageManager: argPackageManager ?? answers.packageManager,
204
+ installDeps: argInstall ?? answers.installDeps,
163
205
  framework
164
206
  };
165
207
  await createProject(config);
@@ -231,14 +273,27 @@ function planFiles(config) {
231
273
  { path: "CLAUDE.md", content: generateClaudeMd() },
232
274
  { path: "src/client/index.html", content: generateIndexHtml() },
233
275
  { path: "src/client/styles.css", content: generateStyles() },
276
+ // The derived type contract (framework-independent): AppData/AppRouteContext come from
277
+ // taujs.config.ts, so client code never hand-writes a payload shape.
278
+ { path: "src/client/app-types.ts", content: generateAppTypes() },
234
279
  // server (framework-independent — a single shared source)
235
280
  { path: "src/server/index.ts", content: generateServerIndex() },
236
281
  { path: "src/server/services/registry.ts", content: generateServiceRegistry() },
237
282
  { path: "src/server/services/example.service.ts", content: generateExampleService() },
238
283
  { path: "src/server/types.d.ts", content: generateServiceTypesAugmentation() },
239
- { path: "src/client/public/favicon.svg", content: generateFavicon() }
284
+ { path: "src/client/public/favicon.svg", content: generateFavicon() },
285
+ // Solid's managed compiler owns a DISJOINT tsconfig project: it must claim the client TSX and
286
+ // nothing else. Pointing it at the root tsconfig would make it claim `src/server/**` too, which
287
+ // is not Solid TSX and is compiled by the server toolchain.
288
+ ...framework === "solid" ? [{ path: "tsconfig.solid.json", json: generateSolidCompilerTsConfig() }] : []
240
289
  ];
241
- const client = framework === "vue" ? [
290
+ const client = framework === "solid" ? [
291
+ { path: "src/client/App.tsx", content: generateAppComponentSolid() },
292
+ { path: "src/client/renderId.ts", content: generateSolidRenderId() },
293
+ { path: "src/client/entry-client.tsx", content: generateEntryClientSolid() },
294
+ { path: "src/client/entry-server.tsx", content: generateEntryServerSolid() },
295
+ { path: "src/client/vite-env.d.ts", content: generateViteEnv() }
296
+ ] : framework === "vue" ? [
242
297
  { path: "src/client/App.vue", content: generateAppVue() },
243
298
  { path: "src/client/HomePage.vue", content: generateHomePageVue() },
244
299
  { path: "src/client/StreamingPage.vue", content: generateStreamingPageVue() },
@@ -273,10 +328,10 @@ function generatePackageJson(projectName, framework) {
273
328
  type: "module",
274
329
  scripts: {
275
330
  dev: "cross-env NODE_ENV=development tsx watch --ignore vite.config.ts --trace-warnings --tsconfig ./src/server/tsconfig.json ./src/server/index.ts --loglevel verbose",
276
- "build:client": "tsx build.ts",
277
- "build:entry-server": "cross-env BUILD_MODE=ssr tsx build.ts",
331
+ "build:client": "cross-env NODE_ENV=production tsx build.ts",
332
+ "build:entry-server": "cross-env NODE_ENV=production BUILD_MODE=ssr tsx build.ts",
278
333
  "build:server": "esbuild src/server/index.ts --bundle --platform=node --format=esm --outfile=dist/server/index.js --external:fastify --external:@taujs/server --external:@taujs/vue",
279
- build: "tsx build.ts && cross-env BUILD_MODE=ssr tsx build.ts && esbuild src/server/index.ts --bundle --platform=node --format=esm --outfile=dist/server/index.js --external:fastify --external:@taujs/server --external:@taujs/vue",
334
+ build: "cross-env NODE_ENV=production tsx build.ts && cross-env NODE_ENV=production BUILD_MODE=ssr tsx build.ts && esbuild src/server/index.ts --bundle --platform=node --format=esm --outfile=dist/server/index.js --external:fastify --external:@taujs/server --external:@taujs/vue",
280
335
  start: "cross-env NODE_ENV=production node dist/server/index.js",
281
336
  lint: "vue-tsc --noEmit"
282
337
  },
@@ -292,6 +347,9 @@ function generatePackageJson(projectName, framework) {
292
347
  "@types/node": "^22.10.5",
293
348
  "@vitejs/plugin-vue": "^6.0.0",
294
349
  "cross-env": "^7.0.3",
350
+ // `build:server` invokes the esbuild BINARY directly, so it must be a declared dependency
351
+ // of the generated project - it is not inherited from vite's own copy.
352
+ esbuild: "^0.25.0",
295
353
  tsx: "^4.19.3",
296
354
  typescript: "^5.7.3",
297
355
  vite: "^7.1.11",
@@ -299,6 +357,43 @@ function generatePackageJson(projectName, framework) {
299
357
  }
300
358
  };
301
359
  }
360
+ if (framework === "solid") {
361
+ return {
362
+ name: projectName,
363
+ version: "0.1.0",
364
+ private: true,
365
+ type: "module",
366
+ scripts: {
367
+ dev: "cross-env NODE_ENV=development tsx watch --ignore vite.config.ts --trace-warnings --tsconfig ./src/server/tsconfig.json ./src/server/index.ts --loglevel verbose",
368
+ "build:client": "cross-env NODE_ENV=production tsx build.ts",
369
+ "build:entry-server": "cross-env NODE_ENV=production BUILD_MODE=ssr tsx build.ts",
370
+ "build:server": "esbuild src/server/index.ts --bundle --platform=node --format=esm --outfile=dist/server/index.js --external:fastify --external:@taujs/server --external:@taujs/solid",
371
+ build: "cross-env NODE_ENV=production tsx build.ts && cross-env NODE_ENV=production BUILD_MODE=ssr tsx build.ts && esbuild src/server/index.ts --bundle --platform=node --format=esm --outfile=dist/server/index.js --external:fastify --external:@taujs/server --external:@taujs/solid",
372
+ start: "cross-env NODE_ENV=production node dist/server/index.js",
373
+ lint: "tsc --noEmit"
374
+ },
375
+ dependencies: {
376
+ "@taujs/server": "latest",
377
+ "@taujs/solid": "latest",
378
+ fastify: "^5.2.0",
379
+ "solid-js": "^1.9.0"
380
+ },
381
+ devDependencies: {
382
+ "@taujs/mcp": "latest",
383
+ "@types/node": "^22.10.5",
384
+ "cross-env": "^7.0.3",
385
+ // `build:server` invokes the esbuild BINARY directly, so it must be a declared dependency
386
+ // of the generated project - it is not inherited from vite's own copy.
387
+ esbuild: "^0.25.0",
388
+ tsx: "^4.19.3",
389
+ typescript: "^5.7.3",
390
+ vite: "^7.1.11",
391
+ // The managed compiler instantiates this internally with `ssr: true` forced; the app never
392
+ // adds it to `plugins` itself.
393
+ "vite-plugin-solid": "^2.11.0"
394
+ }
395
+ };
396
+ }
302
397
  return {
303
398
  name: projectName,
304
399
  version: "0.1.0",
@@ -306,10 +401,10 @@ function generatePackageJson(projectName, framework) {
306
401
  type: "module",
307
402
  scripts: {
308
403
  dev: "cross-env NODE_ENV=development tsx watch --ignore vite.config.ts --trace-warnings --tsconfig ./src/server/tsconfig.json ./src/server/index.ts --loglevel verbose",
309
- "build:client": "tsx build.ts",
310
- "build:entry-server": "cross-env BUILD_MODE=ssr tsx build.ts",
404
+ "build:client": "cross-env NODE_ENV=production tsx build.ts",
405
+ "build:entry-server": "cross-env NODE_ENV=production BUILD_MODE=ssr tsx build.ts",
311
406
  "build:server": "esbuild src/server/index.ts --bundle --platform=node --format=esm --outfile=dist/server/index.js --external:fastify --external:@taujs/server --external:@taujs/react",
312
- build: "tsx build.ts && cross-env BUILD_MODE=ssr tsx build.ts && esbuild src/server/index.ts --bundle --platform=node --format=esm --outfile=dist/server/index.js --external:fastify --external:@taujs/server --external:@taujs/react",
407
+ build: "cross-env NODE_ENV=production tsx build.ts && cross-env NODE_ENV=production BUILD_MODE=ssr tsx build.ts && esbuild src/server/index.ts --bundle --platform=node --format=esm --outfile=dist/server/index.js --external:fastify --external:@taujs/server --external:@taujs/react",
313
408
  start: "cross-env NODE_ENV=production node dist/server/index.js",
314
409
  lint: "tsc --noEmit"
315
410
  },
@@ -327,6 +422,9 @@ function generatePackageJson(projectName, framework) {
327
422
  "@types/react-dom": "^19.0.2",
328
423
  "@vitejs/plugin-react": "^4.6.0",
329
424
  "cross-env": "^7.0.3",
425
+ // `build:server` invokes the esbuild BINARY directly, so it must be a declared dependency
426
+ // of the generated project - it is not inherited from vite's own copy.
427
+ esbuild: "^0.25.0",
330
428
  tsx: "^4.19.3",
331
429
  typescript: "^5.7.3",
332
430
  vite: "^7.1.11"
@@ -351,8 +449,10 @@ function generateTsConfig(framework) {
351
449
  target: "ES2022",
352
450
  module: "ESNext",
353
451
  lib: ["ES2022", "DOM", "DOM.Iterable"],
354
- // Vue SFCs are typed by vue-tsc; React needs the automatic JSX runtime.
452
+ // Vue SFCs are typed by vue-tsc; React needs the automatic JSX runtime; Solid PRESERVES JSX
453
+ // for its own Babel transform and types it through `solid-js`.
355
454
  ...framework === "react" ? { jsx: "react-jsx" } : {},
455
+ ...framework === "solid" ? { jsx: "preserve", jsxImportSource: "solid-js" } : {},
356
456
  moduleResolution: "bundler",
357
457
  resolveJsonModule: true,
358
458
  allowImportingTsExtensions: true,
@@ -378,11 +478,23 @@ function generateServerTsConfig() {
378
478
  };
379
479
  }
380
480
  function generateTaujsConfig(framework) {
381
- const pluginImport = framework === "vue" ? `
382
- import { pluginVue } from '@taujs/vue/plugin';` : "";
383
- const pluginsLine = framework === "vue" ? `
384
- plugins: [pluginVue()],` : "";
385
- return `import { defineConfig } from '@taujs/server/config';${pluginImport}
481
+ const rendererImport = framework === "vue" ? `
482
+ import { vueRenderer } from '@taujs/vue/renderer';` : framework === "solid" ? `
483
+ import { solidRenderer } from '@taujs/solid/renderer';` : `
484
+ import { reactRenderer } from '@taujs/react/renderer';`;
485
+ const rendererLine = framework === "vue" ? `
486
+ renderer: vueRenderer(),` : framework === "solid" ? `
487
+ renderer: solidRenderer({ project: './tsconfig.solid.json' }),` : `
488
+ renderer: reactRenderer({ project: './tsconfig.json' }),`;
489
+ return `import { createServiceData, defineConfig } from '@taujs/server/config';${rendererImport}
490
+
491
+ import type { ServiceRegistry } from './src/server/services/registry.ts';
492
+
493
+ // Registry-typed service declarations: the helper carries each method's RESULT type into
494
+ // RouteData, so the app's types derive from this file instead of being hand-written
495
+ // (see src/client/app-types.ts). Closure handlers and ctx.call remain available for dynamic
496
+ // dispatch - https://taujs.dev/guides/services/
497
+ const serviceData = createServiceData<ServiceRegistry>();
386
498
 
387
499
  export default defineConfig({
388
500
  server: {
@@ -399,17 +511,15 @@ export default defineConfig({
399
511
  apps: [
400
512
  {
401
513
  appId: 'main',
402
- entryPoint: '',${pluginsLine}
514
+ entryPoint: '',${rendererLine}
403
515
  routes: [
404
516
  {
405
517
  path: '/',
406
518
  attr: {
407
519
  render: 'ssr',
408
520
  hydrate: true,
409
- // Direct service invocation: standard SSR
410
- data: async (params, ctx) => {
411
- return ctx.call('example', 'greet', { name: 'SSR' });
412
- },
521
+ // Declared service edge: RouteData<typeof config, '/'> is greet()'s resolved result
522
+ data: serviceData('example', 'greet', () => ({ name: 'SSR' })),
413
523
  },
414
524
  },
415
525
  {
@@ -417,12 +527,8 @@ export default defineConfig({
417
527
  attr: {
418
528
  render: 'streaming',
419
529
  hydrate: true,
420
- // Descriptor-based data: resolved by the server
421
- data: async (params) => ({
422
- args: { name: 'Streaming' },
423
- serviceName: 'example',
424
- serviceMethod: 'greet',
425
- }),
530
+ // The same declared edge on a streaming route: the shell streams while greet() resolves
531
+ data: serviceData('example', 'greet', () => ({ name: 'Streaming' })),
426
532
  // meta recommended for streaming routes for SEO/social and render timing
427
533
  meta: {
428
534
  title: "\u03C4js \u2014 Streaming",
@@ -483,8 +589,10 @@ function generateReadme(projectName, packageManager, framework) {
483
589
  const clientTree = framework === "vue" ? `\u2502 \u2502 \u251C\u2500\u2500 App.vue # Root component (route switch)
484
590
  \u2502 \u2502 \u251C\u2500\u2500 HomePage.vue # SSR route (useSSRData + v-if)
485
591
  \u2502 \u2502 \u251C\u2500\u2500 StreamingPage.vue # Streaming route (await useSSRDataAsync)
592
+ \u2502 \u2502 \u251C\u2500\u2500 app-types.ts # Types derived from taujs.config.ts
486
593
  \u2502 \u2502 \u251C\u2500\u2500 entry-client.ts # Client hydration entry
487
594
  \u2502 \u2502 \u251C\u2500\u2500 entry-server.ts # SSR render entry` : `\u2502 \u2502 \u251C\u2500\u2500 App.tsx # Root component
595
+ \u2502 \u2502 \u251C\u2500\u2500 app-types.ts # Types derived from taujs.config.ts
488
596
  \u2502 \u2502 \u251C\u2500\u2500 entry-client.tsx # Client hydration entry
489
597
  \u2502 \u2502 \u251C\u2500\u2500 entry-server.tsx # SSR render entry`;
490
598
  const mainUi = framework === "vue" ? "App.vue" : "App.tsx";
@@ -579,13 +687,11 @@ import { useSSRStore } from '@taujs/react';
579
687
 
580
688
  import "./styles.css";
581
689
 
582
- type GreetingData = {
583
- message: string;
584
- timestamp: string;
585
- };
690
+ import type { AppData } from './app-types';
586
691
 
587
692
  function GreetingCard() {
588
- const data = useSSRStore<GreetingData>();
693
+ // AppData is derived from taujs.config.ts - greet()'s result, never hand-written.
694
+ const data = useSSRStore<AppData>();
589
695
 
590
696
  return (
591
697
  <section className="card card--primary">
@@ -639,9 +745,9 @@ export function App() {
639
745
  which is ideal for predictable latency and caching.
640
746
  </p>
641
747
  <p>
642
- <strong>STREAM:</strong> The <code>/streaming</code> route uses a service descriptor
643
- and returns a Promise. The <code>&lt;Suspense&gt;</code> boundary above shows
644
- a fallback while the server resolves it, then progressively streams the final content.
748
+ <strong>STREAM:</strong> The <code>/streaming</code> route declares the same typed
749
+ service edge with streaming rendering. The <code>&lt;Suspense&gt;</code> boundary above
750
+ shows a fallback while the server resolves it, then progressively streams the final content.
645
751
  </p>
646
752
  </section>
647
753
 
@@ -879,6 +985,33 @@ code {
879
985
  text-align: center;
880
986
  }`;
881
987
  }
988
+ function generateAppTypes() {
989
+ return `/**
990
+ * The application's type contract, DERIVED from taujs.config.ts - never hand-written.
991
+ * Every import here is type-only, so nothing from the config or server reaches the client bundle.
992
+ */
993
+ import type config from '../../taujs.config.ts';
994
+ import type { RouteContext, RouteData } from '@taujs/server/config';
995
+
996
+ /** The route-discriminated context the host passes to appComponent and headContent. */
997
+ export type AppRouteContext = RouteContext<typeof config>;
998
+
999
+ /**
1000
+ * The union of every declared route's resolved data - both scaffold routes resolve greet(), so
1001
+ * one shared type serves the whole app.
1002
+ *
1003
+ * A declared route WITHOUT \`attr.data\` contributes \`Record<string, undefined>\` (the server
1004
+ * supplies \`{}\` for it), so the union stays usable when such routes exist: reads become
1005
+ * \`T | undefined\`, making code account for the no-data route. When routes diverge further,
1006
+ * narrow per route with the derived aliases below - still from the config, never hand-written.
1007
+ */
1008
+ export type AppData = RouteData<typeof config>;
1009
+
1010
+ // Optional narrowing when routes diverge:
1011
+ // export type HomeData = RouteData<typeof config, '/'>;
1012
+ // export type StreamingData = RouteData<typeof config, '/streaming'>;
1013
+ `;
1014
+ }
882
1015
  function generateViteEnv() {
883
1016
  return `/// <reference types="vite/client" />
884
1017
  `;
@@ -969,13 +1102,11 @@ function generateHomePageVue() {
969
1102
  return `<script setup lang="ts">
970
1103
  import { useSSRData } from '@taujs/vue';
971
1104
 
972
- type GreetingData = {
973
- message: string;
974
- timestamp: string;
975
- };
1105
+ import type { AppData } from './app-types';
976
1106
 
977
1107
  // Fallback idiom: non-blocking; \`data\` is undefined until ready, guarded with v-if.
978
- const data = useSSRData<GreetingData>();
1108
+ // AppData is derived from taujs.config.ts - greet()'s result, never hand-written.
1109
+ const data = useSSRData<AppData>();
979
1110
  </script>
980
1111
 
981
1112
  <template>
@@ -994,13 +1125,11 @@ function generateStreamingPageVue() {
994
1125
  return `<script setup lang="ts">
995
1126
  import { useSSRDataAsync } from '@taujs/vue';
996
1127
 
997
- type GreetingData = {
998
- message: string;
999
- timestamp: string;
1000
- };
1128
+ import type { AppData } from './app-types';
1001
1129
 
1002
1130
  // Suspense idiom: async setup blocks on the data, so streamed routes deliver it in the payload.
1003
- const data = await useSSRDataAsync<GreetingData>();
1131
+ // AppData is derived from taujs.config.ts - greet()'s result, never hand-written.
1132
+ const data = await useSSRDataAsync<AppData>();
1004
1133
  </script>
1005
1134
 
1006
1135
  <template>
@@ -1028,13 +1157,17 @@ function generateEntryServerVue() {
1028
1157
 
1029
1158
  import App from './App.vue';
1030
1159
 
1031
- export const { renderSSR, renderStream } = createRenderer({
1160
+ import type { AppData, AppRouteContext } from './app-types';
1161
+
1162
+ // Generics derived from taujs.config.ts: typed data for headContent and the store, typed
1163
+ // routeContext for appComponent.
1164
+ export const { renderSSR, renderStream } = createRenderer<AppData, AppRouteContext>({
1032
1165
  appComponent: App,
1033
1166
  headContent: ({ data, meta }) => \`
1034
1167
  <title>\${meta?.title || "\u03C4js - Composing systems, not just apps"}</title>
1035
1168
  <meta name="description" content="\${
1036
1169
  meta?.description ||
1037
- (data as { message?: string })?.message ||
1170
+ data?.message ||
1038
1171
  "\u03C4js - Composing systems, not just apps"
1039
1172
  }">
1040
1173
  \`,
@@ -1057,7 +1190,11 @@ function generateEntryServer() {
1057
1190
  return `import { createRenderer } from '@taujs/react';
1058
1191
  import { App } from './App';
1059
1192
 
1060
- export const { renderSSR, renderStream } = createRenderer({
1193
+ import type { AppData, AppRouteContext } from './app-types';
1194
+
1195
+ // Generics derived from taujs.config.ts: typed data for headContent and the store, typed
1196
+ // routeContext for appComponent.
1197
+ export const { renderSSR, renderStream } = createRenderer<AppData, AppRouteContext>({
1061
1198
  appComponent: () => <App />,
1062
1199
  headContent: ({ data, meta }) => \`
1063
1200
  <title>\${meta?.title || "\u03C4js - Composing systems, not just apps"}</title>
@@ -1071,6 +1208,87 @@ export const { renderSSR, renderStream } = createRenderer({
1071
1208
  });
1072
1209
  `;
1073
1210
  }
1211
+ function generateSolidCompilerTsConfig() {
1212
+ return {
1213
+ compilerOptions: {
1214
+ jsx: "preserve",
1215
+ jsxImportSource: "solid-js"
1216
+ },
1217
+ include: ["src/client/**/*.tsx"]
1218
+ };
1219
+ }
1220
+ function generateSolidRenderId() {
1221
+ return `/**
1222
+ * The renderId is a SHARED constant: the server renders Solid's markers and serialised data under
1223
+ * this namespace, and the client must hydrate under the SAME one. It is imported by BOTH entries
1224
+ * on purpose - a literal duplicated in two files is a hydration bug waiting to happen.
1225
+ */
1226
+ export const RENDER_ID = 'app';
1227
+ `;
1228
+ }
1229
+ function generateAppComponentSolid() {
1230
+ return `import { Show } from 'solid-js';
1231
+ import { useSSRStore } from '@taujs/solid';
1232
+
1233
+ import type { AppData } from './app-types';
1234
+
1235
+ export function App() {
1236
+ // Route data arrives through the store, which the renderer provides. On the server it is already
1237
+ // committed before the render begins; on the client it is seeded from window.__INITIAL_DATA__.
1238
+ // AppData is derived from taujs.config.ts - greet()'s result, never hand-written.
1239
+ const store = useSSRStore<AppData>();
1240
+
1241
+ return (
1242
+ <main>
1243
+ <h1>\u03C4js + Solid</h1>
1244
+ <Show when={store.data().message} fallback={<p>No route data.</p>}>
1245
+ <p>{store.data().message}</p>
1246
+ </Show>
1247
+ </main>
1248
+ );
1249
+ }
1250
+ `;
1251
+ }
1252
+ function generateEntryClientSolid() {
1253
+ return `import { hydrateApp } from '@taujs/solid';
1254
+
1255
+ import { App } from './App';
1256
+ import { RENDER_ID } from './renderId';
1257
+
1258
+ hydrateApp({
1259
+ app: () => <App />,
1260
+ renderId: RENDER_ID,
1261
+ rootElementId: 'root',
1262
+ onHydrationError: (error) => {
1263
+ console.error('Hydration failed:', error);
1264
+ },
1265
+ });
1266
+ `;
1267
+ }
1268
+ function generateEntryServerSolid() {
1269
+ return `import { createRenderer } from '@taujs/solid';
1270
+
1271
+ import { App } from './App';
1272
+ import { RENDER_ID } from './renderId';
1273
+
1274
+ import type { AppData, AppRouteContext } from './app-types';
1275
+
1276
+ // Generics derived from taujs.config.ts: typed data for headContent and the store, typed
1277
+ // routeContext for appComponent.
1278
+ export const { renderSSR, renderStream } = createRenderer<AppData, AppRouteContext>({
1279
+ appComponent: () => <App />,
1280
+ renderId: RENDER_ID,
1281
+ headContent: ({ data, meta }) => \`
1282
+ <title>\${meta?.title || "\u03C4js - Composing systems, not just apps"}</title>
1283
+ <meta name="description" content="\${
1284
+ meta?.description ||
1285
+ data?.message ||
1286
+ "\u03C4js - Composing systems, not just apps"
1287
+ }">
1288
+ \`,
1289
+ });
1290
+ `;
1291
+ }
1074
1292
  function generateServerIndex() {
1075
1293
  return `import { createServer } from '@taujs/server';
1076
1294
  import config from '../../taujs.config.ts';
@@ -1104,7 +1322,9 @@ export type ServiceRegistry = typeof serviceRegistry;
1104
1322
  `;
1105
1323
  }
1106
1324
  function generateServiceTypesAugmentation() {
1107
- return `declare module '@taujs/server/config' {
1325
+ return `import '@taujs/server/config';
1326
+
1327
+ declare module '@taujs/server/config' {
1108
1328
  interface ServiceContext {
1109
1329
  tenantId?: string;
1110
1330
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taujs/create-taujs",
3
- "version": "0.3.2",
3
+ "version": "0.5.0",
4
4
  "description": "Scaffold a new τjs application",
5
5
  "author": "Aoede <taujs@aoede.uk.net> (https://www.aoede.uk.net)",
6
6
  "homepage": "https://taujs.dev/",