@emdash-cms/cloudflare 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/index.d.mts +81 -0
- package/dist/auth/index.mjs +147 -0
- package/dist/cache/config.d.mts +52 -0
- package/dist/cache/config.mjs +55 -0
- package/dist/cache/runtime.d.mts +40 -0
- package/dist/cache/runtime.mjs +191 -0
- package/dist/d1-introspector-bZf0_ylK.mjs +57 -0
- package/dist/db/d1.d.mts +43 -0
- package/dist/db/d1.mjs +74 -0
- package/dist/db/do.d.mts +96 -0
- package/dist/db/do.mjs +489 -0
- package/dist/db/playground-middleware.d.mts +20 -0
- package/dist/db/playground-middleware.mjs +533 -0
- package/dist/db/playground.d.mts +39 -0
- package/dist/db/playground.mjs +26 -0
- package/dist/do-class-DY2Ba2RJ.mjs +174 -0
- package/dist/do-class-x5Xh_G62.d.mts +73 -0
- package/dist/do-dialect-BhFcRSFQ.mjs +58 -0
- package/dist/do-playground-routes-CmwFeGwJ.mjs +49 -0
- package/dist/do-types-CY0G0oyh.d.mts +14 -0
- package/dist/images-4RT9Ag8_.d.mts +76 -0
- package/dist/index.d.mts +200 -0
- package/dist/index.mjs +214 -0
- package/dist/media/images-runtime.d.mts +10 -0
- package/dist/media/images-runtime.mjs +215 -0
- package/dist/media/stream-runtime.d.mts +10 -0
- package/dist/media/stream-runtime.mjs +218 -0
- package/dist/plugins/index.d.mts +32 -0
- package/dist/plugins/index.mjs +163 -0
- package/dist/sandbox/index.d.mts +255 -0
- package/dist/sandbox/index.mjs +945 -0
- package/dist/storage/r2.d.mts +31 -0
- package/dist/storage/r2.mjs +116 -0
- package/dist/stream-DdbcvKi0.d.mts +78 -0
- package/package.json +109 -0
- package/src/auth/cloudflare-access.ts +303 -0
- package/src/auth/index.ts +16 -0
- package/src/cache/config.ts +81 -0
- package/src/cache/runtime.ts +328 -0
- package/src/cloudflare.d.ts +31 -0
- package/src/db/d1-introspector.ts +120 -0
- package/src/db/d1.ts +112 -0
- package/src/db/do-class.ts +275 -0
- package/src/db/do-dialect.ts +125 -0
- package/src/db/do-playground-routes.ts +65 -0
- package/src/db/do-preview-routes.ts +48 -0
- package/src/db/do-preview-sign.ts +100 -0
- package/src/db/do-preview.ts +268 -0
- package/src/db/do-types.ts +12 -0
- package/src/db/do.ts +62 -0
- package/src/db/playground-middleware.ts +340 -0
- package/src/db/playground-toolbar.ts +341 -0
- package/src/db/playground.ts +49 -0
- package/src/db/preview-toolbar.ts +220 -0
- package/src/index.ts +285 -0
- package/src/media/images-runtime.ts +353 -0
- package/src/media/images.ts +114 -0
- package/src/media/stream-runtime.ts +392 -0
- package/src/media/stream.ts +118 -0
- package/src/plugins/index.ts +7 -0
- package/src/plugins/vectorize-search.ts +393 -0
- package/src/sandbox/bridge.ts +1008 -0
- package/src/sandbox/index.ts +13 -0
- package/src/sandbox/runner.ts +357 -0
- package/src/sandbox/types.ts +181 -0
- package/src/sandbox/wrapper.ts +238 -0
- package/src/storage/r2.ts +200 -0
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
import "../d1-introspector-bZf0_ylK.mjs";
|
|
2
|
+
import { t as PreviewDODialect } from "../do-dialect-BhFcRSFQ.mjs";
|
|
3
|
+
import { t as isBlockedInPlayground } from "../do-playground-routes-CmwFeGwJ.mjs";
|
|
4
|
+
import { env } from "cloudflare:workers";
|
|
5
|
+
import { Kysely, sql } from "kysely";
|
|
6
|
+
import { ulid } from "ulidx";
|
|
7
|
+
import { defineMiddleware } from "astro:middleware";
|
|
8
|
+
import virtualConfig from "virtual:emdash/config";
|
|
9
|
+
|
|
10
|
+
//#region src/db/playground-toolbar.ts
|
|
11
|
+
const RE_AMP = /&/g;
|
|
12
|
+
const RE_QUOT = /"/g;
|
|
13
|
+
const RE_LT = /</g;
|
|
14
|
+
const RE_GT = />/g;
|
|
15
|
+
function renderPlaygroundToolbar(config) {
|
|
16
|
+
const { createdAt, ttl, editMode } = config;
|
|
17
|
+
return `
|
|
18
|
+
<!-- EmDash Playground Toolbar -->
|
|
19
|
+
<div id="emdash-playground-toolbar" data-created-at="${escapeAttr(createdAt)}" data-ttl="${ttl}" data-edit-mode="${editMode}">
|
|
20
|
+
<div class="ec-pg-inner">
|
|
21
|
+
<span class="ec-pg-badge">Playground</span>
|
|
22
|
+
|
|
23
|
+
<div class="ec-pg-divider"></div>
|
|
24
|
+
|
|
25
|
+
<label class="ec-pg-toggle" title="Toggle visual editing">
|
|
26
|
+
<input type="checkbox" id="ec-pg-edit-toggle" ${editMode ? "checked" : ""} />
|
|
27
|
+
<span class="ec-pg-toggle-track">
|
|
28
|
+
<span class="ec-pg-toggle-thumb"></span>
|
|
29
|
+
</span>
|
|
30
|
+
<span class="ec-pg-toggle-label">Edit</span>
|
|
31
|
+
</label>
|
|
32
|
+
|
|
33
|
+
<div class="ec-pg-divider"></div>
|
|
34
|
+
|
|
35
|
+
<span class="ec-pg-status" id="ec-pg-status"></span>
|
|
36
|
+
|
|
37
|
+
<div class="ec-pg-divider"></div>
|
|
38
|
+
|
|
39
|
+
<button class="ec-pg-btn ec-pg-btn--reset" id="ec-pg-reset" title="Reset playground">
|
|
40
|
+
<svg class="ec-pg-icon" id="ec-pg-reset-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/></svg>
|
|
41
|
+
</button>
|
|
42
|
+
|
|
43
|
+
<a class="ec-pg-btn ec-pg-btn--deploy" href="https://docs.emdashcms.com/getting-started" target="_blank" rel="noopener">
|
|
44
|
+
Deploy your own
|
|
45
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>
|
|
46
|
+
</a>
|
|
47
|
+
|
|
48
|
+
<button class="ec-pg-btn ec-pg-close" id="ec-pg-dismiss" title="Dismiss toolbar">
|
|
49
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
|
50
|
+
</button>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
<style>
|
|
55
|
+
#emdash-playground-toolbar {
|
|
56
|
+
position: fixed;
|
|
57
|
+
bottom: 16px;
|
|
58
|
+
left: 50%;
|
|
59
|
+
transform: translateX(-50%);
|
|
60
|
+
z-index: 999999;
|
|
61
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
62
|
+
font-size: 13px;
|
|
63
|
+
line-height: 1;
|
|
64
|
+
-webkit-font-smoothing: antialiased;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
#emdash-playground-toolbar.ec-pg-hidden {
|
|
68
|
+
display: none;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.ec-pg-inner {
|
|
72
|
+
display: flex;
|
|
73
|
+
align-items: center;
|
|
74
|
+
gap: 10px;
|
|
75
|
+
padding: 8px 12px 8px 16px;
|
|
76
|
+
background: #1a1a1a;
|
|
77
|
+
color: #e0e0e0;
|
|
78
|
+
border-radius: 999px;
|
|
79
|
+
box-shadow: 0 4px 24px rgba(0,0,0,0.3), 0 0 0 1px rgba(255,255,255,0.08);
|
|
80
|
+
white-space: nowrap;
|
|
81
|
+
user-select: none;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.ec-pg-badge {
|
|
85
|
+
display: inline-flex;
|
|
86
|
+
align-items: center;
|
|
87
|
+
padding: 3px 8px;
|
|
88
|
+
border-radius: 999px;
|
|
89
|
+
font-size: 11px;
|
|
90
|
+
font-weight: 600;
|
|
91
|
+
letter-spacing: 0.02em;
|
|
92
|
+
text-transform: uppercase;
|
|
93
|
+
background: rgba(234,179,8,0.2);
|
|
94
|
+
color: #facc15;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.ec-pg-divider {
|
|
98
|
+
width: 1px;
|
|
99
|
+
height: 16px;
|
|
100
|
+
background: rgba(255,255,255,0.15);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/* Edit toggle */
|
|
104
|
+
.ec-pg-toggle {
|
|
105
|
+
display: inline-flex;
|
|
106
|
+
align-items: center;
|
|
107
|
+
gap: 6px;
|
|
108
|
+
cursor: pointer;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.ec-pg-toggle input {
|
|
112
|
+
position: absolute;
|
|
113
|
+
opacity: 0;
|
|
114
|
+
width: 0;
|
|
115
|
+
height: 0;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.ec-pg-toggle-track {
|
|
119
|
+
position: relative;
|
|
120
|
+
width: 28px;
|
|
121
|
+
height: 16px;
|
|
122
|
+
border-radius: 999px;
|
|
123
|
+
background: rgba(255,255,255,0.15);
|
|
124
|
+
transition: background 0.15s;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.ec-pg-toggle input:checked + .ec-pg-toggle-track {
|
|
128
|
+
background: #3b82f6;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.ec-pg-toggle-thumb {
|
|
132
|
+
position: absolute;
|
|
133
|
+
top: 2px;
|
|
134
|
+
left: 2px;
|
|
135
|
+
width: 12px;
|
|
136
|
+
height: 12px;
|
|
137
|
+
border-radius: 50%;
|
|
138
|
+
background: #fff;
|
|
139
|
+
transition: transform 0.15s;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.ec-pg-toggle input:checked + .ec-pg-toggle-track .ec-pg-toggle-thumb {
|
|
143
|
+
transform: translateX(12px);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.ec-pg-toggle-label {
|
|
147
|
+
font-size: 12px;
|
|
148
|
+
font-weight: 500;
|
|
149
|
+
color: #999;
|
|
150
|
+
transition: color 0.15s;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.ec-pg-toggle input:checked ~ .ec-pg-toggle-label {
|
|
154
|
+
color: #e0e0e0;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.ec-pg-status {
|
|
158
|
+
display: inline-flex;
|
|
159
|
+
align-items: center;
|
|
160
|
+
gap: 6px;
|
|
161
|
+
font-size: 12px;
|
|
162
|
+
color: #999;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.ec-pg-status--warning {
|
|
166
|
+
color: #fbbf24;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.ec-pg-status--expired {
|
|
170
|
+
color: #f87171;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.ec-pg-btn {
|
|
174
|
+
display: inline-flex;
|
|
175
|
+
align-items: center;
|
|
176
|
+
justify-content: center;
|
|
177
|
+
background: none;
|
|
178
|
+
border: none;
|
|
179
|
+
color: #888;
|
|
180
|
+
cursor: pointer;
|
|
181
|
+
padding: 4px;
|
|
182
|
+
border-radius: 4px;
|
|
183
|
+
transition: color 0.15s, background 0.15s;
|
|
184
|
+
font-family: inherit;
|
|
185
|
+
text-decoration: none;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.ec-pg-btn:hover {
|
|
189
|
+
color: #fff;
|
|
190
|
+
background: rgba(255,255,255,0.08);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.ec-pg-btn--deploy {
|
|
194
|
+
gap: 5px;
|
|
195
|
+
padding: 5px 10px;
|
|
196
|
+
font-size: 12px;
|
|
197
|
+
font-weight: 500;
|
|
198
|
+
color: #facc15;
|
|
199
|
+
background: rgba(234,179,8,0.12);
|
|
200
|
+
border-radius: 999px;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.ec-pg-btn--deploy:hover {
|
|
204
|
+
background: rgba(234,179,8,0.22);
|
|
205
|
+
color: #fde047;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.ec-pg-icon {
|
|
209
|
+
transition: transform 0.3s;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.ec-pg-btn:disabled {
|
|
213
|
+
opacity: 0.4;
|
|
214
|
+
cursor: not-allowed;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.ec-pg-btn:disabled:hover {
|
|
218
|
+
color: #888;
|
|
219
|
+
background: none;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
@keyframes ec-pg-spin {
|
|
223
|
+
to { transform: rotate(360deg); }
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.ec-pg-spinning .ec-pg-icon {
|
|
227
|
+
animation: ec-pg-spin 0.8s linear infinite;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/* Edit mode: editable hover styles (mirrors the visual editing toolbar CSS) */
|
|
231
|
+
body:has(#emdash-playground-toolbar[data-edit-mode="true"]) [data-emdash-ref] {
|
|
232
|
+
transition: box-shadow 0.15s, background-color 0.15s;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
body:has(#emdash-playground-toolbar[data-edit-mode="true"]) [data-emdash-ref]:hover {
|
|
236
|
+
box-shadow: 0 0 0 2px rgba(59,130,246,0.5);
|
|
237
|
+
border-radius: 4px;
|
|
238
|
+
background-color: rgba(59,130,246,0.04);
|
|
239
|
+
cursor: text;
|
|
240
|
+
}
|
|
241
|
+
</style>
|
|
242
|
+
|
|
243
|
+
<script>
|
|
244
|
+
(function() {
|
|
245
|
+
var toolbar = document.getElementById("emdash-playground-toolbar");
|
|
246
|
+
var statusEl = document.getElementById("ec-pg-status");
|
|
247
|
+
var resetBtn = document.getElementById("ec-pg-reset");
|
|
248
|
+
var dismissBtn = document.getElementById("ec-pg-dismiss");
|
|
249
|
+
var editToggle = document.getElementById("ec-pg-edit-toggle");
|
|
250
|
+
if (!toolbar || !statusEl || !resetBtn || !dismissBtn || !editToggle) return;
|
|
251
|
+
|
|
252
|
+
var createdAt = toolbar.getAttribute("data-created-at");
|
|
253
|
+
var ttl = parseInt(toolbar.getAttribute("data-ttl") || "3600", 10);
|
|
254
|
+
|
|
255
|
+
function getRemaining() {
|
|
256
|
+
if (!createdAt) return 0;
|
|
257
|
+
var created = new Date(createdAt).getTime();
|
|
258
|
+
var expiresAt = created + ttl * 1000;
|
|
259
|
+
return Math.max(0, Math.floor((expiresAt - Date.now()) / 1000));
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function formatRemaining(seconds) {
|
|
263
|
+
if (seconds <= 0) return "Expired";
|
|
264
|
+
var m = Math.floor(seconds / 60);
|
|
265
|
+
if (m >= 60) {
|
|
266
|
+
var h = Math.floor(m / 60);
|
|
267
|
+
m = m % 60;
|
|
268
|
+
return h + "h " + m + "m";
|
|
269
|
+
}
|
|
270
|
+
return m + "m remaining";
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function updateStatus() {
|
|
274
|
+
var remaining = getRemaining();
|
|
275
|
+
statusEl.textContent = formatRemaining(remaining);
|
|
276
|
+
if (remaining <= 0) {
|
|
277
|
+
statusEl.className = "ec-pg-status ec-pg-status--expired";
|
|
278
|
+
} else if (remaining < 300) {
|
|
279
|
+
statusEl.className = "ec-pg-status ec-pg-status--warning";
|
|
280
|
+
} else {
|
|
281
|
+
statusEl.className = "ec-pg-status";
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
updateStatus();
|
|
286
|
+
// Update every 30s -- no seconds shown so no need for frequent updates
|
|
287
|
+
var interval = setInterval(updateStatus, 30000);
|
|
288
|
+
|
|
289
|
+
// Edit mode toggle -- sets cookie and reloads
|
|
290
|
+
editToggle.addEventListener("change", function() {
|
|
291
|
+
if (editToggle.checked) {
|
|
292
|
+
document.cookie = "emdash-edit-mode=true;path=/;samesite=lax";
|
|
293
|
+
toolbar.setAttribute("data-edit-mode", "true");
|
|
294
|
+
} else {
|
|
295
|
+
document.cookie = "emdash-edit-mode=;path=/;expires=Thu, 01 Jan 1970 00:00:00 GMT";
|
|
296
|
+
toolbar.setAttribute("data-edit-mode", "false");
|
|
297
|
+
}
|
|
298
|
+
if (document.startViewTransition) {
|
|
299
|
+
document.startViewTransition(function() { location.replace(location.href); });
|
|
300
|
+
} else {
|
|
301
|
+
location.replace(location.href);
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
resetBtn.addEventListener("click", function() {
|
|
306
|
+
resetBtn.disabled = true;
|
|
307
|
+
resetBtn.classList.add("ec-pg-spinning");
|
|
308
|
+
statusEl.className = "ec-pg-status";
|
|
309
|
+
statusEl.textContent = "Resetting\\u2026";
|
|
310
|
+
location.href = "/_playground/reset";
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
dismissBtn.addEventListener("click", function() {
|
|
314
|
+
toolbar.classList.add("ec-pg-hidden");
|
|
315
|
+
clearInterval(interval);
|
|
316
|
+
});
|
|
317
|
+
})();
|
|
318
|
+
<\/script>
|
|
319
|
+
`;
|
|
320
|
+
}
|
|
321
|
+
function escapeAttr(str) {
|
|
322
|
+
return str.replace(RE_AMP, "&").replace(RE_QUOT, """).replace(RE_LT, "<").replace(RE_GT, ">");
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
//#endregion
|
|
326
|
+
//#region src/db/playground-middleware.ts
|
|
327
|
+
/**
|
|
328
|
+
* Playground middleware — injected by the EmDash integration as order: "pre".
|
|
329
|
+
*
|
|
330
|
+
* Runs BEFORE the EmDash runtime init middleware. Creates a per-session
|
|
331
|
+
* Durable Object database, runs migrations, applies the seed, creates an
|
|
332
|
+
* anonymous admin user, and sets the DB in ALS via runWithContext().
|
|
333
|
+
*
|
|
334
|
+
* By the time the runtime middleware runs, the ALS-scoped DB is ready.
|
|
335
|
+
* The runtime's `db` getter checks ALS first, so all init queries
|
|
336
|
+
* (migrations, FTS, cron, manifest) operate on the real DO database.
|
|
337
|
+
*
|
|
338
|
+
* This module is registered via `addMiddleware({ entrypoint: "..." })` in
|
|
339
|
+
* the integration, NOT in the user's src/middleware.ts.
|
|
340
|
+
*/
|
|
341
|
+
/** Default TTL for playground data (1 hour) */
|
|
342
|
+
const DEFAULT_TTL = 3600;
|
|
343
|
+
/** Cookie name for playground session */
|
|
344
|
+
const COOKIE_NAME = "emdash_playground";
|
|
345
|
+
/** Playground admin user constants */
|
|
346
|
+
const PLAYGROUND_USER_ID = "playground-admin";
|
|
347
|
+
const PLAYGROUND_USER_EMAIL = "playground@emdashcms.com";
|
|
348
|
+
const PLAYGROUND_USER_NAME = "Playground User";
|
|
349
|
+
const PLAYGROUND_USER_ROLE = 50;
|
|
350
|
+
const PLAYGROUND_USER = {
|
|
351
|
+
id: PLAYGROUND_USER_ID,
|
|
352
|
+
email: PLAYGROUND_USER_EMAIL,
|
|
353
|
+
name: PLAYGROUND_USER_NAME,
|
|
354
|
+
role: PLAYGROUND_USER_ROLE
|
|
355
|
+
};
|
|
356
|
+
/** Track which DOs have been initialized this Worker lifetime */
|
|
357
|
+
const initializedSessions = /* @__PURE__ */ new Set();
|
|
358
|
+
/**
|
|
359
|
+
* Read the DO binding name from the virtual config.
|
|
360
|
+
* The database config has the binding in `config.database.config.binding`.
|
|
361
|
+
*/
|
|
362
|
+
function getBindingName() {
|
|
363
|
+
const binding = virtualConfig?.database?.config?.binding;
|
|
364
|
+
if (!binding) throw new Error("Playground middleware: no database binding found in config. Ensure database: playgroundDatabase({ binding: '...' }) is set.");
|
|
365
|
+
return binding;
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Get a PreviewDBStub for the given session token.
|
|
369
|
+
*/
|
|
370
|
+
function getStub(binding, token) {
|
|
371
|
+
const ns = env[binding];
|
|
372
|
+
if (!ns) throw new Error(`Playground binding "${binding}" not found in environment`);
|
|
373
|
+
const namespace = ns;
|
|
374
|
+
const doId = namespace.idFromName(token);
|
|
375
|
+
return namespace.get(doId);
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Get the full DO stub for direct RPC calls (e.g. setTtlAlarm).
|
|
379
|
+
*/
|
|
380
|
+
function getFullStub(binding, token) {
|
|
381
|
+
const ns = env[binding];
|
|
382
|
+
if (!ns) throw new Error(`Playground binding "${binding}" not found in environment`);
|
|
383
|
+
const namespace = ns;
|
|
384
|
+
const doId = namespace.idFromName(token);
|
|
385
|
+
return namespace.get(doId);
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Derive a created-at timestamp from the ULID session token.
|
|
389
|
+
*/
|
|
390
|
+
function getSessionCreatedAt(token) {
|
|
391
|
+
try {
|
|
392
|
+
const ENCODING = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
393
|
+
let time = 0;
|
|
394
|
+
const chars = token.toUpperCase().slice(0, 10);
|
|
395
|
+
for (const char of chars) time = time * 32 + ENCODING.indexOf(char);
|
|
396
|
+
return new Date(time).toISOString();
|
|
397
|
+
} catch {
|
|
398
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Initialize a playground DO: run migrations, apply seed, create admin user.
|
|
403
|
+
*/
|
|
404
|
+
async function initializePlayground(db, token) {
|
|
405
|
+
try {
|
|
406
|
+
const { rows } = await sql`
|
|
407
|
+
SELECT value FROM options WHERE name = ${"emdash:setup_complete"}
|
|
408
|
+
`.execute(db);
|
|
409
|
+
if (rows.length > 0) return;
|
|
410
|
+
} catch {}
|
|
411
|
+
console.log(`[playground] Initializing session ${token}`);
|
|
412
|
+
const { runMigrations } = await import("emdash/db");
|
|
413
|
+
try {
|
|
414
|
+
const migrations = await runMigrations(db);
|
|
415
|
+
console.log(`[playground] Migrations applied: ${migrations.applied.length}`);
|
|
416
|
+
} catch (migrationError) {
|
|
417
|
+
if ((migrationError instanceof Error ? migrationError.message : String(migrationError)).includes("already exists")) {
|
|
418
|
+
console.log(`[playground] Migrations skipped (tables already exist)`);
|
|
419
|
+
try {
|
|
420
|
+
await sql`
|
|
421
|
+
INSERT OR IGNORE INTO options (name, value)
|
|
422
|
+
VALUES (${"emdash:setup_complete"}, ${JSON.stringify(true)})
|
|
423
|
+
`.execute(db);
|
|
424
|
+
} catch {}
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
throw migrationError;
|
|
428
|
+
}
|
|
429
|
+
const { loadSeed } = await import("emdash/seed");
|
|
430
|
+
const { applySeed } = await import("emdash");
|
|
431
|
+
const seedResult = await applySeed(db, await loadSeed(), {
|
|
432
|
+
includeContent: true,
|
|
433
|
+
onConflict: "skip",
|
|
434
|
+
skipMediaDownload: true
|
|
435
|
+
});
|
|
436
|
+
console.log(`[playground] Seed applied: ${seedResult.collections.created} collections, ${seedResult.content.created} content entries`);
|
|
437
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
438
|
+
try {
|
|
439
|
+
await sql`
|
|
440
|
+
INSERT INTO users (id, email, name, role, email_verified, created_at, updated_at)
|
|
441
|
+
VALUES (${PLAYGROUND_USER_ID}, ${PLAYGROUND_USER_EMAIL}, ${PLAYGROUND_USER_NAME},
|
|
442
|
+
${PLAYGROUND_USER_ROLE}, ${1}, ${now}, ${now})
|
|
443
|
+
`.execute(db);
|
|
444
|
+
} catch {}
|
|
445
|
+
try {
|
|
446
|
+
await sql`
|
|
447
|
+
INSERT INTO options (name, value)
|
|
448
|
+
VALUES (${"emdash:setup_complete"}, ${JSON.stringify(true)})
|
|
449
|
+
`.execute(db);
|
|
450
|
+
} catch {}
|
|
451
|
+
try {
|
|
452
|
+
await sql`
|
|
453
|
+
INSERT OR REPLACE INTO options (name, value)
|
|
454
|
+
VALUES (${"emdash:site_title"}, ${JSON.stringify("EmDash Playground")})
|
|
455
|
+
`.execute(db);
|
|
456
|
+
} catch {}
|
|
457
|
+
console.log(`[playground] Session ${token} initialized`);
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Inject playground toolbar HTML into an HTML response.
|
|
461
|
+
*/
|
|
462
|
+
async function injectPlaygroundToolbar(response, config) {
|
|
463
|
+
if (!response.headers.get("content-type")?.includes("text/html")) return response;
|
|
464
|
+
const html = await response.text();
|
|
465
|
+
if (!html.includes("</body>")) return new Response(html, response);
|
|
466
|
+
const toolbarHtml = renderPlaygroundToolbar(config);
|
|
467
|
+
const injected = html.replace("</body>", `${toolbarHtml}</body>`);
|
|
468
|
+
return new Response(injected, {
|
|
469
|
+
status: response.status,
|
|
470
|
+
headers: response.headers
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
const onRequest = defineMiddleware(async (context, next) => {
|
|
474
|
+
const { url, cookies } = context;
|
|
475
|
+
const ttl = DEFAULT_TTL;
|
|
476
|
+
const binding = getBindingName();
|
|
477
|
+
if (url.pathname === "/playground") {
|
|
478
|
+
let token = cookies.get(COOKIE_NAME)?.value;
|
|
479
|
+
if (!token) {
|
|
480
|
+
token = ulid();
|
|
481
|
+
cookies.set(COOKIE_NAME, token, {
|
|
482
|
+
httpOnly: true,
|
|
483
|
+
sameSite: "lax",
|
|
484
|
+
path: "/",
|
|
485
|
+
maxAge: ttl
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
const stub = getStub(binding, token);
|
|
489
|
+
const db = new Kysely({ dialect: new PreviewDODialect({ getStub: () => stub }) });
|
|
490
|
+
if (!initializedSessions.has(token)) {
|
|
491
|
+
await initializePlayground(db, token);
|
|
492
|
+
initializedSessions.add(token);
|
|
493
|
+
await getFullStub(binding, token).setTtlAlarm(ttl);
|
|
494
|
+
}
|
|
495
|
+
return context.redirect("/_emdash/admin");
|
|
496
|
+
}
|
|
497
|
+
if (url.pathname === "/_playground/reset") {
|
|
498
|
+
cookies.delete(COOKIE_NAME, { path: "/" });
|
|
499
|
+
return context.redirect("/playground");
|
|
500
|
+
}
|
|
501
|
+
if (isBlockedInPlayground(url.pathname)) return Response.json({ error: {
|
|
502
|
+
code: "PLAYGROUND_MODE",
|
|
503
|
+
message: "Not available in playground mode"
|
|
504
|
+
} }, { status: 403 });
|
|
505
|
+
const token = cookies.get(COOKIE_NAME)?.value;
|
|
506
|
+
if (!token) return context.redirect("/playground");
|
|
507
|
+
const stub = getStub(binding, token);
|
|
508
|
+
const db = new Kysely({ dialect: new PreviewDODialect({ getStub: () => stub }) });
|
|
509
|
+
if (!initializedSessions.has(token)) try {
|
|
510
|
+
await initializePlayground(db, token);
|
|
511
|
+
initializedSessions.add(token);
|
|
512
|
+
await getFullStub(binding, token).setTtlAlarm(ttl);
|
|
513
|
+
} catch (error) {
|
|
514
|
+
console.error("Playground initialization failed:", error);
|
|
515
|
+
return Response.json({ error: {
|
|
516
|
+
code: "PLAYGROUND_INIT_ERROR",
|
|
517
|
+
message: "Failed to initialize playground"
|
|
518
|
+
} }, { status: 500 });
|
|
519
|
+
}
|
|
520
|
+
Object.assign(context.locals, {
|
|
521
|
+
__playgroundDb: db,
|
|
522
|
+
user: PLAYGROUND_USER
|
|
523
|
+
});
|
|
524
|
+
const editMode = cookies.get("emdash-edit-mode")?.value === "true";
|
|
525
|
+
return injectPlaygroundToolbar(await next(), {
|
|
526
|
+
createdAt: getSessionCreatedAt(token),
|
|
527
|
+
ttl,
|
|
528
|
+
editMode
|
|
529
|
+
});
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
//#endregion
|
|
533
|
+
export { onRequest };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { t as PreviewDOConfig } from "../do-types-CY0G0oyh.mjs";
|
|
2
|
+
import { t as EmDashPreviewDB } from "../do-class-x5Xh_G62.mjs";
|
|
3
|
+
import { Dialect } from "kysely";
|
|
4
|
+
|
|
5
|
+
//#region src/db/do-playground-routes.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* Playground mode route gating.
|
|
8
|
+
*
|
|
9
|
+
* Unlike preview mode (which blocks everything except read-only API routes),
|
|
10
|
+
* playground mode allows most routes including the admin UI and write APIs.
|
|
11
|
+
* Only auth, setup, and abuse-prone routes are blocked.
|
|
12
|
+
*
|
|
13
|
+
* Pure function -- no Worker or Cloudflare dependencies.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Check whether a request should be blocked in playground mode.
|
|
17
|
+
*
|
|
18
|
+
* Playground allows most CMS functionality: content CRUD, schema editing,
|
|
19
|
+
* taxonomies, menus, widgets, search, settings, and the full admin UI.
|
|
20
|
+
* Only auth, setup, user management, media uploads, and plugin
|
|
21
|
+
* installation are blocked.
|
|
22
|
+
*/
|
|
23
|
+
declare function isBlockedInPlayground(pathname: string): boolean;
|
|
24
|
+
//#endregion
|
|
25
|
+
//#region src/db/playground.d.ts
|
|
26
|
+
/**
|
|
27
|
+
* Create a playground DO dialect from config.
|
|
28
|
+
*
|
|
29
|
+
* Returns a dialect that throws if any query is executed outside of
|
|
30
|
+
* the playground middleware's ALS context. In normal operation, the
|
|
31
|
+
* middleware overrides this DB via runWithContext() on every request.
|
|
32
|
+
*
|
|
33
|
+
* This factory exists to satisfy the virtual module system's
|
|
34
|
+
* createDialect() contract. The EmDash runtime creates a singleton
|
|
35
|
+
* DB from it, but all actual queries go through the ALS-scoped DB.
|
|
36
|
+
*/
|
|
37
|
+
declare function createDialect(_config: PreviewDOConfig): Dialect;
|
|
38
|
+
//#endregion
|
|
39
|
+
export { EmDashPreviewDB, createDialect, isBlockedInPlayground };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import "../d1-introspector-bZf0_ylK.mjs";
|
|
2
|
+
import { t as PreviewDODialect } from "../do-dialect-BhFcRSFQ.mjs";
|
|
3
|
+
import { t as EmDashPreviewDB } from "../do-class-DY2Ba2RJ.mjs";
|
|
4
|
+
import { t as isBlockedInPlayground } from "../do-playground-routes-CmwFeGwJ.mjs";
|
|
5
|
+
|
|
6
|
+
//#region src/db/playground.ts
|
|
7
|
+
/**
|
|
8
|
+
* Create a playground DO dialect from config.
|
|
9
|
+
*
|
|
10
|
+
* Returns a dialect that throws if any query is executed outside of
|
|
11
|
+
* the playground middleware's ALS context. In normal operation, the
|
|
12
|
+
* middleware overrides this DB via runWithContext() on every request.
|
|
13
|
+
*
|
|
14
|
+
* This factory exists to satisfy the virtual module system's
|
|
15
|
+
* createDialect() contract. The EmDash runtime creates a singleton
|
|
16
|
+
* DB from it, but all actual queries go through the ALS-scoped DB.
|
|
17
|
+
*/
|
|
18
|
+
function createDialect(_config) {
|
|
19
|
+
const notInitialized = { async query() {
|
|
20
|
+
throw new Error("Playground database not initialized. Ensure the playground middleware is registered in src/middleware.ts and all requests go through it.");
|
|
21
|
+
} };
|
|
22
|
+
return new PreviewDODialect({ getStub: () => notInitialized });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
//#endregion
|
|
26
|
+
export { EmDashPreviewDB, createDialect, isBlockedInPlayground };
|