emdash 0.4.0 → 0.6.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/dist/{adapters-C2BzVy0p.d.mts → adapters-Di31kZ28.d.mts} +16 -1
- package/dist/adapters-Di31kZ28.d.mts.map +1 -0
- package/dist/{apply-Cma_PiF6.mjs → apply-B4MsLM-w.mjs} +27 -12
- package/dist/apply-B4MsLM-w.mjs.map +1 -0
- package/dist/astro/index.d.mts +6 -6
- package/dist/astro/index.d.mts.map +1 -1
- package/dist/astro/index.mjs +208 -34
- package/dist/astro/index.mjs.map +1 -1
- package/dist/astro/middleware/auth.d.mts +5 -5
- package/dist/astro/middleware/auth.d.mts.map +1 -1
- package/dist/astro/middleware/auth.mjs +34 -9
- package/dist/astro/middleware/auth.mjs.map +1 -1
- package/dist/astro/middleware/redirect.mjs +1 -1
- package/dist/astro/middleware/request-context.d.mts.map +1 -1
- package/dist/astro/middleware/request-context.mjs +5 -3
- package/dist/astro/middleware/request-context.mjs.map +1 -1
- package/dist/astro/middleware/setup.mjs +1 -1
- package/dist/astro/middleware.d.mts.map +1 -1
- package/dist/astro/middleware.mjs +460 -180
- package/dist/astro/middleware.mjs.map +1 -1
- package/dist/astro/types.d.mts +8 -8
- package/dist/{byline-WuOq9MFJ.mjs → byline-C4OVd8b3.mjs} +3 -19
- package/dist/byline-C4OVd8b3.mjs.map +1 -0
- package/dist/{bylines-C_Wsnz4L.mjs → bylines-hPTW79hw.mjs} +20 -33
- package/dist/bylines-hPTW79hw.mjs.map +1 -0
- package/dist/{cache-E3Dts-yT.mjs → cache-BkKBuIvS.mjs} +1 -1
- package/dist/{cache-E3Dts-yT.mjs.map → cache-BkKBuIvS.mjs.map} +1 -1
- package/dist/chunks-HGz06Soa.mjs +19 -0
- package/dist/chunks-HGz06Soa.mjs.map +1 -0
- package/dist/cli/index.mjs +9 -8
- package/dist/cli/index.mjs.map +1 -1
- package/dist/client/cf-access.d.mts +1 -1
- package/dist/client/index.d.mts +1 -1
- package/dist/client/index.mjs +1 -1
- package/dist/{config-DkxPrM9l.mjs → config-BXwuX8Bx.mjs} +1 -1
- package/dist/{config-DkxPrM9l.mjs.map → config-BXwuX8Bx.mjs.map} +1 -1
- package/dist/{connection-B4zVnQIa.mjs → connection-2igzM-AT.mjs} +19 -2
- package/dist/connection-2igzM-AT.mjs.map +1 -0
- package/dist/database/instrumentation.d.mts +45 -0
- package/dist/database/instrumentation.d.mts.map +1 -0
- package/dist/database/instrumentation.mjs +61 -0
- package/dist/database/instrumentation.mjs.map +1 -0
- package/dist/db/index.d.mts +3 -3
- package/dist/db/index.mjs.map +1 -1
- package/dist/db/libsql.d.mts +1 -1
- package/dist/db/postgres.d.mts +1 -1
- package/dist/db/sqlite.d.mts +1 -1
- package/dist/db-errors-D0UT85nC.mjs +41 -0
- package/dist/db-errors-D0UT85nC.mjs.map +1 -0
- package/dist/{default-PUx9RK6u.mjs → default-CME5YdZ3.mjs} +1 -1
- package/dist/{default-PUx9RK6u.mjs.map → default-CME5YdZ3.mjs.map} +1 -1
- package/dist/{error-HBeQbVhV.mjs → error-CiYn9yDu.mjs} +1 -1
- package/dist/{error-HBeQbVhV.mjs.map → error-CiYn9yDu.mjs.map} +1 -1
- package/dist/{index-CRg3PWfZ.d.mts → index-BYv0mB9g.d.mts} +135 -19
- package/dist/index-BYv0mB9g.d.mts.map +1 -0
- package/dist/index.d.mts +11 -11
- package/dist/index.mjs +20 -18
- package/dist/{load-BhSSm-TS.mjs → load-CBcmDIot.mjs} +1 -1
- package/dist/{load-BhSSm-TS.mjs.map → load-CBcmDIot.mjs.map} +1 -1
- package/dist/{loader-BYzwzORf.mjs → loader-DeiBJEMe.mjs} +18 -12
- package/dist/loader-DeiBJEMe.mjs.map +1 -0
- package/dist/{manifest-schema-BsXINkQD.mjs → manifest-schema-V30qsMft.mjs} +1 -1
- package/dist/{manifest-schema-BsXINkQD.mjs.map → manifest-schema-V30qsMft.mjs.map} +1 -1
- package/dist/media/index.d.mts +1 -1
- package/dist/media/index.mjs +1 -1
- package/dist/media/local-runtime.d.mts +7 -7
- package/dist/{mode-CyPLdO3C.mjs → mode-CpNnGkPz.mjs} +1 -1
- package/dist/{mode-CyPLdO3C.mjs.map → mode-CpNnGkPz.mjs.map} +1 -1
- package/dist/page/index.d.mts +11 -2
- package/dist/page/index.d.mts.map +1 -1
- package/dist/page/index.mjs +23 -1
- package/dist/page/index.mjs.map +1 -1
- package/dist/{placeholder-DntBEQo7.mjs → placeholder-C-fk5hYI.mjs} +1 -1
- package/dist/{placeholder-DntBEQo7.mjs.map → placeholder-C-fk5hYI.mjs.map} +1 -1
- package/dist/{placeholder-BBCtpTES.d.mts → placeholder-tzpqGWII.d.mts} +1 -1
- package/dist/{placeholder-BBCtpTES.d.mts.map → placeholder-tzpqGWII.d.mts.map} +1 -1
- package/dist/plugins/adapt-sandbox-entry.d.mts +5 -5
- package/dist/plugins/adapt-sandbox-entry.mjs +1 -1
- package/dist/{query-B6Vu0d2i.mjs → query-Bk_3vKvU.mjs} +78 -11
- package/dist/query-Bk_3vKvU.mjs.map +1 -0
- package/dist/{registry-BgnP3ysR.mjs → registry-Ci3WxVAr.mjs} +133 -97
- package/dist/registry-Ci3WxVAr.mjs.map +1 -0
- package/dist/request-cache-DiR961CV.mjs +79 -0
- package/dist/request-cache-DiR961CV.mjs.map +1 -0
- package/dist/request-context.d.mts +19 -16
- package/dist/request-context.d.mts.map +1 -1
- package/dist/request-context.mjs.map +1 -1
- package/dist/{runner-DYv3rX8P.d.mts → runner-Fl2NcUUz.d.mts} +2 -2
- package/dist/{runner-DYv3rX8P.d.mts.map → runner-Fl2NcUUz.d.mts.map} +1 -1
- package/dist/runtime.d.mts +6 -6
- package/dist/runtime.mjs +1 -1
- package/dist/{search-B5p9D36n.mjs → search-DI4bM2w9.mjs} +110 -209
- package/dist/search-DI4bM2w9.mjs.map +1 -0
- package/dist/seed/index.d.mts +2 -2
- package/dist/seed/index.mjs +8 -7
- package/dist/seo/index.d.mts +1 -1
- package/dist/storage/local.d.mts +1 -1
- package/dist/storage/local.mjs +1 -1
- package/dist/storage/s3.d.mts +1 -1
- package/dist/storage/s3.mjs +1 -1
- package/dist/taxonomies-DbrKzDju.mjs +308 -0
- package/dist/taxonomies-DbrKzDju.mjs.map +1 -0
- package/dist/{tokens-DKHiCYCB.mjs → tokens-BFPFx3CA.mjs} +1 -1
- package/dist/{tokens-DKHiCYCB.mjs.map → tokens-BFPFx3CA.mjs.map} +1 -1
- package/dist/{transport-BtcQ-Z7T.mjs → transport-BykRfpyy.mjs} +1 -1
- package/dist/{transport-BtcQ-Z7T.mjs.map → transport-BykRfpyy.mjs.map} +1 -1
- package/dist/{transport-CKQA_G44.d.mts → transport-H4Iwx7tC.d.mts} +1 -1
- package/dist/{transport-CKQA_G44.d.mts.map → transport-H4Iwx7tC.d.mts.map} +1 -1
- package/dist/{types-BmkQR1En.d.mts → types-6CUZRrZP.d.mts} +1 -1
- package/dist/{types-BmkQR1En.d.mts.map → types-6CUZRrZP.d.mts.map} +1 -1
- package/dist/{types-B6BzlZxx.d.mts → types-8xrvl_68.d.mts} +1 -1
- package/dist/{types-B6BzlZxx.d.mts.map → types-8xrvl_68.d.mts.map} +1 -1
- package/dist/{types-Dz9_WMS6.mjs → types-BH2L167P.mjs} +1 -1
- package/dist/{types-Dz9_WMS6.mjs.map → types-BH2L167P.mjs.map} +1 -1
- package/dist/{types-DNZpaCBk.d.mts → types-CFWjXmus.d.mts} +1 -1
- package/dist/{types-DNZpaCBk.d.mts.map → types-CFWjXmus.d.mts.map} +1 -1
- package/dist/{types-gLYVCXCQ.d.mts → types-CnZYHyLW.d.mts} +55 -5
- package/dist/types-CnZYHyLW.d.mts.map +1 -0
- package/dist/{types-xxCWI3j0.mjs → types-DDS4MxsT.mjs} +11 -3
- package/dist/types-DDS4MxsT.mjs.map +1 -0
- package/dist/{types-BYWYxLcp.d.mts → types-DgrIP0tF.d.mts} +9 -2
- package/dist/types-DgrIP0tF.d.mts.map +1 -0
- package/dist/{validate-CcNRWH6I.d.mts → validate-CaLH1Ia2.d.mts} +5 -52
- package/dist/validate-CaLH1Ia2.d.mts.map +1 -0
- package/dist/{validate-DuZDIxfy.mjs → validate-CqsNItbt.mjs} +2 -2
- package/dist/{validate-DuZDIxfy.mjs.map → validate-CqsNItbt.mjs.map} +1 -1
- package/dist/version-Uaf2ynPX.mjs +7 -0
- package/dist/{version-DlTDRdpv.mjs.map → version-Uaf2ynPX.mjs.map} +1 -1
- package/package.json +10 -5
- package/src/after.ts +62 -0
- package/src/api/handlers/oauth-authorization.ts +2 -32
- package/src/api/handlers/oauth-clients.ts +40 -4
- package/src/api/handlers/taxonomies.ts +13 -0
- package/src/api/oauth/redirect-uri.ts +34 -0
- package/src/api/openapi/document.ts +126 -118
- package/src/api/schemas/auth.ts +7 -0
- package/src/api/schemas/media.ts +26 -15
- package/src/api/schemas/schema.ts +1 -0
- package/src/astro/integration/font-provider.ts +176 -0
- package/src/astro/integration/index.ts +42 -0
- package/src/astro/integration/routes.ts +17 -1
- package/src/astro/integration/runtime.ts +63 -0
- package/src/astro/integration/virtual-modules.ts +41 -39
- package/src/astro/integration/vite-config.ts +16 -5
- package/src/astro/middleware/auth.ts +39 -6
- package/src/astro/middleware/request-context.ts +15 -3
- package/src/astro/middleware.ts +340 -263
- package/src/astro/routes/admin.astro +10 -5
- package/src/astro/routes/api/auth/invite/register-options.ts +78 -0
- package/src/astro/routes/api/auth/passkey/verify.ts +5 -1
- package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +5 -0
- package/src/astro/routes/api/import/wordpress/execute.ts +1 -1
- package/src/astro/routes/api/import/wordpress-plugin/execute.ts +1 -1
- package/src/astro/routes/api/media/upload-url.ts +10 -2
- package/src/astro/routes/api/media.ts +10 -7
- package/src/astro/routes/api/oauth/register.ts +178 -0
- package/src/astro/routes/api/oauth/token.ts +15 -0
- package/src/astro/routes/api/openapi.json.ts +15 -5
- package/src/astro/routes/api/schema/collections/[slug]/fields/[fieldSlug].ts +2 -0
- package/src/astro/routes/api/schema/collections/[slug]/fields/index.ts +1 -0
- package/src/astro/routes/api/schema/collections/[slug]/fields/reorder.ts +1 -0
- package/src/astro/routes/api/search/index.ts +5 -0
- package/src/astro/routes/api/search/suggest.ts +3 -0
- package/src/astro/routes/api/taxonomies/index.ts +1 -0
- package/src/astro/routes/api/well-known/oauth-authorization-server.ts +6 -4
- package/src/bylines/index.ts +22 -45
- package/src/components/EmDashHead.astro +23 -7
- package/src/components/Table.astro +73 -41
- package/src/components/index.ts +2 -12
- package/src/components/marks.ts +20 -0
- package/src/database/connection.ts +23 -1
- package/src/database/instrumentation.ts +98 -0
- package/src/db/adapters.ts +15 -0
- package/src/emdash-runtime.ts +309 -91
- package/src/index.ts +6 -0
- package/src/loader.ts +19 -24
- package/src/menus/index.ts +6 -3
- package/src/page/index.ts +1 -1
- package/src/page/seo-contributions.ts +36 -0
- package/src/plugins/context.ts +1 -0
- package/src/plugins/email-console.ts +9 -2
- package/src/plugins/types.ts +8 -0
- package/src/query.ts +104 -7
- package/src/request-cache.ts +106 -0
- package/src/request-context.ts +19 -0
- package/src/schema/query.ts +5 -2
- package/src/schema/registry.ts +243 -166
- package/src/schema/types.ts +13 -2
- package/src/schema/zod-generator.ts +4 -0
- package/src/search/fts-manager.ts +19 -5
- package/src/search/query.ts +4 -3
- package/src/seed/apply.ts +15 -1
- package/src/settings/index.ts +24 -5
- package/src/taxonomies/index.ts +324 -124
- package/src/utils/db-errors.ts +46 -0
- package/src/virtual-modules.d.ts +31 -10
- package/src/widgets/index.ts +54 -25
- package/dist/adapters-C2BzVy0p.d.mts.map +0 -1
- package/dist/apply-Cma_PiF6.mjs.map +0 -1
- package/dist/byline-WuOq9MFJ.mjs.map +0 -1
- package/dist/bylines-C_Wsnz4L.mjs.map +0 -1
- package/dist/connection-B4zVnQIa.mjs.map +0 -1
- package/dist/index-CRg3PWfZ.d.mts.map +0 -1
- package/dist/loader-BYzwzORf.mjs.map +0 -1
- package/dist/query-B6Vu0d2i.mjs.map +0 -1
- package/dist/registry-BgnP3ysR.mjs.map +0 -1
- package/dist/search-B5p9D36n.mjs.map +0 -1
- package/dist/types-BYWYxLcp.d.mts.map +0 -1
- package/dist/types-gLYVCXCQ.d.mts.map +0 -1
- package/dist/types-xxCWI3j0.mjs.map +0 -1
- package/dist/validate-CcNRWH6I.d.mts.map +0 -1
- package/dist/version-DlTDRdpv.mjs +0 -7
package/src/astro/middleware.ts
CHANGED
|
@@ -6,19 +6,16 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { defineMiddleware } from "astro:middleware";
|
|
9
|
-
import { Kysely } from "kysely";
|
|
9
|
+
import type { Kysely } from "kysely";
|
|
10
10
|
// Import from virtual modules (populated by integration at build time)
|
|
11
11
|
// @ts-ignore - virtual module
|
|
12
12
|
import virtualConfig from "virtual:emdash/config";
|
|
13
13
|
// @ts-ignore - virtual module
|
|
14
14
|
import {
|
|
15
15
|
createDialect as virtualCreateDialect,
|
|
16
|
-
|
|
17
|
-
getD1Binding as virtualGetD1Binding,
|
|
18
|
-
getDefaultConstraint as virtualGetDefaultConstraint,
|
|
19
|
-
getBookmarkCookieName as virtualGetBookmarkCookieName,
|
|
20
|
-
createSessionDialect as virtualCreateSessionDialect,
|
|
16
|
+
createRequestScopedDb as virtualCreateRequestScopedDb,
|
|
21
17
|
} from "virtual:emdash/dialect";
|
|
18
|
+
import type { RequestScopedDbOpts } from "virtual:emdash/dialect";
|
|
22
19
|
// @ts-ignore - virtual module
|
|
23
20
|
import { mediaProviders as virtualMediaProviders } from "virtual:emdash/media-providers";
|
|
24
21
|
// @ts-ignore - virtual module
|
|
@@ -33,6 +30,11 @@ import { sandboxedPlugins as virtualSandboxedPlugins } from "virtual:emdash/sand
|
|
|
33
30
|
// @ts-ignore - virtual module
|
|
34
31
|
import { createStorage as virtualCreateStorage } from "virtual:emdash/storage";
|
|
35
32
|
|
|
33
|
+
import {
|
|
34
|
+
createRecorder,
|
|
35
|
+
flushRecorder,
|
|
36
|
+
isInstrumentationEnabled,
|
|
37
|
+
} from "../database/instrumentation.js";
|
|
36
38
|
import {
|
|
37
39
|
EmDashRuntime,
|
|
38
40
|
type RuntimeDependencies,
|
|
@@ -43,7 +45,7 @@ import { setI18nConfig } from "../i18n/config.js";
|
|
|
43
45
|
import type { Database, Storage } from "../index.js";
|
|
44
46
|
import type { SandboxRunner } from "../plugins/sandbox/types.js";
|
|
45
47
|
import type { ResolvedPlugin } from "../plugins/types.js";
|
|
46
|
-
import { runWithContext } from "../request-context.js";
|
|
48
|
+
import { getRequestContext, runWithContext } from "../request-context.js";
|
|
47
49
|
import type { EmDashConfig } from "./integration/runtime.js";
|
|
48
50
|
import type { EmDashHandlers } from "./types.js";
|
|
49
51
|
|
|
@@ -128,9 +130,17 @@ function buildDependencies(config: EmDashConfig): RuntimeDependencies {
|
|
|
128
130
|
}
|
|
129
131
|
|
|
130
132
|
/**
|
|
131
|
-
* Get or create the runtime instance
|
|
133
|
+
* Get or create the runtime instance.
|
|
134
|
+
*
|
|
135
|
+
* When `initTimings` is provided, any timing samples recorded during a
|
|
136
|
+
* genuine cold init are appended. Subsequent warm calls (hitting the
|
|
137
|
+
* cached instance) push nothing — callers should treat an empty array
|
|
138
|
+
* as "warm, nothing to report".
|
|
132
139
|
*/
|
|
133
|
-
async function getRuntime(
|
|
140
|
+
async function getRuntime(
|
|
141
|
+
config: EmDashConfig,
|
|
142
|
+
initTimings?: Array<{ name: string; dur: number; desc?: string }>,
|
|
143
|
+
): Promise<EmDashRuntime> {
|
|
134
144
|
// Return cached instance if available
|
|
135
145
|
if (runtimeInstance) {
|
|
136
146
|
return runtimeInstance;
|
|
@@ -142,13 +152,13 @@ async function getRuntime(config: EmDashConfig): Promise<EmDashRuntime> {
|
|
|
142
152
|
if (runtimeInitializing) {
|
|
143
153
|
// Poll until the initializing request finishes
|
|
144
154
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
145
|
-
return getRuntime(config);
|
|
155
|
+
return getRuntime(config, initTimings);
|
|
146
156
|
}
|
|
147
157
|
|
|
148
158
|
runtimeInitializing = true;
|
|
149
159
|
try {
|
|
150
160
|
const deps = buildDependencies(config);
|
|
151
|
-
const runtime = await EmDashRuntime.create(deps);
|
|
161
|
+
const runtime = await EmDashRuntime.create(deps, initTimings);
|
|
152
162
|
runtimeInstance = runtime;
|
|
153
163
|
return runtime;
|
|
154
164
|
} finally {
|
|
@@ -156,18 +166,44 @@ async function getRuntime(config: EmDashConfig): Promise<EmDashRuntime> {
|
|
|
156
166
|
}
|
|
157
167
|
}
|
|
158
168
|
|
|
169
|
+
/**
|
|
170
|
+
* Astro attaches AstroCookies to outgoing responses via a well-known global
|
|
171
|
+
* symbol. Cloning a Response (`new Response(body, init)`) drops non-header
|
|
172
|
+
* metadata, so any middleware that wraps the response must explicitly forward
|
|
173
|
+
* this symbol or `cookies.set()` calls will be silently dropped.
|
|
174
|
+
*/
|
|
175
|
+
const ASTRO_COOKIES_SYMBOL = Symbol.for("astro.cookies");
|
|
176
|
+
|
|
159
177
|
/**
|
|
160
178
|
* Baseline security headers applied to all responses.
|
|
161
179
|
* Admin routes get additional headers (strict CSP) from auth middleware.
|
|
162
180
|
*/
|
|
163
|
-
function
|
|
181
|
+
function finalizeResponse(
|
|
182
|
+
response: Response,
|
|
183
|
+
serverTimings?: Array<{ name: string; dur: number; desc?: string }>,
|
|
184
|
+
): Response {
|
|
164
185
|
const res = new Response(response.body, response);
|
|
186
|
+
const astroCookies = Reflect.get(response, ASTRO_COOKIES_SYMBOL);
|
|
187
|
+
if (astroCookies !== undefined) {
|
|
188
|
+
Reflect.set(res, ASTRO_COOKIES_SYMBOL, astroCookies);
|
|
189
|
+
}
|
|
165
190
|
res.headers.set("X-Content-Type-Options", "nosniff");
|
|
166
191
|
res.headers.set("Referrer-Policy", "strict-origin-when-cross-origin");
|
|
167
192
|
res.headers.set("Permissions-Policy", "camera=(), microphone=(), geolocation=(), payment=()");
|
|
168
193
|
if (!res.headers.has("Content-Security-Policy")) {
|
|
169
194
|
res.headers.set("X-Frame-Options", "SAMEORIGIN");
|
|
170
195
|
}
|
|
196
|
+
if (serverTimings && serverTimings.length > 0) {
|
|
197
|
+
res.headers.set(
|
|
198
|
+
"Server-Timing",
|
|
199
|
+
serverTimings
|
|
200
|
+
.map((t) => {
|
|
201
|
+
const dur = Math.round(t.dur);
|
|
202
|
+
return t.desc ? `${t.name};dur=${dur};desc="${t.desc}"` : `${t.name};dur=${dur}`;
|
|
203
|
+
})
|
|
204
|
+
.join(", "),
|
|
205
|
+
);
|
|
206
|
+
}
|
|
171
207
|
return res;
|
|
172
208
|
}
|
|
173
209
|
|
|
@@ -175,279 +211,320 @@ function setBaselineSecurityHeaders(response: Response): Response {
|
|
|
175
211
|
const PUBLIC_RUNTIME_ROUTES = new Set(["/sitemap.xml", "/robots.txt"]);
|
|
176
212
|
const SITEMAP_COLLECTION_RE = /^\/sitemap-[a-z][a-z0-9_]*\.xml$/;
|
|
177
213
|
|
|
214
|
+
/**
|
|
215
|
+
* Ask the configured database adapter for a per-request scoped Kysely. The
|
|
216
|
+
* adapter encapsulates any per-request semantics (D1 sessions, read-replica
|
|
217
|
+
* routing, bookmark cookies, etc.); core just forwards the cookie jar and
|
|
218
|
+
* request flags and wraps next() in ALS if a scope was returned.
|
|
219
|
+
*/
|
|
220
|
+
function createRequestScopedDb(
|
|
221
|
+
opts: RequestScopedDbOpts,
|
|
222
|
+
): { db: Kysely<Database>; commit: () => void } | null {
|
|
223
|
+
if (typeof virtualCreateRequestScopedDb !== "function") return null;
|
|
224
|
+
// eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- adapter returns Kysely<unknown>; cast to Database since core owns that type
|
|
225
|
+
const fn = virtualCreateRequestScopedDb as (
|
|
226
|
+
o: RequestScopedDbOpts,
|
|
227
|
+
) => { db: Kysely<Database>; commit: () => void } | null;
|
|
228
|
+
return fn(opts);
|
|
229
|
+
}
|
|
230
|
+
|
|
178
231
|
export const onRequest = defineMiddleware(async (context, next) => {
|
|
179
232
|
const { request, locals, cookies } = context;
|
|
180
233
|
const url = context.url;
|
|
181
234
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
235
|
+
const queryRecorder = isInstrumentationEnabled()
|
|
236
|
+
? createRecorder(url.pathname, request.method, request.headers.get("x-perf-phase") ?? "default")
|
|
237
|
+
: undefined;
|
|
238
|
+
|
|
239
|
+
const run = async (): Promise<Response> => {
|
|
240
|
+
// Process /_emdash routes and public routes with an active session
|
|
241
|
+
// (logged-in editors need the runtime for toolbar/visual editing on public pages)
|
|
242
|
+
const isEmDashRoute = url.pathname.startsWith("/_emdash");
|
|
243
|
+
const isPublicRuntimeRoute =
|
|
244
|
+
PUBLIC_RUNTIME_ROUTES.has(url.pathname) || SITEMAP_COLLECTION_RE.test(url.pathname);
|
|
245
|
+
|
|
246
|
+
// Check for edit mode cookie - editors viewing public pages need the runtime
|
|
247
|
+
// so auth middleware can verify their session for visual editing
|
|
248
|
+
const hasEditCookie = cookies.get("emdash-edit-mode")?.value === "true";
|
|
249
|
+
const hasPreviewToken = url.searchParams.has("_preview");
|
|
250
|
+
|
|
251
|
+
// Playground mode: the playground middleware stashes the per-session DO database
|
|
252
|
+
// on locals.__playgroundDb. When present, use runWithContext() to make it
|
|
253
|
+
// available to getDb() and the runtime's db getter via the correct ALS instance.
|
|
254
|
+
const playgroundDb = locals.__playgroundDb;
|
|
255
|
+
|
|
256
|
+
// Read the Astro session user once up-front. Both the anonymous fast path
|
|
257
|
+
// and the full doInit path need this, and the session store is network-backed
|
|
258
|
+
// (KV / Durable Object) so we want to avoid re-fetching on the hot path.
|
|
259
|
+
// Skipped entirely for prerendered requests — they have no session.
|
|
260
|
+
const sessionUser = context.isPrerendered ? null : await context.session?.get("user");
|
|
192
261
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
262
|
+
if (!isEmDashRoute && !isPublicRuntimeRoute && !hasEditCookie && !hasPreviewToken) {
|
|
263
|
+
if (!sessionUser && !playgroundDb) {
|
|
264
|
+
const timings: Array<{ name: string; dur: number; desc?: string }> = [];
|
|
265
|
+
const mwStart = performance.now();
|
|
266
|
+
|
|
267
|
+
// On a fresh deployment the database may be completely empty.
|
|
268
|
+
// Public pages call getSiteSettings() / getMenu() via getDb(), which
|
|
269
|
+
// bypasses runtime init and would crash with "no such table: options".
|
|
270
|
+
// Do a one-time lightweight probe using the same getDb() instance the
|
|
271
|
+
// page will use: if the migrations table doesn't exist, no migrations
|
|
272
|
+
// have ever run -- redirect to the setup wizard.
|
|
273
|
+
if (!setupVerified) {
|
|
274
|
+
const t0 = performance.now();
|
|
275
|
+
try {
|
|
276
|
+
const { getDb } = await import("../loader.js");
|
|
277
|
+
const db = await getDb();
|
|
278
|
+
await db
|
|
279
|
+
.selectFrom("_emdash_migrations" as keyof Database)
|
|
280
|
+
.selectAll()
|
|
281
|
+
.limit(1)
|
|
282
|
+
.execute();
|
|
283
|
+
setupVerified = true;
|
|
284
|
+
} catch {
|
|
285
|
+
// Table doesn't exist -> fresh database, redirect to setup
|
|
286
|
+
return context.redirect("/_emdash/admin/setup");
|
|
287
|
+
}
|
|
288
|
+
timings.push({ name: "setup", dur: performance.now() - t0, desc: "Setup probe" });
|
|
289
|
+
}
|
|
197
290
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
.
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
291
|
+
// Initialize the runtime for page:metadata and page:fragments hooks.
|
|
292
|
+
// The runtime is a cached singleton — after the first request,
|
|
293
|
+
// getRuntime() is just a null-check. This enables SEO plugins to
|
|
294
|
+
// contribute meta tags for all visitors, not just logged-in editors.
|
|
295
|
+
const config = getConfig();
|
|
296
|
+
if (config) {
|
|
297
|
+
// Sub-phase timings are populated only on the cold init. Warm
|
|
298
|
+
// requests hit the cached runtime and leave this empty.
|
|
299
|
+
const initSubTimings: Array<{ name: string; dur: number; desc?: string }> = [];
|
|
300
|
+
const t0 = performance.now();
|
|
301
|
+
try {
|
|
302
|
+
const runtime = await getRuntime(config, initSubTimings);
|
|
303
|
+
setupVerified = true;
|
|
304
|
+
// eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- partial object; getPageRuntime() only checks for these two methods
|
|
305
|
+
locals.emdash = {
|
|
306
|
+
collectPageMetadata: runtime.collectPageMetadata.bind(runtime),
|
|
307
|
+
collectPageFragments: runtime.collectPageFragments.bind(runtime),
|
|
308
|
+
} as EmDashHandlers;
|
|
309
|
+
} catch {
|
|
310
|
+
// Non-fatal — EmDashHead will fall back to base SEO contributions
|
|
311
|
+
}
|
|
312
|
+
timings.push({ name: "rt", dur: performance.now() - t0, desc: "Runtime init" });
|
|
313
|
+
// Append cold-only sub-phase timings so the breakdown is visible
|
|
314
|
+
// in Server-Timing (rt.db, rt.fts, rt.plugins, rt.site,
|
|
315
|
+
// rt.sandbox, rt.market, rt.hooks, rt.cron).
|
|
316
|
+
for (const sub of initSubTimings) timings.push(sub);
|
|
220
317
|
}
|
|
221
|
-
}
|
|
222
318
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
319
|
+
// Even on the anonymous fast path we ask the adapter for a per-request
|
|
320
|
+
// scoped db. For D1 with read replication this routes anonymous reads
|
|
321
|
+
// to the nearest replica; for other adapters it's a no-op.
|
|
322
|
+
const anonScoped = createRequestScopedDb({
|
|
323
|
+
config: config?.database?.config,
|
|
324
|
+
isAuthenticated: false,
|
|
325
|
+
isWrite: request.method !== "GET" && request.method !== "HEAD",
|
|
326
|
+
cookies,
|
|
327
|
+
url,
|
|
328
|
+
});
|
|
329
|
+
const runAnon = async () => {
|
|
330
|
+
const t0 = performance.now();
|
|
331
|
+
const response = await next();
|
|
332
|
+
timings.push({ name: "render", dur: performance.now() - t0, desc: "Page render" });
|
|
333
|
+
timings.push({ name: "mw", dur: performance.now() - mwStart, desc: "Total middleware" });
|
|
334
|
+
return finalizeResponse(response, timings);
|
|
335
|
+
};
|
|
336
|
+
if (anonScoped) {
|
|
337
|
+
const parent = getRequestContext();
|
|
338
|
+
const ctx = parent
|
|
339
|
+
? { ...parent, db: anonScoped.db }
|
|
340
|
+
: { editMode: false, db: anonScoped.db };
|
|
341
|
+
return runWithContext(ctx, async () => {
|
|
342
|
+
const response = await runAnon();
|
|
343
|
+
anonScoped.commit();
|
|
344
|
+
return response;
|
|
345
|
+
});
|
|
239
346
|
}
|
|
347
|
+
return runAnon();
|
|
240
348
|
}
|
|
241
|
-
|
|
242
|
-
const response = await next();
|
|
243
|
-
return setBaselineSecurityHeaders(response);
|
|
244
349
|
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
const config = getConfig();
|
|
248
|
-
if (!config) {
|
|
249
|
-
console.error("EmDash: No configuration found");
|
|
250
|
-
return next();
|
|
251
|
-
}
|
|
252
350
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
try {
|
|
258
|
-
// Get or create runtime
|
|
259
|
-
const runtime = await getRuntime(config);
|
|
260
|
-
|
|
261
|
-
// Runtime init runs migrations, so the DB is guaranteed set up
|
|
262
|
-
setupVerified = true;
|
|
263
|
-
|
|
264
|
-
// Get manifest (cached after first call)
|
|
265
|
-
const manifest = await runtime.getManifest();
|
|
266
|
-
|
|
267
|
-
// Attach to locals for route handlers
|
|
268
|
-
locals.emdashManifest = manifest;
|
|
269
|
-
locals.emdash = {
|
|
270
|
-
// Content handlers
|
|
271
|
-
handleContentList: runtime.handleContentList.bind(runtime),
|
|
272
|
-
handleContentGet: runtime.handleContentGet.bind(runtime),
|
|
273
|
-
handleContentCreate: runtime.handleContentCreate.bind(runtime),
|
|
274
|
-
handleContentUpdate: runtime.handleContentUpdate.bind(runtime),
|
|
275
|
-
handleContentDelete: runtime.handleContentDelete.bind(runtime),
|
|
276
|
-
|
|
277
|
-
// Trash handlers
|
|
278
|
-
handleContentListTrashed: runtime.handleContentListTrashed.bind(runtime),
|
|
279
|
-
handleContentRestore: runtime.handleContentRestore.bind(runtime),
|
|
280
|
-
handleContentPermanentDelete: runtime.handleContentPermanentDelete.bind(runtime),
|
|
281
|
-
handleContentCountTrashed: runtime.handleContentCountTrashed.bind(runtime),
|
|
282
|
-
handleContentGetIncludingTrashed: runtime.handleContentGetIncludingTrashed.bind(runtime),
|
|
283
|
-
|
|
284
|
-
// Duplicate handler
|
|
285
|
-
handleContentDuplicate: runtime.handleContentDuplicate.bind(runtime),
|
|
286
|
-
|
|
287
|
-
// Publishing & Scheduling handlers
|
|
288
|
-
handleContentPublish: runtime.handleContentPublish.bind(runtime),
|
|
289
|
-
handleContentUnpublish: runtime.handleContentUnpublish.bind(runtime),
|
|
290
|
-
handleContentSchedule: runtime.handleContentSchedule.bind(runtime),
|
|
291
|
-
handleContentUnschedule: runtime.handleContentUnschedule.bind(runtime),
|
|
292
|
-
handleContentCountScheduled: runtime.handleContentCountScheduled.bind(runtime),
|
|
293
|
-
handleContentDiscardDraft: runtime.handleContentDiscardDraft.bind(runtime),
|
|
294
|
-
handleContentCompare: runtime.handleContentCompare.bind(runtime),
|
|
295
|
-
handleContentTranslations: runtime.handleContentTranslations.bind(runtime),
|
|
296
|
-
|
|
297
|
-
// Media handlers
|
|
298
|
-
handleMediaList: runtime.handleMediaList.bind(runtime),
|
|
299
|
-
handleMediaGet: runtime.handleMediaGet.bind(runtime),
|
|
300
|
-
handleMediaCreate: runtime.handleMediaCreate.bind(runtime),
|
|
301
|
-
handleMediaUpdate: runtime.handleMediaUpdate.bind(runtime),
|
|
302
|
-
handleMediaDelete: runtime.handleMediaDelete.bind(runtime),
|
|
303
|
-
|
|
304
|
-
// Revision handlers
|
|
305
|
-
handleRevisionList: runtime.handleRevisionList.bind(runtime),
|
|
306
|
-
handleRevisionGet: runtime.handleRevisionGet.bind(runtime),
|
|
307
|
-
handleRevisionRestore: runtime.handleRevisionRestore.bind(runtime),
|
|
308
|
-
|
|
309
|
-
// Plugin routes
|
|
310
|
-
handlePluginApiRoute: runtime.handlePluginApiRoute.bind(runtime),
|
|
311
|
-
getPluginRouteMeta: runtime.getPluginRouteMeta.bind(runtime),
|
|
312
|
-
|
|
313
|
-
// Media provider methods
|
|
314
|
-
getMediaProvider: runtime.getMediaProvider.bind(runtime),
|
|
315
|
-
getMediaProviderList: runtime.getMediaProviderList.bind(runtime),
|
|
316
|
-
|
|
317
|
-
// Page contribution methods (for EmDashHead/EmDashBodyStart/EmDashBodyEnd)
|
|
318
|
-
collectPageMetadata: runtime.collectPageMetadata.bind(runtime),
|
|
319
|
-
collectPageFragments: runtime.collectPageFragments.bind(runtime),
|
|
320
|
-
|
|
321
|
-
// Direct access (for advanced use cases)
|
|
322
|
-
storage: runtime.storage,
|
|
323
|
-
db: runtime.db,
|
|
324
|
-
hooks: runtime.hooks,
|
|
325
|
-
email: runtime.email,
|
|
326
|
-
configuredPlugins: runtime.configuredPlugins,
|
|
327
|
-
|
|
328
|
-
// Configuration (for checking database type, auth mode, etc.)
|
|
329
|
-
config,
|
|
330
|
-
|
|
331
|
-
// Manifest invalidation (call after schema changes)
|
|
332
|
-
invalidateManifest: runtime.invalidateManifest.bind(runtime),
|
|
333
|
-
|
|
334
|
-
// Sandbox runner (for marketplace plugin install/update)
|
|
335
|
-
getSandboxRunner: runtime.getSandboxRunner.bind(runtime),
|
|
336
|
-
|
|
337
|
-
// Sync marketplace plugin states (after install/update/uninstall)
|
|
338
|
-
syncMarketplacePlugins: runtime.syncMarketplacePlugins.bind(runtime),
|
|
339
|
-
|
|
340
|
-
// Update plugin enabled/disabled status and rebuild hook pipeline
|
|
341
|
-
setPluginStatus: runtime.setPluginStatus.bind(runtime),
|
|
342
|
-
};
|
|
343
|
-
} catch (error) {
|
|
344
|
-
console.error("EmDash middleware error:", error);
|
|
351
|
+
const config = getConfig();
|
|
352
|
+
if (!config) {
|
|
353
|
+
console.error("EmDash: No configuration found");
|
|
354
|
+
return finalizeResponse(await next());
|
|
345
355
|
}
|
|
346
356
|
|
|
347
|
-
//
|
|
348
|
-
//
|
|
349
|
-
//
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
const
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
357
|
+
// In playground mode, wrap the entire runtime init + request handling in
|
|
358
|
+
// runWithContext so that getDatabase() and all init queries use the real
|
|
359
|
+
// DO database via the same AsyncLocalStorage instance as the loader.
|
|
360
|
+
const doInit = async () => {
|
|
361
|
+
const timings: Array<{ name: string; dur: number; desc?: string }> = [];
|
|
362
|
+
const mwStart = performance.now();
|
|
363
|
+
|
|
364
|
+
try {
|
|
365
|
+
// Get or create runtime. Sub-phase timings (rt.db, rt.fts, rt.plugins,
|
|
366
|
+
// rt.site, rt.sandbox, rt.market, rt.hooks, rt.cron) are populated
|
|
367
|
+
// only on the cold init — subsequent warm calls find the cached
|
|
368
|
+
// instance and `initSubTimings` stays empty.
|
|
369
|
+
const initSubTimings: Array<{ name: string; dur: number; desc?: string }> = [];
|
|
370
|
+
let t0 = performance.now();
|
|
371
|
+
const runtime = await getRuntime(config, initSubTimings);
|
|
372
|
+
timings.push({ name: "rt", dur: performance.now() - t0, desc: "Runtime init" });
|
|
373
|
+
// Forward any sub-phase samples so cold-start breakdown is visible
|
|
374
|
+
// in Server-Timing. Each phase appears prefixed "rt." to distinguish
|
|
375
|
+
// from the aggregate "rt" timing above.
|
|
376
|
+
for (const sub of initSubTimings) timings.push(sub);
|
|
377
|
+
|
|
378
|
+
// Runtime init runs migrations, so the DB is guaranteed set up
|
|
379
|
+
setupVerified = true;
|
|
380
|
+
|
|
381
|
+
// Get manifest (cached after first call)
|
|
382
|
+
t0 = performance.now();
|
|
383
|
+
const manifest = await runtime.getManifest();
|
|
384
|
+
timings.push({ name: "manifest", dur: performance.now() - t0, desc: "Manifest" });
|
|
385
|
+
|
|
386
|
+
// Attach to locals for route handlers
|
|
387
|
+
locals.emdashManifest = manifest;
|
|
388
|
+
locals.emdash = {
|
|
389
|
+
// Content handlers
|
|
390
|
+
handleContentList: runtime.handleContentList.bind(runtime),
|
|
391
|
+
handleContentGet: runtime.handleContentGet.bind(runtime),
|
|
392
|
+
handleContentCreate: runtime.handleContentCreate.bind(runtime),
|
|
393
|
+
handleContentUpdate: runtime.handleContentUpdate.bind(runtime),
|
|
394
|
+
handleContentDelete: runtime.handleContentDelete.bind(runtime),
|
|
395
|
+
|
|
396
|
+
// Trash handlers
|
|
397
|
+
handleContentListTrashed: runtime.handleContentListTrashed.bind(runtime),
|
|
398
|
+
handleContentRestore: runtime.handleContentRestore.bind(runtime),
|
|
399
|
+
handleContentPermanentDelete: runtime.handleContentPermanentDelete.bind(runtime),
|
|
400
|
+
handleContentCountTrashed: runtime.handleContentCountTrashed.bind(runtime),
|
|
401
|
+
handleContentGetIncludingTrashed: runtime.handleContentGetIncludingTrashed.bind(runtime),
|
|
402
|
+
|
|
403
|
+
// Duplicate handler
|
|
404
|
+
handleContentDuplicate: runtime.handleContentDuplicate.bind(runtime),
|
|
405
|
+
|
|
406
|
+
// Publishing & Scheduling handlers
|
|
407
|
+
handleContentPublish: runtime.handleContentPublish.bind(runtime),
|
|
408
|
+
handleContentUnpublish: runtime.handleContentUnpublish.bind(runtime),
|
|
409
|
+
handleContentSchedule: runtime.handleContentSchedule.bind(runtime),
|
|
410
|
+
handleContentUnschedule: runtime.handleContentUnschedule.bind(runtime),
|
|
411
|
+
handleContentCountScheduled: runtime.handleContentCountScheduled.bind(runtime),
|
|
412
|
+
handleContentDiscardDraft: runtime.handleContentDiscardDraft.bind(runtime),
|
|
413
|
+
handleContentCompare: runtime.handleContentCompare.bind(runtime),
|
|
414
|
+
handleContentTranslations: runtime.handleContentTranslations.bind(runtime),
|
|
415
|
+
|
|
416
|
+
// Media handlers
|
|
417
|
+
handleMediaList: runtime.handleMediaList.bind(runtime),
|
|
418
|
+
handleMediaGet: runtime.handleMediaGet.bind(runtime),
|
|
419
|
+
handleMediaCreate: runtime.handleMediaCreate.bind(runtime),
|
|
420
|
+
handleMediaUpdate: runtime.handleMediaUpdate.bind(runtime),
|
|
421
|
+
handleMediaDelete: runtime.handleMediaDelete.bind(runtime),
|
|
422
|
+
|
|
423
|
+
// Revision handlers
|
|
424
|
+
handleRevisionList: runtime.handleRevisionList.bind(runtime),
|
|
425
|
+
handleRevisionGet: runtime.handleRevisionGet.bind(runtime),
|
|
426
|
+
handleRevisionRestore: runtime.handleRevisionRestore.bind(runtime),
|
|
427
|
+
|
|
428
|
+
// Plugin routes
|
|
429
|
+
handlePluginApiRoute: runtime.handlePluginApiRoute.bind(runtime),
|
|
430
|
+
getPluginRouteMeta: runtime.getPluginRouteMeta.bind(runtime),
|
|
431
|
+
|
|
432
|
+
// Media provider methods
|
|
433
|
+
getMediaProvider: runtime.getMediaProvider.bind(runtime),
|
|
434
|
+
getMediaProviderList: runtime.getMediaProviderList.bind(runtime),
|
|
435
|
+
|
|
436
|
+
// Page contribution methods (for EmDashHead/EmDashBodyStart/EmDashBodyEnd)
|
|
437
|
+
collectPageMetadata: runtime.collectPageMetadata.bind(runtime),
|
|
438
|
+
collectPageFragments: runtime.collectPageFragments.bind(runtime),
|
|
439
|
+
|
|
440
|
+
// Lazy search index health check — search endpoints call this
|
|
441
|
+
// before querying so a crash-corrupted index gets repaired on
|
|
442
|
+
// first use rather than stalling every cold start.
|
|
443
|
+
ensureSearchHealthy: runtime.ensureSearchHealthy.bind(runtime),
|
|
444
|
+
|
|
445
|
+
// Direct access (for advanced use cases)
|
|
446
|
+
storage: runtime.storage,
|
|
447
|
+
db: runtime.db,
|
|
448
|
+
hooks: runtime.hooks,
|
|
449
|
+
email: runtime.email,
|
|
450
|
+
configuredPlugins: runtime.configuredPlugins,
|
|
451
|
+
|
|
452
|
+
// Configuration (for checking database type, auth mode, etc.)
|
|
453
|
+
config,
|
|
454
|
+
|
|
455
|
+
// Manifest invalidation (call after schema changes)
|
|
456
|
+
invalidateManifest: runtime.invalidateManifest.bind(runtime),
|
|
457
|
+
|
|
458
|
+
// Sandbox runner (for marketplace plugin install/update)
|
|
459
|
+
getSandboxRunner: runtime.getSandboxRunner.bind(runtime),
|
|
460
|
+
|
|
461
|
+
// Sync marketplace plugin states (after install/update/uninstall)
|
|
462
|
+
syncMarketplacePlugins: runtime.syncMarketplacePlugins.bind(runtime),
|
|
463
|
+
|
|
464
|
+
// Update plugin enabled/disabled status and rebuild hook pipeline
|
|
465
|
+
setPluginStatus: runtime.setPluginStatus.bind(runtime),
|
|
466
|
+
};
|
|
467
|
+
} catch (error) {
|
|
468
|
+
console.error("EmDash middleware error:", error);
|
|
469
|
+
}
|
|
399
470
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
isAuthenticated &&
|
|
420
|
-
session &&
|
|
421
|
-
typeof session === "object" &&
|
|
422
|
-
"getBookmark" in session
|
|
423
|
-
) {
|
|
424
|
-
// eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- D1DatabaseSession with getBookmark()
|
|
425
|
-
const getBookmark = (session as { getBookmark: () => string | null }).getBookmark;
|
|
426
|
-
const newBookmark = getBookmark.call(session);
|
|
427
|
-
if (newBookmark) {
|
|
428
|
-
response.headers.append(
|
|
429
|
-
"Set-Cookie",
|
|
430
|
-
`${cookieName}=${newBookmark}; Path=/; HttpOnly; SameSite=Lax; Secure`,
|
|
431
|
-
);
|
|
432
|
-
}
|
|
433
|
-
}
|
|
471
|
+
// Ask the adapter for a request-scoped db. When it returns one, we stash
|
|
472
|
+
// it in ALS so the runtime's db getter and loader's getDb() pick it up,
|
|
473
|
+
// then call commit() after next() so the adapter can persist any
|
|
474
|
+
// per-request state (e.g. a D1 bookmark cookie for read-your-writes).
|
|
475
|
+
const scoped = createRequestScopedDb({
|
|
476
|
+
config: config?.database?.config,
|
|
477
|
+
isAuthenticated: !!sessionUser,
|
|
478
|
+
isWrite: request.method !== "GET" && request.method !== "HEAD",
|
|
479
|
+
cookies: context.cookies,
|
|
480
|
+
url,
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
const renderAndFinalize = async () => {
|
|
484
|
+
const t0 = performance.now();
|
|
485
|
+
const response = await next();
|
|
486
|
+
timings.push({ name: "render", dur: performance.now() - t0, desc: "Page render" });
|
|
487
|
+
timings.push({ name: "mw", dur: performance.now() - mwStart, desc: "Total middleware" });
|
|
488
|
+
return finalizeResponse(response, timings);
|
|
489
|
+
};
|
|
434
490
|
|
|
491
|
+
if (scoped) {
|
|
492
|
+
const parent = getRequestContext();
|
|
493
|
+
const ctx = parent ? { ...parent, db: scoped.db } : { editMode: false, db: scoped.db };
|
|
494
|
+
return runWithContext(ctx, async () => {
|
|
495
|
+
const response = await renderAndFinalize();
|
|
496
|
+
scoped.commit();
|
|
435
497
|
return response;
|
|
436
498
|
});
|
|
437
499
|
}
|
|
438
|
-
}
|
|
439
500
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
501
|
+
return renderAndFinalize();
|
|
502
|
+
}; // end doInit
|
|
503
|
+
|
|
504
|
+
if (playgroundDb) {
|
|
505
|
+
// Read the edit-mode cookie to determine if visual editing is active.
|
|
506
|
+
// Default to false -- editing is opt-in via the playground toolbar toggle.
|
|
507
|
+
const editMode = context.cookies.get("emdash-edit-mode")?.value === "true";
|
|
508
|
+
// Playground DBs are per-session isolated instances whose schema is
|
|
509
|
+
// independent of the configured one — flag as isolated so schema-
|
|
510
|
+
// derived caches (manifest, taxonomy defs) rebuild against it.
|
|
511
|
+
const parent = getRequestContext();
|
|
512
|
+
const ctx = parent
|
|
513
|
+
? { ...parent, editMode, db: playgroundDb, dbIsIsolated: true }
|
|
514
|
+
: { editMode, db: playgroundDb, dbIsIsolated: true };
|
|
515
|
+
return runWithContext(ctx, doInit);
|
|
516
|
+
}
|
|
517
|
+
return doInit();
|
|
518
|
+
};
|
|
443
519
|
|
|
444
|
-
if (
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
520
|
+
if (queryRecorder) {
|
|
521
|
+
try {
|
|
522
|
+
return await runWithContext({ editMode: false, queryRecorder }, run);
|
|
523
|
+
} finally {
|
|
524
|
+
flushRecorder(queryRecorder);
|
|
525
|
+
}
|
|
449
526
|
}
|
|
450
|
-
return
|
|
527
|
+
return run();
|
|
451
528
|
});
|
|
452
529
|
|
|
453
530
|
export default onRequest;
|