@timber-js/app 0.1.2 → 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.
Files changed (75) hide show
  1. package/dist/_chunks/{interception-DIaZN1bF.js → interception-c-a3uODY.js} +8 -8
  2. package/dist/_chunks/interception-c-a3uODY.js.map +1 -0
  3. package/dist/adapters/cloudflare.d.ts +2 -2
  4. package/dist/adapters/cloudflare.js +4 -4
  5. package/dist/adapters/cloudflare.js.map +1 -1
  6. package/dist/adapters/nitro.d.ts +1 -1
  7. package/dist/adapters/nitro.js +4 -4
  8. package/dist/adapters/nitro.js.map +1 -1
  9. package/dist/cache/index.js.map +1 -1
  10. package/dist/client/form.d.ts +1 -1
  11. package/dist/client/index.js +3 -3
  12. package/dist/client/index.js.map +1 -1
  13. package/dist/client/use-link-status.d.ts +1 -1
  14. package/dist/client/use-navigation-pending.d.ts +1 -1
  15. package/dist/content/index.d.ts +1 -1
  16. package/dist/cookies/define-cookie.d.ts +2 -2
  17. package/dist/cookies/index.d.ts +1 -1
  18. package/dist/cookies/index.d.ts.map +1 -1
  19. package/dist/cookies/index.js +2 -2
  20. package/dist/cookies/index.js.map +1 -1
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +19 -18
  23. package/dist/index.js.map +1 -1
  24. package/dist/plugins/dev-logs.d.ts +1 -1
  25. package/dist/plugins/dev-server.d.ts.map +1 -1
  26. package/dist/plugins/dynamic-transform.d.ts +1 -1
  27. package/dist/plugins/entries.d.ts.map +1 -1
  28. package/dist/routing/codegen.d.ts +1 -1
  29. package/dist/routing/index.js +1 -1
  30. package/dist/search-params/codecs.d.ts +2 -2
  31. package/dist/search-params/create.d.ts +1 -1
  32. package/dist/search-params/index.js +4 -4
  33. package/dist/search-params/index.js.map +1 -1
  34. package/dist/server/action-client.d.ts +1 -1
  35. package/dist/server/dev-fetch-instrumentation.d.ts +22 -0
  36. package/dist/server/dev-fetch-instrumentation.d.ts.map +1 -0
  37. package/dist/server/dev-logger.d.ts.map +1 -1
  38. package/dist/server/form-flash.d.ts +1 -1
  39. package/dist/server/index.js +2 -2
  40. package/dist/server/index.js.map +1 -1
  41. package/dist/server/route-element-builder.d.ts.map +1 -1
  42. package/dist/server/rsc-entry/index.d.ts.map +1 -1
  43. package/dist/shims/link.d.ts +1 -1
  44. package/package.json +4 -4
  45. package/src/adapters/cloudflare.ts +4 -4
  46. package/src/adapters/nitro.ts +4 -4
  47. package/src/cache/index.ts +1 -1
  48. package/src/client/form.tsx +1 -1
  49. package/src/client/index.ts +1 -1
  50. package/src/client/use-link-status.ts +1 -1
  51. package/src/client/use-navigation-pending.ts +1 -1
  52. package/src/content/index.ts +1 -1
  53. package/src/cookies/define-cookie.ts +2 -2
  54. package/src/cookies/index.ts +2 -6
  55. package/src/index.ts +8 -1
  56. package/src/plugins/cache-transform.ts +1 -1
  57. package/src/plugins/dev-logs.ts +2 -2
  58. package/src/plugins/dev-server.ts +6 -4
  59. package/src/plugins/dynamic-transform.ts +2 -2
  60. package/src/plugins/entries.ts +8 -1
  61. package/src/plugins/shims.ts +10 -10
  62. package/src/routing/codegen.ts +9 -9
  63. package/src/search-params/codecs.ts +2 -2
  64. package/src/search-params/create.ts +3 -3
  65. package/src/search-params/index.ts +1 -1
  66. package/src/server/action-client.ts +1 -1
  67. package/src/server/asset-headers.ts +1 -1
  68. package/src/server/dev-fetch-instrumentation.ts +96 -0
  69. package/src/server/dev-logger.ts +49 -0
  70. package/src/server/form-flash.ts +1 -1
  71. package/src/server/index.ts +1 -1
  72. package/src/server/route-element-builder.ts +2 -5
  73. package/src/server/rsc-entry/index.ts +4 -0
  74. package/src/shims/link.ts +1 -1
  75. 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;AAQ9D,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
+ {"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;AA2lBD,OAAO,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;8BA3epD,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC;AA6ehD,wBAAiE"}
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"}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Shim: next/link → @timber/app/client Link
2
+ * Shim: next/link → @timber-js/app/client Link
3
3
  *
4
4
  * Re-exports timber's Link component so libraries that import next/link
5
5
  * get the timber equivalent without modification.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timber-js/app",
3
- "version": "0.1.2",
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",
@@ -90,15 +90,15 @@
90
90
  "dependencies": {
91
91
  "@opentelemetry/api": "^1.9.0",
92
92
  "@opentelemetry/context-async-hooks": "^2.6.0",
93
- "@opentelemetry/sdk-trace-base": "^2.6.0",
94
- "@vitejs/plugin-react": "^6.0.0",
95
- "@vitejs/plugin-rsc": "~0.5.21"
93
+ "@opentelemetry/sdk-trace-base": "^2.6.0"
96
94
  },
