@decocms/start 0.19.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/.cursor/skills/deco-api-call-dedup/SKILL.md +443 -0
- package/.cursor/skills/deco-apps-architecture/SKILL.md +255 -0
- package/.cursor/skills/deco-apps-architecture/app-pattern.md +288 -0
- package/.cursor/skills/deco-apps-architecture/commerce-types.md +239 -0
- package/.cursor/skills/deco-apps-architecture/new-app-guide.md +268 -0
- package/.cursor/skills/deco-apps-architecture/scripts-codegen.md +148 -0
- package/.cursor/skills/deco-apps-architecture/shared-utils.md +181 -0
- package/.cursor/skills/deco-apps-architecture/vtex-deep-structure.md +253 -0
- package/.cursor/skills/deco-apps-architecture/website-app.md +169 -0
- package/.cursor/skills/deco-apps-vtex-porting/SKILL.md +189 -0
- package/.cursor/skills/deco-apps-vtex-porting/adaptation-patterns.md +335 -0
- package/.cursor/skills/deco-apps-vtex-porting/commerce-porting.md +155 -0
- package/.cursor/skills/deco-apps-vtex-porting/cookie-auth-patterns.md +148 -0
- package/.cursor/skills/deco-apps-vtex-porting/structure-map.md +234 -0
- package/.cursor/skills/deco-apps-vtex-porting/transform-mapping.md +99 -0
- package/.cursor/skills/deco-apps-vtex-porting/website-porting.md +194 -0
- package/.cursor/skills/deco-apps-vtex-review/SKILL.md +234 -0
- package/.cursor/skills/deco-async-rendering-architecture/SKILL.md +270 -0
- package/.cursor/skills/deco-async-rendering-site-guide/SKILL.md +417 -0
- package/.cursor/skills/deco-cms-layout-caching/SKILL.md +293 -0
- package/.cursor/skills/deco-cms-route-config/SKILL.md +388 -0
- package/.cursor/skills/deco-core-architecture/SKILL.md +185 -0
- package/.cursor/skills/deco-core-architecture/blocks.md +196 -0
- package/.cursor/skills/deco-core-architecture/deco-vs-deco-start.md +191 -0
- package/.cursor/skills/deco-core-architecture/engine.md +220 -0
- package/.cursor/skills/deco-core-architecture/hooks-components.md +157 -0
- package/.cursor/skills/deco-core-architecture/plugins-clients.md +136 -0
- package/.cursor/skills/deco-core-architecture/runtime.md +116 -0
- package/.cursor/skills/deco-core-architecture/site-usage.md +165 -0
- package/.cursor/skills/deco-e2e-testing/SKILL.md +372 -0
- package/.cursor/skills/deco-e2e-testing/discovery.md +337 -0
- package/.cursor/skills/deco-e2e-testing/scripts/scaffold.sh +81 -0
- package/.cursor/skills/deco-e2e-testing/selectors.md +175 -0
- package/.cursor/skills/deco-e2e-testing/templates/package.json +18 -0
- package/.cursor/skills/deco-e2e-testing/templates/playwright.config.ts +65 -0
- package/.cursor/skills/deco-e2e-testing/templates/scripts/baseline.ts +279 -0
- package/.cursor/skills/deco-e2e-testing/templates/scripts/run-e2e.ts +194 -0
- package/.cursor/skills/deco-e2e-testing/templates/specs/ecommerce-flow.spec.ts +612 -0
- package/.cursor/skills/deco-e2e-testing/templates/tsconfig.json +12 -0
- package/.cursor/skills/deco-e2e-testing/templates/utils/metrics-collector.ts +918 -0
- package/.cursor/skills/deco-e2e-testing/troubleshooting.md +602 -0
- package/.cursor/skills/deco-edge-caching/SKILL.md +316 -0
- package/.cursor/skills/deco-full-analysis/SKILL.md +898 -0
- package/.cursor/skills/deco-full-analysis/checklists/asset-optimization.md +251 -0
- package/.cursor/skills/deco-full-analysis/checklists/bug-fix.md +189 -0
- package/.cursor/skills/deco-full-analysis/checklists/cache-strategy.md +144 -0
- package/.cursor/skills/deco-full-analysis/checklists/dependency-update.md +150 -0
- package/.cursor/skills/deco-full-analysis/checklists/hydration-fix.md +191 -0
- package/.cursor/skills/deco-full-analysis/checklists/image-optimization.md +180 -0
- package/.cursor/skills/deco-full-analysis/checklists/loader-optimization.md +165 -0
- package/.cursor/skills/deco-full-analysis/checklists/seo-fix.md +183 -0
- package/.cursor/skills/deco-full-analysis/checklists/site-cleanup.md +281 -0
- package/.cursor/skills/deco-full-analysis/discovery.md +548 -0
- package/.cursor/skills/deco-incident-debugging/SKILL.md +378 -0
- package/.cursor/skills/deco-incident-debugging/headless-mode.md +510 -0
- package/.cursor/skills/deco-incident-debugging/learnings-index.md +227 -0
- package/.cursor/skills/deco-incident-debugging/triage-workflow.md +312 -0
- package/.cursor/skills/deco-islands-migration/SKILL.md +251 -0
- package/.cursor/skills/deco-loader-n-plus-1-detector/SKILL.md +275 -0
- package/.cursor/skills/deco-performance-audit/SKILL.md +530 -0
- package/.cursor/skills/deco-performance-audit/tools-reference.md +428 -0
- package/.cursor/skills/deco-performance-audit/workflow.md +457 -0
- package/.cursor/skills/deco-server-functions-invoke/SKILL.md +92 -0
- package/.cursor/skills/deco-server-functions-invoke/architecture.md +166 -0
- package/.cursor/skills/deco-server-functions-invoke/generator.md +122 -0
- package/.cursor/skills/deco-server-functions-invoke/problem.md +98 -0
- package/.cursor/skills/deco-server-functions-invoke/troubleshooting.md +110 -0
- package/.cursor/skills/deco-site-deployment/SKILL.md +396 -0
- package/.cursor/skills/deco-site-memory-debugging/SKILL.md +121 -0
- package/.cursor/skills/deco-site-memory-debugging/cdp-connection.md +222 -0
- package/.cursor/skills/deco-site-memory-debugging/memory-analysis.md +362 -0
- package/.cursor/skills/deco-site-patterns/SKILL.md +124 -0
- package/.cursor/skills/deco-site-patterns/app-composition.md +337 -0
- package/.cursor/skills/deco-site-patterns/client-patterns.md +341 -0
- package/.cursor/skills/deco-site-patterns/cms-wiring.md +230 -0
- package/.cursor/skills/deco-site-patterns/section-patterns.md +340 -0
- package/.cursor/skills/deco-site-scaling-tuning/SKILL.md +240 -0
- package/.cursor/skills/deco-site-scaling-tuning/analysis-scripts.md +267 -0
- package/.cursor/skills/deco-start-architecture/SKILL.md +218 -0
- package/.cursor/skills/deco-start-architecture/admin-protocol.md +156 -0
- package/.cursor/skills/deco-start-architecture/cms-resolution.md +201 -0
- package/.cursor/skills/deco-start-architecture/code-quality.md +158 -0
- package/.cursor/skills/deco-start-architecture/gap-analysis.md +129 -0
- package/.cursor/skills/deco-start-architecture/sdk-utilities.md +197 -0
- package/.cursor/skills/deco-start-architecture/worker-entry-caching.md +154 -0
- package/.cursor/skills/deco-startup-analysis/SKILL.md +248 -0
- package/.cursor/skills/deco-storefront-test-checklist/SKILL.md +369 -0
- package/.cursor/skills/deco-tanstack-hydration-fixes/SKILL.md +468 -0
- package/.cursor/skills/deco-tanstack-navigation/SKILL.md +681 -0
- package/.cursor/skills/deco-tanstack-search/SKILL.md +411 -0
- package/.cursor/skills/deco-tanstack-storefront-patterns/SKILL.md +1013 -0
- package/.cursor/skills/deco-to-tanstack-migration/SKILL.md +518 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/codemod-commands.md +174 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/commerce/README.md +78 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/deco-framework/README.md +128 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/gotchas.md +719 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/imports/README.md +70 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/platform-hooks/README.md +154 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/signals/README.md +220 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/vite-config/README.md +78 -0
- package/.cursor/skills/deco-to-tanstack-migration/templates/package-json.md +55 -0
- package/.cursor/skills/deco-to-tanstack-migration/templates/root-route.md +110 -0
- package/.cursor/skills/deco-to-tanstack-migration/templates/router.md +96 -0
- package/.cursor/skills/deco-to-tanstack-migration/templates/setup-ts.md +167 -0
- package/.cursor/skills/deco-to-tanstack-migration/templates/vite-config.md +122 -0
- package/.cursor/skills/deco-to-tanstack-migration/templates/worker-entry.md +67 -0
- package/.cursor/skills/deco-typescript-fixes/SKILL.md +178 -0
- package/.cursor/skills/deco-typescript-fixes/common-fixes.md +330 -0
- package/.cursor/skills/deco-typescript-fixes/strategy.md +148 -0
- package/.cursor/skills/deco-variant-selection-perf/SKILL.md +272 -0
- package/.cursor/skills/deco-vtex-fetch-cache/SKILL.md +225 -0
- package/.cursor/skills/find-skills/SKILL.md +133 -0
- package/.cursor/skills/incident-report/SKILL.md +179 -0
- package/.cursor/skills/incident-report/references/5-whys.md +75 -0
- package/.cursor/skills/incident-report/templates/client-report.md +187 -0
- package/.cursor/skills/incident-report/templates/internal-report.md +206 -0
- package/.cursor/skills/template-skill/SKILL.md +38 -0
- package/.github/workflows/release.yml +32 -0
- package/.releaserc.json +25 -0
- package/CLAUDE.md +135 -0
- package/GAP_ANALYSIS.md +224 -0
- package/GAP_ANALYSIS_V2.md +1013 -0
- package/biome.json +39 -0
- package/knip.json +5 -0
- package/package.json +87 -0
- package/scripts/generate-blocks.ts +69 -0
- package/scripts/generate-invoke.ts +378 -0
- package/scripts/generate-schema.ts +657 -0
- package/src/admin/cors.ts +29 -0
- package/src/admin/decofile.ts +72 -0
- package/src/admin/index.ts +24 -0
- package/src/admin/invoke.ts +163 -0
- package/src/admin/liveControls.ts +29 -0
- package/src/admin/meta.ts +70 -0
- package/src/admin/render.ts +205 -0
- package/src/admin/schema.ts +686 -0
- package/src/admin/setup.ts +44 -0
- package/src/cms/index.ts +59 -0
- package/src/cms/loader.ts +180 -0
- package/src/cms/registry.ts +162 -0
- package/src/cms/resolve.ts +1005 -0
- package/src/cms/sectionLoaders.ts +294 -0
- package/src/hooks/DecoPageRenderer.tsx +444 -0
- package/src/hooks/LazySection.tsx +109 -0
- package/src/hooks/LiveControls.tsx +108 -0
- package/src/hooks/SectionErrorFallback.tsx +85 -0
- package/src/hooks/index.ts +8 -0
- package/src/index.ts +5 -0
- package/src/matchers/builtins.ts +184 -0
- package/src/matchers/posthog.ts +154 -0
- package/src/middleware/decoState.ts +55 -0
- package/src/middleware/healthMetrics.ts +131 -0
- package/src/middleware/index.ts +80 -0
- package/src/middleware/liveness.ts +21 -0
- package/src/middleware/observability.ts +205 -0
- package/src/routes/adminRoutes.ts +83 -0
- package/src/routes/cmsRoute.ts +302 -0
- package/src/routes/components.tsx +34 -0
- package/src/routes/index.ts +15 -0
- package/src/sdk/analytics.ts +72 -0
- package/src/sdk/cacheHeaders.ts +268 -0
- package/src/sdk/cachedLoader.ts +206 -0
- package/src/sdk/clx.ts +3 -0
- package/src/sdk/cookie.ts +39 -0
- package/src/sdk/createInvoke.ts +57 -0
- package/src/sdk/csp.ts +59 -0
- package/src/sdk/env.ts +27 -0
- package/src/sdk/index.ts +63 -0
- package/src/sdk/instrumentedFetch.ts +137 -0
- package/src/sdk/invoke.ts +133 -0
- package/src/sdk/mergeCacheControl.ts +150 -0
- package/src/sdk/redirects.ts +217 -0
- package/src/sdk/requestContext.ts +184 -0
- package/src/sdk/serverTimings.ts +68 -0
- package/src/sdk/signal.ts +41 -0
- package/src/sdk/sitemap.ts +143 -0
- package/src/sdk/urlUtils.ts +117 -0
- package/src/sdk/useDevice.ts +82 -0
- package/src/sdk/useId.ts +7 -0
- package/src/sdk/useScript.ts +101 -0
- package/src/sdk/workerEntry.ts +703 -0
- package/src/sdk/wrapCaughtErrors.ts +107 -0
- package/src/types/index.ts +39 -0
- package/src/types/widgets.ts +13 -0
- package/tsconfig.json +13 -0
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# Analysis Scripts
|
|
2
|
+
|
|
3
|
+
Ready-to-use Python scripts for discovering optimal scaling parameters via Prometheus.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Port-forward Prometheus
|
|
9
|
+
PROM_POD=$(kubectl get pods -n monitoring -l app.kubernetes.io/name=prometheus -o jsonpath='{.items[0].metadata.name}')
|
|
10
|
+
kubectl port-forward -n monitoring $PROM_POD 19090:9090 &
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Script 1: Full Scaling Analysis
|
|
14
|
+
|
|
15
|
+
Collects all relevant metrics and produces a CPU vs latency correlation table.
|
|
16
|
+
|
|
17
|
+
**Usage:** Change `SITENAME` and run. Uses 12h of data by default.
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
#!/usr/bin/env python3
|
|
21
|
+
"""Discover optimal scaling parameters for a Deco site."""
|
|
22
|
+
import json, urllib.request, urllib.parse, time
|
|
23
|
+
from datetime import datetime
|
|
24
|
+
|
|
25
|
+
PROM = "http://127.0.0.1:19090"
|
|
26
|
+
SITENAME = "CHANGE_ME" # <-- Set this
|
|
27
|
+
NS = f"sites-{SITENAME}"
|
|
28
|
+
HOURS = 12
|
|
29
|
+
END = int(time.time())
|
|
30
|
+
START = END - HOURS * 3600
|
|
31
|
+
|
|
32
|
+
def prom_qr(query, step=120):
|
|
33
|
+
params = urllib.parse.urlencode({"query": query, "start": START, "end": END, "step": step})
|
|
34
|
+
with urllib.request.urlopen(f"{PROM}/api/v1/query_range?{params}") as r:
|
|
35
|
+
return json.loads(r.read())
|
|
36
|
+
|
|
37
|
+
def prom_q(query):
|
|
38
|
+
params = urllib.parse.urlencode({"query": query})
|
|
39
|
+
with urllib.request.urlopen(f"{PROM}/api/v1/query?{params}") as r:
|
|
40
|
+
return json.loads(r.read())
|
|
41
|
+
|
|
42
|
+
def to_map(data):
|
|
43
|
+
m = {}
|
|
44
|
+
for r in data['data']['result']:
|
|
45
|
+
for ts, v in r['values']:
|
|
46
|
+
try:
|
|
47
|
+
f = float(v)
|
|
48
|
+
if str(v) not in ('NaN', '+Inf', '-Inf'):
|
|
49
|
+
m[int(float(ts))] = f
|
|
50
|
+
except: pass
|
|
51
|
+
return m
|
|
52
|
+
|
|
53
|
+
# --- Current config ---
|
|
54
|
+
print("=" * 70)
|
|
55
|
+
print(f"SCALING ANALYSIS: {SITENAME} (last {HOURS}h)")
|
|
56
|
+
print("=" * 70)
|
|
57
|
+
|
|
58
|
+
target = prom_q(f'autoscaler_target_concurrency_per_pod{{namespace_name="{NS}"}}')
|
|
59
|
+
for r in target['data']['result']:
|
|
60
|
+
print(f" Current target: {r['value'][1]} conc/pod")
|
|
61
|
+
|
|
62
|
+
# --- Collect metrics ---
|
|
63
|
+
cpu = to_map(prom_qr(f'avg(rate(container_cpu_usage_seconds_total{{namespace="{NS}", container="app"}}[2m]))', 120))
|
|
64
|
+
# Fallback: try container="user-container" if "app" yields nothing
|
|
65
|
+
if not cpu:
|
|
66
|
+
cpu = to_map(prom_qr(f'avg(rate(container_cpu_usage_seconds_total{{namespace="{NS}", container!="", container!="POD", container!="queue-proxy"}}[2m]))', 120))
|
|
67
|
+
|
|
68
|
+
pods_m = to_map(prom_qr(f'autoscaler_actual_pods{{namespace_name="{NS}"}}', 120))
|
|
69
|
+
conc_m = to_map(prom_qr(f'autoscaler_stable_request_concurrency{{namespace_name="{NS}"}}', 120))
|
|
70
|
+
panic_m = to_map(prom_qr(f'autoscaler_panic_mode{{namespace_name="{NS}"}}', 120))
|
|
71
|
+
|
|
72
|
+
# Try direct latency (requires queue-proxy PodMonitor)
|
|
73
|
+
lat_m = to_map(prom_qr(
|
|
74
|
+
f'avg(rate(revision_app_request_latencies_sum{{namespace="{NS}"}}[2m]) / rate(revision_app_request_latencies_count{{namespace="{NS}"}}[2m]))', 120
|
|
75
|
+
))
|
|
76
|
+
has_latency = bool(lat_m)
|
|
77
|
+
if has_latency:
|
|
78
|
+
print(" ✓ Direct latency metrics available (queue-proxy)")
|
|
79
|
+
else:
|
|
80
|
+
print(" ✗ No direct latency metrics — using concurrency as proxy")
|
|
81
|
+
|
|
82
|
+
# --- Build data points ---
|
|
83
|
+
common = sorted(set(cpu.keys()) & set(pods_m.keys()) & set(conc_m.keys()))
|
|
84
|
+
points = []
|
|
85
|
+
for ts in common:
|
|
86
|
+
c = cpu[ts]; p = pods_m[ts]; tc = conc_m[ts]
|
|
87
|
+
if c > 0 and p > 0:
|
|
88
|
+
points.append({
|
|
89
|
+
'ts': ts, 'cpu_m': c * 1000, 'pods': p,
|
|
90
|
+
'total_conc': tc, 'cpod': tc / p,
|
|
91
|
+
'panic': panic_m.get(ts, 0),
|
|
92
|
+
'lat_ms': lat_m.get(ts),
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
if not points:
|
|
96
|
+
print(" ERROR: No data points. Check namespace and metrics availability.")
|
|
97
|
+
exit(1)
|
|
98
|
+
|
|
99
|
+
print(f" Data points: {len(points)}")
|
|
100
|
+
|
|
101
|
+
# --- Panic events ---
|
|
102
|
+
panic_count = sum(1 for i in range(1, len(points)) if points[i]['panic'] == 1 and points[i-1]['panic'] == 0)
|
|
103
|
+
print(f" Panic events: {panic_count}")
|
|
104
|
+
|
|
105
|
+
# --- Pod count stats ---
|
|
106
|
+
pod_vals = [p['pods'] for p in points]
|
|
107
|
+
print(f" Pods: avg={sum(pod_vals)/len(pod_vals):.0f}, min={min(pod_vals):.0f}, max={max(pod_vals):.0f}")
|
|
108
|
+
|
|
109
|
+
# --- CPU per pod bucketed analysis ---
|
|
110
|
+
print(f"\n{'CPU range':>12} {'Avg C/pod':>10} {'P95 C/pod':>10} {'Avg Pods':>9} {'Points':>7}", end="")
|
|
111
|
+
if has_latency:
|
|
112
|
+
print(f" {'Avg Lat':>9} {'P95 Lat':>9}", end="")
|
|
113
|
+
print()
|
|
114
|
+
|
|
115
|
+
cpu_buckets = [
|
|
116
|
+
(0, 200, '0-200m'), (200, 300, '200-300m'), (300, 400, '300-400m'),
|
|
117
|
+
(400, 500, '400-500m'), (500, 600, '500-600m'), (600, 700, '600-700m'),
|
|
118
|
+
(700, 1500, '700m+'),
|
|
119
|
+
]
|
|
120
|
+
|
|
121
|
+
for lo, hi, label in cpu_buckets:
|
|
122
|
+
bp = [p for p in points if lo <= p['cpu_m'] < hi]
|
|
123
|
+
if bp:
|
|
124
|
+
concs = sorted([p['cpod'] for p in bp])
|
|
125
|
+
avg_c = sum(concs) / len(concs)
|
|
126
|
+
p95_i = min(int(len(concs) * 0.95), len(concs) - 1)
|
|
127
|
+
avg_pods = sum(p['pods'] for p in bp) / len(bp)
|
|
128
|
+
line = f" {label:>12} {avg_c:10.1f} {concs[p95_i]:10.1f} {avg_pods:9.0f} {len(bp):7}"
|
|
129
|
+
if has_latency:
|
|
130
|
+
lats = sorted([p['lat_ms'] for p in bp if p['lat_ms'] is not None])
|
|
131
|
+
if lats:
|
|
132
|
+
avg_l = sum(lats) / len(lats)
|
|
133
|
+
p95_l = lats[min(int(len(lats) * 0.95), len(lats) - 1)]
|
|
134
|
+
line += f" {avg_l:8.1f}ms {p95_l:8.1f}ms"
|
|
135
|
+
print(line)
|
|
136
|
+
|
|
137
|
+
# --- Latency inflation factor ---
|
|
138
|
+
print(f"\nLatency inflation factor (excess > 1.0 = latency degrading):")
|
|
139
|
+
for threshold in [300, 350, 400, 450, 500, 550, 600]:
|
|
140
|
+
below = [p for p in points if p['cpu_m'] < threshold]
|
|
141
|
+
above = [p for p in points if p['cpu_m'] >= threshold]
|
|
142
|
+
if below and above:
|
|
143
|
+
avg_c_b = sum(p['cpod'] for p in below) / len(below)
|
|
144
|
+
avg_c_a = sum(p['cpod'] for p in above) / len(above)
|
|
145
|
+
avg_p_b = sum(p['pods'] for p in below) / len(below)
|
|
146
|
+
avg_p_a = sum(p['pods'] for p in above) / len(above)
|
|
147
|
+
conc_ratio = avg_c_a / avg_c_b if avg_c_b > 0 else 0
|
|
148
|
+
pods_ratio = avg_p_b / avg_p_a if avg_p_a > 0 else 0
|
|
149
|
+
excess = conc_ratio / pods_ratio if pods_ratio > 0 else 0
|
|
150
|
+
marker = " ★ INFLECTION" if 1.3 < excess < 1.7 else ""
|
|
151
|
+
print(f" CPU <{threshold}m vs >={threshold}m: excess={excess:.2f}x{marker}")
|
|
152
|
+
|
|
153
|
+
# --- Recommendation ---
|
|
154
|
+
print(f"\n{'='*70}")
|
|
155
|
+
print("RECOMMENDATION")
|
|
156
|
+
print(f"{'='*70}")
|
|
157
|
+
|
|
158
|
+
# Find inflection: first threshold where excess drops below 1.5
|
|
159
|
+
inflection = None
|
|
160
|
+
for threshold in [300, 350, 400, 450, 500, 550, 600]:
|
|
161
|
+
below = [p for p in points if p['cpu_m'] < threshold]
|
|
162
|
+
above = [p for p in points if p['cpu_m'] >= threshold]
|
|
163
|
+
if below and above:
|
|
164
|
+
avg_c_b = sum(p['cpod'] for p in below) / len(below)
|
|
165
|
+
avg_c_a = sum(p['cpod'] for p in above) / len(above)
|
|
166
|
+
avg_p_b = sum(p['pods'] for p in below) / len(below)
|
|
167
|
+
avg_p_a = sum(p['pods'] for p in above) / len(above)
|
|
168
|
+
conc_ratio = avg_c_a / avg_c_b if avg_c_b > 0 else 0
|
|
169
|
+
pods_ratio = avg_p_b / avg_p_a if avg_p_a > 0 else 0
|
|
170
|
+
excess = conc_ratio / pods_ratio if pods_ratio > 0 else 0
|
|
171
|
+
if excess < 1.5 and inflection is None:
|
|
172
|
+
inflection = threshold
|
|
173
|
+
|
|
174
|
+
# Get CPU request
|
|
175
|
+
cpu_req = prom_q(f'kube_pod_container_resource_requests{{namespace="{NS}", container!="queue-proxy", resource="cpu"}}')
|
|
176
|
+
cpu_req_m = 0
|
|
177
|
+
for r in cpu_req['data']['result']:
|
|
178
|
+
cpu_req_m = float(r['value'][1]) * 1000
|
|
179
|
+
break
|
|
180
|
+
|
|
181
|
+
if inflection:
|
|
182
|
+
print(f" Latency inflection point: ~{inflection}m CPU")
|
|
183
|
+
print(f" CPU request: {cpu_req_m:.0f}m")
|
|
184
|
+
print(f" Recommended: CPU scaling at target={inflection} (millicores)")
|
|
185
|
+
print(f"\n SiteState scaling config:")
|
|
186
|
+
print(f' .scaling.metric = {{"type": "cpu", "target": {inflection}}}')
|
|
187
|
+
else:
|
|
188
|
+
print(" No clear inflection found. Consider:")
|
|
189
|
+
print(" - More data needed (run for 12-24h)")
|
|
190
|
+
print(" - App may be IO-bound (keep concurrency scaling)")
|
|
191
|
+
if panic_count > 0:
|
|
192
|
+
print(f" - {panic_count} panic events detected — consider switching to CPU scaling anyway to break the loop")
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Script 2: Post-Change Monitoring
|
|
196
|
+
|
|
197
|
+
Run after applying scaling changes to verify they're working.
|
|
198
|
+
|
|
199
|
+
```python
|
|
200
|
+
#!/usr/bin/env python3
|
|
201
|
+
"""Monitor scaling behavior after parameter change."""
|
|
202
|
+
import json, urllib.request, urllib.parse, time
|
|
203
|
+
from datetime import datetime
|
|
204
|
+
|
|
205
|
+
PROM = "http://127.0.0.1:19090"
|
|
206
|
+
SITENAME = "CHANGE_ME" # <-- Set this
|
|
207
|
+
NS = f"sites-{SITENAME}"
|
|
208
|
+
END = int(time.time())
|
|
209
|
+
START = END - 2 * 3600 # last 2 hours
|
|
210
|
+
|
|
211
|
+
def prom_qr(query, step=60):
|
|
212
|
+
params = urllib.parse.urlencode({"query": query, "start": START, "end": END, "step": step})
|
|
213
|
+
with urllib.request.urlopen(f"{PROM}/api/v1/query_range?{params}") as r:
|
|
214
|
+
return json.loads(r.read())
|
|
215
|
+
|
|
216
|
+
def to_map(data):
|
|
217
|
+
m = {}
|
|
218
|
+
for r in data['data']['result']:
|
|
219
|
+
for ts, v in r['values']:
|
|
220
|
+
try:
|
|
221
|
+
f = float(v)
|
|
222
|
+
if str(v) not in ('NaN', '+Inf', '-Inf'):
|
|
223
|
+
m[int(float(ts))] = f
|
|
224
|
+
except: pass
|
|
225
|
+
return m
|
|
226
|
+
|
|
227
|
+
cpu = to_map(prom_qr(f'avg(rate(container_cpu_usage_seconds_total{{namespace="{NS}", container="app"}}[2m]))'))
|
|
228
|
+
if not cpu:
|
|
229
|
+
cpu = to_map(prom_qr(f'avg(rate(container_cpu_usage_seconds_total{{namespace="{NS}", container!="", container!="POD", container!="queue-proxy"}}[2m]))'))
|
|
230
|
+
pods_m = to_map(prom_qr(f'autoscaler_actual_pods{{namespace_name="{NS}"}}'))
|
|
231
|
+
panic_m = to_map(prom_qr(f'autoscaler_panic_mode{{namespace_name="{NS}"}}'))
|
|
232
|
+
|
|
233
|
+
common = sorted(set(cpu.keys()) & set(pods_m.keys()))
|
|
234
|
+
|
|
235
|
+
print(f"POST-CHANGE MONITORING: {SITENAME} (last 2h)")
|
|
236
|
+
print(f"{'Time':>6} {'Pods':>5} {'CPU/pod':>8} {'Panic':>5}")
|
|
237
|
+
prev_pods = None
|
|
238
|
+
for ts in common:
|
|
239
|
+
p = pods_m[ts]; c = cpu[ts] * 1000; pa = panic_m.get(ts, 0)
|
|
240
|
+
t = datetime.fromtimestamp(ts).strftime('%H:%M')
|
|
241
|
+
ps = "PANIC" if pa == 1 else ""
|
|
242
|
+
change = ""
|
|
243
|
+
if prev_pods and abs(p - prev_pods) >= 2:
|
|
244
|
+
direction = "↑" if p > prev_pods else "↓"
|
|
245
|
+
change = f" {direction}{abs(p-prev_pods):.0f}"
|
|
246
|
+
print(f" {t} {p:5.0f} {c:7.0f}m {ps}{change}")
|
|
247
|
+
prev_pods = p
|
|
248
|
+
|
|
249
|
+
# Summary
|
|
250
|
+
if common:
|
|
251
|
+
pod_vals = [pods_m[ts] for ts in common]
|
|
252
|
+
cpu_vals = [cpu[ts] * 1000 for ts in common]
|
|
253
|
+
panics = sum(1 for i in range(1, len(common)) if panic_m.get(common[i], 0) == 1 and panic_m.get(common[i-1], 0) == 0)
|
|
254
|
+
print(f"\n Pods: avg={sum(pod_vals)/len(pod_vals):.0f}, min={min(pod_vals):.0f}, max={max(pod_vals):.0f}")
|
|
255
|
+
print(f" CPU/pod: avg={sum(cpu_vals)/len(cpu_vals):.0f}m, max={max(cpu_vals):.0f}m")
|
|
256
|
+
print(f" Panic events: {panics}")
|
|
257
|
+
print(f" Pod stability: {'STABLE' if max(pod_vals) - min(pod_vals) < 5 else 'OSCILLATING'}")
|
|
258
|
+
|
|
259
|
+
# Check for HPA (if using CPU scaling)
|
|
260
|
+
print(f"\nHPA status:")
|
|
261
|
+
import subprocess
|
|
262
|
+
try:
|
|
263
|
+
result = subprocess.run(['kubectl', 'get', 'hpa', '-n', NS], capture_output=True, text=True)
|
|
264
|
+
print(result.stdout if result.stdout else " No HPA found (still using KPA?)")
|
|
265
|
+
except:
|
|
266
|
+
print(" Could not check HPA")
|
|
267
|
+
```
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: deco-start-architecture
|
|
3
|
+
description: Architecture reference for @decocms/start — the Deco framework for TanStack Start/React/Cloudflare Workers. Covers the three-layer architecture (@decocms/start + @decocms/apps + site), admin protocol (meta, decofile, invoke, render), CMS block resolution with generic recursive resolver, dynamic schema registries, section registry, worker entry with edge caching, SDK utilities (useDevice, useScript with minification, observability, health metrics), matchers, middleware, hooks, schema generation, and the comprehensive gap analysis vs deco-cx/deco. Includes a prioritized roadmap (Tier 0-3 complete, plus Tier 2.5 framework improvements). Use when working on deco-start, understanding the framework, adding features, debugging admin protocol issues, or planning what to port next from deco-cx/deco.
|
|
4
|
+
globs:
|
|
5
|
+
- "**/workerEntry.ts"
|
|
6
|
+
- "**/cacheHeaders.ts"
|
|
7
|
+
- "**/LiveControls.tsx"
|
|
8
|
+
- "**/DecoPageRenderer.tsx"
|
|
9
|
+
- "**/resolve.ts"
|
|
10
|
+
- "**/setup.ts"
|
|
11
|
+
- "**/.decofile"
|
|
12
|
+
- "**/meta.gen.json"
|
|
13
|
+
- "**/blocks.gen.ts"
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Sub-documents
|
|
17
|
+
|
|
18
|
+
| Document | Topic |
|
|
19
|
+
|----------|-------|
|
|
20
|
+
| [admin-protocol.md](./admin-protocol.md) | Admin protocol — meta, decofile, invoke, render, CORS, LiveControls |
|
|
21
|
+
| [cms-resolution.md](./cms-resolution.md) | CMS block loading, page resolution, section registry, matchers |
|
|
22
|
+
| [worker-entry-caching.md](./worker-entry-caching.md) | Cloudflare Worker entry, edge caching, segment keys, cache profiles |
|
|
23
|
+
| [sdk-utilities.md](./sdk-utilities.md) | All SDK utilities — useScript, signal, analytics, cookies, redirects, sitemap |
|
|
24
|
+
| [gap-analysis.md](./gap-analysis.md) | Feature-by-feature comparison with deco-cx/deco + prioritized roadmap |
|
|
25
|
+
| [code-quality.md](./code-quality.md) | Code quality tools, scripts, recommendations |
|
|
26
|
+
|
|
27
|
+
# @decocms/start Architecture
|
|
28
|
+
|
|
29
|
+
Reference for `@decocms/start` — the Deco framework for TanStack Start storefronts on Cloudflare Workers.
|
|
30
|
+
|
|
31
|
+
## Three-Layer Architecture
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
Layer 1: @decocms/start (this repo)
|
|
35
|
+
Framework: CMS bridge, admin protocol, worker entry, caching, rendering
|
|
36
|
+
|
|
|
37
|
+
Layer 2: @decocms/apps (apps-start)
|
|
38
|
+
Commerce: VTEX/Shopify loaders, types, hooks, transforms
|
|
39
|
+
|
|
|
40
|
+
Layer 3: Site repo (e.g., espacosmart-storefront)
|
|
41
|
+
UI: components, routes, styles, contexts, sections
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Repository Structure
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
deco-start/
|
|
48
|
+
|-- package.json # v0.6.0, exports map, peer deps
|
|
49
|
+
|-- tsconfig.json # ES2022, bundler resolution, strictNullChecks
|
|
50
|
+
|-- .releaserc.json # semantic-release (Angular preset)
|
|
51
|
+
|-- CLAUDE.md # AI guidance document
|
|
52
|
+
|-- GAP_ANALYSIS_V2.md # Detailed gap analysis vs deco-cx/deco
|
|
53
|
+
|
|
|
54
|
+
|-- src/
|
|
55
|
+
| |-- index.ts # Barrel: re-exports admin, cms, hooks, types, middleware
|
|
56
|
+
| |
|
|
57
|
+
| |-- admin/ # Admin protocol handlers (9 files)
|
|
58
|
+
| | |-- setup.ts # Client-safe config (setMetaData, setInvokeLoaders, setInvokeActions, setRenderShell, register*Schema)
|
|
59
|
+
| | |-- meta.ts # GET /live/_meta handler (auto-invalidates on decofile change)
|
|
60
|
+
| | |-- schema.ts # composeMeta(), dynamic schema registries (loaders + matchers)
|
|
61
|
+
| | |-- decofile.ts # GET/POST /.decofile handlers (revision tracking, cache invalidation)
|
|
62
|
+
| | |-- invoke.ts # POST /deco/invoke handler (form-data, select, actions, batch, nested resolve)
|
|
63
|
+
| | |-- render.ts # POST /live/previews/* handler
|
|
64
|
+
| | |-- liveControls.ts # Admin-storefront bridge script
|
|
65
|
+
| | |-- cors.ts # CORS for admin origins
|
|
66
|
+
| | |-- index.ts # Barrel export
|
|
67
|
+
| |
|
|
68
|
+
| |-- cms/ # CMS block resolution (4 files)
|
|
69
|
+
| | |-- loader.ts # loadBlocks, findPageByPath, getAllPages, withBlocksOverride, getRevision, onChange
|
|
70
|
+
| | |-- registry.ts # registerSection, getSection, getSectionRegistry
|
|
71
|
+
| | |-- resolve.ts # resolveValue, resolveDecoPage, internalResolve, registerCommerceLoader, registerMatcher, addSkipResolveType, set*Handler
|
|
72
|
+
| | |-- index.ts # Barrel export
|
|
73
|
+
| |
|
|
74
|
+
| |-- hooks/ # React components/hooks (5 files)
|
|
75
|
+
| | |-- LiveControls.tsx # Admin bridge component
|
|
76
|
+
| | |-- DecoPageRenderer.tsx # Renders sections with Suspense
|
|
77
|
+
| | |-- LazySection.tsx # IntersectionObserver lazy loading
|
|
78
|
+
| | |-- SectionErrorFallback.tsx # Per-section error boundary
|
|
79
|
+
| | |-- index.ts
|
|
80
|
+
| |
|
|
81
|
+
| |-- middleware/ # Request middleware (5 files)
|
|
82
|
+
| | |-- observability.ts # configureTracer, configureMeter, withTracing, logRequest, MetricNames, recordRequestMetric, recordCacheMetric
|
|
83
|
+
| | |-- healthMetrics.ts # trackRequest, getHealthMetrics, handleHealthCheck (/deco/_health)
|
|
84
|
+
| | |-- liveness.ts # /deco/_liveness health probe (integrated with /deco/_health)
|
|
85
|
+
| | |-- decoState.ts # buildDecoState per request
|
|
86
|
+
| | |-- index.ts
|
|
87
|
+
| |
|
|
88
|
+
| |-- sdk/ # SDK utilities (21 files)
|
|
89
|
+
| | |-- workerEntry.ts # createDecoWorkerEntry (CF Worker wrapper)
|
|
90
|
+
| | |-- cacheHeaders.ts # detectCacheProfile, routeCacheDefaults
|
|
91
|
+
| | |-- cachedLoader.ts # In-memory SWR loader cache
|
|
92
|
+
| | |-- mergeCacheControl.ts # Cache-Control merge
|
|
93
|
+
| | |-- analytics.ts # useSendEvent, ANALYTICS_SCRIPT, gtmScript
|
|
94
|
+
| | |-- useScript.ts # useScript (with minification + LRU cache), usePartialSection, useSection
|
|
95
|
+
| | |-- useDevice.ts # Server-side device detection (detectDevice, useDevice, checkMobile/Tablet/Desktop)
|
|
96
|
+
| | |-- signal.ts # Reactive signal (replaces @preact/signals)
|
|
97
|
+
| | |-- clx.ts # CSS class utility
|
|
98
|
+
| | |-- cookie.ts # Cookie get/set/delete (client + server)
|
|
99
|
+
| | |-- invoke.ts # createInvokeProxy, batchInvoke, invokeQueryOptions
|
|
100
|
+
| | |-- redirects.ts # CMS redirect loading + matching
|
|
101
|
+
| | |-- sitemap.ts # Sitemap XML generation
|
|
102
|
+
| | |-- csp.ts # CSP frame-ancestors for admin
|
|
103
|
+
| | |-- urlUtils.ts # UTM stripping, canonical URLs
|
|
104
|
+
| | |-- requestContext.ts # AsyncLocalStorage per-request context
|
|
105
|
+
| | |-- serverTimings.ts # Server-Timing header
|
|
106
|
+
| | |-- instrumentedFetch.ts # Fetch with logging/tracing
|
|
107
|
+
| | |-- useId.ts # React useId wrapper
|
|
108
|
+
| | |-- wrapCaughtErrors.ts # Deferred error proxy for resilient rendering
|
|
109
|
+
| | |-- index.ts
|
|
110
|
+
| |
|
|
111
|
+
| |-- matchers/ # Feature flag matchers (2 files)
|
|
112
|
+
| | |-- builtins.ts # cookie, cron, host, pathname, queryString
|
|
113
|
+
| | |-- posthog.ts # PostHog integration
|
|
114
|
+
| |
|
|
115
|
+
| |-- types/ # Type definitions (2 files)
|
|
116
|
+
| | |-- index.ts # FnContext, App, Section, SectionProps, Flag, etc.
|
|
117
|
+
| | |-- widgets.ts # ImageWidget, HTMLWidget, VideoWidget aliases
|
|
118
|
+
|
|
|
119
|
+
|-- scripts/
|
|
120
|
+
| |-- generate-blocks.ts # .deco/blocks/*.json -> blocks.gen.ts
|
|
121
|
+
| |-- generate-schema.ts # TypeScript props -> JSON Schema (meta.gen.json)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Package Exports
|
|
125
|
+
|
|
126
|
+
| Import Path | File | Purpose |
|
|
127
|
+
|-------------|------|---------|
|
|
128
|
+
| `@decocms/start` | `src/index.ts` | Main barrel |
|
|
129
|
+
| `@decocms/start/admin` | `src/admin/index.ts` | Admin protocol |
|
|
130
|
+
| `@decocms/start/cms` | `src/cms/index.ts` | Block resolution |
|
|
131
|
+
| `@decocms/start/hooks` | `src/hooks/index.ts` | React components |
|
|
132
|
+
| `@decocms/start/middleware` | `src/middleware/index.ts` | Request middleware |
|
|
133
|
+
| `@decocms/start/sdk` | `src/sdk/index.ts` | SDK utilities |
|
|
134
|
+
| `@decocms/start/sdk/workerEntry` | `src/sdk/workerEntry.ts` | CF Worker entry |
|
|
135
|
+
| `@decocms/start/sdk/cacheHeaders` | `src/sdk/cacheHeaders.ts` | Cache profiles |
|
|
136
|
+
| `@decocms/start/sdk/invoke` | `src/sdk/invoke.ts` | Invoke proxy |
|
|
137
|
+
| `@decocms/start/types` | `src/types/index.ts` | Type definitions |
|
|
138
|
+
| `@decocms/start/types/widgets` | `src/types/widgets.ts` | Widget type aliases |
|
|
139
|
+
| `@decocms/start/sdk/useDevice` | `src/sdk/useDevice.ts` | Server-side device detection |
|
|
140
|
+
| `@decocms/start/middleware/healthMetrics` | `src/middleware/healthMetrics.ts` | Health metrics + `/deco/_health` |
|
|
141
|
+
| `@decocms/start/matchers/builtins` | `src/matchers/builtins.ts` | Built-in matchers |
|
|
142
|
+
|
|
143
|
+
## Key Concepts
|
|
144
|
+
|
|
145
|
+
### 1. Worker Entry (Edge Layer)
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
Request -> createDecoWorkerEntry(serverEntry, options)
|
|
149
|
+
|-- tryAdminRoute() <- /live/_meta, /.decofile, /live/previews/*
|
|
150
|
+
|-- cache purge check <- __deco_purge_cache
|
|
151
|
+
|-- static asset bypass <- /assets/*, favicon, sprites
|
|
152
|
+
|-- Cloudflare edge cache <- caches.open() with profile-based TTLs
|
|
153
|
+
|-- serverEntry.fetch() <- TanStack Start handles the rest
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### 2. Admin Protocol
|
|
157
|
+
|
|
158
|
+
| Route | Method | Handler | Purpose |
|
|
159
|
+
|-------|--------|---------|---------|
|
|
160
|
+
| `/live/_meta` | GET | `handleMeta` | JSON Schema + manifest |
|
|
161
|
+
| `/.decofile` | GET | `handleDecofileRead` | CMS content blocks |
|
|
162
|
+
| `/.decofile` | POST | `handleDecofileReload` | Hot reload blocks |
|
|
163
|
+
| `/deco/invoke` | POST | `handleInvoke` | Execute loaders/actions |
|
|
164
|
+
| `/live/previews/*` | POST | `handleRender` | Section preview in admin |
|
|
165
|
+
| `/deco/_liveness` | GET | `handleLiveness` | Health probe |
|
|
166
|
+
| `/deco/_health` | GET | `handleHealthCheck` | Detailed health metrics (uptime, memory, cache, requests) |
|
|
167
|
+
|
|
168
|
+
### 3. CMS Resolution
|
|
169
|
+
|
|
170
|
+
```
|
|
171
|
+
[CMS decofile] [setup.ts] [resolveDecoPage]
|
|
172
|
+
Section props with Commerce loaders Generic recursive resolver:
|
|
173
|
+
__resolveType: "vtex/..." --> registered by key --> 1. Check commerce loaders
|
|
174
|
+
+ matchers 2. Check decofile blocks
|
|
175
|
+
+ schema registries 3. DanglingReference fallback
|
|
176
|
+
+ memoization + depth protection
|
|
177
|
+
|
|
|
178
|
+
v
|
|
179
|
+
[React Component]
|
|
180
|
+
Receives plain data
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### 4. Section Rendering
|
|
184
|
+
|
|
185
|
+
```tsx
|
|
186
|
+
<DecoPageRenderer sections={resolvedSections}>
|
|
187
|
+
{sections.map((section, i) => (
|
|
188
|
+
<SectionErrorBoundary key={i}>
|
|
189
|
+
<Suspense fallback={<div />}>
|
|
190
|
+
{isBelowFold(i) ? (
|
|
191
|
+
<LazySection><SectionComponent {...section.props} /></LazySection>
|
|
192
|
+
) : (
|
|
193
|
+
<SectionComponent {...section.props} />
|
|
194
|
+
)}
|
|
195
|
+
</Suspense>
|
|
196
|
+
</SectionErrorBoundary>
|
|
197
|
+
))}
|
|
198
|
+
</DecoPageRenderer>
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Dependencies
|
|
202
|
+
|
|
203
|
+
- **Peer**: `@tanstack/store` >= 0.7.0, `react` ^19, `react-dom` ^19
|
|
204
|
+
- **Dev**: `ts-morph` (schema gen), `typescript` ^5.9
|
|
205
|
+
|
|
206
|
+
## Implementation Status
|
|
207
|
+
|
|
208
|
+
Tier 0 (production-blocking), Tier 1 (quality), Tier 2 (DX/completeness), and Tier 2.5 (framework improvements) are ALL DONE. See [gap-analysis.md](./gap-analysis.md) for details on Tier 3 items remaining.
|
|
209
|
+
|
|
210
|
+
### Tier 2.5 Highlights (PR #3: feat/framework-improvements)
|
|
211
|
+
- Dynamic schema registries (loaders + matchers) — replaces hardcoded `KNOWN_LOADERS`
|
|
212
|
+
- Generic recursive resolver with memoization, depth protection, DanglingReference handler
|
|
213
|
+
- `useDevice` server-side (User-Agent + RequestContext)
|
|
214
|
+
- `/deco/_health` endpoint with uptime, memory, cache stats, request metrics
|
|
215
|
+
- Enhanced observability: `MeterAdapter`, `MetricNames`, context propagation
|
|
216
|
+
- Enhanced invoke: FormData/URLEncoded parsing, `?select=`, actions, nested `__resolveType`
|
|
217
|
+
- Decofile revision tracking + `onChange` listeners + meta auto-invalidation
|
|
218
|
+
- `useScript` minification + LRU cache
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# Admin Protocol
|
|
2
|
+
|
|
3
|
+
The admin panel (`admin.deco.cx`) communicates with self-hosted storefronts via HTTP endpoints handled in the Cloudflare Worker entry, NOT inside TanStack Start's server.
|
|
4
|
+
|
|
5
|
+
Important: Admin routes MUST be handled in `workerEntry.ts`, NOT inside `createServerEntry` - Vite strips custom fetch logic from server entries in production builds.
|
|
6
|
+
|
|
7
|
+
## Endpoints
|
|
8
|
+
|
|
9
|
+
### GET /live/_meta (`admin/meta.ts`)
|
|
10
|
+
|
|
11
|
+
Returns JSON Schema + manifest for the admin form builder.
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
handleMeta(request: Request): Response
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
- Schema is composed at runtime via `composeMeta()`
|
|
18
|
+
- Content-hash ETag (DJB2) for caching
|
|
19
|
+
- ETag included in JSON body for admin cache busting
|
|
20
|
+
- Returns sections, pages, loaders, matchers, flags definitions
|
|
21
|
+
- Auto-invalidates cached ETag when decofile changes (via `onChange` listener)
|
|
22
|
+
|
|
23
|
+
### GET /.decofile (`admin/decofile.ts`)
|
|
24
|
+
|
|
25
|
+
Returns the current CMS content blocks.
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
handleDecofileRead(request: Request): Response
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
- Includes content-hash `revision` in JSON body
|
|
32
|
+
- Sets `ETag` header from current revision for HTTP caching
|
|
33
|
+
|
|
34
|
+
### POST /.decofile (`admin/decofile.ts`)
|
|
35
|
+
|
|
36
|
+
Hot-reloads CMS blocks without redeployment.
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
handleDecofileReload(request: Request): Response
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
- Calls `setBlocks(newBlocks)` which updates in-memory state, recomputes revision, and notifies `onChange` listeners
|
|
43
|
+
- Explicitly calls `clearLoaderCache()` to ensure data consistency after reload
|
|
44
|
+
|
|
45
|
+
### POST /deco/invoke (`admin/invoke.ts`)
|
|
46
|
+
|
|
47
|
+
Executes loaders/actions by key.
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
handleInvoke(request: Request): Response
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
- Loaders registered via `setInvokeLoaders(map)`
|
|
54
|
+
- Actions registered via `setInvokeActions(map)`
|
|
55
|
+
- Supports multiple body formats: `application/json`, `multipart/form-data`, `application/x-www-form-urlencoded`, URL search params (`?props=...`)
|
|
56
|
+
- `?select=field1,field2` query parameter for partial response filtering
|
|
57
|
+
- Batch invoke: send array of `{ key, props }` to execute multiple in parallel
|
|
58
|
+
- Nested `__resolveType` within payload props are recursively resolved
|
|
59
|
+
- Development mode includes stack traces in error responses
|
|
60
|
+
|
|
61
|
+
### POST /live/previews/* (`admin/render.ts`)
|
|
62
|
+
|
|
63
|
+
Renders a section for preview in the admin iframe.
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
handleRender(request: Request): Response
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
- Uses `setRenderShell(config)` to wrap preview in HTML shell
|
|
70
|
+
- Shell must include `data-theme="light"` for DaisyUI v4 color variables
|
|
71
|
+
- Renders section component with provided props
|
|
72
|
+
|
|
73
|
+
## Setup (`admin/setup.ts`)
|
|
74
|
+
|
|
75
|
+
Client-safe configuration (no `node:` imports or AsyncLocalStorage):
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
// Called in site's setup.ts
|
|
79
|
+
setMetaData(metaJson); // Set schema data from meta.gen.json
|
|
80
|
+
setInvokeLoaders(loaderMap); // Register loaders for /deco/invoke
|
|
81
|
+
setInvokeActions(actionMap); // Register actions for /deco/invoke
|
|
82
|
+
setRenderShell(shellConfig); // Configure preview HTML wrapper
|
|
83
|
+
registerLoaderSchema(key, schema); // Register a single loader schema dynamically
|
|
84
|
+
registerLoaderSchemas(schemas); // Register multiple loader schemas at once
|
|
85
|
+
registerMatcherSchema(key, schema); // Register a single matcher schema dynamically
|
|
86
|
+
registerMatcherSchemas(schemas); // Register multiple matcher schemas at once
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Schema Composition (`admin/schema.ts`)
|
|
90
|
+
|
|
91
|
+
`composeMeta()` injects framework-level schemas at runtime:
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
[generate-schema.ts] [setup.ts] [composeMeta()]
|
|
95
|
+
Scans src/sections/ --> Imports meta.gen.json --> Injects page schema,
|
|
96
|
+
Produces section-only Calls setMetaData() merges definitions,
|
|
97
|
+
meta.gen.json populates pages root
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Key: `toBase64()` must produce padded output matching `btoa()` - admin uses `btoa()` for definition refs.
|
|
101
|
+
|
|
102
|
+
### Dynamic Schema Registries
|
|
103
|
+
|
|
104
|
+
Loader and matcher schemas are now managed via runtime registries instead of hardcoded lists:
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
// Loaders — runtime registry replaces old KNOWN_LOADERS array
|
|
108
|
+
registerLoaderSchema("vtex/loaders/productList.ts", {
|
|
109
|
+
inputSchema: { ... },
|
|
110
|
+
outputSchema: { ... },
|
|
111
|
+
tags: ["product-list"], // used by wrapResolvableProperties to filter product-list loaders
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Matchers — same pattern
|
|
115
|
+
registerMatcherSchema("website/matchers/device.ts", {
|
|
116
|
+
inputSchema: { ... },
|
|
117
|
+
});
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
`buildLoaderDefinitions()` and `buildMatcherDefinitions()` now read from these registries at composition time. The `getProductListLoaderKeys()` function dynamically filters loaders tagged with `product-list`.
|
|
121
|
+
|
|
122
|
+
## CORS (`admin/cors.ts`)
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
isAdminOrLocalhost(origin: string): boolean // Check if origin is admin
|
|
126
|
+
corsHeaders(origin: string): Headers // CORS headers for admin
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Allows: `admin.deco.cx`, `localhost:*`.
|
|
130
|
+
|
|
131
|
+
## LiveControls (`admin/liveControls.ts`)
|
|
132
|
+
|
|
133
|
+
Inline script injected into every page when in admin context:
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
LIVE_CONTROLS_SCRIPT: string // postMessage bridge script
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Provides:
|
|
140
|
+
- `__DECO_STATE` global with decofile state
|
|
141
|
+
- `postMessage` bridge to admin iframe
|
|
142
|
+
- Section selection/highlighting
|
|
143
|
+
- Environment info (site, deploymentId)
|
|
144
|
+
|
|
145
|
+
## LiveControls Component (`hooks/LiveControls.tsx`)
|
|
146
|
+
|
|
147
|
+
React component that renders the bridge script:
|
|
148
|
+
|
|
149
|
+
```tsx
|
|
150
|
+
<LiveControls
|
|
151
|
+
site={site}
|
|
152
|
+
siteId={siteId}
|
|
153
|
+
/>
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Injected in the site's `__root.tsx`.
|