@taujs/create-taujs 0.4.0 → 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 +76 -35
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -273,6 +273,9 @@ function planFiles(config) {
273
273
  { path: "CLAUDE.md", content: generateClaudeMd() },
274
274
  { path: "src/client/index.html", content: generateIndexHtml() },
275
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() },
276
279
  // server (framework-independent — a single shared source)
277
280
  { path: "src/server/index.ts", content: generateServerIndex() },
278
281
  { path: "src/server/services/registry.ts", content: generateServiceRegistry() },
@@ -483,7 +486,15 @@ import { reactRenderer } from '@taujs/react/renderer';`;
483
486
  renderer: vueRenderer(),` : framework === "solid" ? `
484
487
  renderer: solidRenderer({ project: './tsconfig.solid.json' }),` : `
485
488
  renderer: reactRenderer({ project: './tsconfig.json' }),`;
486
- return `import { defineConfig } from '@taujs/server/config';${rendererImport}
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>();
487
498
 
488
499
  export default defineConfig({
489
500
  server: {
@@ -507,10 +518,8 @@ export default defineConfig({
507
518
  attr: {
508
519
  render: 'ssr',
509
520
  hydrate: true,
510
- // Direct service invocation: standard SSR
511
- data: async (params, ctx) => {
512
- return ctx.call('example', 'greet', { name: 'SSR' });
513
- },
521
+ // Declared service edge: RouteData<typeof config, '/'> is greet()'s resolved result
522
+ data: serviceData('example', 'greet', () => ({ name: 'SSR' })),
514
523
  },
515
524
  },
516
525
  {
@@ -518,12 +527,8 @@ export default defineConfig({
518
527
  attr: {
519
528
  render: 'streaming',
520
529
  hydrate: true,
521
- // Descriptor-based data: resolved by the server
522
- data: async (params) => ({
523
- args: { name: 'Streaming' },
524
- serviceName: 'example',
525
- serviceMethod: 'greet',
526
- }),
530
+ // The same declared edge on a streaming route: the shell streams while greet() resolves
531
+ data: serviceData('example', 'greet', () => ({ name: 'Streaming' })),
527
532
  // meta recommended for streaming routes for SEO/social and render timing
528
533
  meta: {
529
534
  title: "\u03C4js \u2014 Streaming",
@@ -584,8 +589,10 @@ function generateReadme(projectName, packageManager, framework) {
584
589
  const clientTree = framework === "vue" ? `\u2502 \u2502 \u251C\u2500\u2500 App.vue # Root component (route switch)
585
590
  \u2502 \u2502 \u251C\u2500\u2500 HomePage.vue # SSR route (useSSRData + v-if)
586
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
587
593
  \u2502 \u2502 \u251C\u2500\u2500 entry-client.ts # Client hydration entry
588
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
589
596
  \u2502 \u2502 \u251C\u2500\u2500 entry-client.tsx # Client hydration entry
590
597
  \u2502 \u2502 \u251C\u2500\u2500 entry-server.tsx # SSR render entry`;
591
598
  const mainUi = framework === "vue" ? "App.vue" : "App.tsx";
@@ -680,13 +687,11 @@ import { useSSRStore } from '@taujs/react';
680
687
 
681
688
  import "./styles.css";
682
689
 
683
- type GreetingData = {
684
- message: string;
685
- timestamp: string;
686
- };
690
+ import type { AppData } from './app-types';
687
691
 
