@astroscope/node 2.1.0 → 3.0.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/README.md +8 -5
- package/dist/boot.d.ts +2 -2
- package/dist/boot.d.ts.map +1 -1
- package/dist/csrf-middleware-entrypoint.d.ts +1 -2
- package/dist/csrf-middleware-entrypoint.d.ts.map +1 -1
- package/dist/csrf-middleware-entrypoint.js +1 -1
- package/dist/dev-middleware-entrypoint.d.ts +1 -2
- package/dist/dev-middleware-entrypoint.d.ts.map +1 -1
- package/dist/{prepare-CXZsyAVk.js → duplicate-slashes-C6hmOLl9.js} +94 -38
- package/dist/duplicate-slashes-C6hmOLl9.js.map +1 -0
- package/dist/{excludes-pE23EbmQ.js → excludes-10eDopVB.js} +18 -4
- package/dist/excludes-10eDopVB.js.map +1 -0
- package/dist/{excludes-BDiE3eyp.d.ts → excludes-rvkVGg6B.d.ts} +11 -3
- package/dist/excludes-rvkVGg6B.d.ts.map +1 -0
- package/dist/excludes.d.ts +2 -2
- package/dist/excludes.js +2 -2
- package/dist/graph-DdAxiaxa.js +35 -0
- package/dist/graph-DdAxiaxa.js.map +1 -0
- package/dist/health.d.ts +3 -4
- package/dist/health.d.ts.map +1 -1
- package/dist/image-endpoint.d.ts +1 -2
- package/dist/image-endpoint.d.ts.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +38 -122
- package/dist/index.js.map +1 -1
- package/dist/islands-middleware-entrypoint.d.ts +1 -2
- package/dist/islands-middleware-entrypoint.d.ts.map +1 -1
- package/dist/islands-middleware-entrypoint.js +3 -1
- package/dist/islands-middleware-entrypoint.js.map +1 -1
- package/dist/islands-runtime.iife.js +1 -1
- package/dist/islands.d.ts +57 -2
- package/dist/islands.d.ts.map +1 -0
- package/dist/islands.js +3 -1
- package/dist/log/index.d.ts +5 -5
- package/dist/log/index.d.ts.map +1 -1
- package/dist/native.d.ts +5 -6
- package/dist/native.d.ts.map +1 -1
- package/dist/{prerendered-DVjzWNbW.js → prerendered-Z-Qi-FFK.js} +4 -4
- package/dist/prerendered-Z-Qi-FFK.js.map +1 -0
- package/dist/request-route-DcnZOOM4.js.map +1 -1
- package/dist/route-islands-B53rE5MH.js +73 -0
- package/dist/route-islands-B53rE5MH.js.map +1 -0
- package/dist/route-middleware-entrypoint.d.ts +1 -2
- package/dist/route-middleware-entrypoint.d.ts.map +1 -1
- package/dist/{route-store-CSYR4G5e.d.ts → route-store-neUA2nBb.d.ts} +10 -14
- package/dist/route-store-neUA2nBb.d.ts.map +1 -0
- package/dist/routes-D1o_WrJ5.js +39 -0
- package/dist/routes-D1o_WrJ5.js.map +1 -0
- package/dist/server.d.ts +2 -3
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +68 -45
- package/dist/server.js.map +1 -1
- package/dist/telemetry-B9aziFyQ.js +202 -0
- package/dist/telemetry-B9aziFyQ.js.map +1 -0
- package/dist/telemetry.d.ts +68 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +2 -0
- package/dist/transform-Bz5z5w2Y.js +594 -0
- package/dist/transform-Bz5z5w2Y.js.map +1 -0
- package/package.json +13 -11
- package/dist/excludes-BDiE3eyp.d.ts.map +0 -1
- package/dist/excludes-pE23EbmQ.js.map +0 -1
- package/dist/prepare-CXZsyAVk.js.map +0 -1
- package/dist/prerendered-DVjzWNbW.js.map +0 -1
- package/dist/route-store-CSYR4G5e.d.ts.map +0 -1
- package/dist/strip-build-paths-C6eb1QdG.js +0 -63
- package/dist/strip-build-paths-C6eb1QdG.js.map +0 -1
- package/dist/transform-BqV8SOEm.js +0 -195
- package/dist/transform-BqV8SOEm.js.map +0 -1
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ Opinionated, cloud-friendly Node adapter for Astro: boot lifecycle, health probe
|
|
|
14
14
|
- **Island preloading** — each island's JS is preloaded in parallel instead of being discovered module by module, removing the hydration request waterfall ([Island preloading](#island-preloading))
|
|
15
15
|
- **Pre-compressed static serving** — build-time brotli/gzip variants, negotiated per `Accept-Encoding`
|
|
16
16
|
- **Native mounts** — http-native handlers (`oidc-provider`, ACME) mounted on the adapter's server
|
|
17
|
-
- **Build tweaks** — SSR sourcemaps
|
|
17
|
+
- **Build tweaks** — SSR sourcemaps
|
|
18
18
|
- **Image processing off unless configured** — without an explicit `image.service`, any `astro:assets` use fails loudly instead of opening astro's on-demand sharp endpoint ([Image processing](#image-processing))
|
|
19
19
|
- **Dev restart machinery** — changes to the boot file or entry seams restart the dev server behind a holding page
|
|
20
20
|
- **Dev island warmup** — `.astro` sources are scanned for `client:*` components; their deps are pre-optimized and their module graphs warmed at server start, preventing "504 Outdated Optimize Dep" hydration failures from vite's lazy dep discovery
|
|
@@ -174,12 +174,14 @@ The adapter itself emits exactly one info line on startup — `server ready { ho
|
|
|
174
174
|
`node({ telemetry })` ships the standard bundle: NodeSDK, fetch instrumentation via undici (diagnostics-channel based — no `--import` needed), Node runtime + host metrics, and a Prometheus reader on `0.0.0.0:9464`. **Prod: on by default. Dev: off by default** (opt in with `telemetry: { dev: true }`).
|
|
175
175
|
|
|
176
176
|
- server spans start at the native handler with W3C context extraction; the route pattern and span name are enriched by the internal middleware
|
|
177
|
-
- request metrics: `http.server.request.duration`, `http.server.active_requests`, `astro.action.duration
|
|
177
|
+
- request metrics: `http.server.request.duration`, `http.server.active_requests`, `astro.action.duration` (semconv bucket boundaries, 5 ms – 10 s); fetch metrics come from the undici instrumentation
|
|
178
|
+
- `astro.render.failures` {`http.route`} counts renders that failed after the status went out. Such a response is truncated on the wire (astro's behavior — the status cannot change anymore), so the counter is the alert signal; the request log line reads `request truncated` at error level next to the failure itself, and the server span carries `astro.response.truncated`
|
|
178
179
|
- the boot lifecycle gets `startup` (with `boot`/`warmup`/`listen` children) and `shutdown` (with `drain`/`onShutdown`) spans
|
|
179
180
|
- trace exporters are driven by the standard `OTEL_*` env vars; without a configured OTLP endpoint, trace exporting defaults to `none` (no failing localhost exports)
|
|
180
181
|
- `OTEL_SDK_DISABLED=true` turns the SDK off entirely
|
|
182
|
+
- the other astroscope packages record through the same SDK, no-op without it: `astro.island.render.duration`, `astro.island.check.duration`, `astro.island.render.failures` (`@astroscope/react`); `astro.wormhole.handler.duration`, `astro.wormhole.handler.failures` and a `wormhole <name>` span per handler (`@astroscope/wormhole`); `astro.i18n.missing`, `astro.i18n.translations.age` (`@astroscope/i18n`)
|
|
181
183
|
|
|
182
|
-
There is no helper for tracing server-render sections: measuring a subtree's render would require buffering it, which disables streaming. Wrap frontmatter awaits with `tracer.startActiveSpan()` instead — that's where the time lives, and it nests under the request span without touching the stream.
|
|
184
|
+
There is no helper for tracing server-render sections: measuring a subtree's render would require buffering it, which disables streaming. Wrap frontmatter awaits with `tracer.startActiveSpan()` from `@opentelemetry/api` instead — that's where the time lives, and it nests under the request span without touching the stream.
|
|
183
185
|
|
|
184
186
|
## Health checks
|
|
185
187
|
|
|
@@ -228,7 +230,7 @@ export function onStartup() {
|
|
|
228
230
|
```typescript
|
|
229
231
|
import { RECOMMENDED_EXCLUDES, shouldExclude, withExcluded } from '@astroscope/node/excludes';
|
|
230
232
|
|
|
231
|
-
// pre-defined sets: DEV_EXCLUDES (vite dev paths), ASTRO_STATIC_EXCLUDES (/_astro/, /_image),
|
|
233
|
+
// pre-defined sets: DEV_EXCLUDES (vite dev paths), ASTRO_STATIC_EXCLUDES (/_astro/, /_image, /_i18n/),
|
|
232
234
|
// STATIC_EXCLUDES (favicon, robots.txt, ...), RECOMMENDED_EXCLUDES (dev + astro internals)
|
|
233
235
|
|
|
234
236
|
// in your own middleware
|
|
@@ -255,7 +257,8 @@ At build time, every compressible file in `dist/client` gets max-quality `.br` (
|
|
|
255
257
|
Always on, no configuration:
|
|
256
258
|
|
|
257
259
|
- **SSR sourcemaps** — the server bundle gets sourcemaps for readable stack traces; client bundles stay unmapped so browsers can't fetch source
|
|
258
|
-
|
|
260
|
+
|
|
261
|
+
SSR effect stripping for React islands lives in [`@astroscope/react`](../react).
|
|
259
262
|
|
|
260
263
|
## Image processing
|
|
261
264
|
|
package/dist/boot.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { t as BootContext } from "./types-D0uMBi2M.js";
|
|
|
8
8
|
* libraries can use it to adapt behavior to dev mode without requiring
|
|
9
9
|
* consumers to thread the flag through.
|
|
10
10
|
*/
|
|
11
|
-
declare function getBootContext(): BootContext | undefined;
|
|
11
|
+
export declare function getBootContext(): BootContext | undefined;
|
|
12
12
|
//#endregion
|
|
13
|
-
export {
|
|
13
|
+
export type { BootContext };
|
|
14
14
|
//# sourceMappingURL=boot.d.ts.map
|
package/dist/boot.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"boot.d.ts","names":[],"sources":["../src/lifecycle/context.ts"],"mappings":";;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"boot.d.ts","names":[],"sources":["../src/lifecycle/context.ts"],"mappings":";;;;;;;;;;wBAiBgB,kBAAkB"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
//#region src/csrf/middleware-entrypoint.d.ts
|
|
2
|
-
declare const onRequest: import("astro").MiddlewareHandler;
|
|
2
|
+
export declare const onRequest: import("astro").MiddlewareHandler;
|
|
3
3
|
//#endregion
|
|
4
|
-
export { onRequest };
|
|
5
4
|
//# sourceMappingURL=csrf-middleware-entrypoint.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"csrf-middleware-entrypoint.d.ts","names":[],"sources":["../src/csrf/middleware-entrypoint.ts"],"mappings":";
|
|
1
|
+
{"version":3,"file":"csrf-middleware-entrypoint.d.ts","names":[],"sources":["../src/csrf/middleware-entrypoint.ts"],"mappings":";qBAKa,2BAAS"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as shouldExclude } from "./excludes-
|
|
1
|
+
import { a as shouldExclude } from "./excludes-10eDopVB.js";
|
|
2
2
|
import { excludePatterns } from "virtual:@astroscope/node/csrf";
|
|
3
3
|
//#region src/csrf/middleware.ts
|
|
4
4
|
const FORBIDDEN_METHODS = /* @__PURE__ */ new Set([
|
|
@@ -8,7 +8,6 @@ import { MiddlewareHandler } from "astro";
|
|
|
8
8
|
* match the current generation, the error is recorded for an aggregated log
|
|
9
9
|
* line and a 503 is returned (the client socket is usually gone too).
|
|
10
10
|
*/
|
|
11
|
-
declare const onRequest: MiddlewareHandler;
|
|
11
|
+
export declare const onRequest: MiddlewareHandler;
|
|
12
12
|
//#endregion
|
|
13
|
-
export { onRequest };
|
|
14
13
|
//# sourceMappingURL=dev-middleware-entrypoint.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dev-middleware-entrypoint.d.ts","names":[],"sources":["../src/dev-mode/middleware-entrypoint.ts"],"mappings":";;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"dev-middleware-entrypoint.d.ts","names":[],"sources":["../src/dev-mode/middleware-entrypoint.ts"],"mappings":";;;;;;;;;;qBAWa,WAAW"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { t as emit } from "./events-CgoM3Fvu.js";
|
|
2
|
-
import { r as getLogStore } from "./request-route-DcnZOOM4.js";
|
|
2
|
+
import { r as getLogStore, t as overrideRequestRoute } from "./request-route-DcnZOOM4.js";
|
|
3
3
|
import { n as log, t as generateReqId } from "./log-B69HEBvg.js";
|
|
4
|
+
import { a as createUpDownCounter, l as bindGauges, n as createCounter, r as createHistogram, t as DURATION_BUCKETS } from "./telemetry-B9aziFyQ.js";
|
|
4
5
|
import fs from "node:fs";
|
|
5
6
|
import pino from "pino";
|
|
6
|
-
import { ROOT_CONTEXT, SpanKind, SpanStatusCode,
|
|
7
|
+
import { ROOT_CONTEXT, SpanKind, SpanStatusCode, context, isSpanContextValid, propagation, trace } from "@opentelemetry/api";
|
|
7
8
|
import { createMatcher } from "@entwico/dash/match";
|
|
8
9
|
//#region src/lifecycle/lifecycle.ts
|
|
9
10
|
async function runStartup(boot, context) {
|
|
@@ -21,55 +22,51 @@ async function runShutdown(boot, context) {
|
|
|
21
22
|
}
|
|
22
23
|
//#endregion
|
|
23
24
|
//#region src/observability/telemetry/metrics.ts
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
function getActionDuration() {
|
|
43
|
-
return actionDuration ??= metrics.getMeter(LIB_NAME$1).createHistogram("astro.action.duration", {
|
|
44
|
-
description: "Duration of Astro action executions",
|
|
45
|
-
unit: "s",
|
|
46
|
-
valueType: ValueType.DOUBLE
|
|
47
|
-
});
|
|
48
|
-
}
|
|
25
|
+
const httpRequestDuration = createHistogram("http.server.request.duration", {
|
|
26
|
+
description: "Duration of HTTP server requests",
|
|
27
|
+
unit: "s",
|
|
28
|
+
buckets: DURATION_BUCKETS.http
|
|
29
|
+
});
|
|
30
|
+
const httpActiveRequests = createUpDownCounter("http.server.active_requests", {
|
|
31
|
+
description: "Number of active HTTP server requests",
|
|
32
|
+
unit: "{request}"
|
|
33
|
+
});
|
|
34
|
+
const actionDuration = createHistogram("astro.action.duration", {
|
|
35
|
+
description: "Duration of Astro action executions",
|
|
36
|
+
unit: "s",
|
|
37
|
+
buckets: DURATION_BUCKETS.http
|
|
38
|
+
});
|
|
39
|
+
const renderFailures = createCounter("astro.render.failures", {
|
|
40
|
+
description: "Responses that failed while streaming, after the status was sent",
|
|
41
|
+
unit: "{response}"
|
|
42
|
+
});
|
|
49
43
|
/**
|
|
50
44
|
* Record the start of an HTTP request. Returns a function to call when the
|
|
51
45
|
* request ends. Route is unknown at the native-handler level, so active
|
|
52
46
|
* requests carry only the method.
|
|
53
47
|
*/
|
|
54
48
|
function recordHttpRequestStart(method) {
|
|
55
|
-
|
|
49
|
+
httpActiveRequests.add(1, { "http.request.method": method });
|
|
56
50
|
return () => {
|
|
57
|
-
|
|
51
|
+
httpActiveRequests.add(-1, { "http.request.method": method });
|
|
58
52
|
};
|
|
59
53
|
}
|
|
60
54
|
function recordHttpRequestDuration(attributes, durationMs) {
|
|
61
|
-
|
|
55
|
+
httpRequestDuration.record(durationMs / 1e3, {
|
|
62
56
|
"http.request.method": attributes.method,
|
|
63
57
|
"http.route": attributes.route ?? "",
|
|
64
58
|
"http.response.status_code": attributes.status
|
|
65
59
|
});
|
|
66
60
|
}
|
|
67
61
|
function recordActionDuration(attributes, durationMs) {
|
|
68
|
-
|
|
62
|
+
actionDuration.record(durationMs / 1e3, {
|
|
69
63
|
"astro.action.name": attributes.name,
|
|
70
64
|
"http.response.status_code": attributes.status
|
|
71
65
|
});
|
|
72
66
|
}
|
|
67
|
+
function recordRenderFailure(route) {
|
|
68
|
+
renderFailures.add(1, { "http.route": route ?? "" });
|
|
69
|
+
}
|
|
73
70
|
//#endregion
|
|
74
71
|
//#region src/observability/instrument.ts
|
|
75
72
|
const LIB_NAME = "@astroscope/node";
|
|
@@ -140,7 +137,8 @@ function createRequestInstrumentation(config) {
|
|
|
140
137
|
method,
|
|
141
138
|
route: void 0,
|
|
142
139
|
routeOverride: false,
|
|
143
|
-
actionName: isAction ? pathname.slice(10).replace(/\/$/, "") : void 0
|
|
140
|
+
actionName: isAction ? pathname.slice(10).replace(/\/$/, "") : void 0,
|
|
141
|
+
truncated: false
|
|
144
142
|
};
|
|
145
143
|
let span;
|
|
146
144
|
let firstByteSpan;
|
|
@@ -193,16 +191,18 @@ function createRequestInstrumentation(config) {
|
|
|
193
191
|
if (finalized) return;
|
|
194
192
|
finalized = true;
|
|
195
193
|
const status = res.statusCode;
|
|
194
|
+
const truncated = record.truncated;
|
|
196
195
|
const responseTime = performance.now() - startTime;
|
|
197
196
|
const ttfb = roundTime((firstByteTime ?? performance.now()) - startTime);
|
|
198
|
-
if (requestLogger) requestLogger[status >= 500 ? "error" : status >= 400 ? "warn" : "info"]({
|
|
197
|
+
if (requestLogger) requestLogger[truncated || status >= 500 ? "error" : status >= 400 ? "warn" : "info"]({
|
|
199
198
|
res: { statusCode: status },
|
|
200
199
|
responseTime: roundTime(responseTime),
|
|
201
200
|
ttfb,
|
|
202
201
|
responseSize,
|
|
203
202
|
...record.route && { route: record.route },
|
|
204
|
-
...aborted && { aborted: true }
|
|
205
|
-
|
|
203
|
+
...aborted && { aborted: true },
|
|
204
|
+
...truncated && { truncated: true }
|
|
205
|
+
}, truncated ? "request truncated" : aborted ? "request aborted" : "request completed");
|
|
206
206
|
if (firstByteSpan && firstByteTime === void 0) {
|
|
207
207
|
firstByteSpan.setStatus({
|
|
208
208
|
code: SpanStatusCode.ERROR,
|
|
@@ -214,7 +214,13 @@ function createRequestInstrumentation(config) {
|
|
|
214
214
|
span.setAttribute("http.response.status_code", status);
|
|
215
215
|
span.setAttribute("http.response.body.size", responseSize);
|
|
216
216
|
span.setAttribute("ttfb", ttfb);
|
|
217
|
-
if (
|
|
217
|
+
if (truncated) {
|
|
218
|
+
span.setAttribute("astro.response.truncated", true);
|
|
219
|
+
span.setStatus({
|
|
220
|
+
code: SpanStatusCode.ERROR,
|
|
221
|
+
message: "response truncated"
|
|
222
|
+
});
|
|
223
|
+
} else if (aborted || status >= 400) span.setStatus({
|
|
218
224
|
code: SpanStatusCode.ERROR,
|
|
219
225
|
message: aborted ? "request aborted" : `HTTP ${status}`
|
|
220
226
|
});
|
|
@@ -232,6 +238,7 @@ function createRequestInstrumentation(config) {
|
|
|
232
238
|
name: record.actionName,
|
|
233
239
|
status
|
|
234
240
|
}, responseTime);
|
|
241
|
+
if (truncated) recordRenderFailure(record.route);
|
|
235
242
|
}
|
|
236
243
|
};
|
|
237
244
|
res.once("finish", () => finalize(false));
|
|
@@ -347,6 +354,7 @@ async function startTelemetry(options) {
|
|
|
347
354
|
...prometheus && { metricReaders: [new PrometheusExporter(prometheus)] }
|
|
348
355
|
});
|
|
349
356
|
sdk.start();
|
|
357
|
+
bindGauges();
|
|
350
358
|
new HostMetrics().start();
|
|
351
359
|
g[TELEMETRY_KEY] = { shutdown: () => sdk.shutdown() };
|
|
352
360
|
if (prometheus) log.debug({
|
|
@@ -406,6 +414,54 @@ async function preparePlatform(options) {
|
|
|
406
414
|
await constructRootLogger((await options.seams.log?.())?.default, { dev: options.dev });
|
|
407
415
|
}
|
|
408
416
|
//#endregion
|
|
409
|
-
|
|
417
|
+
//#region src/server/duplicate-slashes.ts
|
|
418
|
+
/**
|
|
419
|
+
* Duplicate slashes in a request path (`//weine`, `/seminare///x`) are not
|
|
420
|
+
* a distinct resource: astro collapses them before routing and renders the
|
|
421
|
+
* page as if the path were clean, so every such spelling is duplicate
|
|
422
|
+
* content. Astro itself only redirects duplicate *trailing* slashes; this
|
|
423
|
+
* handler redirects the rest, before native mounts and static serving, so a
|
|
424
|
+
* page, an endpoint, a public asset and a mounted handler all answer alike.
|
|
425
|
+
*
|
|
426
|
+
* Only literal slashes in the raw request target count — `%2F` stays as
|
|
427
|
+
* sent. Absolute-form targets (`GET http://host/x`) and `*` are not paths
|
|
428
|
+
* and pass through untouched.
|
|
429
|
+
*/
|
|
430
|
+
const DUPLICATE_SLASHES = /\/{2,}/g;
|
|
431
|
+
/**
|
|
432
|
+
* Decide whether a raw request target with duplicate slashes in its path
|
|
433
|
+
* should redirect to the collapsed path. Returns `undefined` when the path
|
|
434
|
+
* is already clean — and when the collapsed path would start with `/\`,
|
|
435
|
+
* which browsers resolve as a scheme-relative url: that request is left to
|
|
436
|
+
* astro, which treats such paths as internal instead of echoing them into
|
|
437
|
+
* a `Location` header.
|
|
438
|
+
*/
|
|
439
|
+
function evaluateDuplicateSlashes(url, method) {
|
|
440
|
+
if (!url.startsWith("/")) return void 0;
|
|
441
|
+
const queryIndex = url.indexOf("?");
|
|
442
|
+
const pathname = queryIndex === -1 ? url : url.slice(0, queryIndex);
|
|
443
|
+
if (!pathname.includes("//")) return void 0;
|
|
444
|
+
const collapsed = pathname.replace(DUPLICATE_SLASHES, "/");
|
|
445
|
+
if (collapsed.startsWith("/\\")) return void 0;
|
|
446
|
+
return {
|
|
447
|
+
status: method === "GET" || method === "HEAD" ? 301 : 308,
|
|
448
|
+
location: queryIndex === -1 ? collapsed : collapsed + url.slice(queryIndex)
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Redirect a request whose path carries duplicate slashes to the collapsed
|
|
453
|
+
* path. Returns `false` when the request needs no redirect — the caller
|
|
454
|
+
* continues with mounts, static files and astro.
|
|
455
|
+
*/
|
|
456
|
+
function redirectDuplicateSlashes(req, res) {
|
|
457
|
+
const redirect = evaluateDuplicateSlashes(req.url ?? "", req.method);
|
|
458
|
+
if (!redirect) return false;
|
|
459
|
+
overrideRequestRoute("duplicate-slashes");
|
|
460
|
+
res.writeHead(redirect.status, { location: redirect.location });
|
|
461
|
+
res.end();
|
|
462
|
+
return true;
|
|
463
|
+
}
|
|
464
|
+
//#endregion
|
|
465
|
+
export { createRequestInstrumentation as a, dumpEarlyLogs as i, preparePlatform as n, runShutdown as o, shutdownTelemetry as r, runStartup as s, redirectDuplicateSlashes as t };
|
|
410
466
|
|
|
411
|
-
//# sourceMappingURL=
|
|
467
|
+
//# sourceMappingURL=duplicate-slashes-C6hmOLl9.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"duplicate-slashes-C6hmOLl9.js","names":[],"sources":["../src/lifecycle/lifecycle.ts","../src/observability/telemetry/metrics.ts","../src/observability/instrument.ts","../src/observability/log/construct.ts","../src/observability/telemetry/sdk.ts","../src/platform/env.ts","../src/platform/prepare.ts","../src/server/duplicate-slashes.ts"],"sourcesContent":["import { emit } from './events.js';\nimport type { BootContext } from './types.js';\n\nexport interface BootModule {\n onStartup?: ((context: BootContext) => Promise<void> | void) | undefined;\n onShutdown?: ((context: BootContext) => Promise<void> | void) | undefined;\n}\n\nexport async function runStartup(boot: BootModule, context: BootContext): Promise<void> {\n await emit('beforeOnStartup', context);\n await boot.onStartup?.(context);\n await emit('afterOnStartup', context);\n}\n\nexport async function runShutdown(boot: BootModule, context: BootContext): Promise<void> {\n try {\n await emit('beforeOnShutdown', context);\n await boot.onShutdown?.(context);\n } finally {\n await emit('afterOnShutdown', context);\n }\n}\n","import { DURATION_BUCKETS, createCounter, createHistogram, createUpDownCounter } from './telemetry.js';\n\nconst httpRequestDuration = createHistogram('http.server.request.duration', {\n description: 'Duration of HTTP server requests',\n unit: 's',\n buckets: DURATION_BUCKETS.http,\n});\n\nconst httpActiveRequests = createUpDownCounter('http.server.active_requests', {\n description: 'Number of active HTTP server requests',\n unit: '{request}',\n});\n\nconst actionDuration = createHistogram('astro.action.duration', {\n description: 'Duration of Astro action executions',\n unit: 's',\n buckets: DURATION_BUCKETS.http,\n});\n\nconst renderFailures = createCounter('astro.render.failures', {\n description: 'Responses that failed while streaming, after the status was sent',\n unit: '{response}',\n});\n\n/**\n * Record the start of an HTTP request. Returns a function to call when the\n * request ends. Route is unknown at the native-handler level, so active\n * requests carry only the method.\n */\nexport function recordHttpRequestStart(method: string): () => void {\n httpActiveRequests.add(1, { 'http.request.method': method });\n\n return () => {\n httpActiveRequests.add(-1, { 'http.request.method': method });\n };\n}\n\nexport function recordHttpRequestDuration(\n attributes: { method: string; route: string | undefined; status: number },\n durationMs: number,\n): void {\n httpRequestDuration.record(durationMs / 1000, {\n 'http.request.method': attributes.method,\n 'http.route': attributes.route ?? '',\n 'http.response.status_code': attributes.status,\n });\n}\n\nexport function recordActionDuration(attributes: { name: string; status: number }, durationMs: number): void {\n actionDuration.record(durationMs / 1000, {\n 'astro.action.name': attributes.name,\n 'http.response.status_code': attributes.status,\n });\n}\n\nexport function recordRenderFailure(route: string | undefined): void {\n renderFailures.add(1, { 'http.route': route ?? '' });\n}\n","import type { IncomingMessage, ServerResponse } from 'node:http';\nimport { createMatcher } from '@entwico/dash/match';\nimport { ROOT_CONTEXT, SpanKind, SpanStatusCode, context, propagation, trace } from '@opentelemetry/api';\nimport type { Logger } from 'pino';\nimport type { ExcludePattern } from '../excludes/excludes.js';\nimport { generateReqId } from './log/index.js';\nimport { type RequestRecord, getLogStore } from './log/store.js';\nimport {\n recordActionDuration,\n recordHttpRequestDuration,\n recordHttpRequestStart,\n recordRenderFailure,\n} from './telemetry/metrics.js';\n\nconst LIB_NAME = '@astroscope/node';\nconst ACTIONS_PREFIX = '/_actions/';\nconst REQUEST_ID_PATTERN = /^[\\w.-]{1,64}$/;\n\nconst roundTime = (n: number) => Math.round(n * 100) / 100;\n\nexport interface RequestLoggingConfig {\n exclude: ExcludePattern[];\n extended: boolean;\n}\n\nexport interface RequestTelemetryConfig {\n exclude: ExcludePattern[];\n}\n\nexport interface RequestInstrumentationConfig {\n logging: RequestLoggingConfig | false;\n telemetry: RequestTelemetryConfig | false;\n}\n\nfunction getClientIp(req: IncomingMessage): string | undefined {\n const forwarded = req.headers['x-forwarded-for'];\n const first = Array.isArray(forwarded) ? forwarded[0] : forwarded;\n\n return (\n first?.split(',')[0]?.trim() ??\n (req.headers['x-real-ip'] as string | undefined) ??\n (req.headers['cf-connecting-ip'] as string | undefined)\n );\n}\n\nfunction resolveReqId(req: IncomingMessage): string {\n const incoming = req.headers['x-request-id'];\n const value = Array.isArray(incoming) ? incoming[0] : incoming;\n\n return value && REQUEST_ID_PATTERN.test(value) ? value : generateReqId();\n}\n\nfunction chunkSize(chunk: unknown): number {\n if (chunk == null) return 0;\n if (ArrayBuffer.isView(chunk)) return chunk.byteLength;\n if (typeof chunk === 'string') return Buffer.byteLength(chunk);\n\n return 0;\n}\n\n/**\n * Wraps the native request/response with logging and telemetry: a request\n * logger in async context (real status, response size, aborted-vs-completed\n * on `finish`/`close`), a SERVER span with propagation extraction, and\n * request metrics. Both concerns honor their own exclude patterns; when both\n * are excluded the request passes through untouched.\n */\nexport function createRequestInstrumentation(config: RequestInstrumentationConfig) {\n const tracer = trace.getTracer(LIB_NAME);\n const store = getLogStore();\n const loggingExcluded = config.logging ? createMatcher(config.logging.exclude) : () => true;\n const telemetryExcluded = config.telemetry ? createMatcher(config.telemetry.exclude) : () => true;\n\n return (req: IncomingMessage, res: ServerResponse, inner: () => void): void => {\n const url = req.url ?? '';\n const queryIndex = url.indexOf('?');\n const pathname = queryIndex === -1 ? url : url.slice(0, queryIndex);\n const method = req.method ?? 'GET';\n\n const logging = config.logging && !loggingExcluded(pathname) ? config.logging : false;\n const telemetry = config.telemetry && !telemetryExcluded(pathname) ? config.telemetry : false;\n\n if (!logging && !telemetry) {\n inner();\n\n return;\n }\n\n const startTime = performance.now();\n const isAction = pathname.startsWith(ACTIONS_PREFIX);\n\n let requestLogger: Logger | undefined;\n\n if (logging && store.root) {\n const reqId = resolveReqId(req);\n const reqData: Record<string, unknown> = { method, url: pathname };\n\n // extended logging includes potentially sensitive data\n if (logging.extended) {\n reqData['query'] = queryIndex === -1 ? '' : url.slice(queryIndex + 1);\n reqData['headers'] = req.headers;\n reqData['remoteAddress'] = getClientIp(req) ?? req.socket.remoteAddress;\n }\n\n requestLogger = store.root.child({ reqId, req: reqData });\n\n res.setHeader('x-request-id', reqId);\n }\n\n const record: RequestRecord = {\n logger: requestLogger,\n url,\n method,\n route: undefined,\n routeOverride: false,\n actionName: isAction ? pathname.slice(ACTIONS_PREFIX.length).replace(/\\/$/, '') : undefined,\n truncated: false,\n };\n\n let span: ReturnType<typeof tracer.startSpan> | undefined;\n let firstByteSpan: ReturnType<typeof tracer.startSpan> | undefined;\n let endActiveRequest: (() => void) | undefined;\n\n if (telemetry) {\n const parentContext = propagation.extract(ROOT_CONTEXT, req.headers);\n const contentLength = req.headers['content-length'];\n const clientIp = getClientIp(req);\n const host = req.headers['host'];\n\n span = tracer.startSpan(\n isAction ? `ACTION ${record.actionName}` : method,\n {\n kind: SpanKind.SERVER,\n attributes: {\n 'http.request.method': method,\n 'url.path': pathname,\n 'url.query': queryIndex === -1 ? '' : url.slice(queryIndex + 1),\n 'url.scheme': 'http',\n 'user_agent.original': req.headers['user-agent'] ?? '',\n ...(host && { 'server.address': host }),\n ...(contentLength && { 'http.request.body.size': parseInt(contentLength) }),\n ...(clientIp && { 'client.address': clientIp }),\n },\n },\n parentContext,\n );\n\n firstByteSpan = tracer.startSpan('response:first-byte', undefined, trace.setSpan(parentContext, span));\n\n endActiveRequest = recordHttpRequestStart(method);\n }\n\n let responseSize = 0;\n let firstByteTime: number | undefined;\n\n const originalWrite = res.write.bind(res);\n const originalEnd = res.end.bind(res);\n\n const markFirstByte = (): void => {\n if (firstByteTime !== undefined) return;\n\n firstByteTime = performance.now();\n\n if (firstByteSpan) {\n firstByteSpan.setAttribute('http.response.status_code', res.statusCode);\n firstByteSpan.end();\n }\n };\n\n res.write = ((chunk: unknown, ...rest: unknown[]) => {\n markFirstByte();\n responseSize += chunkSize(chunk);\n\n return (originalWrite as (...args: unknown[]) => boolean)(chunk, ...rest);\n }) as typeof res.write;\n\n res.end = ((chunk: unknown, ...rest: unknown[]) => {\n markFirstByte();\n responseSize += chunkSize(chunk);\n\n return (originalEnd as (...args: unknown[]) => ServerResponse)(chunk, ...rest);\n }) as typeof res.end;\n\n let finalized = false;\n\n const finalize = (aborted: boolean): void => {\n if (finalized) return;\n\n finalized = true;\n\n const status = res.statusCode;\n const truncated = record.truncated;\n const responseTime = performance.now() - startTime;\n const ttfb = roundTime((firstByteTime ?? performance.now()) - startTime);\n\n if (requestLogger) {\n const level = truncated || status >= 500 ? 'error' : status >= 400 ? 'warn' : 'info';\n\n requestLogger[level](\n {\n res: { statusCode: status },\n responseTime: roundTime(responseTime),\n ttfb,\n responseSize,\n ...(record.route && { route: record.route }),\n ...(aborted && { aborted: true }),\n ...(truncated && { truncated: true }),\n },\n truncated ? 'request truncated' : aborted ? 'request aborted' : 'request completed',\n );\n }\n\n if (firstByteSpan && firstByteTime === undefined) {\n firstByteSpan.setStatus({ code: SpanStatusCode.ERROR, message: 'request aborted' });\n firstByteSpan.end();\n }\n\n if (span) {\n span.setAttribute('http.response.status_code', status);\n span.setAttribute('http.response.body.size', responseSize);\n span.setAttribute('ttfb', ttfb);\n\n if (truncated) {\n span.setAttribute('astro.response.truncated', true);\n span.setStatus({ code: SpanStatusCode.ERROR, message: 'response truncated' });\n } else if (aborted || status >= 400) {\n span.setStatus({ code: SpanStatusCode.ERROR, message: aborted ? 'request aborted' : `HTTP ${status}` });\n } else {\n span.setStatus({ code: SpanStatusCode.OK });\n }\n\n span.end();\n }\n\n if (telemetry) {\n endActiveRequest?.();\n recordHttpRequestDuration({ method, route: record.route, status }, responseTime);\n\n if (record.actionName) {\n recordActionDuration({ name: record.actionName, status }, responseTime);\n }\n\n if (truncated) {\n recordRenderFailure(record.route);\n }\n }\n };\n\n res.once('finish', () => finalize(false));\n res.once('close', () => finalize(!res.writableFinished));\n\n const run = (): void => store.requestStorage.run(record, inner);\n\n if (span) {\n context.with(trace.setSpan(context.active(), span), run);\n } else {\n run();\n }\n };\n}\n","import { isSpanContextValid, trace } from '@opentelemetry/api';\nimport pino, { type Bindings, type Logger, type LoggerOptions } from 'pino';\nimport { getLogStore } from './store.js';\n\n/**\n * Contract of the `src/log.ts` entry seam: pino logger options, or a factory\n * producing them. Never a logger instance — the platform constructs the\n * logger itself (after instrumentation, so trace correlation works).\n */\nexport type LoggerOptionsFactory = LoggerOptions | ((ctx: { dev: boolean }) => LoggerOptions | Promise<LoggerOptions>);\n\n/**\n * Compose the user mixin (if any) with platform trace correlation: when a\n * span is active, every entry carries `trace_id` / `span_id` / `trace_flags`.\n */\nfunction composeMixin(userMixin: LoggerOptions['mixin']): NonNullable<LoggerOptions['mixin']> {\n return (mergeObject, level, logger) => {\n const user = userMixin ? userMixin(mergeObject, level, logger) : {};\n const spanContext = trace.getActiveSpan()?.spanContext();\n\n if (!spanContext || !isSpanContextValid(spanContext)) return user;\n\n return {\n ...user,\n trace_id: spanContext.traceId,\n span_id: spanContext.spanId,\n trace_flags: `0${spanContext.traceFlags.toString(16)}`,\n };\n };\n}\n\n/**\n * Construct the root logger from the app's options seam and replay any logs\n * buffered before construction (original timestamps kept as `bufferedTime`).\n * In dev this runs once per generation; the buffer only exists the first time.\n */\nexport async function constructRootLogger(\n factory: LoggerOptionsFactory | undefined,\n ctx: { dev: boolean },\n): Promise<Logger> {\n const store = getLogStore();\n const options = (typeof factory === 'function' ? await factory(ctx) : factory) ?? {};\n const root = pino({ level: 'info', ...options, mixin: composeMixin(options.mixin) });\n\n store.root = root;\n\n for (const entry of store.buffer.splice(0)) {\n const bindings = entry.bindings.length ? (Object.assign({}, ...entry.bindings) as Bindings) : {};\n const target = root.child({ ...bindings, bufferedTime: new Date(entry.time).toISOString() });\n\n (target[entry.level] as (...args: unknown[]) => void)(...entry.args);\n }\n\n if (store.dropped > 0) {\n root.warn({ dropped: store.dropped }, 'early log buffer overflowed, entries dropped');\n store.dropped = 0;\n }\n\n return root;\n}\n\n/**\n * Failure path for startups that die before the logger exists: dump the\n * buffered entries to the console so no phase is silent.\n */\nexport function dumpEarlyLogs(): void {\n const store = getLogStore();\n\n if (store.root) return;\n\n for (const entry of store.buffer.splice(0)) {\n const bindings = entry.bindings.length ? Object.assign({}, ...entry.bindings) : undefined;\n\n console.error(\n new Date(entry.time).toISOString(),\n entry.level.toUpperCase(),\n ...(bindings ? [bindings] : []),\n ...entry.args,\n );\n }\n\n if (store.dropped > 0) {\n console.error(`(${store.dropped} early log entries dropped)`);\n store.dropped = 0;\n }\n}\n","import { log } from '../log/index.js';\nimport { bindGauges } from './gauges.js';\n\n/**\n * Platform-owned telemetry bundle: NodeSDK with undici (fetch) and node\n * runtime instrumentation, host metrics, and a Prometheus reader. Trace\n * exporters are driven by standard `OTEL_*` env vars; without any of them\n * traces stay off (no failing localhost OTLP exports).\n *\n * Guarded per process (dev restarts are in-process; a re-created NodeSDK\n * would double-register instrumentations and leak the Prometheus port).\n */\n\nconst TELEMETRY_KEY = Symbol.for('@astroscope/node/telemetry');\n\ninterface TelemetryHandle {\n shutdown: () => Promise<void>;\n}\n\nexport interface TelemetrySdkOptions {\n prometheus: { host?: string | undefined; port?: number | undefined } | false;\n}\n\nfunction getHandle(): TelemetryHandle | undefined {\n return (globalThis as Record<symbol, unknown>)[TELEMETRY_KEY] as TelemetryHandle | undefined;\n}\n\nfunction defaultEnv(key: string, value: string): void {\n if (!process.env[key]) process.env[key] = value;\n}\n\nexport async function startTelemetry(options: TelemetrySdkOptions): Promise<void> {\n const g = globalThis as Record<symbol, unknown>;\n\n if (g[TELEMETRY_KEY]) return;\n\n if (process.env['OTEL_SDK_DISABLED'] === 'true') {\n log.debug('telemetry disabled via OTEL_SDK_DISABLED');\n\n return;\n }\n\n // without an explicitly configured exporter target, exporting traces to the\n // default localhost OTLP endpoint would fail on every flush\n if (!process.env['OTEL_EXPORTER_OTLP_ENDPOINT'] && !process.env['OTEL_EXPORTER_OTLP_TRACES_ENDPOINT']) {\n defaultEnv('OTEL_TRACES_EXPORTER', 'none');\n }\n\n defaultEnv('OTEL_METRICS_EXPORTER', 'none');\n defaultEnv('OTEL_LOGS_EXPORTER', 'none');\n\n const [\n { NodeSDK },\n { UndiciInstrumentation },\n { RuntimeNodeInstrumentation },\n { PrometheusExporter },\n { HostMetrics },\n ] = await Promise.all([\n import('@opentelemetry/sdk-node'),\n import('@opentelemetry/instrumentation-undici'),\n import('@opentelemetry/instrumentation-runtime-node'),\n import('@opentelemetry/exporter-prometheus'),\n import('@opentelemetry/host-metrics'),\n ]);\n\n const prometheus = options.prometheus\n ? {\n host: process.env['OTEL_EXPORTER_PROMETHEUS_HOST'] ?? options.prometheus.host ?? '0.0.0.0',\n port: process.env['OTEL_EXPORTER_PROMETHEUS_PORT']\n ? Number(process.env['OTEL_EXPORTER_PROMETHEUS_PORT'])\n : (options.prometheus.port ?? 9464),\n }\n : false;\n\n const sdk = new NodeSDK({\n instrumentations: [new UndiciInstrumentation(), new RuntimeNodeInstrumentation()],\n ...(prometheus && { metricReaders: [new PrometheusExporter(prometheus)] }),\n });\n\n sdk.start();\n bindGauges();\n\n const hostMetrics = new HostMetrics();\n\n hostMetrics.start();\n\n g[TELEMETRY_KEY] = {\n shutdown: () => sdk.shutdown(),\n } satisfies TelemetryHandle;\n\n if (prometheus) {\n log.debug({ host: prometheus.host, port: prometheus.port }, 'prometheus metrics listening');\n }\n}\n\n/**\n * Flush and shut the SDK down. Prod-only (dev keeps the SDK for the process\n * lifetime across generations).\n */\nexport async function shutdownTelemetry(): Promise<void> {\n const handle = getHandle();\n\n if (!handle) return;\n\n delete (globalThis as Record<symbol, unknown>)[TELEMETRY_KEY];\n\n await handle.shutdown();\n}\n","import fs from 'node:fs';\nimport { log } from '../observability/log/index.js';\n\n/**\n * Platform env loading (position −1, before the config seam):\n * `CONFIG_PATH` → `./.env` → none. Existing process env vars win\n */\nexport function loadEnvFiles(): void {\n const configPath = process.env['CONFIG_PATH'];\n\n if (configPath) {\n process.loadEnvFile(configPath);\n\n log.debug({ path: configPath }, 'loaded env file from CONFIG_PATH');\n\n return;\n }\n\n if (fs.existsSync('.env')) {\n process.loadEnvFile('.env');\n log.debug({ path: '.env' }, 'loaded env file');\n\n return;\n }\n\n log.debug('no env file loaded');\n}\n","import { type LoggerOptionsFactory, constructRootLogger } from '../observability/log/construct.js';\nimport { type TelemetrySdkOptions, startTelemetry } from '../observability/telemetry/sdk.js';\nimport { loadEnvFiles } from './env.js';\n\nconst INSTRUMENTATION_KEY = Symbol.for('@astroscope/node/instrumentation');\n\nexport interface InstrumentationContext {\n dev: boolean;\n}\n\ninterface InstrumentationSeam {\n register?: ((ctx: InstrumentationContext) => void | Promise<void>) | undefined;\n}\n\ninterface LogSeam {\n default?: LoggerOptionsFactory | undefined;\n}\n\nexport interface PlatformSeams {\n /** `src/config.ts` — validation runs at import; a throw fails the startup */\n config?: (() => Promise<unknown>) | undefined;\n /** `src/instrumentation.ts` — extra instrumentation, once per process */\n instrumentation?: (() => Promise<InstrumentationSeam>) | undefined;\n /** `src/log.ts` — pino logger options (or a factory), never an instance */\n log?: (() => Promise<LogSeam>) | undefined;\n}\n\nexport interface PreparePlatformOptions {\n dev: boolean;\n telemetry: TelemetrySdkOptions | false;\n seams: PlatformSeams;\n}\n\n/**\n * The platform sequence in front of the boot lifecycle:\n * env → config → instrumentation (platform SDK + `register`, once per\n * process) → logger construction (after instrumentation, so entries carry\n * trace correlation). Prod runs it once in `startServer()`; dev re-runs it\n * per generation with the once-per-process parts guarded.\n */\nexport async function preparePlatform(options: PreparePlatformOptions): Promise<void> {\n loadEnvFiles();\n\n await options.seams.config?.();\n\n const g = globalThis as Record<symbol, unknown>;\n\n if (!g[INSTRUMENTATION_KEY]) {\n g[INSTRUMENTATION_KEY] = true;\n\n if (options.telemetry) {\n await startTelemetry(options.telemetry);\n }\n\n const instrumentation = await options.seams.instrumentation?.();\n\n await instrumentation?.register?.({ dev: options.dev });\n }\n\n const logSeam = await options.seams.log?.();\n\n await constructRootLogger(logSeam?.default, { dev: options.dev });\n}\n","import type { IncomingMessage, ServerResponse } from 'node:http';\nimport { overrideRequestRoute } from '../observability/request-route.js';\n\n/**\n * Duplicate slashes in a request path (`//weine`, `/seminare///x`) are not\n * a distinct resource: astro collapses them before routing and renders the\n * page as if the path were clean, so every such spelling is duplicate\n * content. Astro itself only redirects duplicate *trailing* slashes; this\n * handler redirects the rest, before native mounts and static serving, so a\n * page, an endpoint, a public asset and a mounted handler all answer alike.\n *\n * Only literal slashes in the raw request target count — `%2F` stays as\n * sent. Absolute-form targets (`GET http://host/x`) and `*` are not paths\n * and pass through untouched.\n */\n\nconst DUPLICATE_SLASHES = /\\/{2,}/g;\n\nexport interface DuplicateSlashRedirect {\n status: 301 | 308;\n location: string;\n}\n\n/**\n * Decide whether a raw request target with duplicate slashes in its path\n * should redirect to the collapsed path. Returns `undefined` when the path\n * is already clean — and when the collapsed path would start with `/\\`,\n * which browsers resolve as a scheme-relative url: that request is left to\n * astro, which treats such paths as internal instead of echoing them into\n * a `Location` header.\n */\nexport function evaluateDuplicateSlashes(url: string, method: string | undefined): DuplicateSlashRedirect | undefined {\n if (!url.startsWith('/')) return undefined;\n\n const queryIndex = url.indexOf('?');\n const pathname = queryIndex === -1 ? url : url.slice(0, queryIndex);\n\n if (!pathname.includes('//')) return undefined;\n\n const collapsed = pathname.replace(DUPLICATE_SLASHES, '/');\n\n if (collapsed.startsWith('/\\\\')) return undefined;\n\n return {\n status: method === 'GET' || method === 'HEAD' ? 301 : 308,\n location: queryIndex === -1 ? collapsed : collapsed + url.slice(queryIndex),\n };\n}\n\n/**\n * Redirect a request whose path carries duplicate slashes to the collapsed\n * path. Returns `false` when the request needs no redirect — the caller\n * continues with mounts, static files and astro.\n */\nexport function redirectDuplicateSlashes(req: IncomingMessage, res: ServerResponse): boolean {\n const redirect = evaluateDuplicateSlashes(req.url ?? '', req.method);\n\n if (!redirect) return false;\n\n overrideRequestRoute('duplicate-slashes');\n\n res.writeHead(redirect.status, { location: redirect.location });\n res.end();\n\n return true;\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
import { matchesAny } from "@entwico/dash/match";
|
|
2
|
+
//#region src/excludes/serialize.ts
|
|
3
|
+
/**
|
|
4
|
+
* Serialize exclude patterns to JavaScript code for use in virtual modules.
|
|
5
|
+
* Handles RegExp objects which JSON.stringify cannot serialize.
|
|
6
|
+
*/
|
|
7
|
+
function serializeExcludePatterns(patterns) {
|
|
8
|
+
return `[${patterns.map((p) => "pattern" in p ? `{ pattern: ${p.pattern.toString()} }` : JSON.stringify(p)).join(", ")}]`;
|
|
9
|
+
}
|
|
10
|
+
//#endregion
|
|
2
11
|
//#region src/excludes/excludes.ts
|
|
3
12
|
/**
|
|
4
13
|
* Vite/Astro dev server paths - only relevant in development.
|
|
@@ -11,9 +20,14 @@ const DEV_EXCLUDES = [
|
|
|
11
20
|
{ prefix: "/node_modules/" }
|
|
12
21
|
];
|
|
13
22
|
/**
|
|
14
|
-
* Astro internal paths for static assets and image optimization
|
|
23
|
+
* Astro internal paths for static assets and image optimization, plus the
|
|
24
|
+
* platform's own asset routes (`/_i18n/` translation chunks).
|
|
15
25
|
*/
|
|
16
|
-
const ASTRO_STATIC_EXCLUDES = [
|
|
26
|
+
const ASTRO_STATIC_EXCLUDES = [
|
|
27
|
+
{ prefix: "/_astro/" },
|
|
28
|
+
{ prefix: "/_image" },
|
|
29
|
+
{ prefix: "/_i18n/" }
|
|
30
|
+
];
|
|
17
31
|
/**
|
|
18
32
|
* Common static asset paths.
|
|
19
33
|
*/
|
|
@@ -73,6 +87,6 @@ function withExcluded(middleware, exclude) {
|
|
|
73
87
|
};
|
|
74
88
|
}
|
|
75
89
|
//#endregion
|
|
76
|
-
export { shouldExclude as a, STATIC_EXCLUDES as i, DEV_EXCLUDES as n, withExcluded as o, RECOMMENDED_EXCLUDES as r, ASTRO_STATIC_EXCLUDES as t };
|
|
90
|
+
export { shouldExclude as a, STATIC_EXCLUDES as i, DEV_EXCLUDES as n, withExcluded as o, RECOMMENDED_EXCLUDES as r, serializeExcludePatterns as s, ASTRO_STATIC_EXCLUDES as t };
|
|
77
91
|
|
|
78
|
-
//# sourceMappingURL=excludes-
|
|
92
|
+
//# sourceMappingURL=excludes-10eDopVB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"excludes-10eDopVB.js","names":[],"sources":["../src/excludes/serialize.ts","../src/excludes/excludes.ts"],"sourcesContent":["import type { ExcludePattern } from './excludes.js';\n\n/**\n * Serialize exclude patterns to JavaScript code for use in virtual modules.\n * Handles RegExp objects which JSON.stringify cannot serialize.\n */\nexport function serializeExcludePatterns(patterns: ExcludePattern[]): string {\n return `[${patterns.map((p) => ('pattern' in p ? `{ pattern: ${p.pattern.toString()} }` : JSON.stringify(p))).join(', ')}]`;\n}\n","import { type StringPattern, matchesAny } from '@entwico/dash/match';\nimport type { APIContext, MiddlewareHandler } from 'astro';\n\n/**\n * Patterns accepted by @astroscope/node exclude options. The serializable\n * subset of dash's `StringPattern` — no matcher functions, since integration\n * options cross a virtual-module boundary.\n */\nexport type ExcludePattern = Exclude<StringPattern, (value: string) => boolean>;\n\nexport { serializeExcludePatterns } from './serialize.js';\n\n/**\n * Vite/Astro dev server paths - only relevant in development.\n */\nexport const DEV_EXCLUDES: ExcludePattern[] = [\n { prefix: '/@id/' },\n { prefix: '/@fs/' },\n { prefix: '/@vite/' },\n { prefix: '/src/' },\n { prefix: '/node_modules/' },\n];\n\n/**\n * Astro internal paths for static assets and image optimization, plus the\n * platform's own asset routes (`/_i18n/` translation chunks).\n */\nexport const ASTRO_STATIC_EXCLUDES: ExcludePattern[] = [\n { prefix: '/_astro/' },\n { prefix: '/_image' },\n { prefix: '/_i18n/' },\n];\n\n/**\n * Common static asset paths.\n */\nexport const STATIC_EXCLUDES: ExcludePattern[] = [\n { exact: '/favicon.ico' },\n { exact: '/robots.txt' },\n { exact: '/sitemap.xml' },\n { exact: '/browserconfig.xml' },\n { exact: '/manifest.json' },\n { exact: '/manifest.webmanifest' },\n];\n\n/**\n * Recommended excludes for middleware.\n * Includes dev paths and Astro internals.\n *\n * @example\n * ```ts\n * createI18nMiddleware({\n * locale: (ctx) => ctx.locals.session?.locale ?? 'en',\n * exclude: [\n * ...RECOMMENDED_EXCLUDES,\n * { exact: \"/health\" }, // your health endpoint\n * ],\n * })\n * ```\n */\nexport const RECOMMENDED_EXCLUDES: ExcludePattern[] = [...DEV_EXCLUDES, ...ASTRO_STATIC_EXCLUDES];\n\n/**\n * Check if a request should be excluded based on patterns or a function.\n */\nexport function shouldExclude(\n ctx: APIContext,\n exclude: readonly StringPattern[] | ((context: APIContext) => boolean) | undefined,\n): boolean {\n if (!exclude) return false;\n\n if (typeof exclude === 'function') {\n return exclude(ctx);\n }\n\n return matchesAny(ctx.url.pathname, exclude);\n}\n\n/**\n * Wraps a middleware to skip execution for excluded paths.\n * Useful for third-party middlewares that don't have built-in exclude support.\n *\n * @example\n * ```ts\n * import { RECOMMENDED_EXCLUDES, withExcluded } from '@astroscope/node/excludes';\n * import { someExternalMiddleware } from 'some-package';\n *\n * export const onRequest = sequence(\n * withExcluded(someExternalMiddleware(), [\n * ...RECOMMENDED_EXCLUDES,\n * { prefix: '/api/webhooks/' },\n * ]),\n * );\n * ```\n */\nexport function withExcluded(\n middleware: MiddlewareHandler,\n exclude: readonly StringPattern[] | ((context: APIContext) => boolean),\n): MiddlewareHandler {\n return (ctx, next) => {\n if (shouldExclude(ctx, exclude)) {\n return next();\n }\n\n return middleware(ctx, next);\n };\n}\n"],"mappings":";;;;;;AAMA,SAAgB,yBAAyB,UAAoC;CAC3E,OAAO,IAAI,SAAS,KAAK,MAAO,aAAa,IAAI,cAAc,EAAE,QAAQ,SAAS,EAAE,MAAM,KAAK,UAAU,CAAC,CAAE,CAAC,CAAC,KAAK,IAAI,EAAE;AAC3H;;;;;;ACOA,MAAa,eAAiC;CAC5C,EAAE,QAAQ,QAAQ;CAClB,EAAE,QAAQ,QAAQ;CAClB,EAAE,QAAQ,UAAU;CACpB,EAAE,QAAQ,QAAQ;CAClB,EAAE,QAAQ,iBAAiB;AAC7B;;;;;AAMA,MAAa,wBAA0C;CACrD,EAAE,QAAQ,WAAW;CACrB,EAAE,QAAQ,UAAU;CACpB,EAAE,QAAQ,UAAU;AACtB;;;;AAKA,MAAa,kBAAoC;CAC/C,EAAE,OAAO,eAAe;CACxB,EAAE,OAAO,cAAc;CACvB,EAAE,OAAO,eAAe;CACxB,EAAE,OAAO,qBAAqB;CAC9B,EAAE,OAAO,iBAAiB;CAC1B,EAAE,OAAO,wBAAwB;AACnC;;;;;;;;;;;;;;;;AAiBA,MAAa,uBAAyC,CAAC,GAAG,cAAc,GAAG,qBAAqB;;;;AAKhG,SAAgB,cACd,KACA,SACS;CACT,IAAI,CAAC,SAAS,OAAO;CAErB,IAAI,OAAO,YAAY,YACrB,OAAO,QAAQ,GAAG;CAGpB,OAAO,WAAW,IAAI,IAAI,UAAU,OAAO;AAC7C;;;;;;;;;;;;;;;;;;AAmBA,SAAgB,aACd,YACA,SACmB;CACnB,QAAQ,KAAK,SAAS;EACpB,IAAI,cAAc,KAAK,OAAO,GAC5B,OAAO,KAAK;EAGd,OAAO,WAAW,KAAK,IAAI;CAC7B;AACF"}
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { StringPattern } from "@entwico/dash/match";
|
|
2
2
|
import { APIContext, MiddlewareHandler } from "astro";
|
|
3
|
+
//#region src/excludes/serialize.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Serialize exclude patterns to JavaScript code for use in virtual modules.
|
|
6
|
+
* Handles RegExp objects which JSON.stringify cannot serialize.
|
|
7
|
+
*/
|
|
8
|
+
declare function serializeExcludePatterns(patterns: ExcludePattern[]): string;
|
|
9
|
+
//#endregion
|
|
3
10
|
//#region src/excludes/excludes.d.ts
|
|
4
11
|
/**
|
|
5
12
|
* Patterns accepted by @astroscope/node exclude options. The serializable
|
|
@@ -12,7 +19,8 @@ type ExcludePattern = Exclude<StringPattern, (value: string) => boolean>;
|
|
|
12
19
|
*/
|
|
13
20
|
declare const DEV_EXCLUDES: ExcludePattern[];
|
|
14
21
|
/**
|
|
15
|
-
* Astro internal paths for static assets and image optimization
|
|
22
|
+
* Astro internal paths for static assets and image optimization, plus the
|
|
23
|
+
* platform's own asset routes (`/_i18n/` translation chunks).
|
|
16
24
|
*/
|
|
17
25
|
declare const ASTRO_STATIC_EXCLUDES: ExcludePattern[];
|
|
18
26
|
/**
|
|
@@ -58,5 +66,5 @@ declare function shouldExclude(ctx: APIContext, exclude: readonly StringPattern[
|
|
|
58
66
|
*/
|
|
59
67
|
declare function withExcluded(middleware: MiddlewareHandler, exclude: readonly StringPattern[] | ((context: APIContext) => boolean)): MiddlewareHandler;
|
|
60
68
|
//#endregion
|
|
61
|
-
export { STATIC_EXCLUDES as a, RECOMMENDED_EXCLUDES as i, DEV_EXCLUDES as n, shouldExclude as o, ExcludePattern as r, withExcluded as s, ASTRO_STATIC_EXCLUDES as t };
|
|
62
|
-
//# sourceMappingURL=excludes-
|
|
69
|
+
export { STATIC_EXCLUDES as a, serializeExcludePatterns as c, RECOMMENDED_EXCLUDES as i, DEV_EXCLUDES as n, shouldExclude as o, ExcludePattern as r, withExcluded as s, ASTRO_STATIC_EXCLUDES as t };
|
|
70
|
+
//# sourceMappingURL=excludes-rvkVGg6B.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"excludes-rvkVGg6B.d.ts","names":[],"sources":["../src/excludes/serialize.ts","../src/excludes/excludes.ts"],"mappings":";;;;;;;iBAMgB,yBAAyB,UAAU;;;;;;;;KCEvC,iBAAiB,QAAQ,gBAAgB;;;;cAOxC,cAAc;;;;;cAYd,uBAAuB;;;;cASvB,iBAAiB;;;;;;;;;;;;;;;;cAwBjB,sBAAsB;;;;iBAKnB,cACd,KAAK,YACL,kBAAkB,oBAAoB,SAAS;;;;;;;;;;;;;;;;;;iBA4BjC,aACd,YAAY,mBACZ,kBAAkB,oBAAoB,SAAS,0BAC9C"}
|
package/dist/excludes.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as STATIC_EXCLUDES, i as RECOMMENDED_EXCLUDES, n as DEV_EXCLUDES, o as shouldExclude, r as ExcludePattern, s as withExcluded, t as ASTRO_STATIC_EXCLUDES } from "./excludes-
|
|
2
|
-
export { ASTRO_STATIC_EXCLUDES, DEV_EXCLUDES, ExcludePattern, RECOMMENDED_EXCLUDES, STATIC_EXCLUDES, shouldExclude, withExcluded };
|
|
1
|
+
import { a as STATIC_EXCLUDES, c as serializeExcludePatterns, i as RECOMMENDED_EXCLUDES, n as DEV_EXCLUDES, o as shouldExclude, r as ExcludePattern, s as withExcluded, t as ASTRO_STATIC_EXCLUDES } from "./excludes-rvkVGg6B.js";
|
|
2
|
+
export { ASTRO_STATIC_EXCLUDES, DEV_EXCLUDES, ExcludePattern, RECOMMENDED_EXCLUDES, STATIC_EXCLUDES, serializeExcludePatterns, shouldExclude, withExcluded };
|
package/dist/excludes.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as shouldExclude, i as STATIC_EXCLUDES, n as DEV_EXCLUDES, o as withExcluded, r as RECOMMENDED_EXCLUDES, t as ASTRO_STATIC_EXCLUDES } from "./excludes-
|
|
2
|
-
export { ASTRO_STATIC_EXCLUDES, DEV_EXCLUDES, RECOMMENDED_EXCLUDES, STATIC_EXCLUDES, shouldExclude, withExcluded };
|
|
1
|
+
import { a as shouldExclude, i as STATIC_EXCLUDES, n as DEV_EXCLUDES, o as withExcluded, r as RECOMMENDED_EXCLUDES, s as serializeExcludePatterns, t as ASTRO_STATIC_EXCLUDES } from "./excludes-10eDopVB.js";
|
|
2
|
+
export { ASTRO_STATIC_EXCLUDES, DEV_EXCLUDES, RECOMMENDED_EXCLUDES, STATIC_EXCLUDES, serializeExcludePatterns, shouldExclude, withExcluded };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
//#region src/islands/graph.ts
|
|
2
|
+
function createClosure(chunks, edges) {
|
|
3
|
+
const cache = /* @__PURE__ */ new Map();
|
|
4
|
+
const walk = (fileName, result, visited) => {
|
|
5
|
+
if (visited.has(fileName)) return;
|
|
6
|
+
visited.add(fileName);
|
|
7
|
+
const entry = chunks[fileName];
|
|
8
|
+
if (!entry) return;
|
|
9
|
+
for (const dep of edges(entry)) {
|
|
10
|
+
result.add(dep);
|
|
11
|
+
walk(dep, result, visited);
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
return (fileName) => {
|
|
15
|
+
const cached = cache.get(fileName);
|
|
16
|
+
if (cached) return cached;
|
|
17
|
+
const result = /* @__PURE__ */ new Set();
|
|
18
|
+
walk(fileName, result, /* @__PURE__ */ new Set());
|
|
19
|
+
result.delete(fileName);
|
|
20
|
+
const list = [...result];
|
|
21
|
+
cache.set(fileName, list);
|
|
22
|
+
return list;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function createChunkGraph(manifest) {
|
|
26
|
+
return {
|
|
27
|
+
staticClosure: createClosure(manifest.chunks, (entry) => entry.i ?? []),
|
|
28
|
+
fullClosure: createClosure(manifest.chunks, (entry) => [...entry.i ?? [], ...entry.d ?? []]),
|
|
29
|
+
has: (fileName) => fileName in manifest.chunks
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
export { createChunkGraph as t };
|
|
34
|
+
|
|
35
|
+
//# sourceMappingURL=graph-DdAxiaxa.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph-DdAxiaxa.js","names":[],"sources":["../src/islands/graph.ts"],"sourcesContent":["import type { IslandsManifest } from './types.js';\n\n/**\n * Closure computation over the chunk import graph, memoized per manifest. The\n * manifest stores direct edges only — flattened closures repeat heavily across\n * entries and would bloat the JSON.\n */\nexport type ChunkGraph = {\n /** transitive static imports of a chunk, the chunk itself excluded */\n staticClosure(fileName: string): string[];\n /** transitive static + dynamic imports of a chunk, the chunk itself excluded */\n fullClosure(fileName: string): string[];\n has(fileName: string): boolean;\n};\n\nfunction createClosure(\n chunks: IslandsManifest['chunks'],\n edges: (entry: { i?: string[] | undefined; d?: string[] | undefined }) => string[],\n): (fileName: string) => string[] {\n const cache = new Map<string, string[]>();\n\n const walk = (fileName: string, result: Set<string>, visited: Set<string>): void => {\n if (visited.has(fileName)) {\n return;\n }\n\n visited.add(fileName);\n\n const entry = chunks[fileName];\n\n if (!entry) {\n return;\n }\n\n for (const dep of edges(entry)) {\n result.add(dep);\n walk(dep, result, visited);\n }\n };\n\n return (fileName) => {\n const cached = cache.get(fileName);\n\n if (cached) {\n return cached;\n }\n\n const result = new Set<string>();\n\n walk(fileName, result, new Set());\n result.delete(fileName);\n\n const list = [...result];\n\n cache.set(fileName, list);\n\n return list;\n };\n}\n\nexport function createChunkGraph(manifest: IslandsManifest): ChunkGraph {\n return {\n staticClosure: createClosure(manifest.chunks, (entry) => entry.i ?? []),\n fullClosure: createClosure(manifest.chunks, (entry) => [...(entry.i ?? []), ...(entry.d ?? [])]),\n has: (fileName) => fileName in manifest.chunks,\n };\n}\n"],"mappings":";AAeA,SAAS,cACP,QACA,OACgC;CAChC,MAAM,wBAAQ,IAAI,IAAsB;CAExC,MAAM,QAAQ,UAAkB,QAAqB,YAA+B;EAClF,IAAI,QAAQ,IAAI,QAAQ,GACtB;EAGF,QAAQ,IAAI,QAAQ;EAEpB,MAAM,QAAQ,OAAO;EAErB,IAAI,CAAC,OACH;EAGF,KAAK,MAAM,OAAO,MAAM,KAAK,GAAG;GAC9B,OAAO,IAAI,GAAG;GACd,KAAK,KAAK,QAAQ,OAAO;EAC3B;CACF;CAEA,QAAQ,aAAa;EACnB,MAAM,SAAS,MAAM,IAAI,QAAQ;EAEjC,IAAI,QACF,OAAO;EAGT,MAAM,yBAAS,IAAI,IAAY;EAE/B,KAAK,UAAU,wBAAQ,IAAI,IAAI,CAAC;EAChC,OAAO,OAAO,QAAQ;EAEtB,MAAM,OAAO,CAAC,GAAG,MAAM;EAEvB,MAAM,IAAI,UAAU,IAAI;EAExB,OAAO;CACT;AACF;AAEA,SAAgB,iBAAiB,UAAuC;CACtE,OAAO;EACL,eAAe,cAAc,SAAS,SAAS,UAAU,MAAM,KAAK,CAAC,CAAC;EACtE,aAAa,cAAc,SAAS,SAAS,UAAU,CAAC,GAAI,MAAM,KAAK,CAAC,GAAI,GAAI,MAAM,KAAK,CAAC,CAAE,CAAC;EAC/F,MAAM,aAAa,YAAY,SAAS;CAC1C;AACF"}
|
package/dist/health.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
//#region src/health/health.d.ts
|
|
2
|
-
interface HealthCheckResult {
|
|
2
|
+
export interface HealthCheckResult {
|
|
3
3
|
status: 'healthy' | 'unhealthy';
|
|
4
4
|
latency?: number | undefined;
|
|
5
5
|
error?: string | undefined;
|
|
6
6
|
}
|
|
7
|
-
interface HealthCheckDefinition {
|
|
7
|
+
export interface HealthCheckDefinition {
|
|
8
8
|
/**
|
|
9
9
|
* Unique name for this health check.
|
|
10
10
|
*/
|
|
@@ -32,7 +32,6 @@ interface HealthCheckDefinition {
|
|
|
32
32
|
* files can call it unconditionally. Returns an unregister function; checks
|
|
33
33
|
* still registered after `onShutdown` are removed automatically.
|
|
34
34
|
*/
|
|
35
|
-
declare function registerHealthCheck(definition: HealthCheckDefinition): () => void;
|
|
35
|
+
export declare function registerHealthCheck(definition: HealthCheckDefinition): () => void;
|
|
36
36
|
//#endregion
|
|
37
|
-
export { HealthCheckDefinition, HealthCheckResult, registerHealthCheck };
|
|
38
37
|
//# sourceMappingURL=health.d.ts.map
|
package/dist/health.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"health.d.ts","names":[],"sources":["../src/health/health.ts"],"mappings":";
|
|
1
|
+
{"version":3,"file":"health.d.ts","names":[],"sources":["../src/health/health.ts"],"mappings":";iBAEiB;EACf;EACA;EACA;;iBAGe;;;;EAIf;;;;;EAMA,aAAa,QAAQ,4BAA4B;;;;;EAMjD;;;;;EAMA;;;;;;;;;wBAUc,oBAAoB,YAAY"}
|
package/dist/image-endpoint.d.ts
CHANGED
|
@@ -5,7 +5,6 @@ import { APIRoute } from "astro";
|
|
|
5
5
|
* clients are concerned the endpoint does not exist, so requests answer 404 —
|
|
6
6
|
* a 400 would advertise a live processing surface behind the route.
|
|
7
7
|
*/
|
|
8
|
-
declare const GET: APIRoute;
|
|
8
|
+
export declare const GET: APIRoute;
|
|
9
9
|
//#endregion
|
|
10
|
-
export { GET };
|
|
11
10
|
//# sourceMappingURL=image-endpoint.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"image-endpoint.d.ts","names":[],"sources":["../src/image/endpoint-disabled.ts"],"mappings":";;;;;;;
|
|
1
|
+
{"version":3,"file":"image-endpoint.d.ts","names":[],"sources":["../src/image/endpoint-disabled.ts"],"mappings":";;;;;;;qBAOa,KAAK"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { r as ExcludePattern } from "./excludes-
|
|
1
|
+
import { r as ExcludePattern } from "./excludes-rvkVGg6B.js";
|
|
2
2
|
import "./construct-BGlPfWMF.js";
|
|
3
|
-
import { c as IslandEmitter, i as registerIslandEmitter, l as IslandInfo, s as IslandEmission, t as getRequestRouteData } from "./route-store-
|
|
3
|
+
import { c as IslandEmitter, i as registerIslandEmitter, l as IslandInfo, s as IslandEmission, t as getRequestRouteData } from "./route-store-neUA2nBb.js";
|
|
4
4
|
import { t as BootContext } from "./types-D0uMBi2M.js";
|
|
5
5
|
import { n as BootEventName, t as BootEventHandler } from "./events-u7J3ezJR.js";
|
|
6
6
|
import { AstroIntegration } from "astro";
|
|
@@ -173,7 +173,7 @@ interface NodeOptions {
|
|
|
173
173
|
* as plain code around `server.listen()` instead of being injected into the
|
|
174
174
|
* build output.
|
|
175
175
|
*/
|
|
176
|
-
|
|
176
|
+
export default function node(options?: NodeOptions): AstroIntegration;
|
|
177
177
|
//#endregion
|
|
178
178
|
//#region src/platform/prepare.d.ts
|
|
179
179
|
interface InstrumentationContext {
|
|
@@ -186,5 +186,5 @@ interface BootModule {
|
|
|
186
186
|
onShutdown?: ((context: BootContext) => Promise<void> | void) | undefined;
|
|
187
187
|
}
|
|
188
188
|
//#endregion
|
|
189
|
-
export { type BootContext, type BootEventHandler, type BootEventName, type BootModule, type HealthProbePaths, type InstrumentationContext, type IslandEmission, type IslandEmitter, type IslandInfo, type NodeBootOptions, type NodeHealthOptions, type NodeLoggingOptions, type NodeOptions, type NodePrometheusOptions, type NodeTelemetryOptions,
|
|
189
|
+
export { type BootContext, type BootEventHandler, type BootEventName, type BootModule, type HealthProbePaths, type InstrumentationContext, type IslandEmission, type IslandEmitter, type IslandInfo, type NodeBootOptions, type NodeHealthOptions, type NodeLoggingOptions, type NodeOptions, type NodePrometheusOptions, type NodeTelemetryOptions, getRequestRouteData, registerIslandEmitter };
|
|
190
190
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/types.ts","../src/integration/integration.ts","../src/platform/prepare.ts","../src/lifecycle/lifecycle.ts"],"mappings":";;;;;;;UAEiB;;;;;EAKf;;;;;;EAOA;;;;;UAMe;EACf;EACA;EACA;EACA;;UAGe;;;;;EAKf;;;;;EAMA;;;;;EAMA,QAAQ;;UAGO;;;;;EAKf,UAAU;;;;;;EAOV;;;;;;EAOA;;UAGe;;;;;EAKf;;;;;EAMA;;UAGe;;;;;EAKf,UAAU;;;;;EAMV,aAAa;;;;;;EAOb;;UAGe;;;;;;;;EAQf,OAAO;;;;;;;EAQP,UAAU;;;;;;;EAQV,YAAY;;;;;;EAOZ,SAAS;;;;;;;EAQT;IAAS,UAAU;;;;;;;;;;;EAWnB;;;;;;;;;;;;EAaA;;;;;EAMA;;;;;;EAOA;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/types.ts","../src/integration/integration.ts","../src/platform/prepare.ts","../src/lifecycle/lifecycle.ts"],"mappings":";;;;;;;UAEiB;;;;;EAKf;;;;;;EAOA;;;;;UAMe;EACf;EACA;EACA;EACA;;UAGe;;;;;EAKf;;;;;EAMA;;;;;EAMA,QAAQ;;UAGO;;;;;EAKf,UAAU;;;;;;EAOV;;;;;;EAOA;;UAGe;;;;;EAKf;;;;;EAMA;;UAGe;;;;;EAKf,UAAU;;;;;EAMV,aAAa;;;;;;EAOb;;UAGe;;;;;;;;EAQf,OAAO;;;;;;;EAQP,UAAU;;;;;;;EAQV,YAAY;;;;;;EAOZ,SAAS;;;;;;;EAQT;IAAS,UAAU;;;;;;;;;;;EAWnB;;;;;;;;;;;;EAaA;;;;;EAMA;;;;;;EAOA;;;;;;;;;;wBC1GsB,KAAK,UAAS,cAAmB;;;UCpExC;EACf;;;;UCJe;EACf,cAAc,SAAS,gBAAgB;EACvC,eAAe,SAAS,gBAAgB"}
|