@timber-js/app 0.1.3 → 0.1.4
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/_chunks/{interception-DIaZN1bF.js → interception-c-a3uODY.js} +8 -8
- package/dist/_chunks/interception-c-a3uODY.js.map +1 -0
- package/dist/adapters/cloudflare.d.ts +2 -2
- package/dist/adapters/cloudflare.js +4 -4
- package/dist/adapters/cloudflare.js.map +1 -1
- package/dist/adapters/nitro.d.ts +1 -1
- package/dist/adapters/nitro.js +4 -4
- package/dist/adapters/nitro.js.map +1 -1
- package/dist/cache/index.js.map +1 -1
- package/dist/client/form.d.ts +1 -1
- package/dist/client/index.js +3 -3
- package/dist/client/index.js.map +1 -1
- package/dist/client/use-link-status.d.ts +1 -1
- package/dist/client/use-navigation-pending.d.ts +1 -1
- package/dist/content/index.d.ts +1 -1
- package/dist/cookies/define-cookie.d.ts +2 -2
- package/dist/cookies/index.d.ts +1 -1
- package/dist/cookies/index.d.ts.map +1 -1
- package/dist/cookies/index.js +2 -2
- package/dist/cookies/index.js.map +1 -1
- package/dist/index.js +13 -13
- package/dist/index.js.map +1 -1
- package/dist/plugins/dev-logs.d.ts +1 -1
- package/dist/plugins/dynamic-transform.d.ts +1 -1
- package/dist/routing/codegen.d.ts +1 -1
- package/dist/routing/index.js +1 -1
- package/dist/search-params/codecs.d.ts +2 -2
- package/dist/search-params/create.d.ts +1 -1
- package/dist/search-params/index.js +4 -4
- package/dist/search-params/index.js.map +1 -1
- package/dist/server/action-client.d.ts +1 -1
- package/dist/server/dev-fetch-instrumentation.d.ts +22 -0
- package/dist/server/dev-fetch-instrumentation.d.ts.map +1 -0
- package/dist/server/dev-logger.d.ts.map +1 -1
- package/dist/server/form-flash.d.ts +1 -1
- package/dist/server/index.js +2 -2
- package/dist/server/index.js.map +1 -1
- package/dist/server/route-element-builder.d.ts.map +1 -1
- package/dist/server/rsc-entry/index.d.ts.map +1 -1
- package/dist/shims/link.d.ts +1 -1
- package/package.json +1 -1
- package/src/adapters/cloudflare.ts +4 -4
- package/src/adapters/nitro.ts +4 -4
- package/src/cache/index.ts +1 -1
- package/src/client/form.tsx +1 -1
- package/src/client/index.ts +1 -1
- package/src/client/use-link-status.ts +1 -1
- package/src/client/use-navigation-pending.ts +1 -1
- package/src/content/index.ts +1 -1
- package/src/cookies/define-cookie.ts +2 -2
- package/src/cookies/index.ts +2 -6
- package/src/plugins/cache-transform.ts +1 -1
- package/src/plugins/dev-logs.ts +2 -2
- package/src/plugins/dynamic-transform.ts +2 -2
- package/src/plugins/shims.ts +10 -10
- package/src/routing/codegen.ts +9 -9
- package/src/search-params/codecs.ts +2 -2
- package/src/search-params/create.ts +3 -3
- package/src/search-params/index.ts +1 -1
- package/src/server/action-client.ts +1 -1
- package/src/server/asset-headers.ts +1 -1
- package/src/server/dev-fetch-instrumentation.ts +96 -0
- package/src/server/dev-logger.ts +49 -0
- package/src/server/form-flash.ts +1 -1
- package/src/server/index.ts +1 -1
- package/src/server/route-element-builder.ts +2 -5
- package/src/server/rsc-entry/index.ts +4 -0
- package/src/shims/link.ts +1 -1
- package/dist/_chunks/interception-DIaZN1bF.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route-element-builder.d.ts","sourceRoot":"","sources":["../../src/server/route-element-builder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAKH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"route-element-builder.d.ts","sourceRoot":"","sources":["../../src/server/route-element-builder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAKH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAK9D,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAO7D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAIzD,qDAAqD;AACrD,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;CACvC;AAED,+CAA+C;AAC/C,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC;IAC3C,OAAO,EAAE,mBAAmB,CAAC;CAC9B;AAED,+CAA+C;AAC/C,MAAM,WAAW,kBAAkB;IACjC,wFAAwF;IACxF,OAAO,EAAE,KAAK,CAAC,YAAY,CAAC;IAC5B,2CAA2C;IAC3C,YAAY,EAAE,WAAW,EAAE,CAAC;IAC5B,wDAAwD;IACxD,gBAAgB,EAAE,oBAAoB,EAAE,CAAC;IACzC,qCAAqC;IACrC,QAAQ,EAAE,mBAAmB,EAAE,CAAC;IAChC,4DAA4D;IAC5D,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED;;;GAGG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;aAE7B,MAAM,EAAE,UAAU,GAAG,cAAc;aACnC,gBAAgB,EAAE,oBAAoB,EAAE;aACxC,QAAQ,EAAE,mBAAmB,EAAE;gBAF/B,MAAM,EAAE,UAAU,GAAG,cAAc,EACnC,gBAAgB,EAAE,oBAAoB,EAAE,EACxC,QAAQ,EAAE,mBAAmB,EAAE;CAIlD;AAID;;;;;;;;;GASG;AACH,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,UAAU,EACjB,YAAY,CAAC,EAAE,mBAAmB,GACjC,OAAO,CAAC,kBAAkB,CAAC,CAyS7B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/index.ts"],"names":[],"mappings":"AA0EA;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAE/F;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/index.ts"],"names":[],"mappings":"AA0EA;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAE/F;AA+lBD,OAAO,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;8BA3epD,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC;AA6ehD,wBAAiE"}
|
package/dist/shims/link.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@timber-js/app",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Vite-native React framework for Cloudflare Workers — correct HTTP semantics, real status codes, pages that work without JavaScript",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -15,7 +15,7 @@ const IMMUTABLE_CACHE = 'public, max-age=31536000, immutable';
|
|
|
15
15
|
const STATIC_CACHE = 'public, max-age=3600, must-revalidate';
|
|
16
16
|
|
|
17
17
|
function generateHeadersFile(): string {
|
|
18
|
-
return `# Auto-generated by @timber/app — static asset cache headers.
|
|
18
|
+
return `# Auto-generated by @timber-js/app — static asset cache headers.
|
|
19
19
|
# See design/25-production-deployments.md §"CDN / Edge Cache"
|
|
20
20
|
|
|
21
21
|
/assets/*
|
|
@@ -46,7 +46,7 @@ const bindingsAls = new AsyncLocalStorage<Record<string, unknown>>();
|
|
|
46
46
|
*
|
|
47
47
|
* @example
|
|
48
48
|
* ```ts
|
|
49
|
-
* import { getCloudflareBindings } from '@timber/app/adapters/cloudflare'
|
|
49
|
+
* import { getCloudflareBindings } from '@timber-js/app/adapters/cloudflare'
|
|
50
50
|
*
|
|
51
51
|
* export default async function Page() {
|
|
52
52
|
* const { MY_KV, MY_DB } = getCloudflareBindings()
|
|
@@ -103,7 +103,7 @@ export interface CloudflareAdapterOptions {
|
|
|
103
103
|
*
|
|
104
104
|
* @example
|
|
105
105
|
* ```ts
|
|
106
|
-
* import { cloudflare } from '@timber/app/adapters/cloudflare'
|
|
106
|
+
* import { cloudflare } from '@timber-js/app/adapters/cloudflare'
|
|
107
107
|
*
|
|
108
108
|
* export default {
|
|
109
109
|
* output: 'server',
|
|
@@ -229,7 +229,7 @@ export function generateWorkerEntry(
|
|
|
229
229
|
// ESM guarantees imports are evaluated in order.
|
|
230
230
|
const manifestImport = hasManifestInit ? "import './_timber-manifest-init.js'\n" : '';
|
|
231
231
|
|
|
232
|
-
return `// Generated by @timber/app/adapters/cloudflare
|
|
232
|
+
return `// Generated by @timber-js/app/adapters/cloudflare
|
|
233
233
|
// Do not edit — this file is regenerated on each build.
|
|
234
234
|
|
|
235
235
|
${manifestImport}import handler from '${rscEntryRelative}'
|
package/src/adapters/nitro.ts
CHANGED
|
@@ -16,7 +16,7 @@ const IMMUTABLE_CACHE = 'public, max-age=31536000, immutable';
|
|
|
16
16
|
const STATIC_CACHE = 'public, max-age=3600, must-revalidate';
|
|
17
17
|
|
|
18
18
|
function generateHeadersFile(): string {
|
|
19
|
-
return `# Auto-generated by @timber/app — static asset cache headers.
|
|
19
|
+
return `# Auto-generated by @timber-js/app — static asset cache headers.
|
|
20
20
|
# See design/25-production-deployments.md §"CDN / Edge Cache"
|
|
21
21
|
|
|
22
22
|
/assets/*
|
|
@@ -156,7 +156,7 @@ export interface NitroAdapterOptions {
|
|
|
156
156
|
*
|
|
157
157
|
* @example
|
|
158
158
|
* ```ts
|
|
159
|
-
* import { nitro } from '@timber/app/adapters/nitro'
|
|
159
|
+
* import { nitro } from '@timber-js/app/adapters/nitro'
|
|
160
160
|
*
|
|
161
161
|
* export default {
|
|
162
162
|
* output: 'server',
|
|
@@ -265,7 +265,7 @@ export function generateNitroEntry(
|
|
|
265
265
|
: await handler(webRequest)`
|
|
266
266
|
: ` const webResponse = await handler(webRequest)`;
|
|
267
267
|
|
|
268
|
-
return `// Generated by @timber/app/adapters/nitro
|
|
268
|
+
return `// Generated by @timber-js/app/adapters/nitro
|
|
269
269
|
// Do not edit — this file is regenerated on each build.
|
|
270
270
|
|
|
271
271
|
${manifestImport}${earlyHintsImport}import { defineEventHandler, toWebRequest, sendWebResponse } from 'h3'
|
|
@@ -304,7 +304,7 @@ export function generateNitroConfig(
|
|
|
304
304
|
|
|
305
305
|
const configJson = JSON.stringify(config, null, 2);
|
|
306
306
|
|
|
307
|
-
return `// Generated by @timber/app/adapters/nitro
|
|
307
|
+
return `// Generated by @timber-js/app/adapters/nitro
|
|
308
308
|
// Do not edit — this file is regenerated on each build.
|
|
309
309
|
|
|
310
310
|
import { defineNitroConfig } from 'nitropack/config'
|
package/src/cache/index.ts
CHANGED
package/src/client/form.tsx
CHANGED
|
@@ -51,7 +51,7 @@ export type UseActionStateReturn<TData> = [
|
|
|
51
51
|
* @example
|
|
52
52
|
* ```tsx
|
|
53
53
|
* 'use client'
|
|
54
|
-
* import { useActionState } from '@timber/app/client'
|
|
54
|
+
* import { useActionState } from '@timber-js/app/client'
|
|
55
55
|
* import { createTodo } from './actions'
|
|
56
56
|
*
|
|
57
57
|
* export function NewTodoForm({ flash }) {
|
package/src/client/index.ts
CHANGED
|
@@ -25,7 +25,7 @@ export const LinkStatusContext = createContext<LinkStatus>({ pending: false });
|
|
|
25
25
|
*
|
|
26
26
|
* ```tsx
|
|
27
27
|
* 'use client'
|
|
28
|
-
* import { Link, useLinkStatus } from '@timber/app/client'
|
|
28
|
+
* import { Link, useLinkStatus } from '@timber-js/app/client'
|
|
29
29
|
*
|
|
30
30
|
* function Hint() {
|
|
31
31
|
* const { pending } = useLinkStatus()
|
|
@@ -16,7 +16,7 @@ import { getRouter } from './router-ref.js';
|
|
|
16
16
|
*
|
|
17
17
|
* ```tsx
|
|
18
18
|
* 'use client'
|
|
19
|
-
* import { useNavigationPending } from '@timber/app/client'
|
|
19
|
+
* import { useNavigationPending } from '@timber-js/app/client'
|
|
20
20
|
*
|
|
21
21
|
* export function NavBar() {
|
|
22
22
|
* const isPending = useNavigationPending()
|
package/src/content/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @timber/app/content — Public API for content collections.
|
|
2
|
+
* @timber-js/app/content — Public API for content collections.
|
|
3
3
|
*
|
|
4
4
|
* Re-exports from content-collections and provides timber-specific utilities.
|
|
5
5
|
* Users can import directly from 'content-collections' for generated types,
|
|
@@ -60,8 +60,8 @@ export interface CookieDefinition<T> {
|
|
|
60
60
|
* Define a typed cookie.
|
|
61
61
|
*
|
|
62
62
|
* ```ts
|
|
63
|
-
* import { defineCookie } from '@timber/app/cookies';
|
|
64
|
-
* import { fromSchema } from '@timber/app/search-params';
|
|
63
|
+
* import { defineCookie } from '@timber-js/app/cookies';
|
|
64
|
+
* import { fromSchema } from '@timber-js/app/search-params';
|
|
65
65
|
* import { z } from 'zod/v4';
|
|
66
66
|
*
|
|
67
67
|
* export const themeCookie = defineCookie('theme', {
|
package/src/cookies/index.ts
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
|
-
// @timber/app/cookies — Typed cookie definitions
|
|
1
|
+
// @timber-js/app/cookies — Typed cookie definitions
|
|
2
2
|
// See design/29-cookies.md §"Typed Cookies with Schema Validation"
|
|
3
3
|
|
|
4
4
|
export { defineCookie } from './define-cookie.js';
|
|
5
|
-
export type {
|
|
6
|
-
CookieDefinition,
|
|
7
|
-
CookieCodec,
|
|
8
|
-
DefineCookieOptions,
|
|
9
|
-
} from './define-cookie.js';
|
|
5
|
+
export type { CookieDefinition, CookieCodec, DefineCookieOptions } from './define-cookie.js';
|
|
@@ -162,7 +162,7 @@ export function transformUseCache(code: string, fileId: string): TransformResult
|
|
|
162
162
|
|
|
163
163
|
if (needsImport) {
|
|
164
164
|
// Add the import at the top of the file
|
|
165
|
-
result = `import { registerCachedFunction } from '@timber/app/cache';\n` + result;
|
|
165
|
+
result = `import { registerCachedFunction } from '@timber-js/app/cache';\n` + result;
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
return { code: result, map: null, warnings: warnings.length > 0 ? warnings : undefined };
|
package/src/plugins/dev-logs.ts
CHANGED
|
@@ -178,7 +178,7 @@ function extractCallerLocation(projectRoot: string): string | null {
|
|
|
178
178
|
* (render errors, action errors, route handler errors, etc.).
|
|
179
179
|
*
|
|
180
180
|
* Handles both monorepo paths (timber-app/src/plugins/) and installed
|
|
181
|
-
* package paths (@timber/app/dist/plugins/).
|
|
181
|
+
* package paths (@timber-js/app/dist/plugins/).
|
|
182
182
|
*/
|
|
183
183
|
export function isFrameworkInternalCaller(): boolean {
|
|
184
184
|
const err = new Error();
|
|
@@ -194,7 +194,7 @@ export function isFrameworkInternalCaller(): boolean {
|
|
|
194
194
|
if (line.includes('node:')) continue;
|
|
195
195
|
|
|
196
196
|
// Check if this first real frame is inside timber's own source
|
|
197
|
-
const isTimberPath = line.includes('timber-app/') || line.includes('@timber/app/');
|
|
197
|
+
const isTimberPath = line.includes('timber-app/') || line.includes('@timber-js/app/');
|
|
198
198
|
if (!isTimberPath) return false;
|
|
199
199
|
|
|
200
200
|
// Only filter plugin and adapter internals, not server/ runtime code
|
|
@@ -54,7 +54,7 @@ interface TransformResult {
|
|
|
54
54
|
*
|
|
55
55
|
* Output:
|
|
56
56
|
* ```tsx
|
|
57
|
-
* import { markDynamic as __markDynamic } from '@timber/app/runtime';
|
|
57
|
+
* import { markDynamic as __markDynamic } from '@timber-js/app/runtime';
|
|
58
58
|
* export default async function AddToCartButton({ productId }) {
|
|
59
59
|
* __markDynamic();
|
|
60
60
|
* const user = await getUser()
|
|
@@ -83,7 +83,7 @@ export function transformUseDynamic(code: string): TransformResult | null {
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
// Add the import at the top
|
|
86
|
-
result = `import { markDynamic as __markDynamic } from '@timber/app/runtime';\n` + result;
|
|
86
|
+
result = `import { markDynamic as __markDynamic } from '@timber-js/app/runtime';\n` + result;
|
|
87
87
|
|
|
88
88
|
return { code: result, map: null };
|
|
89
89
|
}
|
package/src/plugins/shims.ts
CHANGED
|
@@ -56,17 +56,17 @@ const CLIENT_SHIM_OVERRIDES: Record<string, string> = {
|
|
|
56
56
|
};
|
|
57
57
|
|
|
58
58
|
/**
|
|
59
|
-
* Map from @timber/app/* subpath imports to real source files.
|
|
59
|
+
* Map from @timber-js/app/* subpath imports to real source files.
|
|
60
60
|
*
|
|
61
|
-
* These resolve subpath imports like `@timber/app/server` to the
|
|
61
|
+
* These resolve subpath imports like `@timber-js/app/server` to the
|
|
62
62
|
* real entry files in the package source.
|
|
63
63
|
*/
|
|
64
64
|
const TIMBER_SUBPATH_MAP: Record<string, string> = {
|
|
65
|
-
'@timber/app/server': resolve(__dirname, '..', 'server', 'index.ts'),
|
|
66
|
-
'@timber/app/client': resolve(__dirname, '..', 'client', 'index.ts'),
|
|
67
|
-
'@timber/app/cache': resolve(__dirname, '..', 'cache', 'index.ts'),
|
|
68
|
-
'@timber/app/search-params': resolve(__dirname, '..', 'search-params', 'index.ts'),
|
|
69
|
-
'@timber/app/routing': resolve(__dirname, '..', 'routing', 'index.ts'),
|
|
65
|
+
'@timber-js/app/server': resolve(__dirname, '..', 'server', 'index.ts'),
|
|
66
|
+
'@timber-js/app/client': resolve(__dirname, '..', 'client', 'index.ts'),
|
|
67
|
+
'@timber-js/app/cache': resolve(__dirname, '..', 'cache', 'index.ts'),
|
|
68
|
+
'@timber-js/app/search-params': resolve(__dirname, '..', 'search-params', 'index.ts'),
|
|
69
|
+
'@timber-js/app/routing': resolve(__dirname, '..', 'routing', 'index.ts'),
|
|
70
70
|
};
|
|
71
71
|
|
|
72
72
|
/**
|
|
@@ -94,13 +94,13 @@ export function timberShims(_ctx: PluginContext): Plugin {
|
|
|
94
94
|
enforce: 'pre',
|
|
95
95
|
|
|
96
96
|
/**
|
|
97
|
-
* Resolve next/* and @timber/app/* imports to shim/source files.
|
|
97
|
+
* Resolve next/* and @timber-js/app/* imports to shim/source files.
|
|
98
98
|
*
|
|
99
99
|
* Resolution order:
|
|
100
100
|
* 1. Check server-only / client-only poison pill packages
|
|
101
101
|
* 2. Strip .js extension from the import specifier
|
|
102
102
|
* 3. Check next/* shim map
|
|
103
|
-
* 4. Check @timber/app/* subpath map
|
|
103
|
+
* 4. Check @timber-js/app/* subpath map
|
|
104
104
|
* 5. Return null (pass through) for unrecognized imports
|
|
105
105
|
*/
|
|
106
106
|
resolveId(id: string) {
|
|
@@ -121,7 +121,7 @@ export function timberShims(_ctx: PluginContext): Plugin {
|
|
|
121
121
|
return SHIM_MAP[cleanId];
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
// Check @timber/app/* subpath map
|
|
124
|
+
// Check @timber-js/app/* subpath map
|
|
125
125
|
if (cleanId in TIMBER_SUBPATH_MAP) {
|
|
126
126
|
return TIMBER_SUBPATH_MAP[cleanId];
|
|
127
127
|
}
|
package/src/routing/codegen.ts
CHANGED
|
@@ -46,7 +46,7 @@ export interface CodegenOptions {
|
|
|
46
46
|
/**
|
|
47
47
|
* Generate a TypeScript declaration file string from a scanned route tree.
|
|
48
48
|
*
|
|
49
|
-
* The output is a `declare module '@timber/app'` block containing the Routes
|
|
49
|
+
* The output is a `declare module '@timber-js/app'` block containing the Routes
|
|
50
50
|
* interface that maps every route path to its params and searchParams shape.
|
|
51
51
|
*/
|
|
52
52
|
export function generateRouteMap(tree: RouteTree, options: CodegenOptions = {}): string {
|
|
@@ -180,7 +180,7 @@ function formatDeclarationFile(routes: RouteEntry[], importBase?: string): strin
|
|
|
180
180
|
// (removing exports like bindUseQueryStates that aren't listed here).
|
|
181
181
|
lines.push('export {};');
|
|
182
182
|
lines.push('');
|
|
183
|
-
lines.push("declare module '@timber/app' {");
|
|
183
|
+
lines.push("declare module '@timber-js/app' {");
|
|
184
184
|
lines.push(' interface Routes {');
|
|
185
185
|
|
|
186
186
|
for (const route of routes) {
|
|
@@ -197,12 +197,12 @@ function formatDeclarationFile(routes: RouteEntry[], importBase?: string): strin
|
|
|
197
197
|
lines.push('}');
|
|
198
198
|
lines.push('');
|
|
199
199
|
|
|
200
|
-
// Generate @timber/app/server augmentation — typed searchParams() generic
|
|
200
|
+
// Generate @timber-js/app/server augmentation — typed searchParams() generic
|
|
201
201
|
const pageRoutes = routes.filter((r) => !r.isApiRoute);
|
|
202
202
|
|
|
203
203
|
if (pageRoutes.length > 0) {
|
|
204
|
-
lines.push("declare module '@timber/app/server' {");
|
|
205
|
-
lines.push(" import type { Routes } from '@timber/app'");
|
|
204
|
+
lines.push("declare module '@timber-js/app/server' {");
|
|
205
|
+
lines.push(" import type { Routes } from '@timber-js/app'");
|
|
206
206
|
lines.push(
|
|
207
207
|
" export function searchParams<R extends keyof Routes>(): Promise<Routes[R]['searchParams']>"
|
|
208
208
|
);
|
|
@@ -210,13 +210,13 @@ function formatDeclarationFile(routes: RouteEntry[], importBase?: string): strin
|
|
|
210
210
|
lines.push('');
|
|
211
211
|
}
|
|
212
212
|
|
|
213
|
-
// Generate overloads for @timber/app/client
|
|
213
|
+
// Generate overloads for @timber-js/app/client
|
|
214
214
|
const dynamicRoutes = routes.filter((r) => r.params.length > 0);
|
|
215
215
|
|
|
216
216
|
if (dynamicRoutes.length > 0 || pageRoutes.length > 0) {
|
|
217
|
-
lines.push("declare module '@timber/app/client' {");
|
|
217
|
+
lines.push("declare module '@timber-js/app/client' {");
|
|
218
218
|
lines.push(
|
|
219
|
-
" import type { SearchParamsDefinition, SetParams, QueryStatesOptions, SearchParamCodec } from '@timber/app/search-params'"
|
|
219
|
+
" import type { SearchParamsDefinition, SetParams, QueryStatesOptions, SearchParamCodec } from '@timber-js/app/search-params'"
|
|
220
220
|
);
|
|
221
221
|
lines.push('');
|
|
222
222
|
|
|
@@ -303,7 +303,7 @@ function formatSearchParamsType(route: RouteEntry, importBase?: string): string
|
|
|
303
303
|
// Use (typeof import('...'))[' default'] instead of import('...').default
|
|
304
304
|
// because with moduleResolution:"bundler", import('...').default is treated as
|
|
305
305
|
// a namespace member access which doesn't work for default exports.
|
|
306
|
-
return `(typeof import('${importPath}'))['default'] extends import('@timber/app/search-params').SearchParamsDefinition<infer T> ? T : never`;
|
|
306
|
+
return `(typeof import('${importPath}'))['default'] extends import('@timber-js/app/search-params').SearchParamsDefinition<infer T> ? T : never`;
|
|
307
307
|
}
|
|
308
308
|
return '{}';
|
|
309
309
|
}
|
|
@@ -63,7 +63,7 @@ function validateSync<Output>(
|
|
|
63
63
|
* Serialize: uses `String()` for primitives, `null` for null/undefined.
|
|
64
64
|
*
|
|
65
65
|
* ```ts
|
|
66
|
-
* import { fromSchema } from '@timber/app/search-params'
|
|
66
|
+
* import { fromSchema } from '@timber-js/app/search-params'
|
|
67
67
|
* import { z } from 'zod/v4'
|
|
68
68
|
*
|
|
69
69
|
* const pageCodec = fromSchema(z.coerce.number().int().min(1).default(1))
|
|
@@ -109,7 +109,7 @@ export function fromSchema<T>(schema: StandardSchemaV1<T>): SearchParamCodec<T>
|
|
|
109
109
|
* and repeated query keys (`?tag=a&tag=b`).
|
|
110
110
|
*
|
|
111
111
|
* ```ts
|
|
112
|
-
* import { fromArraySchema } from '@timber/app/search-params'
|
|
112
|
+
* import { fromArraySchema } from '@timber-js/app/search-params'
|
|
113
113
|
* import { z } from 'zod/v4'
|
|
114
114
|
*
|
|
115
115
|
* const tagsCodec = fromArraySchema(z.array(z.string()).default([]))
|
|
@@ -145,7 +145,7 @@ function getDefaultSerialized<T>(codec: SearchParamCodec<T>): string | null {
|
|
|
145
145
|
* Create a SearchParamsDefinition from a codec map and optional URL key aliases.
|
|
146
146
|
*
|
|
147
147
|
* ```ts
|
|
148
|
-
* import { createSearchParams, fromSchema } from '@timber/app/search-params'
|
|
148
|
+
* import { createSearchParams, fromSchema } from '@timber-js/app/search-params'
|
|
149
149
|
* import { z } from 'zod/v4'
|
|
150
150
|
*
|
|
151
151
|
* export default createSearchParams({
|
|
@@ -290,11 +290,11 @@ function buildDefinition<T extends Record<string, unknown>>(
|
|
|
290
290
|
// ---- useQueryStates ----
|
|
291
291
|
// This is a placeholder that will be replaced by the client runtime.
|
|
292
292
|
// At import time in a server context, calling this throws.
|
|
293
|
-
// The actual implementation wraps nuqs and lives in @timber/app/client.
|
|
293
|
+
// The actual implementation wraps nuqs and lives in @timber-js/app/client.
|
|
294
294
|
function useQueryStates(_options?: QueryStatesOptions): [T, SetParams<T>] {
|
|
295
295
|
throw new Error(
|
|
296
296
|
'useQueryStates() can only be called in a client component. ' +
|
|
297
|
-
'Import from @timber/app/client instead.'
|
|
297
|
+
'Import from @timber-js/app/client instead.'
|
|
298
298
|
);
|
|
299
299
|
}
|
|
300
300
|
|
|
@@ -386,7 +386,7 @@ export function createActionClient<TCtx = Record<string, never>>(
|
|
|
386
386
|
* @example
|
|
387
387
|
* ```ts
|
|
388
388
|
* 'use server'
|
|
389
|
-
* import { validated } from '@timber/app/server'
|
|
389
|
+
* import { validated } from '@timber-js/app/server'
|
|
390
390
|
* import { z } from 'zod'
|
|
391
391
|
*
|
|
392
392
|
* export const createTodo = validated(
|
|
@@ -69,7 +69,7 @@ export function getAssetCacheControl(pathname: string): string {
|
|
|
69
69
|
* Everything else (favicon.ico, robots.txt, etc.) gets a shorter cache.
|
|
70
70
|
*/
|
|
71
71
|
export function generateHeadersFile(): string {
|
|
72
|
-
return `# Auto-generated by @timber/app — static asset cache headers.
|
|
72
|
+
return `# Auto-generated by @timber-js/app — static asset cache headers.
|
|
73
73
|
# See design/25-production-deployments.md §"CDN / Edge Cache"
|
|
74
74
|
|
|
75
75
|
/assets/*
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dev-mode fetch instrumentation — patches globalThis.fetch to create OTEL
|
|
3
|
+
* spans for every fetch call, giving visibility into async data fetching
|
|
4
|
+
* in the dev request log tree.
|
|
5
|
+
*
|
|
6
|
+
* Only activated in dev mode — zero overhead in production.
|
|
7
|
+
*
|
|
8
|
+
* The spans are automatically children of the active OTEL context (e.g. a
|
|
9
|
+
* timber.page or timber.layout span), so they appear nested under the
|
|
10
|
+
* component that initiated the fetch in the dev log tree.
|
|
11
|
+
*
|
|
12
|
+
* Design ref: 17-logging.md §"Dev Logging", LOCAL-289
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import * as api from '@opentelemetry/api';
|
|
16
|
+
|
|
17
|
+
export type DevFetchCleanup = () => void;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Patch globalThis.fetch to wrap every call in an OTEL span.
|
|
21
|
+
*
|
|
22
|
+
* Returns a cleanup function that restores the original fetch.
|
|
23
|
+
* Only call this in dev mode.
|
|
24
|
+
*/
|
|
25
|
+
export function instrumentDevFetch(): DevFetchCleanup {
|
|
26
|
+
const originalFetch = globalThis.fetch;
|
|
27
|
+
const tracer = api.trace.getTracer('timber.js');
|
|
28
|
+
|
|
29
|
+
globalThis.fetch = async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
|
|
30
|
+
const { method, url } = extractFetchInfo(input, init);
|
|
31
|
+
|
|
32
|
+
return tracer.startActiveSpan(
|
|
33
|
+
'timber.fetch',
|
|
34
|
+
{
|
|
35
|
+
attributes: {
|
|
36
|
+
'http.request.method': method,
|
|
37
|
+
'http.url': url,
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
async (span) => {
|
|
41
|
+
try {
|
|
42
|
+
const response = await originalFetch(input, init);
|
|
43
|
+
|
|
44
|
+
span.setAttribute('http.response.status_code', response.status);
|
|
45
|
+
|
|
46
|
+
// Surface cache status from standard headers
|
|
47
|
+
const cacheStatus =
|
|
48
|
+
response.headers.get('X-Cache') ?? response.headers.get('CF-Cache-Status');
|
|
49
|
+
if (cacheStatus) {
|
|
50
|
+
span.setAttribute('timber.cache_status', cacheStatus);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
span.setStatus({ code: api.SpanStatusCode.OK });
|
|
54
|
+
span.end();
|
|
55
|
+
return response;
|
|
56
|
+
} catch (error) {
|
|
57
|
+
span.setStatus({ code: api.SpanStatusCode.ERROR });
|
|
58
|
+
if (error instanceof Error) {
|
|
59
|
+
span.setAttribute('timber.fetch_error', error.message);
|
|
60
|
+
span.recordException(error);
|
|
61
|
+
}
|
|
62
|
+
span.end();
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
return () => {
|
|
70
|
+
globalThis.fetch = originalFetch;
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Extract method and URL from the various fetch() call signatures.
|
|
76
|
+
*/
|
|
77
|
+
function extractFetchInfo(
|
|
78
|
+
input: RequestInfo | URL,
|
|
79
|
+
init?: RequestInit
|
|
80
|
+
): { method: string; url: string } {
|
|
81
|
+
let method = init?.method ?? 'GET';
|
|
82
|
+
let url: string;
|
|
83
|
+
|
|
84
|
+
if (input instanceof Request) {
|
|
85
|
+
url = input.url;
|
|
86
|
+
if (!init?.method) {
|
|
87
|
+
method = input.method;
|
|
88
|
+
}
|
|
89
|
+
} else if (input instanceof URL) {
|
|
90
|
+
url = input.toString();
|
|
91
|
+
} else {
|
|
92
|
+
url = input;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return { method: method.toUpperCase(), url };
|
|
96
|
+
}
|
package/src/server/dev-logger.ts
CHANGED
|
@@ -107,6 +107,11 @@ function spanLabel(span: ReadableSpan): { label: string; env: string } {
|
|
|
107
107
|
const route = attrs['timber.route'] ?? '/';
|
|
108
108
|
return { label: `page ${route}`, env: 'rsc' };
|
|
109
109
|
}
|
|
110
|
+
case 'timber.fetch': {
|
|
111
|
+
const fetchMethod = attrs['http.request.method'] ?? 'GET';
|
|
112
|
+
const fetchUrl = attrs['http.url'] ?? '';
|
|
113
|
+
return { label: `fetch ${fetchMethod} ${fetchUrl}`, env: 'fetch' };
|
|
114
|
+
}
|
|
110
115
|
default:
|
|
111
116
|
return { label: span.name, env: 'rsc' };
|
|
112
117
|
}
|
|
@@ -282,6 +287,43 @@ export function formatSpanTree(spans: ReadableSpan[], config?: DevLoggerConfig):
|
|
|
282
287
|
return lines.join('\n') + '\n';
|
|
283
288
|
}
|
|
284
289
|
|
|
290
|
+
/**
|
|
291
|
+
* Format a fetch span line with method, URL, timing, duration, and cache status.
|
|
292
|
+
*
|
|
293
|
+
* Output: `├─ fetch GET https://api.example.com/data 12ms → 89ms (77ms) [cache: HIT]`
|
|
294
|
+
*/
|
|
295
|
+
function formatFetchLine(
|
|
296
|
+
span: ReadableSpan,
|
|
297
|
+
prefix: string,
|
|
298
|
+
connector: string,
|
|
299
|
+
startMs: number,
|
|
300
|
+
endMs: number,
|
|
301
|
+
durationMs: number
|
|
302
|
+
): string {
|
|
303
|
+
const method = String(span.attributes['http.request.method'] ?? 'GET');
|
|
304
|
+
const url = String(span.attributes['http.url'] ?? '');
|
|
305
|
+
const statusCode = span.attributes['http.response.status_code'] as number | undefined;
|
|
306
|
+
const cacheStatus = span.attributes['timber.cache_status'] as string | undefined;
|
|
307
|
+
const fetchError = span.attributes['timber.fetch_error'] as string | undefined;
|
|
308
|
+
const isError = span.status.code === 2; // SpanStatusCode.ERROR
|
|
309
|
+
|
|
310
|
+
let line = `${prefix}${connector} ${DIM}fetch ${method}${RESET} ${url}`;
|
|
311
|
+
line += ` ${DIM}${startMs}ms → ${endMs}ms (${durationMs}ms)${RESET}`;
|
|
312
|
+
|
|
313
|
+
if (cacheStatus) {
|
|
314
|
+
line += ` ${DIM}[cdn: ${cacheStatus}]${RESET}`;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (isError) {
|
|
318
|
+
const errMsg = fetchError ? `: ${fetchError}` : '';
|
|
319
|
+
line += ` ${RED}ERROR${errMsg}${RESET}`;
|
|
320
|
+
} else if (statusCode && statusCode >= 400) {
|
|
321
|
+
line += ` ${YELLOW}${statusCode}${RESET}`;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return line;
|
|
325
|
+
}
|
|
326
|
+
|
|
285
327
|
/**
|
|
286
328
|
* Format a single span tree node with children, timing, and annotations.
|
|
287
329
|
*/
|
|
@@ -301,6 +343,13 @@ function formatSpanNode(
|
|
|
301
343
|
const durationMs = endMs - startMs;
|
|
302
344
|
const isSlow = durationMs > slowPhaseMs;
|
|
303
345
|
|
|
346
|
+
// Fetch spans get special formatting: no env tag, duration in parens, cache status
|
|
347
|
+
if (node.span.name === 'timber.fetch') {
|
|
348
|
+
const fetchLine = formatFetchLine(node.span, prefix, connector, startMs, endMs, durationMs);
|
|
349
|
+
lines.push(fetchLine);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
|
|
304
353
|
// Access results from span attributes
|
|
305
354
|
const accessResult = node.span.attributes['timber.result'] as string | undefined;
|
|
306
355
|
|
package/src/server/form-flash.ts
CHANGED
|
@@ -60,7 +60,7 @@ const formFlashAls = new AsyncLocalStorage<FormFlashData>();
|
|
|
60
60
|
*
|
|
61
61
|
* ```tsx
|
|
62
62
|
* // app/contact/page.tsx (server component)
|
|
63
|
-
* import { getFormFlash } from '@timber/app/server'
|
|
63
|
+
* import { getFormFlash } from '@timber-js/app/server'
|
|
64
64
|
*
|
|
65
65
|
* export default function ContactPage() {
|
|
66
66
|
* const flash = getFormFlash()
|
package/src/server/index.ts
CHANGED
|
@@ -23,10 +23,7 @@ import type { ManifestSegmentNode } from './route-matcher.js';
|
|
|
23
23
|
import { resolveMetadata, renderMetadataToElements } from './metadata.js';
|
|
24
24
|
import type { HeadElement as MetadataHeadElement } from './metadata.js';
|
|
25
25
|
import type { Metadata } from './types.js';
|
|
26
|
-
import {
|
|
27
|
-
METADATA_ROUTE_CONVENTIONS,
|
|
28
|
-
getMetadataRouteAutoLink,
|
|
29
|
-
} from './metadata-routes.js';
|
|
26
|
+
import { METADATA_ROUTE_CONVENTIONS, getMetadataRouteAutoLink } from './metadata-routes.js';
|
|
30
27
|
import { DenySignal, RedirectSignal } from './primitives.js';
|
|
31
28
|
import { AccessGate } from './access-gate.js';
|
|
32
29
|
import { resolveSlotElement } from './slot-resolver.js';
|
|
@@ -155,7 +152,7 @@ export async function buildRouteElement(
|
|
|
155
152
|
// Load page (leaf segment only)
|
|
156
153
|
if (isLeaf && segment.page) {
|
|
157
154
|
// Load and apply search-params.ts definition before rendering so
|
|
158
|
-
// searchParams() from @timber/app/server returns parsed typed values.
|
|
155
|
+
// searchParams() from @timber-js/app/server returns parsed typed values.
|
|
159
156
|
if (segment.searchParams) {
|
|
160
157
|
const spMod = (await segment.searchParams.load()) as {
|
|
161
158
|
default?: SearchParamsDefinition<Record<string, unknown>>;
|
|
@@ -125,6 +125,10 @@ async function createRequestHandler(manifest: typeof routeManifest, runtimeConfi
|
|
|
125
125
|
const devLogMode = resolveLogMode();
|
|
126
126
|
if (devLogMode !== 'quiet') {
|
|
127
127
|
await initDevTracing({ mode: devLogMode, slowPhaseMs });
|
|
128
|
+
// Patch globalThis.fetch to create OTEL spans for fetch calls.
|
|
129
|
+
// Spans appear as children of the active component span in the dev log tree.
|
|
130
|
+
const { instrumentDevFetch } = await import('../dev-fetch-instrumentation.js');
|
|
131
|
+
instrumentDevFetch();
|
|
128
132
|
}
|
|
129
133
|
}
|
|
130
134
|
|
package/src/shims/link.ts
CHANGED