97
95
  "peerDependencies": {
98
96
  "@content-collections/core": "^0.14.0",
99
97
  "@content-collections/mdx": "^0.2.0",
100
98
  "@content-collections/vite": "^0.2.0",
101
99
  "@mdx-js/rollup": "^3.0.0",
100
+ "@vitejs/plugin-react": "^6.0.0",
101
+ "@vitejs/plugin-rsc": ">=0.5.21",
102
102
  "nuqs": "^2.0.0",
103
103
  "react": "^19.2.4",
104
104
  "react-dom": "^19.2.4",
@@ -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}'
@@ -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'
@@ -1,4 +1,4 @@
1
- // @timber/app/cache — Caching primitives
1
+ // @timber-js/app/cache — Caching primitives
2
2
 
3
3
  export interface CacheHandler {
4
4
  get(key: string): Promise<{ value: unknown; stale: boolean } | null>;
@@ -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 }) {
@@ -1,4 +1,4 @@
1
- // @timber/app/client — Client-side primitives
1
+ // @timber-js/app/client — Client-side primitives
2
2
  // These are the primary imports for client components.
3
3
 
4
4
  export type { RenderErrorDigest } from './types';
@@ -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()
@@ -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', {
@@ -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';
package/src/index.ts CHANGED
@@ -2,6 +2,7 @@ import type { Plugin, PluginOption } from 'vite';
2
2
  import { existsSync } from 'node:fs';
3
3
  import { join } from 'node:path';
4
4
  import { pathToFileURL } from 'node:url';
5
+ import { createRequire } from 'node:module';
5
6
  import react from '@vitejs/plugin-react';
6
7
  import { cacheTransformPlugin } from './plugins/cache-transform';
7
8
  import { timberContent } from './plugins/content';
@@ -353,8 +354,14 @@ export function timber(config?: TimberUserConfig): PluginOption[] {
353
354
  // We do NOT set customBuildApp — the RSC plugin's orchestration is correct
354
355
  // and handles bundle ordering, asset manifest generation, and environment
355
356
  // imports manifest. See @vitejs/plugin-rsc's buildApp implementation.
357
+ // Resolve @vitejs/plugin-rsc from the consumer's project (process.cwd()),
358
+ // not from timber's own node_modules. This is critical for pnpm link:
359
+ // when linked, timber's node_modules has a separate vite instance, and
360
+ // the RSC plugin must use the same vite instance as the dev server.
361
+ const consumerRequire = createRequire(join(process.cwd(), 'package.json'));
362
+ const rscPluginPath = consumerRequire.resolve('@vitejs/plugin-rsc');
356
363
  ctx.timer.start('rsc-plugin-import');
357
- const rscPluginsPromise = import('@vitejs/plugin-rsc').then(({ default: vitePluginRsc }) => {
364
+ const rscPluginsPromise = import(pathToFileURL(rscPluginPath).href).then(({ default: vitePluginRsc }) => {
358
365
  ctx.timer.end('rsc-plugin-import');
359
366
  return vitePluginRsc({
360
367
  serverHandler: false,
@@ -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 };
@@ -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
@@ -12,8 +12,7 @@
12
12
  * Design docs: 18-build-system.md §"Dev Server", 02-rendering-pipeline.md
13
13
  */
14
14
 
15
- import type { Plugin, ViteDevServer } from 'vite';
16
- import { isRunnableDevEnvironment } from 'vite';
15
+ import type { Plugin, ViteDevServer, DevEnvironment } from 'vite';
17
16
  import type { IncomingMessage, ServerResponse } from 'node:http';
18
17
  import { join } from 'node:path';
19
18
  import type { PluginContext } from '#/index.js';
@@ -147,8 +146,11 @@ function createTimberMiddleware(server: ViteDevServer, projectRoot: string) {
147
146
  // environment's module runner for HMR-aware loading.
148
147
  let handler: (req: Request) => Promise<Response>;
149
148
  try {
150
- const rscEnv = server.environments.rsc;
151
- if (!isRunnableDevEnvironment(rscEnv)) {
149
+ const rscEnv = server.environments.rsc as DevEnvironment & { runner?: { import: (id: string) => Promise<any> } };
150
+ // Duck-type check instead of isRunnableDevEnvironment() — the vite
151
+ // import-based check fails across pnpm link boundaries where the
152
+ // linked package resolves a different vite module instance.
153
+ if (!rscEnv?.runner?.import) {
152
154
  throw new Error('[timber] RSC environment is not runnable');
153
155
  }
154
156
  const rscModule = await rscEnv.runner.import(RSC_ENTRY_ID);
@@ -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
  }
@@ -14,10 +14,17 @@
14
14
  import type { Plugin } from 'vite';
15
15
  import { resolve, dirname } from 'node:path';
16
16
  import { fileURLToPath } from 'node:url';
17
+ import { existsSync } from 'node:fs';
17
18
  import type { PluginContext } from '#/index.js';
18
19
 
19
20
  const __dirname = dirname(fileURLToPath(import.meta.url));
20
- const SRC_DIR = resolve(__dirname, '..');
21
+
22
+ // In workspace dev, __dirname is src/plugins/ and src/ is the parent.
23
+ // In published package, entries.ts is bundled into dist/index.js so
24
+ // __dirname is dist/ and src/ is a sibling directory.
25
+ const SRC_DIR = existsSync(resolve(__dirname, '..', 'server', 'rsc-entry', 'index.ts'))
26
+ ? resolve(__dirname, '..')
27
+ : resolve(__dirname, '..', 'src');
21
28
 
22
29
  // ─── Virtual Module IDs ──────────────────────────────────────────────────
23
30
 
@@ -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
  }
@@ -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
 
@@ -1,4 +1,4 @@
1
- // @timber/app/search-params — Typed search params
1
+ // @timber-js/app/search-params — Typed search params
2
2
 
3
3
  // Core types and factory
4
4
  export type {
@@ -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
+ }