@timber-js/app 0.2.0-alpha.122 → 0.2.0-alpha.124
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/LICENSE +8 -0
- package/dist/_chunks/{actions-Dy8vOjFt.js → actions-418ygYKC.js} +5 -5
- package/dist/_chunks/{actions-Dy8vOjFt.js.map → actions-418ygYKC.js.map} +1 -1
- package/dist/_chunks/{cli-schema-sync-VcCf1TFY.js → cli-schema-sync-DFHBj6yZ.js} +59 -12
- package/dist/_chunks/{cli-schema-sync-VcCf1TFY.js.map → cli-schema-sync-DFHBj6yZ.js.map} +1 -1
- package/dist/_chunks/cli-schema-sync-vdobDWwX.js +2 -0
- package/dist/_chunks/{define-CPVQaWbx.js → define-BJMwm0c4.js} +6 -18
- package/dist/_chunks/define-BJMwm0c4.js.map +1 -0
- package/dist/_chunks/{handler-store-CcwNX-gk.js → handler-store-bIAstrxQ.js} +1 -1
- package/dist/_chunks/{handler-store-CcwNX-gk.js.map → handler-store-bIAstrxQ.js.map} +1 -1
- package/dist/_chunks/{merge-search-params-az6ZiBeU.js → merge-search-params-Cm1j99HC.js} +1 -1
- package/dist/_chunks/{merge-search-params-az6ZiBeU.js.map → merge-search-params-Cm1j99HC.js.map} +1 -1
- package/dist/_chunks/{navigation-root-DoRTeBm5.js → navigation-root-DaERBEGV.js} +2 -2
- package/dist/_chunks/{navigation-root-DoRTeBm5.js.map → navigation-root-DaERBEGV.js.map} +1 -1
- package/dist/_chunks/{resolve-schema-cURaYvaD.js → resolve-schema-DU2nbVOi.js} +1 -1
- package/dist/_chunks/{resolve-schema-cURaYvaD.js.map → resolve-schema-DU2nbVOi.js.map} +1 -1
- package/dist/_chunks/{router-ref-CpvjBqdu.js → router-ref-B2-2kIIC.js} +2 -2
- package/dist/_chunks/{router-ref-CpvjBqdu.js.map → router-ref-B2-2kIIC.js.map} +1 -1
- package/dist/_chunks/{segment-context-EsfQpS--.js → segment-context-Bg0pFbLT.js} +1 -1
- package/dist/_chunks/{segment-context-EsfQpS--.js.map → segment-context-Bg0pFbLT.js.map} +1 -1
- package/dist/_chunks/{ssr-data-m9-cNEIw.js → ssr-data-DVX8S7vj.js} +1 -1
- package/dist/_chunks/{ssr-data-m9-cNEIw.js.map → ssr-data-DVX8S7vj.js.map} +1 -1
- package/dist/_chunks/{use-segment-params-CtIAjqK0.js → use-segment-params-ByGqwUuO.js} +5 -9
- package/dist/_chunks/use-segment-params-ByGqwUuO.js.map +1 -0
- package/dist/_chunks/{validation-BMU5MPzP.js → validation-AfEFM5ze.js} +1 -1
- package/dist/_chunks/{validation-BMU5MPzP.js.map → validation-AfEFM5ze.js.map} +1 -1
- package/dist/_chunks/{walkers-tkw7sDlj.js → walkers-DMSGZeCk.js} +2 -2
- package/dist/_chunks/{walkers-tkw7sDlj.js.map → walkers-DMSGZeCk.js.map} +1 -1
- package/dist/cache/index.js +1 -1
- package/dist/cli-schema-sync.d.ts +6 -4
- package/dist/cli-schema-sync.d.ts.map +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +12 -6
- package/dist/cli.js.map +1 -1
- package/dist/client/browser-entry/action-dispatch.d.ts.map +1 -1
- package/dist/client/browser-entry/hydrate.d.ts.map +1 -1
- package/dist/client/browser-entry/post-hydration.d.ts.map +1 -1
- package/dist/client/error-boundary.js +2 -2
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +47 -14
- package/dist/client/index.js.map +1 -1
- package/dist/client/internal.js +87 -64
- package/dist/client/internal.js.map +1 -1
- package/dist/client/link.d.ts.map +1 -1
- package/dist/client/navigation-context.d.ts +1 -0
- package/dist/client/navigation-context.d.ts.map +1 -1
- package/dist/client/router.d.ts.map +1 -1
- package/dist/client/rsc-fetch.d.ts +8 -0
- package/dist/client/rsc-fetch.d.ts.map +1 -1
- package/dist/client/shallow-url.d.ts +10 -0
- package/dist/client/shallow-url.d.ts.map +1 -0
- package/dist/client/use-search-params.d.ts +7 -5
- package/dist/client/use-search-params.d.ts.map +1 -1
- package/dist/cookies/index.js +2 -2
- package/dist/index.js +3 -3
- package/dist/params/index.js +1 -1
- package/dist/routing/index.js +2 -2
- package/dist/segment-params/define.d.ts +0 -6
- package/dist/segment-params/define.d.ts.map +1 -1
- package/dist/segment-params/index.js +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/internal.js +3 -3
- package/package.json +6 -7
- package/src/cli-schema-sync.ts +80 -13
- package/src/cli.ts +18 -7
- package/src/client/browser-entry/action-dispatch.ts +9 -10
- package/src/client/browser-entry/hydrate.ts +2 -0
- package/src/client/browser-entry/post-hydration.ts +13 -6
- package/src/client/index.ts +1 -5
- package/src/client/link.tsx +25 -12
- package/src/client/navigation-context.ts +2 -1
- package/src/client/router.ts +78 -27
- package/src/client/rsc-fetch.ts +20 -5
- package/src/client/shallow-url.ts +22 -0
- package/src/client/use-search-params.ts +25 -44
- package/src/segment-params/define.ts +2 -29
- package/dist/_chunks/cli-schema-sync-4HcQIziG.js +0 -2
- package/dist/_chunks/define-CPVQaWbx.js.map +0 -1
- package/dist/_chunks/use-segment-params-CtIAjqK0.js.map +0 -1
package/dist/routing/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { i as INTERCEPTION_MARKERS, n as classifyUrlSegment, r as DEFAULT_PAGE_EXTENSIONS, t as classifySegment } from "../_chunks/segment-classify-BjfuctV2.js";
|
|
2
|
-
import {
|
|
3
|
-
import { a as generateRouteMap, i as collectInterceptionRewrites, n as collectDynamicSegmentsFromTree, r as validateSchemaAgainstRoutes, t as collectLeafRoutes } from "../_chunks/walkers-
|
|
2
|
+
import { m as scanRoutes } from "../_chunks/cli-schema-sync-DFHBj6yZ.js";
|
|
3
|
+
import { a as generateRouteMap, i as collectInterceptionRewrites, n as collectDynamicSegmentsFromTree, r as validateSchemaAgainstRoutes, t as collectLeafRoutes } from "../_chunks/walkers-DMSGZeCk.js";
|
|
4
4
|
export { DEFAULT_PAGE_EXTENSIONS, INTERCEPTION_MARKERS, classifySegment, classifyUrlSegment, collectDynamicSegmentsFromTree, collectInterceptionRewrites, collectLeafRoutes, generateRouteMap, scanRoutes, validateSchemaAgainstRoutes };
|
|
@@ -14,12 +14,6 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import type { Codec } from '../codec.js';
|
|
16
16
|
import { type StandardSchemaV1 } from '../schema-bridge.js';
|
|
17
|
-
/**
|
|
18
|
-
* Register the client useSegmentParams hook.
|
|
19
|
-
* Called by client entry at module load time to avoid require() at call time.
|
|
20
|
-
* @internal
|
|
21
|
-
*/
|
|
22
|
-
export declare function _registerUseSegmentParams(hook: () => Record<string, string | string[]>): void;
|
|
23
17
|
/**
|
|
24
18
|
* Register the getSegmentParams function. Called once at module load time
|
|
25
19
|
* from request-context.ts to avoid dynamic import at call time.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"define.d.ts","sourceRoot":"","sources":["../../src/segment-params/define.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EACL,KAAK,gBAAgB,EAItB,MAAM,qBAAqB,CAAC;AAW7B;;;;GAIG;AACH,wBAAgB,
|
|
1
|
+
{"version":3,"file":"define.d.ts","sourceRoot":"","sources":["../../src/segment-params/define.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EACL,KAAK,gBAAgB,EAItB,MAAM,qBAAqB,CAAC;AAW7B;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,EAAE,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,GAAG,IAAI,CAExF;AAgBD,8DAA8D;AAC9D,MAAM,MAAM,eAAe,CAAC,CAAC,IAC3B,CAAC,SAAS,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,SAAS,gBAAgB,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;AAEjF,6EAA6E;AAC7E,MAAM,MAAM,UAAU,CAAC,CAAC,GAAG,OAAO,IAAI,KAAK,CAAC,CAAC,CAAC,GAAG,gBAAgB,CAAC,CAAC,CAAC,CAAC;AAErE,YAAY,EAAE,gBAAgB,EAAE,CAAC;AAEjC;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACjE,2EAA2E;IAC3E,KAAK,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC;IAEjD,mEAAmE;IACnE,SAAS,CAAC,MAAM,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAE7C;;;;;;;;;;;;;;;OAeG;IACH,GAAG,IAAI,CAAC,CAAC;IAET;;;;;;;;;;OAUG;IACH,gBAAgB,IAAI,CAAC,CAAC;IAEtB,2BAA2B;IAC3B,MAAM,EAAE;SAAG,CAAC,IAAI,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;KAAE,CAAC;CACzC;AA+FD;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,EACtE,MAAM,EAAE,CAAC,GACR,gBAAgB,CAAC;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAAE,CAAC,CAwF7D"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { n as defineSegmentParams } from "../_chunks/define-BJMwm0c4.js";
|
|
2
2
|
export { defineSegmentParams };
|
package/dist/server/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { a as getSpanId, d as withSpan, o as getTraceId, p as formFlashAls, t as addSpanEvent, v as isDebug, y as isDevMode } from "../_chunks/tracing-DvKLzCFa.js";
|
|
2
2
|
import { t as formatSize } from "../_chunks/format-BjN9HLu9.js";
|
|
3
|
-
import { D as getCookieJar, a as revalidateTag, b as getSegmentParams, c as DenySignal, f as deny, h as waitUntil, i as revalidatePath, l as RedirectSignal, m as redirectExternal, o as coerce, p as redirect, s as parseFormData, u as RedirectType, v as getHeaders } from "../_chunks/actions-
|
|
3
|
+
import { D as getCookieJar, a as revalidateTag, b as getSegmentParams, c as DenySignal, f as deny, h as waitUntil, i as revalidatePath, l as RedirectSignal, m as redirectExternal, o as coerce, p as redirect, s as parseFormData, u as RedirectType, v as getHeaders } from "../_chunks/actions-418ygYKC.js";
|
|
4
4
|
//#region src/server/sensitive-fields.ts
|
|
5
5
|
/**
|
|
6
6
|
* Sensitive field stripping — removes password/token/CVV-style fields
|
package/dist/server/internal.js
CHANGED
|
@@ -3,10 +3,10 @@ import { a as warnRedirectInSuspense, c as warnSuspenseWrappingChildren, i as wa
|
|
|
3
3
|
import { i as getMetadataRouteServePath, n as classifyMetadataRoute, r as getMetadataRouteAutoLink, t as METADATA_ROUTE_CONVENTIONS } from "../_chunks/metadata-routes-DESG8uk5.js";
|
|
4
4
|
import { n as classifyUrlSegment } from "../_chunks/segment-classify-BjfuctV2.js";
|
|
5
5
|
import { a as logProxyError, c as logRequestReceived, d as logSwrRefetchFailed, f as logWaitUntilRejected, h as swallow, i as logMiddlewareShortCircuit, l as logRouteError, m as setLogger, n as logCacheMiss, o as logRenderError, p as logWaitUntilUnsupported, r as logMiddlewareError, s as logRequestCompleted, t as getLogger, u as logSlowRequest } from "../_chunks/logger-DDmK5skc.js";
|
|
6
|
-
import { C as setMatchedSegmentPath, E as getCookie, O as getSetCookieHeaders, S as runWithRequestContext, T as setSegmentParams, _ as getHeader, b as getSegmentParams, c as DenySignal, d as RenderError, g as applyRequestHeaderOverlay, l as RedirectSignal, n as executeAction, o as coerce, r as isRscActionRequest, t as buildNoJsResponse, w as setMutableCookieContext, x as markResponseFlushed, y as getSearchParams } from "../_chunks/actions-
|
|
6
|
+
import { C as setMatchedSegmentPath, E as getCookie, O as getSetCookieHeaders, S as runWithRequestContext, T as setSegmentParams, _ as getHeader, b as getSegmentParams, c as DenySignal, d as RenderError, g as applyRequestHeaderOverlay, l as RedirectSignal, n as executeAction, o as coerce, r as isRscActionRequest, t as buildNoJsResponse, w as setMutableCookieContext, x as markResponseFlushed, y as getSearchParams } from "../_chunks/actions-418ygYKC.js";
|
|
7
7
|
import "../client/error-boundary.js";
|
|
8
|
-
import { n as toBracketKey } from "../_chunks/resolve-schema-
|
|
9
|
-
import "../_chunks/segment-context-
|
|
8
|
+
import { n as toBracketKey } from "../_chunks/resolve-schema-DU2nbVOi.js";
|
|
9
|
+
import "../_chunks/segment-context-Bg0pFbLT.js";
|
|
10
10
|
import { readFile } from "node:fs/promises";
|
|
11
11
|
import "node:crypto";
|
|
12
12
|
import { createElement } from "react";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@timber-js/app",
|
|
3
|
-
"version": "0.2.0-alpha.
|
|
3
|
+
"version": "0.2.0-alpha.124",
|
|
4
4
|
"description": "Vite-native React framework built for Servers and Serverless Platforms — correct HTTP semantics, real status codes, pages that work without JavaScript",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cloudflare-workers",
|
|
@@ -114,11 +114,6 @@
|
|
|
114
114
|
"publishConfig": {
|
|
115
115
|
"access": "public"
|
|
116
116
|
},
|
|
117
|
-
"scripts": {
|
|
118
|
-
"build": "vite build --config vite.lib.config.ts && tsc --emitDeclarationOnly --project tsconfig.json --outDir dist",
|
|
119
|
-
"typecheck": "tsgo --noEmit",
|
|
120
|
-
"prepublishOnly": "pnpm run build"
|
|
121
|
-
},
|
|
122
117
|
"dependencies": {
|
|
123
118
|
"@opentelemetry/api": "^1.9.1",
|
|
124
119
|
"@opentelemetry/context-async-hooks": "^2.6.1",
|
|
@@ -162,5 +157,9 @@
|
|
|
162
157
|
},
|
|
163
158
|
"engines": {
|
|
164
159
|
"node": ">=22.12.0"
|
|
160
|
+
},
|
|
161
|
+
"scripts": {
|
|
162
|
+
"build": "vite build --config vite.lib.config.ts && tsc --emitDeclarationOnly --project tsconfig.json --outDir dist",
|
|
163
|
+
"typecheck": "tsgo --noEmit"
|
|
165
164
|
}
|
|
166
|
-
}
|
|
165
|
+
}
|
package/src/cli-schema-sync.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `timber schema sync` — scan filesystem for dynamic segments
|
|
3
|
-
* missing entries
|
|
4
|
-
*
|
|
5
|
-
* Only adds, never removes or modifies existing entries.
|
|
2
|
+
* `timber schema sync` — scan filesystem for dynamic segments, append
|
|
3
|
+
* missing entries with `codec.string` defaults, and remove stale entries
|
|
4
|
+
* whose segments no longer exist in the filesystem.
|
|
6
5
|
*
|
|
7
6
|
* Design doc: design/41-global-params.md §Auto-Scaffolding
|
|
8
7
|
*/
|
|
@@ -66,6 +65,12 @@ export function findSchemaFile(appDir: string): string | undefined {
|
|
|
66
65
|
return undefined;
|
|
67
66
|
}
|
|
68
67
|
|
|
68
|
+
export function defaultCodecForSegment(seg: string): string {
|
|
69
|
+
if (seg.startsWith('[[...')) return 'codec.optionalCatchAll(codec.string)';
|
|
70
|
+
if (seg.startsWith('[...')) return 'codec.stringArray';
|
|
71
|
+
return 'codec.string';
|
|
72
|
+
}
|
|
73
|
+
|
|
69
74
|
/**
|
|
70
75
|
* Generate the content for a new app/schema.ts with the given segment keys.
|
|
71
76
|
*/
|
|
@@ -77,7 +82,7 @@ function generateNewSchema(segments: string[]): string {
|
|
|
77
82
|
lines.push('export default defineSchema({');
|
|
78
83
|
lines.push(' segmentParams: {');
|
|
79
84
|
for (const seg of segments) {
|
|
80
|
-
lines.push(` '${seg}':
|
|
85
|
+
lines.push(` '${seg}': ${defaultCodecForSegment(seg)},`);
|
|
81
86
|
}
|
|
82
87
|
lines.push(' },');
|
|
83
88
|
lines.push('});');
|
|
@@ -147,7 +152,9 @@ function appendToSchema(content: string, newSegments: string[]): string {
|
|
|
147
152
|
const indent = existingLine ? existingLine[1] : ' ';
|
|
148
153
|
|
|
149
154
|
// Build insertion text
|
|
150
|
-
const insertion = newSegments
|
|
155
|
+
const insertion = newSegments
|
|
156
|
+
.map((seg) => `${indent}'${seg}': ${defaultCodecForSegment(seg)},`)
|
|
157
|
+
.join('\n');
|
|
151
158
|
|
|
152
159
|
// Insert before the closing brace
|
|
153
160
|
const before = content.slice(0, closeBrace);
|
|
@@ -159,6 +166,54 @@ function appendToSchema(content: string, newSegments: string[]): string {
|
|
|
159
166
|
return before + separator + insertion + '\n' + after;
|
|
160
167
|
}
|
|
161
168
|
|
|
169
|
+
/**
|
|
170
|
+
* Remove entries from the segmentParams block whose keys match `staleSegments`.
|
|
171
|
+
* Removes the entire line including trailing comma/whitespace.
|
|
172
|
+
*/
|
|
173
|
+
function removeFromSchema(content: string, staleSegments: string[]): string {
|
|
174
|
+
for (const seg of staleSegments) {
|
|
175
|
+
const escaped = seg.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
176
|
+
const keyRe = new RegExp(`^[ \t]*['"]${escaped}['"][ \t]*:`, 'gm');
|
|
177
|
+
let m: RegExpExecArray | null;
|
|
178
|
+
while ((m = keyRe.exec(content)) !== null) {
|
|
179
|
+
const start = m.index;
|
|
180
|
+
let i = m.index + m[0].length;
|
|
181
|
+
let depth = 0;
|
|
182
|
+
let inString: string | null = null;
|
|
183
|
+
|
|
184
|
+
while (i < content.length) {
|
|
185
|
+
const ch = content[i];
|
|
186
|
+
if (inString) {
|
|
187
|
+
if (ch === '\\') {
|
|
188
|
+
i++;
|
|
189
|
+
} else if (ch === inString) {
|
|
190
|
+
inString = null;
|
|
191
|
+
}
|
|
192
|
+
} else if (ch === '"' || ch === "'" || ch === '`') {
|
|
193
|
+
inString = ch;
|
|
194
|
+
} else if (ch === '(' || ch === '{' || ch === '[') {
|
|
195
|
+
depth++;
|
|
196
|
+
} else if (ch === ')' || ch === '}' || ch === ']') {
|
|
197
|
+
depth--;
|
|
198
|
+
} else if (depth === 0 && ch === ',') {
|
|
199
|
+
i++;
|
|
200
|
+
break;
|
|
201
|
+
} else if (depth === 0 && (ch === '\n' || ch === '}')) {
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
i++;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Consume trailing newline
|
|
208
|
+
if (i < content.length && content[i] === '\n') i++;
|
|
209
|
+
|
|
210
|
+
content = content.slice(0, start) + content.slice(i);
|
|
211
|
+
keyRe.lastIndex = start;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return content;
|
|
215
|
+
}
|
|
216
|
+
|
|
162
217
|
export interface SchemaSyncResult {
|
|
163
218
|
/** Segments found in the filesystem. */
|
|
164
219
|
filesystemSegments: string[];
|
|
@@ -166,6 +221,8 @@ export interface SchemaSyncResult {
|
|
|
166
221
|
existingSegments: string[];
|
|
167
222
|
/** Segments that were added. */
|
|
168
223
|
addedSegments: string[];
|
|
224
|
+
/** Segments that were removed (no longer in filesystem). */
|
|
225
|
+
removedSegments: string[];
|
|
169
226
|
/** Whether a new schema file was created. */
|
|
170
227
|
created: boolean;
|
|
171
228
|
}
|
|
@@ -178,17 +235,18 @@ export function runSchemaSync(projectRoot: string): SchemaSyncResult {
|
|
|
178
235
|
|
|
179
236
|
const filesystemSegments = collectDynamicSegments(appDir);
|
|
180
237
|
|
|
181
|
-
|
|
238
|
+
const schemaPath = findSchemaFile(appDir);
|
|
239
|
+
|
|
240
|
+
if (filesystemSegments.length === 0 && !schemaPath) {
|
|
182
241
|
return {
|
|
183
242
|
filesystemSegments: [],
|
|
184
243
|
existingSegments: [],
|
|
185
244
|
addedSegments: [],
|
|
245
|
+
removedSegments: [],
|
|
186
246
|
created: false,
|
|
187
247
|
};
|
|
188
248
|
}
|
|
189
249
|
|
|
190
|
-
const schemaPath = findSchemaFile(appDir);
|
|
191
|
-
|
|
192
250
|
if (!schemaPath) {
|
|
193
251
|
// Create new schema file
|
|
194
252
|
const newPath = join(appDir, 'schema.ts');
|
|
@@ -197,32 +255,41 @@ export function runSchemaSync(projectRoot: string): SchemaSyncResult {
|
|
|
197
255
|
filesystemSegments,
|
|
198
256
|
existingSegments: [],
|
|
199
257
|
addedSegments: filesystemSegments,
|
|
258
|
+
removedSegments: [],
|
|
200
259
|
created: true,
|
|
201
260
|
};
|
|
202
261
|
}
|
|
203
262
|
|
|
204
|
-
// Read existing schema and
|
|
263
|
+
// Read existing schema and diff
|
|
205
264
|
const content = readFileSync(schemaPath, 'utf-8');
|
|
206
265
|
const existingSegments = parseExistingSchemaKeys(content);
|
|
207
266
|
const missing = filesystemSegments.filter((seg) => !existingSegments.includes(seg));
|
|
267
|
+
const stale = existingSegments.filter((seg) => !filesystemSegments.includes(seg));
|
|
208
268
|
|
|
209
|
-
if (missing.length === 0) {
|
|
269
|
+
if (missing.length === 0 && stale.length === 0) {
|
|
210
270
|
return {
|
|
211
271
|
filesystemSegments,
|
|
212
272
|
existingSegments,
|
|
213
273
|
addedSegments: [],
|
|
274
|
+
removedSegments: [],
|
|
214
275
|
created: false,
|
|
215
276
|
};
|
|
216
277
|
}
|
|
217
278
|
|
|
218
|
-
|
|
219
|
-
|
|
279
|
+
let updated = content;
|
|
280
|
+
if (stale.length > 0) {
|
|
281
|
+
updated = removeFromSchema(updated, stale);
|
|
282
|
+
}
|
|
283
|
+
if (missing.length > 0) {
|
|
284
|
+
updated = appendToSchema(updated, missing);
|
|
285
|
+
}
|
|
220
286
|
writeFileSync(schemaPath, updated, 'utf-8');
|
|
221
287
|
|
|
222
288
|
return {
|
|
223
289
|
filesystemSegments,
|
|
224
290
|
existingSegments,
|
|
225
291
|
addedSegments: missing,
|
|
292
|
+
removedSegments: stale,
|
|
226
293
|
created: false,
|
|
227
294
|
};
|
|
228
295
|
}
|
package/src/cli.ts
CHANGED
|
@@ -188,7 +188,7 @@ export async function runCheck(options: CommandOptions): Promise<void> {
|
|
|
188
188
|
|
|
189
189
|
/**
|
|
190
190
|
* `timber schema sync` — scan filesystem for dynamic segments, diff against
|
|
191
|
-
* app/schema.ts, and append missing entries with codec
|
|
191
|
+
* app/schema.ts, and append missing entries with type-appropriate codec defaults.
|
|
192
192
|
*
|
|
193
193
|
* See design/41-global-params.md §Auto-Scaffolding
|
|
194
194
|
*/
|
|
@@ -201,7 +201,7 @@ export async function runSchema(args: string[]): Promise<void> {
|
|
|
201
201
|
);
|
|
202
202
|
}
|
|
203
203
|
|
|
204
|
-
const { runSchemaSync } = await import('./cli-schema-sync.js');
|
|
204
|
+
const { runSchemaSync, defaultCodecForSegment } = await import('./cli-schema-sync.js');
|
|
205
205
|
const result = runSchemaSync(process.cwd());
|
|
206
206
|
|
|
207
207
|
if (result.filesystemSegments.length === 0) {
|
|
@@ -212,19 +212,30 @@ export async function runSchema(args: string[]): Promise<void> {
|
|
|
212
212
|
if (result.created) {
|
|
213
213
|
console.log(`[timber] Created app/schema.ts with ${result.addedSegments.length} segment(s):`);
|
|
214
214
|
for (const seg of result.addedSegments) {
|
|
215
|
-
console.log(` + ${seg}:
|
|
215
|
+
console.log(` + ${seg}: ${defaultCodecForSegment(seg)}`);
|
|
216
216
|
}
|
|
217
217
|
return;
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
-
if (result.addedSegments.length === 0) {
|
|
220
|
+
if (result.addedSegments.length === 0 && result.removedSegments.length === 0) {
|
|
221
221
|
console.log('[timber] Schema is up to date — all segments are registered.');
|
|
222
222
|
return;
|
|
223
223
|
}
|
|
224
224
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
225
|
+
if (result.addedSegments.length > 0) {
|
|
226
|
+
console.log(`[timber] Added ${result.addedSegments.length} segment(s) to app/schema.ts:`);
|
|
227
|
+
for (const seg of result.addedSegments) {
|
|
228
|
+
console.log(` + ${seg}: ${defaultCodecForSegment(seg)}`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (result.removedSegments.length > 0) {
|
|
233
|
+
console.log(
|
|
234
|
+
`[timber] Removed ${result.removedSegments.length} stale segment(s) from app/schema.ts:`
|
|
235
|
+
);
|
|
236
|
+
for (const seg of result.removedSegments) {
|
|
237
|
+
console.log(` - ${seg}`);
|
|
238
|
+
}
|
|
228
239
|
}
|
|
229
240
|
}
|
|
230
241
|
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import { setServerCallback, encodeReply, createFromFetch } from '../../rsc-runtime/browser.js';
|
|
18
|
-
import { getRouter } from '#client-internal';
|
|
18
|
+
import { getRouter, getRouterOrNull } from '#client-internal';
|
|
19
19
|
import { setHardNavigating } from '../navigation-root.js';
|
|
20
20
|
import { isStaleClientReference, triggerStaleReload } from '../stale-reload.js';
|
|
21
21
|
import { getClientDeploymentId, DEPLOYMENT_ID_HEADER, RELOAD_HEADER } from '../rsc-fetch.js';
|
|
@@ -96,15 +96,14 @@ export function setupServerActions(): void {
|
|
|
96
96
|
// Piggybacked response: wrapper object { _action, _tree }
|
|
97
97
|
// Apply the revalidated tree directly — no separate router.refresh() needed.
|
|
98
98
|
const wrapper = decoded as { _action: unknown; _tree: unknown };
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
'[timber]
|
|
106
|
-
|
|
107
|
-
);
|
|
99
|
+
const router = getRouterOrNull();
|
|
100
|
+
if (router) {
|
|
101
|
+
try {
|
|
102
|
+
const headElements = headElementsJson ? JSON.parse(headElementsJson) : null;
|
|
103
|
+
router.applyRevalidation(wrapper._tree, headElements);
|
|
104
|
+
} catch (e) {
|
|
105
|
+
console.error('[timber] applyRevalidation failed after server action', e);
|
|
106
|
+
}
|
|
108
107
|
}
|
|
109
108
|
return wrapper._action;
|
|
110
109
|
}
|
|
@@ -50,12 +50,14 @@ export function runPreHydration(): void {
|
|
|
50
50
|
setNavigationState({
|
|
51
51
|
params: earlyParams as Record<string, string | string[]>,
|
|
52
52
|
pathname: window.location.pathname,
|
|
53
|
+
search: window.location.search,
|
|
53
54
|
});
|
|
54
55
|
delete (self as unknown as Record<string, unknown>).__timber_params;
|
|
55
56
|
} else {
|
|
56
57
|
setNavigationState({
|
|
57
58
|
params: {},
|
|
58
59
|
pathname: window.location.pathname,
|
|
60
|
+
search: window.location.search,
|
|
59
61
|
});
|
|
60
62
|
}
|
|
61
63
|
}
|
|
@@ -58,12 +58,18 @@ export function setupPostHydration({
|
|
|
58
58
|
delete (self as unknown as Record<string, unknown>).__timber_segments;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
//
|
|
62
|
-
// The
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
//
|
|
66
|
-
|
|
61
|
+
// Cache segment elements from the initial RSC payload after hydration.
|
|
62
|
+
// The element from createFromReadableStream is a Flight thenable that
|
|
63
|
+
// React resolves during hydration. Once resolved (status='fulfilled'),
|
|
64
|
+
// its .value contains the React element tree we can walk.
|
|
65
|
+
// Use requestAnimationFrame to run after hydration commits.
|
|
66
|
+
if (initialElement != null) {
|
|
67
|
+
requestAnimationFrame(() => {
|
|
68
|
+
const el = initialElement as { status?: string; value?: unknown };
|
|
69
|
+
const resolved = el.status === 'fulfilled' && el.value != null ? el.value : initialElement;
|
|
70
|
+
router.cacheElementTree(resolved);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
67
73
|
|
|
68
74
|
// If the hydration path was skipped (no RSC payload), populate the
|
|
69
75
|
// fallback params from server embed here.
|
|
@@ -73,6 +79,7 @@ export function setupPostHydration({
|
|
|
73
79
|
setNavigationState({
|
|
74
80
|
params: lateTimberParams as Record<string, string | string[]>,
|
|
75
81
|
pathname: window.location.pathname,
|
|
82
|
+
search: window.location.search,
|
|
76
83
|
});
|
|
77
84
|
delete (self as unknown as Record<string, unknown>).__timber_params;
|
|
78
85
|
}
|
package/src/client/index.ts
CHANGED
|
@@ -66,6 +66,7 @@ export type { LinkStatus } from './use-link-status';
|
|
|
66
66
|
export { useRouter } from './use-router';
|
|
67
67
|
export type { AppRouterInstance } from './use-router';
|
|
68
68
|
export { usePathname } from './use-pathname';
|
|
69
|
+
export { replaceUrl } from './shallow-url';
|
|
69
70
|
// useSearchParams removed from public exports — lives in next/navigation shim only.
|
|
70
71
|
export { useSelectedLayoutSegment, useSelectedLayoutSegments } from './use-selected-layout-segment';
|
|
71
72
|
|
|
@@ -82,8 +83,3 @@ export { useSegmentParams } from './use-segment-params';
|
|
|
82
83
|
// Cookies — useCookie lives on defineCookie().useCookie().
|
|
83
84
|
// Types still exported for advanced use.
|
|
84
85
|
export type { ClientCookieOptions, CookieSetter } from './use-cookie';
|
|
85
|
-
|
|
86
|
-
// Register the client useSegmentParams hook with defineSegmentParams.
|
|
87
|
-
import { useSegmentParams as _useSegmentParams } from './use-segment-params.js';
|
|
88
|
-
import { _registerUseSegmentParams } from '../segment-params/define.js';
|
|
89
|
-
_registerUseSegmentParams(_useSegmentParams);
|
package/src/client/link.tsx
CHANGED
|
@@ -384,20 +384,28 @@ export function resolveHref(
|
|
|
384
384
|
| SearchParamsDefinition<Record<string, unknown>>
|
|
385
385
|
| undefined;
|
|
386
386
|
values = searchParams;
|
|
387
|
-
if (!definition) {
|
|
388
|
-
throw new Error(
|
|
389
|
-
`<Link> received a flat searchParams object for href "${href}", but no ` +
|
|
390
|
-
`SearchParamsDefinition is registered for that route. ` +
|
|
391
|
-
`Either the route does not export \`searchParams\` from its \`params.ts\`/\`page.tsx\`, ` +
|
|
392
|
-
`or the search-params registry module was not loaded. ` +
|
|
393
|
-
`For external or computed hrefs, pass the legacy \`{ definition, values }\` shape instead.`
|
|
394
|
-
);
|
|
395
|
-
}
|
|
396
387
|
}
|
|
397
388
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
389
|
+
if (definition) {
|
|
390
|
+
const qs = definition.serialize(values);
|
|
391
|
+
if (qs) {
|
|
392
|
+
resolvedPath = `${resolvedPath}?${qs}`;
|
|
393
|
+
}
|
|
394
|
+
} else {
|
|
395
|
+
// No registered definition — serialize flat object as plain query params.
|
|
396
|
+
const usp = new URLSearchParams();
|
|
397
|
+
for (const [key, val] of Object.entries(values)) {
|
|
398
|
+
if (val === undefined || val === null) continue;
|
|
399
|
+
if (Array.isArray(val)) {
|
|
400
|
+
for (const item of val) usp.append(key, String(item));
|
|
401
|
+
} else {
|
|
402
|
+
usp.set(key, String(val));
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
const qs = usp.toString();
|
|
406
|
+
if (qs) {
|
|
407
|
+
resolvedPath = `${resolvedPath}?${qs}`;
|
|
408
|
+
}
|
|
401
409
|
}
|
|
402
410
|
}
|
|
403
411
|
|
|
@@ -591,6 +599,11 @@ export const Link: LinkFunction = function LinkImpl(props: any) {
|
|
|
591
599
|
return;
|
|
592
600
|
}
|
|
593
601
|
|
|
602
|
+
// Hash-only hrefs — let the browser handle scroll-to-anchor natively.
|
|
603
|
+
// The Navigation API path already skips these via event.hashChange;
|
|
604
|
+
// this covers the fallback (Safari/Firefox) where we drive navigation manually.
|
|
605
|
+
if (resolvedHref.startsWith('#')) return;
|
|
606
|
+
|
|
594
607
|
// History API fallback — prevent default and navigate via router
|
|
595
608
|
event.preventDefault();
|
|
596
609
|
|
|
@@ -48,6 +48,7 @@ import React, { createElement, type ReactNode } from 'react';
|
|
|
48
48
|
export interface NavigationState {
|
|
49
49
|
params: Record<string, string | string[]>;
|
|
50
50
|
pathname: string;
|
|
51
|
+
search: string;
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
// ---------------------------------------------------------------------------
|
|
@@ -153,7 +154,7 @@ const NAV_STATE_KEY = Symbol.for('__timber_nav_state');
|
|
|
153
154
|
function _getNavStateStore(): { current: NavigationState } {
|
|
154
155
|
const g = globalThis as Record<symbol, unknown>;
|
|
155
156
|
if (!g[NAV_STATE_KEY]) {
|
|
156
|
-
g[NAV_STATE_KEY] = { current: { params: {}, pathname: '/' } };
|
|
157
|
+
g[NAV_STATE_KEY] = { current: { params: {}, pathname: '/', search: '' } };
|
|
157
158
|
}
|
|
158
159
|
return g[NAV_STATE_KEY] as { current: NavigationState };
|
|
159
160
|
}
|