688
692
  function GreetingCard() {
689
- const data = useSSRStore<GreetingData>();
693
+ // AppData is derived from taujs.config.ts - greet()'s result, never hand-written.
694
+ const data = useSSRStore<AppData>();
690
695
 
691
696
  return (
692
697
  <section className="card card--primary">
@@ -740,9 +745,9 @@ export function App() {
740
745
  which is ideal for predictable latency and caching.
741
746
  </p>
742
747
  <p>
743
- <strong>STREAM:</strong> The <code>/streaming</code> route uses a service descriptor
744
- and returns a Promise. The <code>&lt;Suspense&gt;</code> boundary above shows
745
- 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.
746
751
  </p>
747
752
  </section>
748
753
 
@@ -980,6 +985,33 @@ code {
980
985
  text-align: center;
981
986
  }`;
982
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
+ }
983
1015
  function generateViteEnv() {
984
1016
  return `/// <reference types="vite/client" />
985
1017
  `;
@@ -1070,13 +1102,11 @@ function generateHomePageVue() {
1070
1102
  return `<script setup lang="ts">
1071
1103
  import { useSSRData } from '@taujs/vue';
1072
1104
 
1073
- type GreetingData = {
1074
- message: string;
1075
- timestamp: string;
1076
- };
1105
+ import type { AppData } from './app-types';
1077
1106
 
1078
1107
  // Fallback idiom: non-blocking; \`data\` is undefined until ready, guarded with v-if.
1079
- const data = useSSRData<GreetingData>();
1108
+ // AppData is derived from taujs.config.ts - greet()'s result, never hand-written.
1109
+ const data = useSSRData<AppData>();
1080
1110
  </script>
1081
1111
 
1082
1112
  <template>
@@ -1095,13 +1125,11 @@ function generateStreamingPageVue() {
1095
1125
  return `<script setup lang="ts">
1096
1126
  import { useSSRDataAsync } from '@taujs/vue';
1097
1127
 
1098
- type GreetingData = {
1099
- message: string;
1100
- timestamp: string;
1101
- };
1128
+ import type { AppData } from './app-types';
1102
1129
 
1103
1130
  // Suspense idiom: async setup blocks on the data, so streamed routes deliver it in the payload.
1104
- 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>();
1105
1133
  </script>
1106
1134
 
1107
1135
  <template>
@@ -1129,13 +1157,17 @@ function generateEntryServerVue() {
1129
1157
 
1130
1158
  import App from './App.vue';
1131
1159
 
1132
- 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>({
1133
1165
  appComponent: App,
1134
1166
  headContent: ({ data, meta }) => \`
1135
1167
  <title>\${meta?.title || "\u03C4js - Composing systems, not just apps"}</title>
1136
1168
  <meta name="description" content="\${
1137
1169
  meta?.description ||
1138
- (data as { message?: string })?.message ||
1170
+ data?.message ||
1139
1171
  "\u03C4js - Composing systems, not just apps"
1140
1172
  }">
1141
1173
  \`,
@@ -1158,7 +1190,11 @@ function generateEntryServer() {
1158
1190
  return `import { createRenderer } from '@taujs/react';
1159
1191
  import { App } from './App';
1160
1192
 
1161
- 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>({
1162
1198
  appComponent: () => <App />,
1163
1199
  headContent: ({ data, meta }) => \`
1164
1200
  <title>\${meta?.title || "\u03C4js - Composing systems, not just apps"}</title>
@@ -1194,12 +1230,13 @@ function generateAppComponentSolid() {
1194
1230
  return `import { Show } from 'solid-js';
1195
1231
  import { useSSRStore } from '@taujs/solid';
1196
1232
 
1197
- type RouteData = { message?: string };
1233
+ import type { AppData } from './app-types';
1198
1234
 
1199
1235
  export function App() {
1200
1236
  // Route data arrives through the store, which the renderer provides. On the server it is already
1201
1237
  // committed before the render begins; on the client it is seeded from window.__INITIAL_DATA__.
1202
- const store = useSSRStore<RouteData>();
1238
+ // AppData is derived from taujs.config.ts - greet()'s result, never hand-written.
1239
+ const store = useSSRStore<AppData>();
1203
1240
 
1204
1241
  return (
1205
1242
  <main>
@@ -1234,7 +1271,11 @@ function generateEntryServerSolid() {
1234
1271
  import { App } from './App';
1235
1272
  import { RENDER_ID } from './renderId';
1236
1273
 
1237
- export const { renderSSR, renderStream } = createRenderer({
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>({
1238
1279
  appComponent: () => <App />,
1239
1280
  renderId: RENDER_ID,
1240
1281
  headContent: ({ data, meta }) => \`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taujs/create-taujs",
3
- "version": "0.4.0",
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/",