@decocms/tanstack 7.10.0 → 7.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -4
- package/src/index.ts +3 -3
- package/src/routes/adminRoutes.test.ts +99 -0
- package/src/routes/adminRoutes.ts +58 -19
- package/src/routes/index.ts +3 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decocms/tanstack",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.11.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Deco framework binding for TanStack Start + Cloudflare Workers",
|
|
6
6
|
"repository": {
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
"lint:unused": "knip"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@decocms/blocks": "7.
|
|
28
|
-
"@decocms/blocks-admin": "7.
|
|
29
|
-
"@decocms/blocks-cli": "7.
|
|
27
|
+
"@decocms/blocks": "7.11.0",
|
|
28
|
+
"@decocms/blocks-admin": "7.11.0",
|
|
29
|
+
"@decocms/blocks-cli": "7.11.0",
|
|
30
30
|
"@deco-cx/warp-node": "^0.3.16",
|
|
31
31
|
"fast-json-patch": "^3.1.0",
|
|
32
32
|
"ws": "^8.18.0"
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
vi.mock("@decocms/blocks-admin", () => ({
|
|
4
|
+
corsHeaders: vi.fn(() => ({})),
|
|
5
|
+
handleInvoke: vi.fn(),
|
|
6
|
+
handleMeta: vi.fn(),
|
|
7
|
+
handleRender: vi.fn(),
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
vi.mock("@decocms/blocks/sdk/observability", () => ({
|
|
11
|
+
withTracing: vi.fn((_name: string, fn: () => unknown) => fn()),
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
import * as adminRoutes from "./adminRoutes";
|
|
15
|
+
import { decoInvokeRouteConfig, decoMetaRouteConfig, decoRenderRouteConfig } from "./adminRoutes";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Regression guard for the dev-HMR brick:
|
|
19
|
+
*
|
|
20
|
+
* TanStack router-core's `BaseRoute.update()` mutates the options object it
|
|
21
|
+
* is handed (`Object.assign(this.options, options)` — injecting `id`/`path`).
|
|
22
|
+
* Before 7.10.0 this module exported shared module-scope literals
|
|
23
|
+
* (`decoMetaRoute`/`decoRenderRoute`/`decoInvokeRoute`); a site passing one
|
|
24
|
+
* by reference polluted it on first execution, and on any HMR re-execution
|
|
25
|
+
* the route constructor threw
|
|
26
|
+
* `Route cannot have both an 'id' and a 'path' option` — every route 500ed
|
|
27
|
+
* until dev restart.
|
|
28
|
+
*
|
|
29
|
+
* The `*RouteConfig()` factories must therefore return a FRESH object on
|
|
30
|
+
* every call, and the shared literals must never be (re-)exported.
|
|
31
|
+
*/
|
|
32
|
+
describe("admin route config factories", () => {
|
|
33
|
+
const cases = [
|
|
34
|
+
{
|
|
35
|
+
name: "decoMetaRouteConfig",
|
|
36
|
+
factory: decoMetaRouteConfig,
|
|
37
|
+
methods: ["GET", "OPTIONS"],
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: "decoRenderRouteConfig",
|
|
41
|
+
factory: decoRenderRouteConfig,
|
|
42
|
+
methods: ["GET", "POST", "OPTIONS"],
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: "decoInvokeRouteConfig",
|
|
46
|
+
factory: decoInvokeRouteConfig,
|
|
47
|
+
methods: ["GET", "POST", "OPTIONS"],
|
|
48
|
+
},
|
|
49
|
+
] as const;
|
|
50
|
+
|
|
51
|
+
for (const { name, factory, methods } of cases) {
|
|
52
|
+
describe(name, () => {
|
|
53
|
+
it("returns a new object on every call", () => {
|
|
54
|
+
expect(factory()).not.toBe(factory());
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("returns structurally equal configs across calls", () => {
|
|
58
|
+
expect(factory()).toEqual(factory());
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it(`exposes server handlers for ${methods.join("/")}`, () => {
|
|
62
|
+
const config = factory() as {
|
|
63
|
+
server: { handlers: Record<string, unknown> };
|
|
64
|
+
};
|
|
65
|
+
expect(Object.keys(config.server.handlers).sort()).toEqual([...methods].sort());
|
|
66
|
+
for (const method of methods) {
|
|
67
|
+
expect(config.server.handlers[method]).toBeTypeOf("function");
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("survives router-core-style mutation without polluting later calls", () => {
|
|
72
|
+
// Simulate BaseRoute.update(): Object.assign(this.options, options)
|
|
73
|
+
// pollutes whatever object createFileRoute was handed.
|
|
74
|
+
const options = factory() as Record<string, unknown>;
|
|
75
|
+
Object.assign(options, { id: "/deco/x", path: "/deco/x" });
|
|
76
|
+
|
|
77
|
+
// The next module execution (HMR) gets a clean object again.
|
|
78
|
+
const next = factory();
|
|
79
|
+
expect(next).not.toHaveProperty("id");
|
|
80
|
+
expect(next).not.toHaveProperty("path");
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
it("does NOT export the removed pre-7.10.0 literals (dev-HMR footgun)", () => {
|
|
86
|
+
// The shared module-scope literals were removed in 7.10.0. Re-exporting
|
|
87
|
+
// them would reintroduce the HMR brick for any site passing them by
|
|
88
|
+
// reference to createFileRoute.
|
|
89
|
+
const removed = ["decoMetaRoute", "decoRenderRoute", "decoInvokeRoute"];
|
|
90
|
+
for (const name of removed) {
|
|
91
|
+
expect(adminRoutes, `"${name}" must not be exported`).not.toHaveProperty(name);
|
|
92
|
+
}
|
|
93
|
+
expect(Object.keys(adminRoutes).sort()).toEqual([
|
|
94
|
+
"decoInvokeRouteConfig",
|
|
95
|
+
"decoMetaRouteConfig",
|
|
96
|
+
"decoRenderRouteConfig",
|
|
97
|
+
]);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
@@ -1,17 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Admin Route Helpers
|
|
3
3
|
*
|
|
4
|
-
* Pre-built server handler
|
|
5
|
-
* Sites
|
|
4
|
+
* Pre-built server handler config factories for the Deco admin protocol
|
|
5
|
+
* routes. Sites call these in their `createFileRoute` definitions to avoid
|
|
6
6
|
* repeating the same CORS + handler boilerplate.
|
|
7
7
|
*
|
|
8
8
|
* @example Site's `src/routes/deco/meta.ts`:
|
|
9
9
|
* ```ts
|
|
10
10
|
* import { createFileRoute } from "@tanstack/react-router";
|
|
11
|
-
* import {
|
|
11
|
+
* import { decoMetaRouteConfig } from "@decocms/tanstack";
|
|
12
12
|
*
|
|
13
|
-
* export const Route = createFileRoute("/deco/meta")(
|
|
13
|
+
* export const Route = createFileRoute("/deco/meta")(decoMetaRouteConfig());
|
|
14
14
|
* ```
|
|
15
|
+
*
|
|
16
|
+
* ## Why factories and not shared config objects? (dev-HMR footgun)
|
|
17
|
+
*
|
|
18
|
+
* TanStack router-core's `BaseRoute.update()` MUTATES the options object it
|
|
19
|
+
* receives (`Object.assign(this.options, options)` — injecting `id` and
|
|
20
|
+
* `path`). Before 7.10.0 this module exported the configs as module-scope
|
|
21
|
+
* LITERALS (`decoMetaRoute` / `decoRenderRoute` / `decoInvokeRoute`); a site
|
|
22
|
+
* passing one by reference (`createFileRoute("/deco/meta")(decoMetaRoute)`)
|
|
23
|
+
* polluted the shared literal on first execution, and any dev-server HMR
|
|
24
|
+
* partial re-execution then re-ran the route file against the still-cached,
|
|
25
|
+
* now-polluted literal — the route constructor threw
|
|
26
|
+
* `Route cannot have both an 'id' and a 'path' option` and every route 500ed
|
|
27
|
+
* until the dev server restarted. The factories hand each `createFileRoute`
|
|
28
|
+
* call its own fresh object, so the mutation is harmless. The base configs
|
|
29
|
+
* below are module-private and deliberately NOT exported.
|
|
15
30
|
*/
|
|
16
31
|
import { corsHeaders, handleInvoke, handleMeta, handleRender } from "@decocms/blocks-admin";
|
|
17
32
|
import { withTracing } from "@decocms/blocks/sdk/observability";
|
|
@@ -54,11 +69,15 @@ function optionsHandler(ctx: { request: Request }): Response {
|
|
|
54
69
|
});
|
|
55
70
|
}
|
|
56
71
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// Base configs — module-PRIVATE. Never export these: router-core's update()
|
|
74
|
+
// mutates whatever object createFileRoute is handed (injects id/path), so a
|
|
75
|
+
// shared exported literal bricks dev HMR (see module doc above). Sites get
|
|
76
|
+
// fresh copies via the *RouteConfig() factories below.
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
|
|
79
|
+
/** Base config for `/deco/meta` — serves JSON Schema + manifest. */
|
|
80
|
+
const decoMetaRoute = {
|
|
62
81
|
server: {
|
|
63
82
|
handlers: {
|
|
64
83
|
GET: withCors(({ request }) =>
|
|
@@ -69,11 +88,8 @@ export const decoMetaRoute = {
|
|
|
69
88
|
},
|
|
70
89
|
};
|
|
71
90
|
|
|
72
|
-
/**
|
|
73
|
-
|
|
74
|
-
* Spread into `createFileRoute("/deco/render")({...})`.
|
|
75
|
-
*/
|
|
76
|
-
export const decoRenderRoute = {
|
|
91
|
+
/** Base config for `/deco/render` — section/page preview in iframe. */
|
|
92
|
+
const decoRenderRoute = {
|
|
77
93
|
server: {
|
|
78
94
|
handlers: {
|
|
79
95
|
GET: withCors(({ request }) =>
|
|
@@ -95,11 +111,8 @@ export const decoRenderRoute = {
|
|
|
95
111
|
},
|
|
96
112
|
};
|
|
97
113
|
|
|
98
|
-
/**
|
|
99
|
-
|
|
100
|
-
* Spread into `createFileRoute("/deco/invoke/$")({...})`.
|
|
101
|
-
*/
|
|
102
|
-
export const decoInvokeRoute = {
|
|
114
|
+
/** Base config for `/deco/invoke/$` — loader/action execution. */
|
|
115
|
+
const decoInvokeRoute = {
|
|
103
116
|
server: {
|
|
104
117
|
handlers: {
|
|
105
118
|
GET: withCors(({ request }) =>
|
|
@@ -112,3 +125,29 @@ export const decoInvokeRoute = {
|
|
|
112
125
|
},
|
|
113
126
|
},
|
|
114
127
|
};
|
|
128
|
+
|
|
129
|
+
// ---------------------------------------------------------------------------
|
|
130
|
+
// Factories — dev-HMR-safe route configs (fresh object per call)
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
// Mirrors the `cmsRouteConfig()` / `cmsHomeRouteConfig()` convention in
|
|
133
|
+
// cmsRoute.ts: a function returning a fresh options object, so router-core's
|
|
134
|
+
// mutating `update()` can never pollute shared module state across HMR
|
|
135
|
+
// re-executions.
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Returns a fresh route config for `/deco/meta` — serves JSON Schema + manifest.
|
|
139
|
+
* Use as `createFileRoute("/deco/meta")(decoMetaRouteConfig())`.
|
|
140
|
+
*/
|
|
141
|
+
export const decoMetaRouteConfig = () => ({ ...decoMetaRoute });
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Returns a fresh route config for `/deco/render` — section/page preview in iframe.
|
|
145
|
+
* Use as `createFileRoute("/deco/render")(decoRenderRouteConfig())`.
|
|
146
|
+
*/
|
|
147
|
+
export const decoRenderRouteConfig = () => ({ ...decoRenderRoute });
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Returns a fresh route config for `/deco/invoke/$` — loader/action execution.
|
|
151
|
+
* Use as `createFileRoute("/deco/invoke/$")(decoInvokeRouteConfig())`.
|
|
152
|
+
*/
|
|
153
|
+
export const decoInvokeRouteConfig = () => ({ ...decoInvokeRoute });
|
package/src/routes/index.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
export type { PageSeo } from "@decocms/blocks/cms";
|
|
2
2
|
export type { Device } from "@decocms/blocks/sdk/useDevice";
|
|
3
3
|
export {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
decoInvokeRouteConfig,
|
|
5
|
+
decoMetaRouteConfig,
|
|
6
|
+
decoRenderRouteConfig,
|
|
7
7
|
} from "./adminRoutes";
|
|
8
8
|
export {
|
|
9
9
|
CmsPagePendingFallback,
|