@tanstack/cta-engine 0.14.1 → 0.14.3
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/dist/add-ons.js +13 -1
- package/dist/create-app.js +1 -1
- package/package.json +1 -1
- package/src/add-ons.ts +12 -1
- package/src/create-app.ts +1 -1
- package/templates/react/add-on/sentry/assets/_dot_cursorrules.append +3 -3
- package/templates/react/add-on/sentry/assets/_dot_env.local.append +9 -0
- package/templates/react/add-on/sentry/assets/src/app/global-middleware.ts +10 -24
- package/templates/react/add-on/sentry/assets/src/routes/demo.sentry.testing.tsx +23 -14
- package/templates/react/add-on/sentry/package.json +1 -3
- package/templates/react/add-on/start/assets/app.config.ts.ejs +15 -2
- package/templates/react/add-on/start/assets/src/client.tsx.ejs +33 -0
- package/templates/react/add-on/start/assets/src/router.tsx.ejs +1 -30
- package/templates/react/add-on/start/assets/src/ssr.tsx.ejs +30 -0
- package/templates/react/add-on/tanstack-query/assets/src/routes/demo.tanstack-query.tsx.ejs +4 -4
- package/tests/snapshots/cra/cr-ts-start-npm.json +2 -2
- package/templates/react/add-on/start/assets/src/client.tsx +0 -8
- package/templates/react/add-on/start/assets/src/ssr.tsx +0 -12
package/dist/add-ons.js
CHANGED
|
@@ -93,7 +93,19 @@ export async function finalizeAddOns(framework, template, chosenAddOnIDs) {
|
|
|
93
93
|
return finalAddOns;
|
|
94
94
|
}
|
|
95
95
|
export async function listAddOns(options, { forcedMode, forcedAddOns = [], }) {
|
|
96
|
-
|
|
96
|
+
let mode = forcedMode;
|
|
97
|
+
if (!mode) {
|
|
98
|
+
if (options.template) {
|
|
99
|
+
mode =
|
|
100
|
+
options.template === 'file-router'
|
|
101
|
+
? 'file-router'
|
|
102
|
+
: 'code-router';
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
mode = 'code-router';
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const addOns = await getAllAddOns(options.framework || DEFAULT_FRAMEWORK, mode);
|
|
97
109
|
for (const addOn of addOns.filter((a) => !forcedAddOns.includes(a.id))) {
|
|
98
110
|
console.log(`${chalk.bold(addOn.id)}: ${addOn.description}`);
|
|
99
111
|
}
|
package/dist/create-app.js
CHANGED
|
@@ -486,6 +486,6 @@ Use the following commands to start your app:
|
|
|
486
486
|
% cd ${options.projectName}
|
|
487
487
|
% ${startCommand}
|
|
488
488
|
|
|
489
|
-
Please
|
|
489
|
+
Please check the README.md for more information on testing, styling, adding routes, react-query, etc.${errorStatement}`);
|
|
490
490
|
}
|
|
491
491
|
}
|
package/package.json
CHANGED
package/src/add-ons.ts
CHANGED
|
@@ -133,9 +133,20 @@ export async function listAddOns(
|
|
|
133
133
|
forcedAddOns?: Array<string>
|
|
134
134
|
},
|
|
135
135
|
) {
|
|
136
|
+
let mode = forcedMode
|
|
137
|
+
if (!mode) {
|
|
138
|
+
if (options.template) {
|
|
139
|
+
mode =
|
|
140
|
+
options.template === 'file-router'
|
|
141
|
+
? 'file-router'
|
|
142
|
+
: ('code-router' as TemplateOptions)
|
|
143
|
+
} else {
|
|
144
|
+
mode = 'code-router' as TemplateOptions
|
|
145
|
+
}
|
|
146
|
+
}
|
|
136
147
|
const addOns = await getAllAddOns(
|
|
137
148
|
options.framework || DEFAULT_FRAMEWORK,
|
|
138
|
-
|
|
149
|
+
mode,
|
|
139
150
|
)
|
|
140
151
|
for (const addOn of addOns.filter((a) => !forcedAddOns.includes(a.id))) {
|
|
141
152
|
console.log(`${chalk.bold(addOn.id)}: ${addOn.description}`)
|
package/src/create-app.ts
CHANGED
|
@@ -792,6 +792,6 @@ Use the following commands to start your app:
|
|
|
792
792
|
% cd ${options.projectName}
|
|
793
793
|
% ${startCommand}
|
|
794
794
|
|
|
795
|
-
Please
|
|
795
|
+
Please check the README.md for more information on testing, styling, adding routes, react-query, etc.${errorStatement}`)
|
|
796
796
|
}
|
|
797
797
|
}
|
|
@@ -6,13 +6,13 @@ Error collection is automatic and configured in `src/router.tsx`.
|
|
|
6
6
|
|
|
7
7
|
## Instrumentation
|
|
8
8
|
|
|
9
|
-
We want our server functions
|
|
9
|
+
We want our server functions instrumented. So if you see a function name like `createServerFn`, you can instrument it with Sentry. You'll need to import `Sentry`:
|
|
10
10
|
|
|
11
11
|
```tsx
|
|
12
|
-
import * as Sentry from '@sentry/
|
|
12
|
+
import * as Sentry from '@sentry/tanstackstart-react'
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
And then wrap the implementation of the server function with `Sentry.startSpan`,
|
|
15
|
+
And then wrap the implementation of the server function with `Sentry.startSpan`, like so:
|
|
16
16
|
|
|
17
17
|
```tsx
|
|
18
18
|
Sentry.startSpan({ name: 'Requesting all the pokemon' }, async () => {
|
|
@@ -1,2 +1,11 @@
|
|
|
1
1
|
# Your Sentry DSN (from your Sentry account)
|
|
2
2
|
VITE_SENTRY_DSN=
|
|
3
|
+
|
|
4
|
+
# Your Sentry organization (from your Sentry account)
|
|
5
|
+
VITE_SENTRY_ORG=
|
|
6
|
+
|
|
7
|
+
# Your Sentry project (from your Sentry account)
|
|
8
|
+
VITE_SENTRY_PROJECT=
|
|
9
|
+
|
|
10
|
+
# Your Sentry authentication token (from your Sentry account)
|
|
11
|
+
SENTRY_AUTH_TOKEN=
|
|
@@ -1,25 +1,11 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
|
|
1
|
+
import * as Sentry from "@sentry/tanstackstart-react";
|
|
2
|
+
import {
|
|
3
|
+
createMiddleware,
|
|
4
|
+
registerGlobalMiddleware,
|
|
5
|
+
} from "@tanstack/react-start";
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
profilesSampleRate: 1.0,
|
|
11
|
-
})
|
|
12
|
-
}).client(() => {
|
|
13
|
-
console.log('Sentry init client')
|
|
14
|
-
Sentry.init({
|
|
15
|
-
dsn: import.meta.env.VITE_SENTRY_DSN,
|
|
16
|
-
tracesSampleRate: 1.0,
|
|
17
|
-
profilesSampleRate: 1.0,
|
|
18
|
-
integrations: [
|
|
19
|
-
Sentry.replayIntegration({
|
|
20
|
-
maskAllText: false,
|
|
21
|
-
blockAllMedia: false,
|
|
22
|
-
})
|
|
23
|
-
]
|
|
24
|
-
})
|
|
25
|
-
})()
|
|
7
|
+
registerGlobalMiddleware({
|
|
8
|
+
middleware: [
|
|
9
|
+
createMiddleware().server(Sentry.sentryGlobalServerMiddlewareHandler()),
|
|
10
|
+
],
|
|
11
|
+
});
|
|
@@ -9,8 +9,7 @@
|
|
|
9
9
|
import * as fs from 'node:fs/promises'
|
|
10
10
|
import { createFileRoute } from '@tanstack/react-router'
|
|
11
11
|
import { createServerFn } from '@tanstack/react-start'
|
|
12
|
-
import * as Sentry from
|
|
13
|
-
import * as SentryServer from '@sentry/node'
|
|
12
|
+
import * as Sentry from "@sentry/tanstackstart-react";
|
|
14
13
|
import { useState, useEffect, useRef } from 'react'
|
|
15
14
|
|
|
16
15
|
export const Route = createFileRoute('/demo/sentry/testing')({
|
|
@@ -21,7 +20,7 @@ export const Route = createFileRoute('/demo/sentry/testing')({
|
|
|
21
20
|
const badServerFunc = createServerFn({
|
|
22
21
|
method: 'GET',
|
|
23
22
|
}).handler(async () => {
|
|
24
|
-
return await
|
|
23
|
+
return await Sentry.startSpan(
|
|
25
24
|
{
|
|
26
25
|
name: 'Reading non-existent file',
|
|
27
26
|
op: 'file.read'
|
|
@@ -31,7 +30,7 @@ const badServerFunc = createServerFn({
|
|
|
31
30
|
await fs.readFile('./doesnt-exist', 'utf-8')
|
|
32
31
|
return true
|
|
33
32
|
} catch (error) {
|
|
34
|
-
|
|
33
|
+
Sentry.captureException(error)
|
|
35
34
|
throw error
|
|
36
35
|
}
|
|
37
36
|
}
|
|
@@ -42,7 +41,7 @@ const badServerFunc = createServerFn({
|
|
|
42
41
|
const goodServerFunc = createServerFn({
|
|
43
42
|
method: 'GET',
|
|
44
43
|
}).handler(async () => {
|
|
45
|
-
return await
|
|
44
|
+
return await Sentry.startSpan(
|
|
46
45
|
{
|
|
47
46
|
name: 'Successful server operation',
|
|
48
47
|
op: 'demo.success'
|
|
@@ -244,6 +243,7 @@ function RouteComponent() {
|
|
|
244
243
|
<div className="space-y-6">
|
|
245
244
|
<div>
|
|
246
245
|
<button
|
|
246
|
+
type="button"
|
|
247
247
|
onClick={() => {
|
|
248
248
|
setDemoStep(prev => prev + 1)
|
|
249
249
|
handleClientError()
|
|
@@ -267,7 +267,8 @@ function RouteComponent() {
|
|
|
267
267
|
<div className="bg-red-900/20 border border-red-500/50 rounded-lg p-2">
|
|
268
268
|
<div className="flex items-center text-red-400 text-sm">
|
|
269
269
|
<svg className="w-4 h-4 mr-2" fill="none" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" stroke="currentColor">
|
|
270
|
-
|
|
270
|
+
<title>Red Warning Sign</title>
|
|
271
|
+
<path d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
|
271
272
|
</svg>
|
|
272
273
|
Client-side error captured and traced
|
|
273
274
|
</div>
|
|
@@ -275,14 +276,15 @@ function RouteComponent() {
|
|
|
275
276
|
<div className="bg-purple-900/20 border border-purple-500/50 rounded-lg p-3">
|
|
276
277
|
<div className="flex items-center justify-between">
|
|
277
278
|
<div className="relative">
|
|
278
|
-
<
|
|
279
|
+
<button
|
|
280
|
+
type="button"
|
|
279
281
|
className={`inline-flex items-center bg-purple-900/40 px-3 py-1.5 rounded-lg border border-purple-500/50 cursor-pointer hover:bg-purple-900/60 transition-all ${copiedSpan === spanOps.clientError ? 'scale-95' : ''}`}
|
|
280
282
|
onClick={() => handleCopy(spanOps.clientError)}
|
|
281
283
|
title="Click to copy operation name"
|
|
282
284
|
>
|
|
283
285
|
<span className="text-purple-300 text-sm font-medium mr-2">span.op</span>
|
|
284
286
|
<code className="text-purple-400 text-sm font-mono">{spanOps.clientError}</code>
|
|
285
|
-
</
|
|
287
|
+
</button>
|
|
286
288
|
{copiedSpan === spanOps.clientError && (
|
|
287
289
|
<div className="absolute -top-8 left-1/2 -translate-x-1/2 bg-green-500/90 text-white text-xs px-2 py-1 rounded animate-fade-out">
|
|
288
290
|
Copied!
|
|
@@ -297,6 +299,7 @@ function RouteComponent() {
|
|
|
297
299
|
|
|
298
300
|
<div>
|
|
299
301
|
<button
|
|
302
|
+
type="button"
|
|
300
303
|
onClick={() => {
|
|
301
304
|
setDemoStep(prev => prev + 1)
|
|
302
305
|
handleClientTrace()
|
|
@@ -332,14 +335,15 @@ function RouteComponent() {
|
|
|
332
335
|
<div className="bg-purple-900/20 border border-purple-500/50 rounded-lg p-3">
|
|
333
336
|
<div className="flex items-center justify-between">
|
|
334
337
|
<div className="relative">
|
|
335
|
-
<
|
|
338
|
+
<button
|
|
339
|
+
type="button"
|
|
336
340
|
className={`inline-flex items-center bg-purple-900/40 px-3 py-1.5 rounded-lg border border-purple-500/50 cursor-pointer hover:bg-purple-900/60 transition-all ${copiedSpan === spanOps.client ? 'scale-95' : ''}`}
|
|
337
341
|
onClick={() => handleCopy(spanOps.client)}
|
|
338
342
|
title="Click to copy operation name"
|
|
339
343
|
>
|
|
340
344
|
<span className="text-purple-300 text-sm font-medium mr-2">span.op</span>
|
|
341
345
|
<code className="text-purple-400 text-sm font-mono">{spanOps.client}</code>
|
|
342
|
-
</
|
|
346
|
+
</button>
|
|
343
347
|
{copiedSpan === spanOps.client && (
|
|
344
348
|
<div className="absolute -top-8 left-1/2 -translate-x-1/2 bg-green-500/90 text-white text-xs px-2 py-1 rounded animate-fade-out">
|
|
345
349
|
Copied!
|
|
@@ -361,6 +365,7 @@ function RouteComponent() {
|
|
|
361
365
|
<div className="space-y-6">
|
|
362
366
|
<div>
|
|
363
367
|
<button
|
|
368
|
+
type="button"
|
|
364
369
|
onClick={() => {
|
|
365
370
|
setDemoStep(prev => prev + 1)
|
|
366
371
|
handleServerError()
|
|
@@ -384,6 +389,7 @@ function RouteComponent() {
|
|
|
384
389
|
<div className="bg-red-900/20 border border-red-500/50 rounded-lg p-3">
|
|
385
390
|
<div className="flex items-center text-red-400 text-sm">
|
|
386
391
|
<svg className="w-4 h-4 mr-2" fill="none" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" stroke="currentColor">
|
|
392
|
+
<title>Red Warning Sign</title>
|
|
387
393
|
<path d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
|
388
394
|
</svg>
|
|
389
395
|
Server-side error captured and traced
|
|
@@ -392,14 +398,15 @@ function RouteComponent() {
|
|
|
392
398
|
<div className="bg-purple-900/20 border border-purple-500/50 rounded-lg p-3">
|
|
393
399
|
<div className="flex items-center justify-between">
|
|
394
400
|
<div className="relative">
|
|
395
|
-
<
|
|
401
|
+
<button
|
|
402
|
+
type="button"
|
|
396
403
|
className={`inline-flex items-center bg-purple-900/40 px-3 py-1.5 rounded-lg border border-purple-500/50 cursor-pointer hover:bg-purple-900/60 transition-all ${copiedSpan === spanOps.serverError ? 'scale-95' : ''}`}
|
|
397
404
|
onClick={() => handleCopy(spanOps.serverError)}
|
|
398
405
|
title="Click to copy operation name"
|
|
399
406
|
>
|
|
400
407
|
<span className="text-purple-300 text-sm font-medium mr-2">span.op</span>
|
|
401
408
|
<code className="text-purple-400 text-sm font-mono">{spanOps.serverError}</code>
|
|
402
|
-
</
|
|
409
|
+
</button>
|
|
403
410
|
{copiedSpan === spanOps.serverError && (
|
|
404
411
|
<div className="absolute -top-8 left-1/2 -translate-x-1/2 bg-green-500/90 text-white text-xs px-2 py-1 rounded animate-fade-out">
|
|
405
412
|
Copied!
|
|
@@ -414,6 +421,7 @@ function RouteComponent() {
|
|
|
414
421
|
|
|
415
422
|
<div>
|
|
416
423
|
<button
|
|
424
|
+
type="button"
|
|
417
425
|
onClick={() => {
|
|
418
426
|
setDemoStep(prev => prev + 1)
|
|
419
427
|
handleServerTrace()
|
|
@@ -449,14 +457,15 @@ function RouteComponent() {
|
|
|
449
457
|
<div className="bg-purple-900/20 border border-purple-500/50 rounded-lg p-3">
|
|
450
458
|
<div className="flex items-center justify-between">
|
|
451
459
|
<div className="relative">
|
|
452
|
-
<
|
|
460
|
+
<button
|
|
461
|
+
type="button"
|
|
453
462
|
className={`inline-flex items-center bg-purple-900/40 px-3 py-1.5 rounded-lg border border-purple-500/50 cursor-pointer hover:bg-purple-900/60 transition-all ${copiedSpan === spanOps.server ? 'scale-95' : ''}`}
|
|
454
463
|
onClick={() => handleCopy(spanOps.server)}
|
|
455
464
|
title="Click to copy operation name"
|
|
456
465
|
>
|
|
457
466
|
<span className="text-purple-300 text-sm font-medium mr-2">span.op</span>
|
|
458
467
|
<code className="text-purple-400 text-sm font-mono">{spanOps.server}</code>
|
|
459
|
-
</
|
|
468
|
+
</button>
|
|
460
469
|
{copiedSpan === spanOps.server && (
|
|
461
470
|
<div className="absolute -top-8 left-1/2 -translate-x-1/2 bg-green-500/90 text-white text-xs px-2 py-1 rounded animate-fade-out">
|
|
462
471
|
Copied!
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { defineConfig } from '@tanstack/react-start/config'
|
|
2
2
|
import viteTsConfigPaths from 'vite-tsconfig-paths'<% if (tailwind) { %>
|
|
3
3
|
import tailwindcss from "@tailwindcss/vite"
|
|
4
|
+
<% } %><% if (addOnEnabled['sentry']) { %>
|
|
5
|
+
import { wrapVinxiConfigWithSentry } from "@sentry/tanstackstart-react";
|
|
4
6
|
<% } %>
|
|
5
|
-
|
|
6
|
-
export default defineConfig({
|
|
7
|
+
const config = defineConfig({
|
|
7
8
|
tsr: {
|
|
8
9
|
appDirectory: 'src',
|
|
9
10
|
},
|
|
@@ -17,3 +18,15 @@ export default defineConfig({
|
|
|
17
18
|
],
|
|
18
19
|
},
|
|
19
20
|
})
|
|
21
|
+
|
|
22
|
+
<% if (addOnEnabled['sentry']) { %>
|
|
23
|
+
export default wrapVinxiConfigWithSentry(config, {
|
|
24
|
+
org: process.env.VITE_SENTRY_ORG,
|
|
25
|
+
project: process.env.VITE_SENTRY_PROJECT,
|
|
26
|
+
authToken: process.env.SENTRY_AUTH_TOKEN,
|
|
27
|
+
// Only print logs for uploading source maps in CI
|
|
28
|
+
// Set to `true` to suppress logs
|
|
29
|
+
silent: !process.env.CI,
|
|
30
|
+
});<% } else { %>
|
|
31
|
+
export default config
|
|
32
|
+
<% } %>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { hydrateRoot } from 'react-dom/client'
|
|
2
|
+
import { StartClient } from '@tanstack/react-start'
|
|
3
|
+
<% if (addOnEnabled['sentry']) { %>
|
|
4
|
+
import * as Sentry from "@sentry/tanstackstart-react";
|
|
5
|
+
<% } %>
|
|
6
|
+
import { createRouter } from './router'
|
|
7
|
+
|
|
8
|
+
const router = createRouter()
|
|
9
|
+
|
|
10
|
+
<% if (addOnEnabled['sentry']) { %>
|
|
11
|
+
Sentry.init({
|
|
12
|
+
dsn: import.meta.env.VITE_SENTRY_DSN,
|
|
13
|
+
integrations: [
|
|
14
|
+
Sentry.tanstackRouterBrowserTracingIntegration(router),
|
|
15
|
+
Sentry.replayIntegration({
|
|
16
|
+
maskAllText: false,
|
|
17
|
+
blockAllMedia: false,
|
|
18
|
+
}),
|
|
19
|
+
],
|
|
20
|
+
// Set tracesSampleRate to 1.0 to capture 100%
|
|
21
|
+
// of transactions for tracing.
|
|
22
|
+
// We recommend adjusting this value in production.
|
|
23
|
+
// Learn more at https://docs.sentry.io/platforms/javascript/configuration/options/#traces-sample-rate
|
|
24
|
+
tracesSampleRate: 1.0,
|
|
25
|
+
// Capture Replay for 10% of all sessions,
|
|
26
|
+
// plus for 100% of sessions with an error.
|
|
27
|
+
// Learn more at https://docs.sentry.io/platforms/javascript/session-replay/configuration/#general-integration-configuration
|
|
28
|
+
replaysSessionSampleRate: 0.1,
|
|
29
|
+
replaysOnErrorSampleRate: 1.0,
|
|
30
|
+
});
|
|
31
|
+
<% } %>
|
|
32
|
+
|
|
33
|
+
hydrateRoot(document, <StartClient router={router} />)
|
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
import { createRouter as createTanstackRouter } from '@tanstack/react-router'<% if (addOnEnabled
|
|
2
|
-
import * as Sentry from '@sentry/react'
|
|
3
|
-
import * as SentryServer from '@sentry/node'
|
|
4
|
-
import { createIsomorphicFn } from '@tanstack/react-start'
|
|
5
|
-
<% } %><% if (addOnEnabled['tanstack-query']) { %>
|
|
1
|
+
import { createRouter as createTanstackRouter } from '@tanstack/react-router'<% if (addOnEnabled['tanstack-query']) { %>
|
|
6
2
|
import { routerWithQueryClient } from '@tanstack/react-router-with-query'
|
|
7
3
|
import * as TanstackQuery from './integrations/tanstack-query/root-provider'
|
|
8
4
|
<% } %>
|
|
@@ -44,31 +40,6 @@ export const createRouter = () => {
|
|
|
44
40
|
return router
|
|
45
41
|
}
|
|
46
42
|
|
|
47
|
-
<% if (addOnEnabled.sentry) { %>
|
|
48
|
-
const router = createRouter()
|
|
49
|
-
createIsomorphicFn().server(() => {
|
|
50
|
-
SentryServer.init({
|
|
51
|
-
dsn: import.meta.env.VITE_SENTRY_DSN,
|
|
52
|
-
tracesSampleRate: 1.0,
|
|
53
|
-
profilesSampleRate: 1.0,
|
|
54
|
-
})
|
|
55
|
-
}).client(() => {
|
|
56
|
-
Sentry.init({
|
|
57
|
-
dsn: import.meta.env.VITE_SENTRY_DSN,
|
|
58
|
-
integrations: [
|
|
59
|
-
Sentry.replayIntegration({
|
|
60
|
-
maskAllText: false,
|
|
61
|
-
blockAllMedia: false,
|
|
62
|
-
}),
|
|
63
|
-
Sentry.tanstackRouterBrowserTracingIntegration(router),
|
|
64
|
-
],
|
|
65
|
-
tracesSampleRate: 1.0,
|
|
66
|
-
replaysSessionSampleRate: 1.0,
|
|
67
|
-
replaysOnErrorSampleRate: 1.0,
|
|
68
|
-
})
|
|
69
|
-
})()
|
|
70
|
-
<% } %>
|
|
71
|
-
|
|
72
43
|
// Register the router instance for type safety
|
|
73
44
|
declare module '@tanstack/react-router' {
|
|
74
45
|
interface Register {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createStartHandler,
|
|
3
|
+
defaultStreamHandler,
|
|
4
|
+
} from '@tanstack/react-start/server'
|
|
5
|
+
import { getRouterManifest } from '@tanstack/react-start/router-manifest'
|
|
6
|
+
|
|
7
|
+
import { createRouter } from './router'
|
|
8
|
+
|
|
9
|
+
<% if (addOnEnabled['sentry']) { %>
|
|
10
|
+
import * as Sentry from "@sentry/tanstackstart-react";
|
|
11
|
+
Sentry.init({
|
|
12
|
+
dsn: process.env.VITE_SENTRY_DSN,
|
|
13
|
+
// Set tracesSampleRate to 1.0 to capture 100%
|
|
14
|
+
// of transactions for tracing.
|
|
15
|
+
// We recommend adjusting this value in production
|
|
16
|
+
// Learn more at
|
|
17
|
+
// https://docs.sentry.io/platforms/javascript/configuration/options/#traces-sample-rate
|
|
18
|
+
tracesSampleRate: 1.0,
|
|
19
|
+
});
|
|
20
|
+
<% } %>
|
|
21
|
+
|
|
22
|
+
let streamHandler = defaultStreamHandler
|
|
23
|
+
<% if (addOnEnabled['sentry']) { %>
|
|
24
|
+
streamHandler = Sentry.wrapStreamHandlerWithSentry(defaultStreamHandler)
|
|
25
|
+
<% } %>
|
|
26
|
+
|
|
27
|
+
export default createStartHandler({
|
|
28
|
+
createRouter,
|
|
29
|
+
getRouterManifest,
|
|
30
|
+
})(streamHandler)
|
|
@@ -24,10 +24,10 @@ function TanStackQueryDemo() {
|
|
|
24
24
|
<% } else { %>
|
|
25
25
|
const { data } = useQuery({
|
|
26
26
|
queryKey: ['people'],
|
|
27
|
-
queryFn: () =>
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
queryFn: () => Promise.resolve([
|
|
28
|
+
{ name: 'John Doe' },
|
|
29
|
+
{ name: 'Jane Doe' },
|
|
30
|
+
]),
|
|
31
31
|
initialData: [],
|
|
32
32
|
})
|
|
33
33
|
<% } %>
|
|
@@ -14,10 +14,10 @@
|
|
|
14
14
|
"/src/routes/demo.start.api-request.tsx": "import { useEffect, useState } from 'react'\n\nimport { createFileRoute } from '@tanstack/react-router'\n\nfunction getNames() {\n return fetch('/api/demo-names').then((res) => res.json())\n}\n\nexport const Route = createFileRoute('/demo/start/api-request')({\n component: Home,\n})\n\nfunction Home() {\n const [names, setNames] = useState<Array<string>>([])\n useEffect(() => {\n getNames().then(setNames)\n }, [])\n\n return (\n <div className=\"p-4\">\n <div>{names.join(', ')}</div>\n </div>\n )\n}\n",
|
|
15
15
|
"/src/routes/demo.start.server-funcs.tsx": "import * as fs from 'node:fs'\nimport { createFileRoute, useRouter } from '@tanstack/react-router'\nimport { createServerFn } from '@tanstack/react-start'\n\nconst filePath = 'count.txt'\n\nasync function readCount() {\n return parseInt(\n await fs.promises.readFile(filePath, 'utf-8').catch(() => '0'),\n )\n}\n\nconst getCount = createServerFn({\n method: 'GET',\n}).handler(() => {\n return readCount()\n})\n\nconst updateCount = createServerFn({ method: 'POST' })\n .validator((d: number) => d)\n .handler(async ({ data }) => {\n const count = await readCount()\n await fs.promises.writeFile(filePath, `${count + data}`)\n })\n\nexport const Route = createFileRoute('/demo/start/server-funcs')({\n component: Home,\n loader: async () => await getCount(),\n})\n\nfunction Home() {\n const router = useRouter()\n const state = Route.useLoaderData()\n\n return (\n <div className=\"p-4\">\n <button\n type=\"button\"\n onClick={() => {\n updateCount({ data: 1 }).then(() => {\n router.invalidate()\n })\n }}\n className=\"bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded\"\n >\n Add 1 to {state}?\n </button>\n </div>\n )\n}\n",
|
|
16
16
|
"/src/routes/index.tsx": "import { createFileRoute } from '@tanstack/react-router'\nimport logo from '../logo.svg'\n\nexport const Route = createFileRoute('/')({\n component: App,\n})\n\nfunction App() {\n return (\n <div className=\"text-center\">\n <header className=\"min-h-screen flex flex-col items-center justify-center bg-[#282c34] text-white text-[calc(10px+2vmin)]\">\n <img\n src={logo}\n className=\"h-[40vmin] pointer-events-none animate-[spin_20s_linear_infinite]\"\n alt=\"logo\"\n />\n <p>\n Edit <code>src/routes/index.tsx</code> and save to reload.\n </p>\n <a\n className=\"text-[#61dafb] hover:underline\"\n href=\"https://reactjs.org\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n Learn React\n </a>\n <a\n className=\"text-[#61dafb] hover:underline\"\n href=\"https://tanstack.com\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n Learn TanStack\n </a>\n </header>\n </div>\n )\n}\n",
|
|
17
|
-
"/src/ssr.tsx": "import {\n createStartHandler,\n defaultStreamHandler,\n} from '@tanstack/react-start/server'\nimport { getRouterManifest } from '@tanstack/react-start/router-manifest'\n\nimport { createRouter } from './router'\n\nexport default createStartHandler({\n createRouter,\n getRouterManifest,\n})(
|
|
17
|
+
"/src/ssr.tsx": "import {\n createStartHandler,\n defaultStreamHandler,\n} from '@tanstack/react-start/server'\nimport { getRouterManifest } from '@tanstack/react-start/router-manifest'\n\nimport { createRouter } from './router'\n\nlet streamHandler = defaultStreamHandler\n\nexport default createStartHandler({\n createRouter,\n getRouterManifest,\n})(streamHandler)\n",
|
|
18
18
|
"/src/styles.css": "@import \"tailwindcss\";\n\nbody {\n @apply m-0;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", \"Roboto\", \"Oxygen\",\n \"Ubuntu\", \"Cantarell\", \"Fira Sans\", \"Droid Sans\", \"Helvetica Neue\",\n sans-serif;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n\ncode {\n font-family: source-code-pro, Menlo, Monaco, Consolas, \"Courier New\",\n monospace;\n}\n",
|
|
19
19
|
"README.md": "Welcome to your new TanStack app! \n\n# Getting Started\n\nTo run this application:\n\n```bash\nnpm install\nnpm run start \n```\n\n# Building For Production\n\nTo build this application for production:\n\n```bash\nnpm run build\n```\n\n## Testing\n\nThis project uses [Vitest](https://vitest.dev/) for testing. You can run the tests with:\n\n```bash\nnpm run test\n```\n\n## Styling\n\nThis project uses [Tailwind CSS](https://tailwindcss.com/) for styling.\n\n\n\n\n## Routing\nThis project uses [TanStack Router](https://tanstack.com/router). The initial setup is a file based router. Which means that the routes are managed as files in `src/routes`.\n\n### Adding A Route\n\nTo add a new route to your application just add another a new file in the `./src/routes` directory.\n\nTanStack will automatically generate the content of the route file for you.\n\nNow that you have two routes you can use a `Link` component to navigate between them.\n\n### Adding Links\n\nTo use SPA (Single Page Application) navigation you will need to import the `Link` component from `@tanstack/react-router`.\n\n```tsx\nimport { Link } from \"@tanstack/react-router\";\n```\n\nThen anywhere in your JSX you can use it like so:\n\n```tsx\n<Link to=\"/about\">About</Link>\n```\n\nThis will create a link that will navigate to the `/about` route.\n\nMore information on the `Link` component can be found in the [Link documentation](https://tanstack.com/router/v1/docs/framework/react/api/router/linkComponent).\n\n### Using A Layout\n\nIn the File Based Routing setup the layout is located in `src/routes/__root.tsx`. Anything you add to the root route will appear in all the routes. The route content will appear in the JSX where you use the `<Outlet />` component.\n\nHere is an example layout that includes a header:\n\n```tsx\nimport { Outlet, createRootRoute } from '@tanstack/react-router'\nimport { TanStackRouterDevtools } from '@tanstack/react-router-devtools'\n\nimport { Link } from \"@tanstack/react-router\";\n\nexport const Route = createRootRoute({\n component: () => (\n <>\n <header>\n <nav>\n <Link to=\"/\">Home</Link>\n <Link to=\"/about\">About</Link>\n </nav>\n </header>\n <Outlet />\n <TanStackRouterDevtools />\n </>\n ),\n})\n```\n\nThe `<TanStackRouterDevtools />` component is not required so you can remove it if you don't want it in your layout.\n\nMore information on layouts can be found in the [Layouts documentation](https://tanstack.com/router/latest/docs/framework/react/guide/routing-concepts#layouts).\n\n\n## Data Fetching\n\nThere are multiple ways to fetch data in your application. You can use TanStack Query to fetch data from a server. But you can also use the `loader` functionality built into TanStack Router to load the data for a route before it's rendered.\n\nFor example:\n\n```tsx\nconst peopleRoute = createRoute({\n getParentRoute: () => rootRoute,\n path: \"/people\",\n loader: async () => {\n const response = await fetch(\"https://swapi.dev/api/people\");\n return response.json() as Promise<{\n results: {\n name: string;\n }[];\n }>;\n },\n component: () => {\n const data = peopleRoute.useLoaderData();\n return (\n <ul>\n {data.results.map((person) => (\n <li key={person.name}>{person.name}</li>\n ))}\n </ul>\n );\n },\n});\n```\n\nLoaders simplify your data fetching logic dramatically. Check out more information in the [Loader documentation](https://tanstack.com/router/latest/docs/framework/react/guide/data-loading#loader-parameters).\n\n### React-Query\n\nReact-Query is an excellent addition or alternative to route loading and integrating it into you application is a breeze.\n\nFirst add your dependencies:\n\n```bash\nnpm install @tanstack/react-query @tanstack/react-query-devtools\n```\n\nNext we'll need to create a query client and provider. We recommend putting those in `main.tsx`.\n\n```tsx\nimport { QueryClient, QueryClientProvider } from \"@tanstack/react-query\";\n\n// ...\n\nconst queryClient = new QueryClient();\n\n// ...\n\nif (!rootElement.innerHTML) {\n const root = ReactDOM.createRoot(rootElement);\n\n root.render(\n <QueryClientProvider client={queryClient}>\n <RouterProvider router={router} />\n </QueryClientProvider>\n );\n}\n```\n\nYou can also add TanStack Query Devtools to the root route (optional).\n\n```tsx\nimport { ReactQueryDevtools } from \"@tanstack/react-query-devtools\";\n\nconst rootRoute = createRootRoute({\n component: () => (\n <>\n <Outlet />\n <ReactQueryDevtools buttonPosition=\"top-right\" />\n <TanStackRouterDevtools />\n </>\n ),\n});\n```\n\nNow you can use `useQuery` to fetch your data.\n\n```tsx\nimport { useQuery } from \"@tanstack/react-query\";\n\nimport \"./App.css\";\n\nfunction App() {\n const { data } = useQuery({\n queryKey: [\"people\"],\n queryFn: () =>\n fetch(\"https://swapi.dev/api/people\")\n .then((res) => res.json())\n .then((data) => data.results as { name: string }[]),\n initialData: [],\n });\n\n return (\n <div>\n <ul>\n {data.map((person) => (\n <li key={person.name}>{person.name}</li>\n ))}\n </ul>\n </div>\n );\n}\n\nexport default App;\n```\n\nYou can find out everything you need to know on how to use React-Query in the [React-Query documentation](https://tanstack.com/query/latest/docs/framework/react/overview).\n\n## State Management\n\nAnother common requirement for React applications is state management. There are many options for state management in React. TanStack Store provides a great starting point for your project.\n\nFirst you need to add TanStack Store as a dependency:\n\n```bash\nnpm install @tanstack/store\n```\n\nNow let's create a simple counter in the `src/App.tsx` file as a demonstration.\n\n```tsx\nimport { useStore } from \"@tanstack/react-store\";\nimport { Store } from \"@tanstack/store\";\nimport \"./App.css\";\n\nconst countStore = new Store(0);\n\nfunction App() {\n const count = useStore(countStore);\n return (\n <div>\n <button onClick={() => countStore.setState((n) => n + 1)}>\n Increment - {count}\n </button>\n </div>\n );\n}\n\nexport default App;\n```\n\nOne of the many nice features of TanStack Store is the ability to derive state from other state. That derived state will update when the base state updates.\n\nLet's check this out by doubling the count using derived state.\n\n```tsx\nimport { useStore } from \"@tanstack/react-store\";\nimport { Store, Derived } from \"@tanstack/store\";\nimport \"./App.css\";\n\nconst countStore = new Store(0);\n\nconst doubledStore = new Derived({\n fn: () => countStore.state * 2,\n deps: [countStore],\n});\ndoubledStore.mount();\n\nfunction App() {\n const count = useStore(countStore);\n const doubledCount = useStore(doubledStore);\n\n return (\n <div>\n <button onClick={() => countStore.setState((n) => n + 1)}>\n Increment - {count}\n </button>\n <div>Doubled - {doubledCount}</div>\n </div>\n );\n}\n\nexport default App;\n```\n\nWe use the `Derived` class to create a new store that is derived from another store. The `Derived` class has a `mount` method that will start the derived store updating.\n\nOnce we've created the derived store we can use it in the `App` component just like we would any other store using the `useStore` hook.\n\nYou can find out everything you need to know on how to use TanStack Store in the [TanStack Store documentation](https://tanstack.com/store/latest).\n\n# Demo files\n\nFiles prefixed with `demo` can be safely deleted. They are there to provide a starting point for you to play around with the features you've installed.\n\n# Learn More\n\nYou can learn more about all of the offerings from TanStack in the [TanStack documentation](https://tanstack.com).\n",
|
|
20
|
-
"app.config.ts": "import { defineConfig } from '@tanstack/react-start/config'\nimport viteTsConfigPaths from 'vite-tsconfig-paths'\nimport tailwindcss from '@tailwindcss/vite'\n\
|
|
20
|
+
"app.config.ts": "import { defineConfig } from '@tanstack/react-start/config'\nimport viteTsConfigPaths from 'vite-tsconfig-paths'\nimport tailwindcss from '@tailwindcss/vite'\n\nconst config = defineConfig({\n tsr: {\n appDirectory: 'src',\n },\n vite: {\n plugins: [\n // this is the plugin that enables path aliases\n viteTsConfigPaths({\n projects: ['./tsconfig.json'],\n }),\n tailwindcss(),\n ],\n },\n})\n\nexport default config\n",
|
|
21
21
|
"package.json": "{\n \"name\": \"TEST\",\n \"private\": true,\n \"type\": \"module\",\n \"scripts\": {\n \"dev\": \"vinxi dev\",\n \"start\": \"vinxi start\",\n \"build\": \"vinxi build\",\n \"serve\": \"vite preview\",\n \"test\": \"vitest run\"\n },\n \"dependencies\": {\n \"@tailwindcss/vite\": \"^4.0.6\",\n \"@tanstack/react-router\": \"^1.114.3\",\n \"@tanstack/react-router-devtools\": \"^1.114.3\",\n \"@tanstack/react-router-with-query\": \"^1.114.3\",\n \"@tanstack/react-start\": \"^1.114.3\",\n \"@tanstack/router-plugin\": \"^1.114.3\",\n \"react\": \"^19.0.0\",\n \"react-dom\": \"^19.0.0\",\n \"tailwindcss\": \"^4.0.6\",\n \"vinxi\": \"^0.5.3\",\n \"vite-tsconfig-paths\": \"^5.1.4\"\n },\n \"devDependencies\": {\n \"@testing-library/dom\": \"^10.4.0\",\n \"@testing-library/react\": \"^16.2.0\",\n \"@types/react\": \"^19.0.8\",\n \"@types/react-dom\": \"^19.0.3\",\n \"@vitejs/plugin-react\": \"^4.3.4\",\n \"jsdom\": \"^26.0.0\",\n \"typescript\": \"^5.7.2\",\n \"vite\": \"^6.1.0\",\n \"vitest\": \"^3.0.5\",\n \"web-vitals\": \"^4.2.4\"\n }\n}",
|
|
22
22
|
"tsconfig.json": "{\n \"include\": [\"**/*.ts\", \"**/*.tsx\"],\n \"compilerOptions\": {\n \"target\": \"ES2022\",\n \"jsx\": \"react-jsx\",\n \"module\": \"ESNext\",\n \"lib\": [\"ES2022\", \"DOM\", \"DOM.Iterable\"],\n \"types\": [\"vite/client\"],\n\n /* Bundler mode */\n \"moduleResolution\": \"bundler\",\n \"allowImportingTsExtensions\": true,\n \"verbatimModuleSyntax\": true,\n \"noEmit\": true,\n\n /* Linting */\n \"skipLibCheck\": true,\n \"strict\": true,\n \"noUnusedLocals\": true,\n \"noUnusedParameters\": true,\n \"noFallthroughCasesInSwitch\": true,\n \"noUncheckedSideEffectImports\": true,\n \"baseUrl\": \".\",\n \"paths\": {\n \"@/*\": [\"./src/*\"],\n }\n }\n}\n"
|
|
23
23
|
},
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createStartHandler,
|
|
3
|
-
defaultStreamHandler,
|
|
4
|
-
} from '@tanstack/react-start/server'
|
|
5
|
-
import { getRouterManifest } from '@tanstack/react-start/router-manifest'
|
|
6
|
-
|
|
7
|
-
import { createRouter } from './router'
|
|
8
|
-
|
|
9
|
-
export default createStartHandler({
|
|
10
|
-
createRouter,
|
|
11
|
-
getRouterManifest,
|
|
12
|
-
})(defaultStreamHandler)
|