@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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astroscope/node",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "Opinionated, cloud-friendly Node adapter for Astro: boot lifecycle, health probes, request logging, telemetry, CSRF and static serving run as plain code around server.listen()",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -39,6 +39,10 @@
|
|
|
39
39
|
"types": "./dist/log/index.d.ts",
|
|
40
40
|
"import": "./dist/log/index.js"
|
|
41
41
|
},
|
|
42
|
+
"./telemetry": {
|
|
43
|
+
"types": "./dist/telemetry.d.ts",
|
|
44
|
+
"import": "./dist/telemetry.js"
|
|
45
|
+
},
|
|
42
46
|
"./events": {
|
|
43
47
|
"types": "./dist/lifecycle/events.d.ts",
|
|
44
48
|
"import": "./dist/lifecycle/events.js"
|
|
@@ -103,22 +107,20 @@
|
|
|
103
107
|
"@entwico/dash": "^1.3.0",
|
|
104
108
|
"@entwico/health-probes": "^1.1.2",
|
|
105
109
|
"@opentelemetry/api": "^1.9.1",
|
|
106
|
-
"@opentelemetry/exporter-prometheus": "^0.
|
|
110
|
+
"@opentelemetry/exporter-prometheus": "^0.222.0",
|
|
107
111
|
"@opentelemetry/host-metrics": "^0.39.0",
|
|
108
|
-
"@opentelemetry/instrumentation-runtime-node": "^0.
|
|
109
|
-
"@opentelemetry/instrumentation-undici": "^0.
|
|
110
|
-
"@opentelemetry/sdk-node": "^0.
|
|
111
|
-
"acorn": "^8.18.0",
|
|
112
|
-
"magic-string": "^1.2.2",
|
|
113
|
-
"parse5-html-rewriting-stream": "^8.0.1",
|
|
112
|
+
"@opentelemetry/instrumentation-runtime-node": "^0.35.0",
|
|
113
|
+
"@opentelemetry/instrumentation-undici": "^0.32.0",
|
|
114
|
+
"@opentelemetry/sdk-node": "^0.222.0",
|
|
114
115
|
"pino": "^10.3.1",
|
|
115
116
|
"send": "^1.2.1"
|
|
116
117
|
},
|
|
117
118
|
"devDependencies": {
|
|
118
119
|
"@types/send": "^1.2.1",
|
|
119
|
-
"astro": "^7.2
|
|
120
|
-
"happy-dom": "^20.
|
|
121
|
-
"
|
|
120
|
+
"astro": "^7.3.2",
|
|
121
|
+
"happy-dom": "^20.14.0",
|
|
122
|
+
"parse5-html-rewriting-stream": "^8.0.1",
|
|
123
|
+
"tsdown": "^0.23.0",
|
|
122
124
|
"typescript": "^6.0.3"
|
|
123
125
|
},
|
|
124
126
|
"peerDependencies": {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"excludes-BDiE3eyp.d.ts","names":[],"sources":["../src/excludes/excludes.ts"],"mappings":";;;;;;;;KAQY,iBAAiB,QAAQ,gBAAgB;;;;cAKxC,cAAc;;;;cAWd,uBAAuB;;;;cAKvB,iBAAiB;;;;;;;;;;;;;;;;cAwBjB,sBAAsB;;;;iBAKnB,cACd,KAAK,YACL,kBAAkB,oBAAoB,SAAS;;;;;;;;;;;;;;;;;;iBA4BjC,aACd,YAAY,mBACZ,kBAAkB,oBAAoB,SAAS,0BAC9C"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"excludes-pE23EbmQ.js","names":[],"sources":["../src/excludes/excludes.ts"],"sourcesContent":["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\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.\n */\nexport const ASTRO_STATIC_EXCLUDES: ExcludePattern[] = [{ prefix: '/_astro/' }, { prefix: '/_image' }];\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":";;;;;AAaA,MAAa,eAAiC;CAC5C,EAAE,QAAQ,QAAQ;CAClB,EAAE,QAAQ,QAAQ;CAClB,EAAE,QAAQ,UAAU;CACpB,EAAE,QAAQ,QAAQ;CAClB,EAAE,QAAQ,iBAAiB;AAC7B;;;;AAKA,MAAa,wBAA0C,CAAC,EAAE,QAAQ,WAAW,GAAG,EAAE,QAAQ,UAAU,CAAC;;;;AAKrG,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 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"prepare-CXZsyAVk.js","names":["LIB_NAME"],"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"],"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 { type Histogram, type UpDownCounter, ValueType, metrics } from '@opentelemetry/api';\n\nconst LIB_NAME = '@astroscope/node';\n\n// lazy initialization so instruments bind to the SDK meter provider\nlet httpRequestDuration: Histogram | null = null;\nlet httpActiveRequests: UpDownCounter | null = null;\nlet actionDuration: Histogram | null = null;\n\nfunction getHttpRequestDuration(): Histogram {\n return (httpRequestDuration ??= metrics.getMeter(LIB_NAME).createHistogram('http.server.request.duration', {\n description: 'Duration of HTTP server requests',\n unit: 's',\n valueType: ValueType.DOUBLE,\n }));\n}\n\nfunction getHttpActiveRequests(): UpDownCounter {\n return (httpActiveRequests ??= metrics.getMeter(LIB_NAME).createUpDownCounter('http.server.active_requests', {\n description: 'Number of active HTTP server requests',\n unit: '{request}',\n valueType: ValueType.INT,\n }));\n}\n\nfunction getActionDuration(): Histogram {\n return (actionDuration ??= metrics.getMeter(LIB_NAME).createHistogram('astro.action.duration', {\n description: 'Duration of Astro action executions',\n unit: 's',\n valueType: ValueType.DOUBLE,\n }));\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 getHttpActiveRequests().add(1, { 'http.request.method': method });\n\n return () => {\n getHttpActiveRequests().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 getHttpRequestDuration().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 getActionDuration().record(durationMs / 1000, {\n 'astro.action.name': attributes.name,\n 'http.response.status_code': attributes.status,\n });\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 { recordActionDuration, recordHttpRequestDuration, recordHttpRequestStart } 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 };\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 responseTime = performance.now() - startTime;\n const ttfb = roundTime((firstByteTime ?? performance.now()) - startTime);\n\n if (requestLogger) {\n const level = 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 },\n 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 (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 };\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';\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\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"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"prerendered-DVjzWNbW.js","names":[],"sources":["../src/islands/prerendered.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport { createIslandsTransformer } from './transform.js';\nimport type { IslandsManifest } from './types.js';\n\nfunction* walkHtmlFiles(dir: string): Generator<string> {\n for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {\n const full = path.join(dir, entry.name);\n\n if (entry.isDirectory()) {\n yield* walkHtmlFiles(full);\n } else if (entry.isFile() && entry.name.endsWith('.html')) {\n yield full;\n }\n }\n}\n\n/**\n * Prerendered pages never pass through the middleware — they get the same rewrite\n * once, at build time, before the client dir is compressed. Runs without a request\n * context, so registered emitters that need one contribute nothing here.\n */\nexport async function transformPrerenderedHtml(clientDir: string, manifest: IslandsManifest): Promise<number> {\n const transformer = createIslandsTransformer(manifest);\n let transformed = 0;\n\n for (const file of walkHtmlFiles(clientDir)) {\n const html = fs.readFileSync(file, 'utf-8');\n const rewriter = transformer.createDocumentRewriter();\n const result = rewriter.write(html) + (await rewriter.end());\n\n if (result !== html) {\n fs.writeFileSync(file, result);\n transformed++;\n }\n }\n\n return transformed;\n}\n"],"mappings":";;;;AAKA,UAAU,cAAc,KAAgC;CACtD,KAAK,MAAM,SAAS,GAAG,YAAY,KAAK,EAAE,eAAe,KAAK,CAAC,GAAG;EAChE,MAAM,OAAO,KAAK,KAAK,KAAK,MAAM,IAAI;EAEtC,IAAI,MAAM,YAAY,GACpB,OAAO,cAAc,IAAI;OACpB,IAAI,MAAM,OAAO,KAAK,MAAM,KAAK,SAAS,OAAO,GACtD,MAAM;CAEV;AACF;;;;;;AAOA,eAAsB,yBAAyB,WAAmB,UAA4C;CAC5G,MAAM,cAAc,yBAAyB,QAAQ;CACrD,IAAI,cAAc;CAElB,KAAK,MAAM,QAAQ,cAAc,SAAS,GAAG;EAC3C,MAAM,OAAO,GAAG,aAAa,MAAM,OAAO;EAC1C,MAAM,WAAW,YAAY,uBAAuB;EACpD,MAAM,SAAS,SAAS,MAAM,IAAI,IAAK,MAAM,SAAS,IAAI;EAE1D,IAAI,WAAW,MAAM;GACnB,GAAG,cAAc,MAAM,MAAM;GAC7B;EACF;CACF;CAEA,OAAO;AACT"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"route-store-CSYR4G5e.d.ts","names":[],"sources":["../src/islands/types.ts","../src/islands/emitters.ts","../src/server/route-store.ts"],"mappings":";;;;;;;KAqBY;EACV;EACA;;EAEA;;EAEA;;EAEA;;;;;;;;;;;;;;;;;KAkBU;EACV;EACA;EACA;;;;;;KAOU,iBAAiB,QAAQ,YAAY,UAAU,2BAA2B;;;;KAK1E;EACV;EACA;;;;;;;KAQU,mBAAmB,SAAS,eAAe;;;;;;;;iBC3CvC,sBAAsB,SAAS;;;;;;;iBAc/B,wBAAwB,SAAS;;;iBCpBjC,oBAAoB,SAAS,SAAS,WAAW;iBAIjD,oBAAoB,SAAS,UAAU"}
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { pathToFileURL } from "node:url";
|
|
4
|
-
//#region src/tweaks/strip-build-paths.ts
|
|
5
|
-
/**
|
|
6
|
-
* Stable stand-in for the build machine's project root in emitted server code.
|
|
7
|
-
* Only relative relationships matter at runtime (see `resolveClientDir`), so the
|
|
8
|
-
* value is never dereferenced — it just has to be consistent across the bundle.
|
|
9
|
-
*/
|
|
10
|
-
const ROOT_PLACEHOLDER = "/_astroscope";
|
|
11
|
-
const TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
12
|
-
".mjs",
|
|
13
|
-
".js",
|
|
14
|
-
".cjs",
|
|
15
|
-
".json",
|
|
16
|
-
".map"
|
|
17
|
-
]);
|
|
18
|
-
const HOISTED_STORE_RE = /(file:\/\/)?\/[^"'`\s]*?\/node_modules\/([^"'`\s]*)/g;
|
|
19
|
-
const MACHINE_PATH_RE = /(file:\/\/)?\/(?:Users|home)\/[^"'`\s]+/g;
|
|
20
|
-
function collectFiles(dir, out) {
|
|
21
|
-
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
22
|
-
const abs = path.join(dir, entry.name);
|
|
23
|
-
if (entry.isDirectory()) collectFiles(abs, out);
|
|
24
|
-
else if (TEXT_EXTENSIONS.has(path.extname(entry.name))) out.push(abs);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Replace the build machine's project root in the server build output with a
|
|
29
|
-
* stable placeholder.
|
|
30
|
-
*
|
|
31
|
-
* Astro serializes absolute `file://` config URLs (root, outDir, srcDir, ...)
|
|
32
|
-
* and absolute component module ids into the SSR manifest, and the compiled
|
|
33
|
-
* chunks and sourcemaps carry the same absolute paths. Shipping them makes the
|
|
34
|
-
* artifact irreproducible and leaks the developer's filesystem layout. The
|
|
35
|
-
* replacement is applied uniformly, so manifest keys keep matching the module
|
|
36
|
-
* ids embedded in chunks, and the server→client relative resolution keeps
|
|
37
|
-
* working.
|
|
38
|
-
*
|
|
39
|
-
* Returns the number of files rewritten.
|
|
40
|
-
*/
|
|
41
|
-
function stripBuildPaths(serverDir, root) {
|
|
42
|
-
const rootFsPosix = root.split(path.sep).join("/").replace(/\/+$/, "");
|
|
43
|
-
const rootUrl = pathToFileURL(rootFsPosix).href;
|
|
44
|
-
const files = [];
|
|
45
|
-
collectFiles(serverDir, files);
|
|
46
|
-
let rewritten = 0;
|
|
47
|
-
for (const file of files) {
|
|
48
|
-
const content = fs.readFileSync(file, "utf-8");
|
|
49
|
-
const replaced = content.replaceAll(rootUrl, `file://${ROOT_PLACEHOLDER}`).replaceAll(rootFsPosix, ROOT_PLACEHOLDER).replace(HOISTED_STORE_RE, (_, proto, suffix) => `${proto ?? ""}${ROOT_PLACEHOLDER}/node_modules/${suffix}`).replace(MACHINE_PATH_RE, (match, proto) => {
|
|
50
|
-
const rel = path.posix.relative(rootFsPosix, proto ? match.slice(proto.length) : match);
|
|
51
|
-
return `${proto ?? ""}${ROOT_PLACEHOLDER}/${rel}`;
|
|
52
|
-
});
|
|
53
|
-
if (replaced !== content) {
|
|
54
|
-
fs.writeFileSync(file, replaced);
|
|
55
|
-
rewritten++;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
return rewritten;
|
|
59
|
-
}
|
|
60
|
-
//#endregion
|
|
61
|
-
export { stripBuildPaths };
|
|
62
|
-
|
|
63
|
-
//# sourceMappingURL=strip-build-paths-C6eb1QdG.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"strip-build-paths-C6eb1QdG.js","names":[],"sources":["../src/tweaks/strip-build-paths.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\n/**\n * Stable stand-in for the build machine's project root in emitted server code.\n * Only relative relationships matter at runtime (see `resolveClientDir`), so the\n * value is never dereferenced — it just has to be consistent across the bundle.\n */\nexport const ROOT_PLACEHOLDER = '/_astroscope';\n\nconst TEXT_EXTENSIONS = new Set(['.mjs', '.js', '.cjs', '.json', '.map']);\n\n// absolute module ids pointing into a dependency store hoisted above the project\n// root (pnpm workspaces); the prefix before the first node_modules is collapsed,\n// the store-relative suffix keeps ids unique and matching. the greedy suffix\n// consumes the whole path token so a nested node_modules is not re-matched\nconst HOISTED_STORE_RE = /(file:\\/\\/)?\\/[^\"'`\\s]*?\\/node_modules\\/([^\"'`\\s]*)/g;\n\n// remaining machine paths outside the root and outside any store (workspace\n// siblings, linked package sources): rewritten relative to the root, which keeps\n// the mapping unique without shipping the home directory or the layout above root\nconst MACHINE_PATH_RE = /(file:\\/\\/)?\\/(?:Users|home)\\/[^\"'`\\s]+/g;\n\nfunction collectFiles(dir: string, out: string[]): void {\n for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {\n const abs = path.join(dir, entry.name);\n\n if (entry.isDirectory()) {\n collectFiles(abs, out);\n } else if (TEXT_EXTENSIONS.has(path.extname(entry.name))) {\n out.push(abs);\n }\n }\n}\n\n/**\n * Replace the build machine's project root in the server build output with a\n * stable placeholder.\n *\n * Astro serializes absolute `file://` config URLs (root, outDir, srcDir, ...)\n * and absolute component module ids into the SSR manifest, and the compiled\n * chunks and sourcemaps carry the same absolute paths. Shipping them makes the\n * artifact irreproducible and leaks the developer's filesystem layout. The\n * replacement is applied uniformly, so manifest keys keep matching the module\n * ids embedded in chunks, and the server→client relative resolution keeps\n * working.\n *\n * Returns the number of files rewritten.\n */\nexport function stripBuildPaths(serverDir: string, root: string): number {\n const rootFsPosix = root.split(path.sep).join('/').replace(/\\/+$/, '');\n const rootUrl = pathToFileURL(rootFsPosix).href;\n\n const files: string[] = [];\n\n collectFiles(serverDir, files);\n\n let rewritten = 0;\n\n for (const file of files) {\n const content = fs.readFileSync(file, 'utf-8');\n const replaced = content\n .replaceAll(rootUrl, `file://${ROOT_PLACEHOLDER}`)\n .replaceAll(rootFsPosix, ROOT_PLACEHOLDER)\n .replace(\n HOISTED_STORE_RE,\n (_, proto: string | undefined, suffix: string) => `${proto ?? ''}${ROOT_PLACEHOLDER}/node_modules/${suffix}`,\n )\n .replace(MACHINE_PATH_RE, (match, proto: string | undefined) => {\n const rel = path.posix.relative(rootFsPosix, proto ? match.slice(proto.length) : match);\n\n return `${proto ?? ''}${ROOT_PLACEHOLDER}/${rel}`;\n });\n\n if (replaced !== content) {\n fs.writeFileSync(file, replaced);\n rewritten++;\n }\n }\n\n return rewritten;\n}\n"],"mappings":";;;;;;;;;AASA,MAAa,mBAAmB;AAEhC,MAAM,kCAAkB,IAAI,IAAI;CAAC;CAAQ;CAAO;CAAQ;CAAS;AAAM,CAAC;AAMxE,MAAM,mBAAmB;AAKzB,MAAM,kBAAkB;AAExB,SAAS,aAAa,KAAa,KAAqB;CACtD,KAAK,MAAM,SAAS,GAAG,YAAY,KAAK,EAAE,eAAe,KAAK,CAAC,GAAG;EAChE,MAAM,MAAM,KAAK,KAAK,KAAK,MAAM,IAAI;EAErC,IAAI,MAAM,YAAY,GACpB,aAAa,KAAK,GAAG;OAChB,IAAI,gBAAgB,IAAI,KAAK,QAAQ,MAAM,IAAI,CAAC,GACrD,IAAI,KAAK,GAAG;CAEhB;AACF;;;;;;;;;;;;;;;AAgBA,SAAgB,gBAAgB,WAAmB,MAAsB;CACvE,MAAM,cAAc,KAAK,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,QAAQ,QAAQ,EAAE;CACrE,MAAM,UAAU,cAAc,WAAW,CAAC,CAAC;CAE3C,MAAM,QAAkB,CAAC;CAEzB,aAAa,WAAW,KAAK;CAE7B,IAAI,YAAY;CAEhB,KAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,UAAU,GAAG,aAAa,MAAM,OAAO;EAC7C,MAAM,WAAW,QACd,WAAW,SAAS,UAAU,kBAAkB,CAAC,CACjD,WAAW,aAAa,gBAAgB,CAAC,CACzC,QACC,mBACC,GAAG,OAA2B,WAAmB,GAAG,SAAS,KAAK,iBAAiB,gBAAgB,QACtG,CAAC,CACA,QAAQ,kBAAkB,OAAO,UAA8B;GAC9D,MAAM,MAAM,KAAK,MAAM,SAAS,aAAa,QAAQ,MAAM,MAAM,MAAM,MAAM,IAAI,KAAK;GAEtF,OAAO,GAAG,SAAS,KAAK,iBAAiB,GAAG;EAC9C,CAAC;EAEH,IAAI,aAAa,SAAS;GACxB,GAAG,cAAc,MAAM,QAAQ;GAC/B;EACF;CACF;CAEA,OAAO;AACT"}
|
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
import { n as log } from "./log-B69HEBvg.js";
|
|
2
|
-
import { n as getIslandEmitters } from "./emitters-C20tjKLp.js";
|
|
3
|
-
import { RewritingStream } from "parse5-html-rewriting-stream";
|
|
4
|
-
//#region src/islands/graph.ts
|
|
5
|
-
function createClosure(chunks, edges) {
|
|
6
|
-
const cache = /* @__PURE__ */ new Map();
|
|
7
|
-
const walk = (fileName, result, visited) => {
|
|
8
|
-
if (visited.has(fileName)) return;
|
|
9
|
-
visited.add(fileName);
|
|
10
|
-
const entry = chunks[fileName];
|
|
11
|
-
if (!entry) return;
|
|
12
|
-
for (const dep of edges(entry)) {
|
|
13
|
-
result.add(dep);
|
|
14
|
-
walk(dep, result, visited);
|
|
15
|
-
}
|
|
16
|
-
};
|
|
17
|
-
return (fileName) => {
|
|
18
|
-
const cached = cache.get(fileName);
|
|
19
|
-
if (cached) return cached;
|
|
20
|
-
const result = /* @__PURE__ */ new Set();
|
|
21
|
-
walk(fileName, result, /* @__PURE__ */ new Set());
|
|
22
|
-
result.delete(fileName);
|
|
23
|
-
const list = [...result];
|
|
24
|
-
cache.set(fileName, list);
|
|
25
|
-
return list;
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
function createChunkGraph(manifest) {
|
|
29
|
-
return {
|
|
30
|
-
staticClosure: createClosure(manifest.chunks, (entry) => entry.i ?? []),
|
|
31
|
-
fullClosure: createClosure(manifest.chunks, (entry) => [...entry.i ?? [], ...entry.d ?? []]),
|
|
32
|
-
has: (fileName) => fileName in manifest.chunks
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
//#endregion
|
|
36
|
-
//#region src/islands/rewriter.ts
|
|
37
|
-
const ISLAND = "astro-island";
|
|
38
|
-
function encodeAttribute(value) {
|
|
39
|
-
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<");
|
|
40
|
-
}
|
|
41
|
-
function createIslandRewriter(onIsland) {
|
|
42
|
-
const stream = new RewritingStream();
|
|
43
|
-
const out = [];
|
|
44
|
-
stream.on("data", (chunk) => {
|
|
45
|
-
out.push(typeof chunk === "string" ? chunk : chunk.toString());
|
|
46
|
-
});
|
|
47
|
-
stream.on("startTag", (tag, rawHtml) => {
|
|
48
|
-
if (tag.tagName === ISLAND) {
|
|
49
|
-
const attrs = {};
|
|
50
|
-
for (const attr of tag.attrs) attrs[attr.name] = attr.value;
|
|
51
|
-
const rewrite = onIsland(attrs);
|
|
52
|
-
if (rewrite?.prepend) stream.emitRaw(rewrite.prepend);
|
|
53
|
-
}
|
|
54
|
-
stream.emitRaw(rawHtml);
|
|
55
|
-
});
|
|
56
|
-
const drain = () => out.splice(0).join("");
|
|
57
|
-
return {
|
|
58
|
-
write(chunk) {
|
|
59
|
-
stream.write(chunk);
|
|
60
|
-
return drain();
|
|
61
|
-
},
|
|
62
|
-
end() {
|
|
63
|
-
return new Promise((resolve, reject) => {
|
|
64
|
-
stream.once("error", reject);
|
|
65
|
-
stream.end(() => resolve(drain()));
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
const IMMEDIATE_DIRECTIVES = /* @__PURE__ */ new Set(["load", "only"]);
|
|
71
|
-
/** urls cannot contain `<\/script>`, but escape `<` anyway so no value ever can */
|
|
72
|
-
function jsonForScript(value) {
|
|
73
|
-
return JSON.stringify(value).replace(/</g, "\\u003c");
|
|
74
|
-
}
|
|
75
|
-
function createIslandsTransformer(manifest) {
|
|
76
|
-
const graph = createChunkGraph(manifest);
|
|
77
|
-
const fileNames = Object.keys(manifest.chunks);
|
|
78
|
-
const resolveCache = /* @__PURE__ */ new Map();
|
|
79
|
-
const resolve = (url) => {
|
|
80
|
-
const cached = resolveCache.get(url);
|
|
81
|
-
if (cached !== void 0) return cached;
|
|
82
|
-
let resolved = null;
|
|
83
|
-
for (const fileName of fileNames) if (url.endsWith(fileName)) {
|
|
84
|
-
const boundary = url.length - fileName.length;
|
|
85
|
-
if (boundary === 0 || url[boundary - 1] === "/") {
|
|
86
|
-
resolved = {
|
|
87
|
-
prefix: url.slice(0, boundary),
|
|
88
|
-
fileName
|
|
89
|
-
};
|
|
90
|
-
break;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
resolveCache.set(url, resolved);
|
|
94
|
-
return resolved;
|
|
95
|
-
};
|
|
96
|
-
const createDocumentRewriter = (context) => {
|
|
97
|
-
const emittedLinks = /* @__PURE__ */ new Set();
|
|
98
|
-
const registeredComponents = /* @__PURE__ */ new Set();
|
|
99
|
-
let runtimeInjected = false;
|
|
100
|
-
return createIslandRewriter((attrs) => {
|
|
101
|
-
const componentUrl = attrs["component-url"];
|
|
102
|
-
if (!componentUrl) return null;
|
|
103
|
-
const component = resolve(componentUrl);
|
|
104
|
-
if (!component) return null;
|
|
105
|
-
const rendererUrl = attrs["renderer-url"] || null;
|
|
106
|
-
const renderer = rendererUrl ? resolve(rendererUrl) : null;
|
|
107
|
-
const closureUrls = (closure) => {
|
|
108
|
-
const urls = /* @__PURE__ */ new Set([componentUrl, ...closure(component.fileName).map((f) => component.prefix + f)]);
|
|
109
|
-
if (rendererUrl) {
|
|
110
|
-
urls.add(rendererUrl);
|
|
111
|
-
for (const f of renderer ? closure(renderer.fileName) : []) urls.add(renderer.prefix + f);
|
|
112
|
-
}
|
|
113
|
-
return [...urls];
|
|
114
|
-
};
|
|
115
|
-
const island = {
|
|
116
|
-
componentUrl,
|
|
117
|
-
rendererUrl,
|
|
118
|
-
client: attrs["client"] || null,
|
|
119
|
-
staticClosure: closureUrls(graph.staticClosure),
|
|
120
|
-
fullClosure: closureUrls(graph.fullClosure)
|
|
121
|
-
};
|
|
122
|
-
const emissions = [{ links: island.staticClosure }];
|
|
123
|
-
for (const emitter of getIslandEmitters()) try {
|
|
124
|
-
const emission = emitter(island, context);
|
|
125
|
-
if (emission) emissions.push(emission);
|
|
126
|
-
} catch (error) {
|
|
127
|
-
log.error({
|
|
128
|
-
err: error,
|
|
129
|
-
componentUrl
|
|
130
|
-
}, "island emitter failed");
|
|
131
|
-
}
|
|
132
|
-
const links = [...new Set(emissions.flatMap((e) => e.links ?? []))];
|
|
133
|
-
const html = emissions.map((e) => e.html ?? "").join("");
|
|
134
|
-
const directive = (island.client ?? "").replace(/-x$/, "");
|
|
135
|
-
if (IMMEDIATE_DIRECTIVES.has(directive)) {
|
|
136
|
-
const fresh = links.filter((url) => !emittedLinks.has(url));
|
|
137
|
-
for (const url of fresh) emittedLinks.add(url);
|
|
138
|
-
return { prepend: html + fresh.map((url) => `<link rel="modulepreload" fetchpriority="low" href="${encodeAttribute(url)}">`).join("") || void 0 };
|
|
139
|
-
}
|
|
140
|
-
if (registeredComponents.has(componentUrl)) return { prepend: html || void 0 };
|
|
141
|
-
registeredComponents.add(componentUrl);
|
|
142
|
-
const runtime = runtimeInjected ? "" : `<script>${manifest.runtimeSource}<\/script>`;
|
|
143
|
-
runtimeInjected = true;
|
|
144
|
-
const imports = [...new Set(emissions.flatMap((e) => e.imports ?? []))];
|
|
145
|
-
const importSet = new Set(imports);
|
|
146
|
-
const entry = { l: links.filter((url) => !importSet.has(url)) };
|
|
147
|
-
if (imports.length > 0) entry["i"] = imports;
|
|
148
|
-
return { prepend: runtime + `<script>(self.__islands__??={})[${jsonForScript(componentUrl)}]=${jsonForScript(entry)};<\/script>` + html || void 0 };
|
|
149
|
-
});
|
|
150
|
-
};
|
|
151
|
-
return { createDocumentRewriter };
|
|
152
|
-
}
|
|
153
|
-
/**
|
|
154
|
-
* The single streaming pass over an html page response: chunks go through the
|
|
155
|
-
* islands rewriter (when present), head content is inserted right after the
|
|
156
|
-
* opening `<head>` tag (which requires buffering the whole document — head
|
|
157
|
-
* content is dev-style delivery, where throughput is irrelevant), and end
|
|
158
|
-
* factories run last, strictly after every island passed the rewriter.
|
|
159
|
-
*/
|
|
160
|
-
function createPageTransformStream(options) {
|
|
161
|
-
const { rewriter, heads, ends } = options;
|
|
162
|
-
const decoder = new TextDecoder();
|
|
163
|
-
const encoder = new TextEncoder();
|
|
164
|
-
const buffered = heads.length > 0 ? [] : null;
|
|
165
|
-
const enqueue = (controller, text) => {
|
|
166
|
-
if (text) controller.enqueue(encoder.encode(text));
|
|
167
|
-
};
|
|
168
|
-
return new TransformStream({
|
|
169
|
-
transform(chunk, controller) {
|
|
170
|
-
let text = decoder.decode(chunk, { stream: true });
|
|
171
|
-
if (rewriter) text = rewriter.write(text);
|
|
172
|
-
if (buffered) buffered.push(text);
|
|
173
|
-
else enqueue(controller, text);
|
|
174
|
-
},
|
|
175
|
-
async flush(controller) {
|
|
176
|
-
let tail = decoder.decode();
|
|
177
|
-
if (rewriter) tail = (tail ? rewriter.write(tail) : "") + await rewriter.end();
|
|
178
|
-
if (buffered) {
|
|
179
|
-
const html = buffered.join("") + tail;
|
|
180
|
-
const head = /<head[^>]*>/i.exec(html);
|
|
181
|
-
const at = head ? head.index + head[0].length : 0;
|
|
182
|
-
enqueue(controller, html.slice(0, at) + heads.join("") + html.slice(at));
|
|
183
|
-
} else enqueue(controller, tail);
|
|
184
|
-
for (const end of ends) try {
|
|
185
|
-
enqueue(controller, end() ?? "");
|
|
186
|
-
} catch (error) {
|
|
187
|
-
log.error({ err: error }, "document emitter failed");
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
//#endregion
|
|
193
|
-
export { createPageTransformStream as n, createIslandsTransformer as t };
|
|
194
|
-
|
|
195
|
-
//# sourceMappingURL=transform-BqV8SOEm.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"transform-BqV8SOEm.js","names":[],"sources":["../src/islands/graph.ts","../src/islands/rewriter.ts","../src/islands/transform.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","import { RewritingStream } from 'parse5-html-rewriting-stream';\n\n/**\n * Streaming HTML rewriter that finds `<astro-island …>` opening tags, hands their\n * attributes to a handler, and prepends the handler's html before the tag — the\n * tag itself is always re-emitted from its raw source, byte-verbatim.\n *\n * Tokenization is parse5's — the reference WHATWG implementation — so every\n * context where island markup is not actually an element to the browser\n * (comments, raw-text elements like `script`/`iframe`/`noscript`, doctypes,\n * quoted attribute values) is skipped by construction rather than by our own\n * spec-tracking; unmodified content passes through as raw source slices.\n * Attribute values arrive with the full entity set decoded.\n *\n * Closing tags are never touched: injection happens before the island's opening\n * tag precisely so the island's subtree — the hydration target — stays untouched.\n *\n * The test suite pins the behavior this package relies on, so a parse5 upgrade\n * that changes it fails here rather than degrading silently — same philosophy as\n * the astro compatibility surface.\n */\n\nexport type IslandTagRewrite = {\n /** html emitted immediately before the island opening tag */\n prepend?: string | undefined;\n};\n\nexport type IslandTagHandler = (attrs: Record<string, string>) => IslandTagRewrite | null;\n\nexport type IslandRewriter = {\n write(chunk: string): string;\n end(): Promise<string>;\n};\n\nconst ISLAND = 'astro-island';\n\nexport function encodeAttribute(value: string): string {\n return value.replace(/&/g, '&').replace(/\"/g, '"').replace(/</g, '<');\n}\n\nexport function createIslandRewriter(onIsland: IslandTagHandler): IslandRewriter {\n const stream = new RewritingStream();\n const out: string[] = [];\n\n stream.on('data', (chunk: string | Buffer) => {\n out.push(typeof chunk === 'string' ? chunk : chunk.toString());\n });\n\n stream.on('startTag', (tag, rawHtml) => {\n if (tag.tagName === ISLAND) {\n const attrs: Record<string, string> = {};\n\n for (const attr of tag.attrs) {\n attrs[attr.name] = attr.value;\n }\n\n const rewrite = onIsland(attrs);\n\n if (rewrite?.prepend) {\n stream.emitRaw(rewrite.prepend);\n }\n }\n\n stream.emitRaw(rawHtml);\n });\n\n const drain = (): string => out.splice(0).join('');\n\n return {\n // a transform stream with an attached data listener processes writes\n // synchronously, so each write can hand back its output right away\n write(chunk) {\n stream.write(chunk);\n\n return drain();\n },\n end() {\n return new Promise((resolve, reject) => {\n stream.once('error', reject);\n stream.end(() => resolve(drain()));\n });\n },\n };\n}\n","import type { APIContext } from 'astro';\nimport { log } from '../observability/log/index.js';\nimport { getIslandEmitters } from './emitters.js';\nimport { createChunkGraph } from './graph.js';\nimport { type IslandRewriter, createIslandRewriter, encodeAttribute } from './rewriter.js';\nimport type { IslandEmission, IslandInfo, IslandsManifest } from './types.js';\n\n/**\n * Turns the manifest into per-document rewriters: resolves each island's chunk\n * closures, collects emissions (the built-in preloader plus registered emitters),\n * and applies the emission policy —\n *\n * - immediate directives (`load`, `only`) get `<link rel=\"modulepreload\">` tags\n * before the tag, deduplicated per document; the browser's speculative preload\n * scanner acts on them from raw bytes, ahead of any script.\n * - deferred directives get an inline script before the first island per\n * component that registers `{l: links, i: imports}` on the preload global,\n * keyed by `component-url`; the gate runtime injects the links and eagerly\n * `import()`s the imports by the directive's own scheduling semantics. A script\n * rather than an attribute: it takes effect at parse time and its effect\n * survives the island element being removed — the island tag itself stays\n * untouched.\n *\n * The gate runtime is inlined once, before the first island that needs it, so\n * pages without deferred islands never carry it — and pages with them install\n * the gates at parse time, without an external fetch astro's inline island\n * machinery would always beat.\n */\n\n/** global registry of `{l, i}` entries per component-url, written by the transform, read by the gate runtime */\nexport const PRELOAD_GLOBAL = '__islands__';\n\nconst IMMEDIATE_DIRECTIVES = new Set(['load', 'only']);\n\n/** urls cannot contain `</script>`, but escape `<` anyway so no value ever can */\nfunction jsonForScript(value: unknown): string {\n return JSON.stringify(value).replace(/</g, '\\\\u003c');\n}\n\ntype ResolvedChunk = { prefix: string; fileName: string };\n\nexport type IslandsTransformer = {\n createDocumentRewriter(context?: APIContext | undefined): IslandRewriter;\n};\n\nexport function createIslandsTransformer(manifest: IslandsManifest): IslandsTransformer {\n const graph = createChunkGraph(manifest);\n const fileNames = Object.keys(manifest.chunks);\n const resolveCache = new Map<string, ResolvedChunk | null>();\n\n // islands carry public urls (base and asset prefix baked in), the manifest holds\n // dist-relative file names — match by suffix at a path boundary, so the observed\n // prefix maps closures and the runtime script back to public urls with no config\n const resolve = (url: string): ResolvedChunk | null => {\n const cached = resolveCache.get(url);\n\n if (cached !== undefined) {\n return cached;\n }\n\n let resolved: ResolvedChunk | null = null;\n\n for (const fileName of fileNames) {\n if (url.endsWith(fileName)) {\n const boundary = url.length - fileName.length;\n\n if (boundary === 0 || url[boundary - 1] === '/') {\n resolved = { prefix: url.slice(0, boundary), fileName };\n break;\n }\n }\n }\n\n resolveCache.set(url, resolved);\n\n return resolved;\n };\n\n const createDocumentRewriter = (context?: APIContext | undefined): IslandRewriter => {\n const emittedLinks = new Set<string>();\n const registeredComponents = new Set<string>();\n let runtimeInjected = false;\n\n return createIslandRewriter((attrs) => {\n const componentUrl = attrs['component-url'];\n\n if (!componentUrl) {\n return null;\n }\n\n const component = resolve(componentUrl);\n\n if (!component) {\n return null;\n }\n\n const rendererUrl = attrs['renderer-url'] || null;\n const renderer = rendererUrl ? resolve(rendererUrl) : null;\n\n const closureUrls = (closure: (fileName: string) => string[]): string[] => {\n const urls = new Set([componentUrl, ...closure(component.fileName).map((f) => component.prefix + f)]);\n\n if (rendererUrl) {\n urls.add(rendererUrl);\n\n for (const f of renderer ? closure(renderer.fileName) : []) {\n urls.add(renderer!.prefix + f);\n }\n }\n\n return [...urls];\n };\n\n const island: IslandInfo = {\n componentUrl,\n rendererUrl,\n client: attrs['client'] || null,\n staticClosure: closureUrls(graph.staticClosure),\n fullClosure: closureUrls(graph.fullClosure),\n };\n\n const emissions: IslandEmission[] = [{ links: island.staticClosure }];\n\n for (const emitter of getIslandEmitters()) {\n try {\n const emission = emitter(island, context);\n\n if (emission) {\n emissions.push(emission);\n }\n } catch (error) {\n // an emitter must never break the page — its contribution is dropped\n log.error({ err: error, componentUrl }, 'island emitter failed');\n }\n }\n\n const links = [...new Set(emissions.flatMap((e) => e.links ?? []))];\n const html = emissions.map((e) => e.html ?? '').join('');\n const directive = (island.client ?? '').replace(/-x$/, '');\n\n if (IMMEDIATE_DIRECTIVES.has(directive)) {\n const fresh = links.filter((url) => !emittedLinks.has(url));\n\n for (const url of fresh) {\n emittedLinks.add(url);\n }\n\n const prepend =\n html +\n fresh.map((url) => `<link rel=\"modulepreload\" fetchpriority=\"low\" href=\"${encodeAttribute(url)}\">`).join('');\n\n return { prepend: prepend || undefined };\n }\n\n if (registeredComponents.has(componentUrl)) {\n return { prepend: html || undefined };\n }\n\n registeredComponents.add(componentUrl);\n\n const runtime = runtimeInjected ? '' : `<script>${manifest.runtimeSource}</script>`;\n\n runtimeInjected = true;\n\n const imports = [...new Set(emissions.flatMap((e) => e.imports ?? []))];\n const importSet = new Set(imports);\n const entry: Record<string, string[]> = { l: links.filter((url) => !importSet.has(url)) };\n\n if (imports.length > 0) {\n entry['i'] = imports;\n }\n\n const register = `<script>(self.${PRELOAD_GLOBAL}??={})[${jsonForScript(componentUrl)}]=${jsonForScript(entry)};</script>`;\n\n return { prepend: runtime + register + html || undefined };\n });\n };\n\n return { createDocumentRewriter };\n}\n\nexport type PageTransformOptions = {\n /** the islands rewriter for this document, when preloading is on (prod with a manifest) */\n rewriter?: IslandRewriter | undefined;\n /** scripts inserted right after the opening `<head>` tag — buffers the response */\n heads: string[];\n /** script factories invoked after the rewriter finished, results appended at stream end */\n ends: (() => string | null)[];\n};\n\n/**\n * The single streaming pass over an html page response: chunks go through the\n * islands rewriter (when present), head content is inserted right after the\n * opening `<head>` tag (which requires buffering the whole document — head\n * content is dev-style delivery, where throughput is irrelevant), and end\n * factories run last, strictly after every island passed the rewriter.\n */\nexport function createPageTransformStream(options: PageTransformOptions): TransformStream<Uint8Array, Uint8Array> {\n const { rewriter, heads, ends } = options;\n const decoder = new TextDecoder();\n const encoder = new TextEncoder();\n const buffered = heads.length > 0 ? ([] as string[]) : null;\n\n const enqueue = (controller: TransformStreamDefaultController<Uint8Array>, text: string): void => {\n if (text) {\n controller.enqueue(encoder.encode(text));\n }\n };\n\n return new TransformStream({\n transform(chunk, controller) {\n let text = decoder.decode(chunk, { stream: true });\n\n if (rewriter) {\n text = rewriter.write(text);\n }\n\n if (buffered) {\n buffered.push(text);\n } else {\n enqueue(controller, text);\n }\n },\n async flush(controller) {\n let tail = decoder.decode();\n\n if (rewriter) {\n tail = (tail ? rewriter.write(tail) : '') + (await rewriter.end());\n }\n\n if (buffered) {\n const html = buffered.join('') + tail;\n // nothing but the doctype, comments and <html> can precede <head>, so the\n // first match cannot sit inside script raw text\n const head = /<head[^>]*>/i.exec(html);\n const at = head ? head.index + head[0].length : 0;\n\n enqueue(controller, html.slice(0, at) + heads.join('') + html.slice(at));\n } else {\n enqueue(controller, tail);\n }\n\n for (const end of ends) {\n try {\n enqueue(controller, end() ?? '');\n } catch (error) {\n // an emitter must never break the page — its contribution is dropped\n log.error({ err: error }, 'document emitter failed');\n }\n }\n },\n });\n}\n"],"mappings":"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"}
|