gemi 0.171.0 → 0.173.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai/index.js +2 -2
- package/dist/ai/index.js.map +1 -1
- package/dist/app/App.d.ts +10 -0
- package/dist/app/App.d.ts.map +1 -1
- package/dist/app/index.js +1 -1
- package/dist/bin/gemi.js +4 -1
- package/dist/bin/gemi.js.map +3 -3
- package/dist/bun/plugin.js +1 -1
- package/dist/bun/preload.js +1 -1
- package/dist/{chunk-thbs63gy.js → chunk-05k9gmr9.js} +1 -1
- package/dist/{chunk-fb77hwk0.js → chunk-0v46hne6.js} +3 -3
- package/dist/{chunk-fb77hwk0.js.map → chunk-0v46hne6.js.map} +1 -1
- package/dist/{chunk-mm45shzv.js → chunk-1hhwqx50.js} +2 -2
- package/dist/{chunk-mm45shzv.js.map → chunk-1hhwqx50.js.map} +1 -1
- package/dist/chunk-1pvfgfhj.js +117 -0
- package/dist/{chunk-3qtn3zjt.js.map → chunk-1pvfgfhj.js.map} +8 -8
- package/dist/chunk-2mvz2pjr.js +5 -0
- package/dist/{chunk-f4nanvkm.js.map → chunk-2mvz2pjr.js.map} +1 -1
- package/dist/{chunk-q3511sb5.js → chunk-2p7zph5z.js} +2 -2
- package/dist/{chunk-q3511sb5.js.map → chunk-2p7zph5z.js.map} +1 -1
- package/dist/{chunk-c46vhvk4.js → chunk-3esnjxv9.js} +2 -2
- package/dist/{chunk-c46vhvk4.js.map → chunk-3esnjxv9.js.map} +1 -1
- package/dist/{chunk-5n21z4n8.js → chunk-4dnr1vg7.js} +2 -2
- package/dist/{chunk-5n21z4n8.js.map → chunk-4dnr1vg7.js.map} +1 -1
- package/dist/chunk-4hhvaa7a.js +5 -0
- package/dist/{chunk-1h6g1hkg.js.map → chunk-4hhvaa7a.js.map} +3 -3
- package/dist/{chunk-df2zeevj.js → chunk-5yvy70d7.js} +2 -2
- package/dist/{chunk-df2zeevj.js.map → chunk-5yvy70d7.js.map} +1 -1
- package/dist/{chunk-31np60bx.js → chunk-6016sfy5.js} +2 -2
- package/dist/{chunk-31np60bx.js.map → chunk-6016sfy5.js.map} +1 -1
- package/dist/{chunk-sdn3dmpp.js → chunk-699j2epd.js} +4 -4
- package/dist/{chunk-sdn3dmpp.js.map → chunk-699j2epd.js.map} +1 -1
- package/dist/{chunk-gwphn604.js → chunk-6rh3brnr.js} +3 -3
- package/dist/{chunk-gwphn604.js.map → chunk-6rh3brnr.js.map} +1 -1
- package/dist/{chunk-s71e6k9m.js → chunk-6srzhv88.js} +3 -3
- package/dist/{chunk-s71e6k9m.js.map → chunk-6srzhv88.js.map} +1 -1
- package/dist/{chunk-0wet87jm.js → chunk-6vmc8eak.js} +2 -2
- package/dist/{chunk-0wet87jm.js.map → chunk-6vmc8eak.js.map} +1 -1
- package/dist/{chunk-rpdekb8t.js → chunk-7gc1jp2z.js} +2 -2
- package/dist/{chunk-rpdekb8t.js.map → chunk-7gc1jp2z.js.map} +1 -1
- package/dist/{chunk-y3s05q6b.js → chunk-7mp369vg.js} +2 -2
- package/dist/{chunk-y3s05q6b.js.map → chunk-7mp369vg.js.map} +1 -1
- package/dist/{chunk-44y62yav.js → chunk-8tzmat5s.js} +3 -3
- package/dist/{chunk-44y62yav.js.map → chunk-8tzmat5s.js.map} +1 -1
- package/dist/{chunk-sv86v56c.js → chunk-97aeny79.js} +3 -3
- package/dist/{chunk-sv86v56c.js.map → chunk-97aeny79.js.map} +1 -1
- package/dist/{chunk-978pc27j.js → chunk-9bcsfce4.js} +2 -2
- package/dist/{chunk-978pc27j.js.map → chunk-9bcsfce4.js.map} +1 -1
- package/dist/{chunk-sp77zwpn.js → chunk-9pet8zmd.js} +2 -2
- package/dist/{chunk-sp77zwpn.js.map → chunk-9pet8zmd.js.map} +1 -1
- package/dist/{chunk-4mtqbcc5.js → chunk-9r4rvyfr.js} +2 -2
- package/dist/{chunk-4mtqbcc5.js.map → chunk-9r4rvyfr.js.map} +1 -1
- package/dist/{chunk-vfvjfw5k.js → chunk-9zcsshgb.js} +2 -2
- package/dist/{chunk-vfvjfw5k.js.map → chunk-9zcsshgb.js.map} +1 -1
- package/dist/{chunk-krdq5s4y.js → chunk-b1grj1t0.js} +2 -2
- package/dist/{chunk-krdq5s4y.js.map → chunk-b1grj1t0.js.map} +1 -1
- package/dist/{chunk-q788x8pm.js → chunk-bs8mrewm.js} +2 -2
- package/dist/{chunk-q788x8pm.js.map → chunk-bs8mrewm.js.map} +1 -1
- package/dist/{chunk-8b9ks729.js → chunk-bsnp7h5w.js} +3 -3
- package/dist/{chunk-8b9ks729.js.map → chunk-bsnp7h5w.js.map} +1 -1
- package/dist/{chunk-z4dhd5pk.js → chunk-c9q856yk.js} +2 -2
- package/dist/{chunk-z4dhd5pk.js.map → chunk-c9q856yk.js.map} +1 -1
- package/dist/{chunk-2v7wq01w.js → chunk-cx05t002.js} +2 -2
- package/dist/{chunk-2v7wq01w.js.map → chunk-cx05t002.js.map} +1 -1
- package/dist/{chunk-p56zntmt.js → chunk-d084zwp9.js} +3 -3
- package/dist/{chunk-p56zntmt.js.map → chunk-d084zwp9.js.map} +1 -1
- package/dist/{chunk-6t1t7h05.js → chunk-d7a79eqm.js} +2 -2
- package/dist/{chunk-6t1t7h05.js.map → chunk-d7a79eqm.js.map} +1 -1
- package/dist/{chunk-aj1jrwwh.js → chunk-df0nx9d6.js} +2 -2
- package/dist/{chunk-aj1jrwwh.js.map → chunk-df0nx9d6.js.map} +1 -1
- package/dist/{chunk-hwpp9b7f.js → chunk-dqj22m1r.js} +2 -2
- package/dist/{chunk-9wcv7e1a.js.map → chunk-dqj22m1r.js.map} +1 -1
- package/dist/chunk-dxtry1gh.js +10 -0
- package/dist/{chunk-212m5z3p.js.map → chunk-dxtry1gh.js.map} +3 -3
- package/dist/{chunk-kbw6y0ag.js → chunk-erwawm84.js} +2 -2
- package/dist/{chunk-kbw6y0ag.js.map → chunk-erwawm84.js.map} +1 -1
- package/dist/{chunk-9ft0ayhf.js → chunk-eykb93pv.js} +2 -2
- package/dist/{chunk-9ft0ayhf.js.map → chunk-eykb93pv.js.map} +1 -1
- package/dist/{chunk-8hfyscnn.js → chunk-f1my4jah.js} +1 -1
- package/dist/{chunk-drwjwtmj.js → chunk-f4wkm07x.js} +2 -2
- package/dist/{chunk-drwjwtmj.js.map → chunk-f4wkm07x.js.map} +1 -1
- package/dist/{chunk-4tt14jnx.js → chunk-fqw81xgw.js} +2 -2
- package/dist/{chunk-4tt14jnx.js.map → chunk-fqw81xgw.js.map} +1 -1
- package/dist/{chunk-hgpvfhtp.js → chunk-g3m7t5zc.js} +1 -1
- package/dist/{chunk-0wk992a5.js → chunk-h041bq3j.js} +2 -2
- package/dist/{chunk-0wk992a5.js.map → chunk-h041bq3j.js.map} +1 -1
- package/dist/{chunk-sxf94q13.js → chunk-h3jb2g2g.js} +2 -2
- package/dist/{chunk-sxf94q13.js.map → chunk-h3jb2g2g.js.map} +1 -1
- package/dist/{chunk-vnk4vaer.js → chunk-hr9twa8v.js} +3 -3
- package/dist/{chunk-vnk4vaer.js.map → chunk-hr9twa8v.js.map} +1 -1
- package/dist/{chunk-21hppqtb.js → chunk-jhn1h70n.js} +2 -2
- package/dist/{chunk-21hppqtb.js.map → chunk-jhn1h70n.js.map} +1 -1
- package/dist/{chunk-wp9hw3we.js → chunk-jv48eg0a.js} +2 -2
- package/dist/{chunk-wp9hw3we.js.map → chunk-jv48eg0a.js.map} +1 -1
- package/dist/{chunk-3682hkq0.js → chunk-k9ndr16q.js} +2 -2
- package/dist/{chunk-3682hkq0.js.map → chunk-k9ndr16q.js.map} +1 -1
- package/dist/{chunk-4qpyz627.js → chunk-kknybxbv.js} +2 -2
- package/dist/{chunk-4qpyz627.js.map → chunk-kknybxbv.js.map} +1 -1
- package/dist/{chunk-a9frf7ym.js → chunk-ksgeqtcv.js} +2 -2
- package/dist/{chunk-a9frf7ym.js.map → chunk-ksgeqtcv.js.map} +1 -1
- package/dist/{chunk-ta2wsjz6.js → chunk-kxgxeead.js} +2 -2
- package/dist/{chunk-ta2wsjz6.js.map → chunk-kxgxeead.js.map} +1 -1
- package/dist/{chunk-xkas0yp8.js → chunk-m0nvysa5.js} +3 -3
- package/dist/{chunk-xkas0yp8.js.map → chunk-m0nvysa5.js.map} +1 -1
- package/dist/{chunk-fcbgshf5.js → chunk-m250ejpg.js} +2 -2
- package/dist/{chunk-fcbgshf5.js.map → chunk-m250ejpg.js.map} +1 -1
- package/dist/{chunk-embwksec.js → chunk-mbshptjq.js} +2 -2
- package/dist/{chunk-embwksec.js.map → chunk-mbshptjq.js.map} +1 -1
- package/dist/{chunk-m6sj1q1q.js → chunk-nfj6k1a6.js} +2 -2
- package/dist/{chunk-m6sj1q1q.js.map → chunk-nfj6k1a6.js.map} +1 -1
- package/dist/{chunk-2xtkh8bh.js → chunk-pbc3w7mq.js} +2 -2
- package/dist/{chunk-2xtkh8bh.js.map → chunk-pbc3w7mq.js.map} +1 -1
- package/dist/{chunk-ztfkmpk5.js → chunk-psmwxtf5.js} +2 -2
- package/dist/{chunk-ztfkmpk5.js.map → chunk-psmwxtf5.js.map} +1 -1
- package/dist/{chunk-777v6242.js → chunk-qfqn7rz8.js} +1 -1
- package/dist/chunk-r1n2fkfz.js +4 -0
- package/dist/chunk-r1n2fkfz.js.map +10 -0
- package/dist/{chunk-ys8nk9ga.js → chunk-r22svpab.js} +3 -3
- package/dist/{chunk-ys8nk9ga.js.map → chunk-r22svpab.js.map} +1 -1
- package/dist/{chunk-jxx9mctk.js → chunk-rjsd8f8q.js} +2 -2
- package/dist/{chunk-jxx9mctk.js.map → chunk-rjsd8f8q.js.map} +1 -1
- package/dist/{chunk-k87tqe29.js → chunk-sgarq1q9.js} +2 -2
- package/dist/{chunk-k87tqe29.js.map → chunk-sgarq1q9.js.map} +1 -1
- package/dist/{chunk-jd079awh.js → chunk-sjg5g92s.js} +2 -2
- package/dist/{chunk-jd079awh.js.map → chunk-sjg5g92s.js.map} +1 -1
- package/dist/{chunk-3rp908gn.js → chunk-srvsz7vm.js} +1 -1
- package/dist/{chunk-gjwf07hy.js → chunk-tbf76adx.js} +2 -2
- package/dist/{chunk-gjwf07hy.js.map → chunk-tbf76adx.js.map} +1 -1
- package/dist/{chunk-17272ccy.js → chunk-tzne38pa.js} +3 -3
- package/dist/{chunk-17272ccy.js.map → chunk-tzne38pa.js.map} +1 -1
- package/dist/{chunk-m950vw4c.js → chunk-vpmbynke.js} +2 -2
- package/dist/{chunk-m950vw4c.js.map → chunk-vpmbynke.js.map} +1 -1
- package/dist/{chunk-s4fb4njt.js → chunk-vzz91xsm.js} +2 -2
- package/dist/{chunk-s4fb4njt.js.map → chunk-vzz91xsm.js.map} +1 -1
- package/dist/{chunk-5nz46dyw.js → chunk-w65gftzm.js} +2 -2
- package/dist/{chunk-5nz46dyw.js.map → chunk-w65gftzm.js.map} +1 -1
- package/dist/{chunk-g1jfc50d.js → chunk-w8hre8gz.js} +2 -2
- package/dist/{chunk-g1jfc50d.js.map → chunk-w8hre8gz.js.map} +1 -1
- package/dist/{chunk-wt1yzzbw.js → chunk-x2e20w8g.js} +1 -1
- package/dist/{chunk-9wcv7e1a.js → chunk-xe126csw.js} +2 -2
- package/dist/{chunk-hwpp9b7f.js.map → chunk-xe126csw.js.map} +1 -1
- package/dist/{chunk-0q65jzhv.js → chunk-xtj7cgsx.js} +2 -2
- package/dist/{chunk-0q65jzhv.js.map → chunk-xtj7cgsx.js.map} +1 -1
- package/dist/chunk-yeaftz5a.js +24 -0
- package/dist/{chunk-376s9rbe.js.map → chunk-yeaftz5a.js.map} +3 -3
- package/dist/{chunk-9akefv1y.js → chunk-ykd2ayw8.js} +2 -2
- package/dist/{chunk-9akefv1y.js.map → chunk-ykd2ayw8.js.map} +1 -1
- package/dist/{chunk-z5g56ds0.js → chunk-ywcfezjq.js} +2 -2
- package/dist/{chunk-z5g56ds0.js.map → chunk-ywcfezjq.js.map} +1 -1
- package/dist/chunk-z58zh5gm.js +58 -0
- package/dist/{chunk-eq3xexqp.js.map → chunk-z58zh5gm.js.map} +3 -3
- package/dist/{chunk-ecdjbd2w.js → chunk-zdpxwpc3.js} +3 -3
- package/dist/{chunk-ecdjbd2w.js.map → chunk-zdpxwpc3.js.map} +1 -1
- package/dist/client/Head.d.ts.map +1 -1
- package/dist/client/index.js +20 -2
- package/dist/client/index.js.map +1 -1
- package/dist/config/index.js +1 -1
- package/dist/console/run.js +2 -2
- package/dist/console/run.js.map +1 -1
- package/dist/container/index.js +2 -2
- package/dist/container/index.js.map +1 -1
- package/dist/database/index.js +2 -2
- package/dist/database/index.js.map +1 -1
- package/dist/email/index.js +2 -2
- package/dist/email/index.js.map +1 -1
- package/dist/facades/Meta.d.ts +10 -0
- package/dist/facades/Meta.d.ts.map +1 -1
- package/dist/facades/index.js +2 -2
- package/dist/facades/index.js.map +1 -1
- package/dist/foundation/index.js +2 -2
- package/dist/foundation/index.js.map +1 -1
- package/dist/http/HttpRequest.d.ts +1 -0
- package/dist/http/HttpRequest.d.ts.map +1 -1
- package/dist/http/Metadata.d.ts +15 -0
- package/dist/http/Metadata.d.ts.map +1 -1
- package/dist/http/contentDisposition.d.ts +1 -1
- package/dist/http/contentDisposition.d.ts.map +1 -1
- package/dist/http/index.js +2 -2
- package/dist/http/index.js.map +1 -1
- package/dist/http/requestContext.d.ts +1 -0
- package/dist/http/requestContext.d.ts.map +1 -1
- package/dist/i18n/dictionaryRuntime.js +2 -2
- package/dist/i18n/dictionaryRuntime.js.map +1 -1
- package/dist/i18n/index.js +2 -2
- package/dist/i18n/index.js.map +1 -1
- package/dist/kernel/index.js +2 -2
- package/dist/kernel/index.js.map +1 -1
- package/dist/orm/index.js +2 -2
- package/dist/orm/index.js.map +1 -1
- package/dist/probe/index.js +2 -2
- package/dist/probe/index.js.map +1 -1
- package/dist/server/devFetch.d.ts.map +1 -1
- package/dist/server/httpProd.d.ts.map +1 -1
- package/dist/server/index.js +2 -2
- package/dist/server/index.js.map +1 -1
- package/dist/services/index.js +2 -2
- package/dist/services/index.js.map +1 -1
- package/dist/services/queue/work.js +2 -2
- package/dist/services/queue/work.js.map +1 -1
- package/dist/services/router/DomainRouter.d.ts +21 -0
- package/dist/services/router/DomainRouter.d.ts.map +1 -1
- package/dist/services/router/ViewRouteDispatcher.d.ts +8 -0
- package/dist/services/router/ViewRouteDispatcher.d.ts.map +1 -1
- package/dist/services/router/config.d.ts +52 -3
- package/dist/services/router/config.d.ts.map +1 -1
- package/dist/services/router/globalMiddleware.d.ts.map +1 -1
- package/dist/services/router/staticCache.d.ts +8 -0
- package/dist/services/router/staticCache.d.ts.map +1 -1
- package/dist/stats/route-table.js +1 -1
- package/dist/support/index.js +2 -2
- package/dist/support/index.js.map +1 -1
- package/dist/vitest/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-1h6g1hkg.js +0 -5
- package/dist/chunk-212m5z3p.js +0 -10
- package/dist/chunk-376s9rbe.js +0 -24
- package/dist/chunk-3qtn3zjt.js +0 -117
- package/dist/chunk-743ympvp.js +0 -4
- package/dist/chunk-743ympvp.js.map +0 -10
- package/dist/chunk-eq3xexqp.js +0 -58
- package/dist/chunk-f4nanvkm.js +0 -5
- /package/dist/{chunk-thbs63gy.js.map → chunk-05k9gmr9.js.map} +0 -0
- /package/dist/{chunk-8hfyscnn.js.map → chunk-f1my4jah.js.map} +0 -0
- /package/dist/{chunk-hgpvfhtp.js.map → chunk-g3m7t5zc.js.map} +0 -0
- /package/dist/{chunk-777v6242.js.map → chunk-qfqn7rz8.js.map} +0 -0
- /package/dist/{chunk-3rp908gn.js.map → chunk-srvsz7vm.js.map} +0 -0
- /package/dist/{chunk-wt1yzzbw.js.map → chunk-x2e20w8g.js.map} +0 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../server/httpProd.ts", "../server/compression.ts", "../server/staticFile.ts", "../vite/precompressAssets.ts", "../server/generateEtag.ts", "../server/modulePreloads.ts", "../client/chunkLoadRecovery.ts", "../server/staticAssetMiss.ts", "../server/unhandledError.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"import { createHash } from \"node:crypto\";\nimport type { WebSocketHandler } from \"bun\";\nimport { join, resolve, sep } from \"node:path\";\nimport { compressResponse } from \"./compression\";\nimport { listPublicFiles, staticFileResponse } from \"./staticFile\";\nimport { stat } from \"node:fs/promises\";\nimport { createStyles } from \"./styles\";\nimport {\n CLIENT_ENTRY_KEY,\n collectCss,\n collectModulePreloads,\n createClientEntry,\n createIslandResolver,\n} from \"./modulePreloads\";\nimport type { App } from \"../app\";\nimport { Instrumentation } from \"./types\";\nimport { printStartupBanner } from \"./banner\";\nimport { isReservedAssetPath, staticAssetMiss } from \"./staticAssetMiss\";\nimport { BUILD_HEADER } from \"../client/chunkLoadRecovery\";\nimport { assetUrl, readBuiltAssetBase } from \"../config/assetBase\";\nimport { isApiPath } from \"../services/router/apiPath\";\nimport { projectRoot } from \"../support/discover\";\nimport { unhandledErrorResponse } from \"./unhandledError\";\nimport { missingRequiredViewError } from \"./requiredViews\";\nimport { serverIdleTimeout } from \"./idleTimeout\";\nimport { applyForwardedTrust, parseClientIpHeaderTrust, parseTrustProxy } from \"./forwardedFor\";\nimport { readStaticAssetsRecord } from \"../vite/precompressAssets\";\nimport type { StaticFileOptions } from \"./staticFile\";\n\n// The rule this file used to spell out itself. It moved to `projectRoot`\n// because discovery needs the same answer during `waitForBoot()`, which is\n// before `ROOT_DIR` below exists — and two spellings of \"where is the project\"\n// is how a job gets looked for in one directory and served from another.\nconst rootDir = projectRoot();\n\nconst appDir = join(rootDir, \"app\");\nconst distDir = join(rootDir, \"dist\");\n\nconst clientDir = join(distDir, \"client\");\n\n// The file in `dist/client` a static-looking pathname names, or `null` when\n// it names nothing there. The pathname is still percent-encoded, and Vite\n// writes files whose names need encoding (a public `my font.woff2`), so it is\n// decoded first. Decoding also turns `%2F` into a separator, which the URL\n// parser's own `..` normalization never saw — so the resolved path is checked\n// to still be inside `dist/client`. A malformed escape is a miss, not a 500.\nfunction clientFilePath(pathname: string): string | null {\n let decoded: string;\n try {\n decoded = decodeURIComponent(pathname);\n } catch {\n return null;\n }\n const path = resolve(clientDir, \".\" + decoded.replace(\"/assets/assets\", \"/assets\"));\n return path.startsWith(clientDir + sep) ? path : null;\n}\n\nasync function isFile(path: string) {\n try {\n return (await stat(path)).isFile();\n } catch {\n return false;\n }\n}\n\n// `app` is built by `Server.start` (from the kernel) and passed in — same as\n// `httpDev` — so prod and dev share one construction path. What differs here is\n// that views/styles are served from the built `dist/` manifests instead of\n// Vite's dev SSR graph.\nexport async function httpProd(app: App, instrumentation: Instrumentation) {\n const manifest = await import(`${distDir}/client/.vite/manifest.json`);\n const serverManifest = await import(`${distDir}/server/.vite/manifest.json`);\n // What the client build was built with, not `GEMI_ASSET_BASE` as it reads\n // now — see `readBuiltAssetBase`. Every URL below that names a chunk goes\n // through it, so the document links exactly what the bundle imports.\n const assetBase = await readBuiltAssetBase(`${distDir}/client`);\n\n process.env.ROOT_DIR = rootDir;\n process.env.APP_DIR = appDir;\n process.env.DIST_DIR = distDir;\n\n const viewImportMap = {};\n const ogMap = {};\n const viewModules = {};\n const cssManifest = {};\n // Which chunks a route's views pull in, so the shell can preload the chain\n // the client would otherwise discover one round trip at a time (#352).\n const modulePreloadManifest: Record<string, string[]> = {};\n const template = (viewName: string, path: string) => `\"${viewName}\": () => import(\"${path}\")`;\n const templates = [];\n\n const requiredViews = app.getRequiredViews.call(app);\n for (const fileName of [\"404\", ...app.getFlatComponentTree.call(app)]) {\n const serverFile = serverManifest[`app/views/${fileName}.tsx`];\n if (!serverFile?.file && requiredViews[fileName]) {\n throw missingRequiredViewError(fileName, requiredViews[fileName]);\n }\n if (!serverFile?.file) {\n console.log(`Server file not found for ${fileName}`);\n console.log(serverFile);\n const files = Object.keys(serverManifest);\n const path = `app/views/${fileName}.tsx`;\n console.log(`${path} not found in server manifest`);\n console.log(files);\n }\n const mod = await import(`${process.env.DIST_DIR}/server/${serverFile?.file}`);\n viewImportMap[fileName] = mod.default;\n ogMap[fileName] = mod.OpenGraph;\n // The whole module, so a streaming render can put the view's\n // `Loading`/`Error` exports into the shell it sends.\n viewModules[fileName] = mod;\n const clientFile = manifest[`app/views/${fileName}.tsx`];\n\n if (clientFile?.css && clientFile?.css.length > 0) {\n cssManifest[fileName] = clientFile?.css;\n }\n if (clientFile) {\n templates.push(template(fileName, assetUrl(clientFile?.file, assetBase)));\n modulePreloadManifest[fileName] = collectModulePreloads(\n manifest,\n `app/views/${fileName}.tsx`,\n assetBase,\n );\n }\n }\n\n const loaders = `{${templates.join(\",\")}}`;\n\n // Booted from the bootstrap script rather than through React's\n // `bootstrapModules`, which would preload it at `fetchPriority=\"low\"` — see\n // `clientEntry` in `ViewRouteDispatcher`.\n const clientEntry = createClientEntry(manifest, assetBase);\n if (!clientEntry) {\n // Loudly, but without dying: this is boot, and an incomplete `dist/` must\n // not cost the API routes and static assets too.\n console.error(\n `Client manifest has no \"${CLIENT_ENTRY_KEY}\" entry — dist/client is incomplete, so documents will render but never hydrate.`,\n );\n }\n\n // Vite's manifest `css` field is a `string[]` — a client entry can emit more\n // than one CSS chunk. Read and concatenate them all instead of interpolating\n // the array into a single path (which only works for a one-element array).\n const appCssFiles: string[] = manifest[\"app/client.tsx\"]?.css ?? [];\n const appCSSContent = (\n await Promise.all(appCssFiles.map((cssFile) => Bun.file(`${distDir}/client/${cssFile}`).text()))\n ).join(\"\\n\");\n\n // The island entries (`<module>?gemi-island`) a static view's document\n // loads; see `injectIslands`.\n const resolveIsland = createIslandResolver(manifest, assetBase);\n\n // Identifies this client build: a static page with `navigation` swaps in\n // only pages of the same build, and a client-side navigation answered by\n // another build loads the page in full (#1056). The manifest names every content-hashed\n // file, so it changes whenever any of them does.\n const buildId = createHash(\"sha256\")\n .update(await Bun.file(`${distDir}/client/.vite/manifest.json`).text())\n .digest(\"base64url\")\n .slice(0, 12);\n\n // A static view's CSS comes from the whole import closure of its views (and\n // its own layout's, which then replaces the app stylesheet): there is no\n // client to fetch a shared chunk's CSS later, as a hydrated page's chunk\n // loader does. Read once per file: the build does not change under a\n // running server.\n const cssFileCache = new Map<string, Promise<string>>();\n const readCss = (file: string) => {\n let text = cssFileCache.get(file);\n if (!text) {\n text = Bun.file(`${distDir}/client/${file}`).text();\n cssFileCache.set(file, text);\n }\n return text;\n };\n const staticStyles = async (views: string[], layout: string | undefined) => {\n const files = new Set<string>();\n for (const view of layout ? [layout, ...views] : views) {\n for (const file of collectCss(manifest, `app/views/${view}.tsx`)) files.add(file);\n }\n const styles = await Promise.all(\n [...files].map(async (file) => ({ id: file, content: await readCss(file) })),\n );\n return createStyles(layout ? styles : [{ content: appCSSContent }, ...styles]);\n };\n\n // Which requests are answered from `dist/client` rather than the app:\n //\n // - `/assets` and everything under it, whatever the extension. Vite writes\n // fonts, GIFs, video and `?url` JSON there too, and an allowlist of\n // extensions always trailed the build — a `.woff2` went to the router\n // and paid for an SSR attempt. The router refuses routes under `/assets`\n // at boot, so there is no app answer to lose, and `staticAssetMiss`\n // turns a miss into the reload stub or a 404.\n // - `/.well-known/*`.\n // - Any other path that names a file the build copied out of `public/`,\n // whatever its extension (#583). Any other path may be an app route, so\n // it is static only when that exact file exists — read once here, since\n // the build output does not change under a running server. A view at\n // `/manifest` keeps its data URL (`/manifest.json`) unless the app also\n // ships `public/manifest.json`, and then the file wins. View data is\n // never a file in `dist/client`, so a client-side navigation pays for no\n // filesystem lookup.\n const publicFiles = await listPublicFiles(clientDir);\n\n // The build assets `gemi build` emitted or carried over, with the\n // precompressed siblings it wrote for each (#789). Read once: the build\n // output does not change under a running server. Without the record (a\n // build made before it existed) every file is served as identity with the\n // old cache policy.\n const staticAssets = new Map<string, StaticFileOptions>();\n for (const [file, encodings] of Object.entries(\n (await readStaticAssetsRecord(clientDir))?.files ?? {},\n )) {\n const path = resolve(clientDir, file);\n if (path.startsWith(clientDir + sep)) {\n staticAssets.set(path, { immutable: true, encodings });\n }\n }\n\n async function requestHandler(req: Request) {\n const { pathname } = new URL(req.url);\n\n if (isApiPath(pathname)) {\n return await handleWithApp(req, pathname);\n }\n\n const distPath = clientFilePath(pathname);\n const isFileRequest =\n isReservedAssetPath(pathname) ||\n pathname.startsWith(\"/.well-known\") ||\n (distPath !== null && publicFiles.has(distPath));\n\n if (isFileRequest) {\n // Served from here whatever the asset base is: a CDN in front of the\n // app uses this origin as the source it fills from. A file, not merely\n // a path that exists: `/assets` itself is `dist/client/assets`, a\n // directory, and streaming one would answer 200 and then fail mid-body.\n //\n // `Bun.file(path).stream()` is lazy — a missing file only throws ENOENT\n // once the body is streamed, which is *after* this handler has returned,\n // with the response already committed as 200. So existence is checked\n // here, per request, even for a path `publicFiles` lists.\n if (!distPath || !(await isFile(distPath))) {\n return staticAssetMiss(pathname) ?? (await handleWithApp(req, pathname));\n }\n\n try {\n return await staticFileResponse(req, distPath, staticAssets.get(distPath));\n } catch (error) {\n app.onException?.(error);\n return new Response(\"Not found\", { status: 404 });\n }\n }\n\n const response = await handleWithApp(req, pathname);\n // A `.json` navigation says which build answered it, so a page of another\n // build loads the target in full rather than rendering data its views were\n // not written for (#1056). A redirect's headers are immutable; it carries\n // no data to mismatch.\n if (pathname.endsWith(\".json\")) {\n try {\n response.headers.set(BUILD_HEADER, buildId);\n } catch {}\n }\n return response;\n }\n\n async function handleWithApp(req: Request, pathname: string) {\n const handler = app.fetch.bind(app);\n\n try {\n const result = await handler(req);\n if (result instanceof Response) {\n return result;\n } else {\n const styles = [];\n\n styles.push({\n content: appCSSContent,\n });\n\n const getStyles = async (\n currentViews: string[] | undefined,\n options?: { static?: boolean; layout?: string },\n ) => {\n // No views is the `404` document (an unmatched path, a missing\n // record, a refusal): it is styled like any other, with the app CSS\n // and the `404` view's own (#951).\n const views = currentViews ?? [\"404\"];\n if (options?.static) {\n return staticStyles(views, options.layout);\n }\n for (const view of views) {\n const clientFile = manifest[`app/views/${view}.tsx`];\n for (const cssFile of clientFile?.css ?? []) {\n const css = Bun.file(`${process.env.DIST_DIR}/client/${cssFile}`);\n styles.push({\n id: cssFile,\n content: await css.text(),\n });\n }\n }\n return createStyles(styles);\n };\n\n return await result({\n getStyles,\n clientEntry,\n modulePreloadManifest,\n loaders,\n viewImportMap,\n viewModules,\n ogMap,\n cssManifest,\n assetBase,\n resolveIsland,\n buildId,\n });\n }\n } catch (err) {\n return unhandledErrorResponse(err, pathname, app.onException);\n }\n }\n\n // Compression is on unless the deployment opts out — set `GEMI_COMPRESSION=off`\n // when a layer in front of the origin already compresses HTML and you would\n // rather not spend origin CPU on it.\n const compressionEnabled = (process.env.GEMI_COMPRESSION ?? \"auto\").toLowerCase() !== \"off\";\n\n // Which `X-Forwarded-For` entries to believe — see `forwardedFor.ts`. Parsed\n // before `Bun.serve` so a bad value fails the boot, not every request.\n const forwardedTrust = parseTrustProxy(process.env.GEMI_TRUST_PROXY);\n // `GEMI_CLIENT_IP_HEADER` and its secret are applied by the app's router\n // (`app.applyTrustedClientIp`); parsed here as well so a bad set fails the\n // boot rather than the first request.\n const clientIpTrust = parseClientIpHeaderTrust(process.env);\n\n // The broadcast socket endpoint, when the app declares `route.channels`.\n const sockets = app.sockets();\n if (sockets && forwardedTrust.kind === \"none\" && !clientIpTrust) {\n console.warn(\n \"[gemi] Broadcast sockets: GEMI_TRUST_PROXY is not set, so a socket's address is its TCP peer. Behind a proxy or load balancer every client shares the proxy's address, and broadcast.maxConnectionsPerIp caps the whole app per process. Set GEMI_TRUST_PROXY (see forwardedFor) or raise that limit.\",\n );\n }\n\n const server = Bun.serve<any>({\n maxRequestBodySize: 10 * 1024 * 1024 * 1024, // 10 GB\n fetch: async (req, server) => {\n // `requestIP` is null for closed/unix sockets — guard so it never\n // throws before the request is handled.\n applyForwardedTrust(req.headers, server.requestIP(req)?.address ?? null, forwardedTrust);\n // A proxy that proves itself with a secret (`route.domains.trustProxy` or\n // `GEMI_PROXY_SECRET`) may name the client in its client-IP header,\n // which then wins.\n app.applyTrustedClientIp(req);\n // Ahead of the instrumentation: an upgrade is not a response to trace,\n // and the hub runs the global middleware itself.\n if (sockets?.matches(req)) {\n return sockets.upgrade(req, server);\n }\n // The app's global middleware goes in front of the static handler as well\n // as the router, so it can refuse `/assets/*` too.\n const res = await instrumentation(req, (req) =>\n app.withGlobalMiddleware(req, requestHandler, (err) =>\n unhandledErrorResponse(err, new URL(req.url).pathname, app.onException),\n ),\n );\n // Applied at the very edge, after instrumentation, so every HTML response\n // goes through the same negotiation — including the ones an app's\n // instrumentation produced itself. Anything that isn't HTML comes back\n // untouched.\n return compressionEnabled ? compressResponse(req, res) : res;\n },\n idleTimeout: serverIdleTimeout(),\n port: process.env.PORT || 5173,\n websocket: sockets?.websocket as WebSocketHandler<any>,\n });\n await sockets?.start(server);\n\n printStartupBanner({ port: server.port, rootDir });\n\n return server;\n}\n",
|
|
5
|
+
"import { createHash } from \"node:crypto\";\nimport type { WebSocketHandler } from \"bun\";\nimport { join, resolve, sep } from \"node:path\";\nimport { compressResponse } from \"./compression\";\nimport { listPublicFiles, staticFileResponse } from \"./staticFile\";\nimport { stat } from \"node:fs/promises\";\nimport { createStyles } from \"./styles\";\nimport {\n CLIENT_ENTRY_KEY,\n collectCss,\n collectModulePreloads,\n createClientEntry,\n createIslandResolver,\n} from \"./modulePreloads\";\nimport type { App } from \"../app\";\nimport { Instrumentation } from \"./types\";\nimport { printStartupBanner } from \"./banner\";\nimport { isBuildChunkPath, isReservedAssetPath, staticAssetMiss } from \"./staticAssetMiss\";\nimport { BUILD_HEADER } from \"../client/chunkLoadRecovery\";\nimport { assetUrl, readBuiltAssetBase } from \"../config/assetBase\";\nimport { isApiPath } from \"../services/router/apiPath\";\nimport { projectRoot } from \"../support/discover\";\nimport { unhandledErrorResponse } from \"./unhandledError\";\nimport { missingRequiredViewError } from \"./requiredViews\";\nimport { serverIdleTimeout } from \"./idleTimeout\";\nimport { applyForwardedTrust, parseClientIpHeaderTrust, parseTrustProxy } from \"./forwardedFor\";\nimport { readStaticAssetsRecord } from \"../vite/precompressAssets\";\nimport type { StaticFileOptions } from \"./staticFile\";\n\n// The rule this file used to spell out itself. It moved to `projectRoot`\n// because discovery needs the same answer during `waitForBoot()`, which is\n// before `ROOT_DIR` below exists — and two spellings of \"where is the project\"\n// is how a job gets looked for in one directory and served from another.\nconst rootDir = projectRoot();\n\nconst appDir = join(rootDir, \"app\");\nconst distDir = join(rootDir, \"dist\");\n\nconst clientDir = join(distDir, \"client\");\n\n// The file in `dist/client` a static-looking pathname names, or `null` when\n// it names nothing there. The pathname is still percent-encoded, and Vite\n// writes files whose names need encoding (a public `my font.woff2`), so it is\n// decoded first. Decoding also turns `%2F` into a separator, which the URL\n// parser's own `..` normalization never saw — so the resolved path is checked\n// to still be inside `dist/client`. A malformed escape is a miss, not a 500.\nfunction clientFilePath(pathname: string): string | null {\n let decoded: string;\n try {\n decoded = decodeURIComponent(pathname);\n } catch {\n return null;\n }\n const path = resolve(clientDir, \".\" + decoded.replace(\"/assets/assets\", \"/assets\"));\n return path.startsWith(clientDir + sep) ? path : null;\n}\n\nasync function isFile(path: string) {\n try {\n return (await stat(path)).isFile();\n } catch {\n return false;\n }\n}\n\n// `app` is built by `Server.start` (from the kernel) and passed in — same as\n// `httpDev` — so prod and dev share one construction path. What differs here is\n// that views/styles are served from the built `dist/` manifests instead of\n// Vite's dev SSR graph.\nexport async function httpProd(app: App, instrumentation: Instrumentation) {\n const manifest = await import(`${distDir}/client/.vite/manifest.json`);\n const serverManifest = await import(`${distDir}/server/.vite/manifest.json`);\n // What the client build was built with, not `GEMI_ASSET_BASE` as it reads\n // now — see `readBuiltAssetBase`. Every URL below that names a chunk goes\n // through it, so the document links exactly what the bundle imports.\n const assetBase = await readBuiltAssetBase(`${distDir}/client`);\n\n process.env.ROOT_DIR = rootDir;\n process.env.APP_DIR = appDir;\n process.env.DIST_DIR = distDir;\n\n const viewImportMap = {};\n const ogMap = {};\n const viewModules = {};\n const cssManifest = {};\n // Which chunks a route's views pull in, so the shell can preload the chain\n // the client would otherwise discover one round trip at a time (#352).\n const modulePreloadManifest: Record<string, string[]> = {};\n const template = (viewName: string, path: string) => `\"${viewName}\": () => import(\"${path}\")`;\n const templates = [];\n\n const requiredViews = app.getRequiredViews.call(app);\n for (const fileName of [\"404\", ...app.getFlatComponentTree.call(app)]) {\n const serverFile = serverManifest[`app/views/${fileName}.tsx`];\n if (!serverFile?.file && requiredViews[fileName]) {\n throw missingRequiredViewError(fileName, requiredViews[fileName]);\n }\n if (!serverFile?.file) {\n console.log(`Server file not found for ${fileName}`);\n console.log(serverFile);\n const files = Object.keys(serverManifest);\n const path = `app/views/${fileName}.tsx`;\n console.log(`${path} not found in server manifest`);\n console.log(files);\n }\n const mod = await import(`${process.env.DIST_DIR}/server/${serverFile?.file}`);\n viewImportMap[fileName] = mod.default;\n ogMap[fileName] = mod.OpenGraph;\n // The whole module, so a streaming render can put the view's\n // `Loading`/`Error` exports into the shell it sends.\n viewModules[fileName] = mod;\n const clientFile = manifest[`app/views/${fileName}.tsx`];\n\n if (clientFile?.css && clientFile?.css.length > 0) {\n cssManifest[fileName] = clientFile?.css;\n }\n if (clientFile) {\n templates.push(template(fileName, assetUrl(clientFile?.file, assetBase)));\n modulePreloadManifest[fileName] = collectModulePreloads(\n manifest,\n `app/views/${fileName}.tsx`,\n assetBase,\n );\n }\n }\n\n const loaders = `{${templates.join(\",\")}}`;\n\n // Booted from the bootstrap script rather than through React's\n // `bootstrapModules`, which would preload it at `fetchPriority=\"low\"` — see\n // `clientEntry` in `ViewRouteDispatcher`.\n const clientEntry = createClientEntry(manifest, assetBase);\n if (!clientEntry) {\n // Loudly, but without dying: this is boot, and an incomplete `dist/` must\n // not cost the API routes and static assets too.\n console.error(\n `Client manifest has no \"${CLIENT_ENTRY_KEY}\" entry — dist/client is incomplete, so documents will render but never hydrate.`,\n );\n }\n\n // Vite's manifest `css` field is a `string[]` — a client entry can emit more\n // than one CSS chunk. Read and concatenate them all instead of interpolating\n // the array into a single path (which only works for a one-element array).\n const appCssFiles: string[] = manifest[\"app/client.tsx\"]?.css ?? [];\n const appCSSContent = (\n await Promise.all(appCssFiles.map((cssFile) => Bun.file(`${distDir}/client/${cssFile}`).text()))\n ).join(\"\\n\");\n\n // The island entries (`<module>?gemi-island`) a static view's document\n // loads; see `injectIslands`.\n const resolveIsland = createIslandResolver(manifest, assetBase);\n\n // Identifies this client build: a static page with `navigation` swaps in\n // only pages of the same build, and a client-side navigation answered by\n // another build loads the page in full (#1056). The manifest names every content-hashed\n // file, so it changes whenever any of them does.\n const buildId = createHash(\"sha256\")\n .update(await Bun.file(`${distDir}/client/.vite/manifest.json`).text())\n .digest(\"base64url\")\n .slice(0, 12);\n\n // A static view's CSS comes from the whole import closure of its views (and\n // its own layout's, which then replaces the app stylesheet): there is no\n // client to fetch a shared chunk's CSS later, as a hydrated page's chunk\n // loader does. Read once per file: the build does not change under a\n // running server.\n const cssFileCache = new Map<string, Promise<string>>();\n const readCss = (file: string) => {\n let text = cssFileCache.get(file);\n if (!text) {\n text = Bun.file(`${distDir}/client/${file}`).text();\n cssFileCache.set(file, text);\n }\n return text;\n };\n const staticStyles = async (views: string[], layout: string | undefined) => {\n const files = new Set<string>();\n for (const view of layout ? [layout, ...views] : views) {\n for (const file of collectCss(manifest, `app/views/${view}.tsx`)) files.add(file);\n }\n const styles = await Promise.all(\n [...files].map(async (file) => ({ id: file, content: await readCss(file) })),\n );\n return createStyles(layout ? styles : [{ content: appCSSContent }, ...styles]);\n };\n\n // Which requests are answered from `dist/client` rather than the app:\n //\n // - `/assets` and everything under it, whatever the extension. Vite writes\n // fonts, GIFs, video and `?url` JSON there too, and an allowlist of\n // extensions always trailed the build — a `.woff2` went to the router\n // and paid for an SSR attempt. The router refuses routes under `/assets`\n // at boot, so there is no app answer to lose, and `staticAssetMiss`\n // turns a miss into the reload stub or a 404.\n // - `/.well-known/*`.\n // - Any other path that names a file the build copied out of `public/`,\n // whatever its extension (#583). Any other path may be an app route, so\n // it is static only when that exact file exists — read once here, since\n // the build output does not change under a running server. A view at\n // `/manifest` keeps its data URL (`/manifest.json`) unless the app also\n // ships `public/manifest.json`, and then the file wins. View data is\n // never a file in `dist/client`, so a client-side navigation pays for no\n // filesystem lookup.\n const publicFiles = await listPublicFiles(clientDir);\n\n // The build assets `gemi build` emitted or carried over, with the\n // precompressed siblings it wrote for each (#789). Read once: the build\n // output does not change under a running server. Without the record (a\n // build made before it existed) every file is served as identity with the\n // old cache policy.\n const staticAssets = new Map<string, StaticFileOptions>();\n for (const [file, encodings] of Object.entries(\n (await readStaticAssetsRecord(clientDir))?.files ?? {},\n )) {\n const path = resolve(clientDir, file);\n if (path.startsWith(clientDir + sep)) {\n staticAssets.set(path, { immutable: true, encodings });\n }\n }\n\n async function requestHandler(req: Request) {\n const { pathname } = new URL(req.url);\n\n if (isApiPath(pathname)) {\n return await handleWithApp(req, pathname);\n }\n\n const distPath = clientFilePath(pathname);\n const isAssetPath = isReservedAssetPath(pathname);\n const isPublicFile = !isAssetPath && distPath !== null && publicFiles.has(distPath);\n const isFileRequest = isAssetPath || pathname.startsWith(\"/.well-known\") || isPublicFile;\n\n // A host whose views opt out of `public/` (#1015) gets its routes there,\n // e.g. a `/robots.txt` of its own.\n if (isPublicFile && !(await staticFilesPolicy(req)).publicFiles) {\n return await handleWithApp(req, pathname);\n }\n\n if (isFileRequest) {\n // Served from here whatever the asset base is: a CDN in front of the\n // app uses this origin as the source it fills from. A file, not merely\n // a path that exists: `/assets` itself is `dist/client/assets`, a\n // directory, and streaming one would answer 200 and then fail mid-body.\n //\n // `Bun.file(path).stream()` is lazy — a missing file only throws ENOENT\n // once the body is streamed, which is *after* this handler has returned,\n // with the response already committed as 200. So existence is checked\n // here, per request, even for a path `publicFiles` lists.\n if (!distPath || !(await isFile(distPath))) {\n // A host whose views own `/assets` (#1015) gets its routes for what\n // the build has no file for; a missing chunk keeps its reload stub.\n if (isAssetPath && !isBuildChunkPath(pathname) && (await staticFilesPolicy(req)).assetRoutes) {\n return await handleWithApp(req, pathname);\n }\n return staticAssetMiss(pathname) ?? (await handleWithApp(req, pathname));\n }\n\n try {\n return await staticFileResponse(req, distPath, staticAssets.get(distPath));\n } catch (error) {\n app.onException?.(error);\n return new Response(\"Not found\", { status: 404 });\n }\n }\n\n const response = await handleWithApp(req, pathname);\n // A `.json` navigation says which build answered it, so a page of another\n // build loads the target in full rather than rendering data its views were\n // not written for (#1056). A redirect's headers are immutable; it carries\n // no data to mismatch.\n if (pathname.endsWith(\".json\")) {\n try {\n response.headers.set(BUILD_HEADER, buildId);\n } catch {}\n }\n return response;\n }\n\n // How static files are served to the request's host (#1015). An app\n // built against a gemi without it serves them as before. A host that fails\n // to resolve (a `custom.resolve` that throws) gets no `public/` file it may\n // not be meant to have, and no `/assets` 404: the request goes to the app,\n // which answers the same failure as for any route.\n async function staticFilesPolicy(req: Request) {\n try {\n return (await app.staticFiles?.(req)) ?? { publicFiles: true, assetRoutes: false };\n } catch {\n return { publicFiles: false, assetRoutes: true };\n }\n }\n\n async function handleWithApp(req: Request, pathname: string) {\n const handler = app.fetch.bind(app);\n\n try {\n const result = await handler(req);\n if (result instanceof Response) {\n return result;\n } else {\n const styles = [];\n\n styles.push({\n content: appCSSContent,\n });\n\n const getStyles = async (\n currentViews: string[] | undefined,\n options?: { static?: boolean; layout?: string },\n ) => {\n // No views is the `404` document (an unmatched path, a missing\n // record, a refusal): it is styled like any other, with the app CSS\n // and the `404` view's own (#951).\n const views = currentViews ?? [\"404\"];\n if (options?.static) {\n return staticStyles(views, options.layout);\n }\n for (const view of views) {\n const clientFile = manifest[`app/views/${view}.tsx`];\n for (const cssFile of clientFile?.css ?? []) {\n const css = Bun.file(`${process.env.DIST_DIR}/client/${cssFile}`);\n styles.push({\n id: cssFile,\n content: await css.text(),\n });\n }\n }\n return createStyles(styles);\n };\n\n return await result({\n getStyles,\n clientEntry,\n modulePreloadManifest,\n loaders,\n viewImportMap,\n viewModules,\n ogMap,\n cssManifest,\n assetBase,\n resolveIsland,\n buildId,\n });\n }\n } catch (err) {\n return unhandledErrorResponse(err, pathname, app.onException);\n }\n }\n\n // Compression is on unless the deployment opts out — set `GEMI_COMPRESSION=off`\n // when a layer in front of the origin already compresses HTML and you would\n // rather not spend origin CPU on it.\n const compressionEnabled = (process.env.GEMI_COMPRESSION ?? \"auto\").toLowerCase() !== \"off\";\n\n // Which `X-Forwarded-For` entries to believe — see `forwardedFor.ts`. Parsed\n // before `Bun.serve` so a bad value fails the boot, not every request.\n const forwardedTrust = parseTrustProxy(process.env.GEMI_TRUST_PROXY);\n // `GEMI_CLIENT_IP_HEADER` and its secret are applied by the app's router\n // (`app.applyTrustedClientIp`); parsed here as well so a bad set fails the\n // boot rather than the first request.\n const clientIpTrust = parseClientIpHeaderTrust(process.env);\n\n // The broadcast socket endpoint, when the app declares `route.channels`.\n const sockets = app.sockets();\n if (sockets && forwardedTrust.kind === \"none\" && !clientIpTrust) {\n console.warn(\n \"[gemi] Broadcast sockets: GEMI_TRUST_PROXY is not set, so a socket's address is its TCP peer. Behind a proxy or load balancer every client shares the proxy's address, and broadcast.maxConnectionsPerIp caps the whole app per process. Set GEMI_TRUST_PROXY (see forwardedFor) or raise that limit.\",\n );\n }\n\n const server = Bun.serve<any>({\n maxRequestBodySize: 10 * 1024 * 1024 * 1024, // 10 GB\n fetch: async (req, server) => {\n // `requestIP` is null for closed/unix sockets — guard so it never\n // throws before the request is handled.\n applyForwardedTrust(req.headers, server.requestIP(req)?.address ?? null, forwardedTrust);\n // A proxy that proves itself with a secret (`route.domains.trustProxy` or\n // `GEMI_PROXY_SECRET`) may name the client in its client-IP header,\n // which then wins.\n app.applyTrustedClientIp(req);\n // Ahead of the instrumentation: an upgrade is not a response to trace,\n // and the hub runs the global middleware itself.\n if (sockets?.matches(req)) {\n return sockets.upgrade(req, server);\n }\n // The app's global middleware goes in front of the static handler as well\n // as the router, so it can refuse `/assets/*` too.\n const res = await instrumentation(req, (req) =>\n app.withGlobalMiddleware(req, requestHandler, (err) =>\n unhandledErrorResponse(err, new URL(req.url).pathname, app.onException),\n ),\n );\n // Applied at the very edge, after instrumentation, so every HTML response\n // goes through the same negotiation — including the ones an app's\n // instrumentation produced itself. Anything that isn't HTML comes back\n // untouched.\n return compressionEnabled ? compressResponse(req, res) : res;\n },\n idleTimeout: serverIdleTimeout(),\n port: process.env.PORT || 5173,\n websocket: sockets?.websocket as WebSocketHandler<any>,\n });\n await sockets?.start(server);\n\n printStartupBanner({ port: server.port, rootDir });\n\n return server;\n}\n",
|
|
6
6
|
"/**\n * Transport-level compression for SSR HTML responses.\n *\n * This lives in the runtime rather than in an app, because every gemi app\n * serves the same shape of response: a streamed `text/html` document whose\n * bulk is critical CSS, markup and the `window.__GEMI_DATA__` hydration\n * payload. Compressing it here means the win is portable across deployments —\n * it does not depend on a CDN being configured to compress on the origin's\n * behalf — and it also cuts origin→edge bandwidth, which edge compression\n * cannot.\n *\n * Compression stays strictly at the transport layer: what the client decodes\n * is byte-for-byte the HTML React rendered, so hydration is unaffected.\n *\n * Deliberately dependency-free apart from `node:zlib`/`node:stream` so it can\n * be unit tested without booting a server.\n */\n\nimport { Duplex } from \"node:stream\";\nimport { constants, createBrotliCompress, createGzip } from \"node:zlib\";\n\n/** The content codings we can produce, in the order we prefer them. */\nexport type ContentEncoding = \"br\" | \"gzip\";\n\n/**\n * Server preference, used to break q-value ties. Brotli comes first: at the\n * quality we use it is both smaller *and* no more expensive than gzip on a\n * representative SSR document (~177 kB → 28.0 kB in ~2.0 ms, versus 31.6 kB in\n * ~2.0 ms for gzip).\n */\nconst SERVER_PREFERENCE: ContentEncoding[] = [\"br\", \"gzip\"];\n\n/**\n * Below this, an encoded body plus its framing overhead is not worth the CPU.\n * Only applied when the response declares a `Content-Length` — a streamed SSR\n * response does not, and buffering one just to measure it would defeat the\n * streaming render.\n */\nconst MIN_COMPRESSIBLE_BYTES = 1024;\n\n/**\n * Brotli quality 5 (of 11). The default, 11, is meant for static assets\n * compressed once at build time — on a streamed document it costs ~175 ms,\n * two orders of magnitude more than quality 5 for ~12% more savings. Quality 5\n * is the usual choice for dynamic content.\n *\n * `LGWIN` 19 caps the window at 512 kB rather than the default 16 MB. An SSR\n * document is far smaller than that, so the ratio is unchanged, and the bound\n * matters under concurrency: the window is allocated per in-flight response.\n */\nconst BROTLI_QUALITY = 5;\nconst BROTLI_WINDOW_BITS = 19;\n\n/** zlib's default level. Level 9 costs measurably more for ~1% on HTML. */\nconst GZIP_LEVEL = 6;\n\nfunction parseQValue(raw: string | undefined): number {\n if (raw === undefined) {\n return 1;\n }\n // `q` is a fixed-point number with at most three decimals (RFC 9110 §12.4.2).\n // Anything else is malformed; treat it as an unqualified \"acceptable\" rather\n // than dropping the encoding, matching what tolerant servers do.\n if (!/^\\d+(\\.\\d{0,3})?$/.test(raw.trim())) {\n return 1;\n }\n const value = Number(raw.trim());\n return Number.isFinite(value) ? Math.min(value, 1) : 1;\n}\n\n/**\n * Parses a request `Accept-Encoding` into `coding -> qvalue`.\n *\n * Returns `null` when the header is *absent*, which is distinct from an empty\n * one. RFC 9110 §12.5.3 says an absent header means any coding is acceptable,\n * but a client that cannot decode and forgets to say so is a broken page, not\n * a slow one — so we treat \"absent\" as \"identity\" and only encode when a\n * client has explicitly opted in. An empty header (`Accept-Encoding:`) is a\n * deliberate \"identity only\" and parses to an empty map.\n */\nexport function parseAcceptEncoding(header: string | null | undefined): Map<string, number> | null {\n if (header === null || header === undefined) {\n return null;\n }\n\n const accepted = new Map<string, number>();\n for (const part of header.split(\",\")) {\n const [rawCoding, ...params] = part.split(\";\");\n const coding = rawCoding.trim().toLowerCase();\n if (coding === \"\") {\n continue;\n }\n\n let q = 1;\n for (const param of params) {\n const separator = param.indexOf(\"=\");\n if (separator === -1) {\n continue;\n }\n if (param.slice(0, separator).trim().toLowerCase() === \"q\") {\n q = parseQValue(param.slice(separator + 1));\n break;\n }\n }\n\n accepted.set(coding, q);\n }\n\n return accepted;\n}\n\n/**\n * Picks the coding to use, or `null` for identity.\n *\n * A `q=0` is a refusal, `*` covers codings the client did not name, and ties\n * fall back to `available`'s order — so a plain `Accept-Encoding: gzip, br`\n * (equal q of 1) resolves to our preference rather than the client's.\n */\nexport function negotiateEncoding(\n header: string | null | undefined,\n available: ContentEncoding[] = SERVER_PREFERENCE,\n): ContentEncoding | null {\n const accepted = parseAcceptEncoding(header);\n if (!accepted) {\n return null;\n }\n\n const wildcard = accepted.get(\"*\");\n let best: ContentEncoding | null = null;\n let bestQ = 0;\n\n for (const coding of available) {\n const q = accepted.get(coding) ?? wildcard ?? 0;\n if (q > bestQ) {\n best = coding;\n bestQ = q;\n }\n }\n\n return best;\n}\n\nfunction hasDirective(headerValue: string | null, directive: string): boolean {\n if (!headerValue) {\n return false;\n }\n // Token match, not substring: `no-transform` must not be found inside\n // something like `x-no-transform`.\n return headerValue\n .split(\",\")\n .some((token) => token.trim().toLowerCase().split(\"=\")[0].trim() === directive);\n}\n\n/**\n * Whether this response's representation is one we may encode — and therefore\n * one that varies by `Accept-Encoding`, even on the requests where we end up\n * sending identity.\n *\n * HTML only, on purpose. JSON view-data and API responses are left alone so\n * that this change cannot alter how they are produced, framed or cached;\n * widening the set is a separate decision.\n */\nexport function isCompressible(method: string, res: Response): boolean {\n // A HEAD response carries no body to encode, and emitting `Content-Encoding`\n // without one would describe a body the client never receives.\n if (method === \"HEAD\") {\n return false;\n }\n\n // 204/304 are bodyless; 206 is a byte range of an already-committed\n // representation and re-encoding it would invalidate its `Content-Range`.\n if (res.status < 200 || res.status === 204 || res.status === 206 || res.status === 304) {\n return false;\n }\n\n if (res.headers.has(\"Content-Range\")) {\n return false;\n }\n\n const contentType = res.headers.get(\"Content-Type\");\n if (!contentType || !/^\\s*text\\/html\\s*(;|$)/i.test(contentType)) {\n return false;\n }\n\n // Only once the status and headers have ruled a range out: on Bun 1.3.14 on\n // Linux, reading `.body` of a Response built on a sliced Blob makes it send\n // past the slice's end (#725), so `.body` is never touched on a non-HTML\n // response this function hands back untouched.\n if (!res.body) {\n return false;\n }\n\n // Already encoded upstream — `identity` is the one value that means \"not\n // encoded\" and is safe to replace.\n const contentEncoding = res.headers.get(\"Content-Encoding\");\n if (contentEncoding && contentEncoding.trim().toLowerCase() !== \"identity\") {\n return false;\n }\n\n // RFC 9110 §5.2.2.6: `no-transform` forbids intermediaries *and* us from\n // changing the representation's encoding.\n if (hasDirective(res.headers.get(\"Cache-Control\"), \"no-transform\")) {\n return false;\n }\n\n const contentLength = res.headers.get(\"Content-Length\");\n if (contentLength !== null && Number(contentLength) < MIN_COMPRESSIBLE_BYTES) {\n return false;\n }\n\n return true;\n}\n\n/**\n * Builds the encoder as a web `TransformStream`-shaped pair.\n *\n * Both codings are driven through `node:zlib` with an explicit per-write flush.\n * Without it the compressor holds output until its internal buffer fills,\n * which would sit on React's shell flush and turn a streamed render into a\n * buffered one — the compression win would be paid for in time to first byte.\n */\nexport function createEncoderStream(encoding: ContentEncoding) {\n const compressor =\n encoding === \"br\"\n ? createBrotliCompress({\n params: {\n [constants.BROTLI_PARAM_QUALITY]: BROTLI_QUALITY,\n [constants.BROTLI_PARAM_LGWIN]: BROTLI_WINDOW_BITS,\n },\n flush: constants.BROTLI_OPERATION_FLUSH,\n })\n : createGzip({ level: GZIP_LEVEL, flush: constants.Z_SYNC_FLUSH });\n\n return Duplex.toWeb(compressor) as unknown as ReadableWritablePair<Uint8Array, Uint8Array>;\n}\n\nfunction withVaryAcceptEncoding(headers: Headers) {\n const vary = headers.get(\"Vary\");\n if (!vary) {\n headers.set(\"Vary\", \"Accept-Encoding\");\n return;\n }\n const tokens = vary.split(\",\").map((token) => token.trim().toLowerCase());\n // `Vary: *` already means \"never reuse for another request\"; narrowing it\n // would be a downgrade.\n if (tokens.includes(\"*\") || tokens.includes(\"accept-encoding\")) {\n return;\n }\n headers.append(\"Vary\", \"Accept-Encoding\");\n}\n\n/**\n * Compresses an SSR HTML response when the client asked for it.\n *\n * Non-HTML responses are returned untouched — same object, same body, so this\n * is safe to apply at the edge of the server for every request.\n *\n * HTML responses always gain `Vary: Accept-Encoding`, whether or not this\n * particular request got an encoded body: without it a shared cache could\n * serve a stored encoded variant to a client that cannot decode it.\n */\nexport function compressResponse(req: Request, res: Response): Response {\n if (!isCompressible(req.method, res)) {\n return res;\n }\n\n const headers = new Headers(res.headers);\n withVaryAcceptEncoding(headers);\n\n const encoding = negotiateEncoding(req.headers.get(\"Accept-Encoding\"));\n if (!encoding) {\n return new Response(res.body, {\n status: res.status,\n statusText: res.statusText,\n headers,\n });\n }\n\n let encoder: ReadableWritablePair<Uint8Array, Uint8Array>;\n try {\n encoder = createEncoderStream(encoding);\n } catch {\n // A compressor we cannot construct is not worth failing a page render\n // over. Serve identity — with the `Vary` we already added.\n return new Response(res.body, {\n status: res.status,\n statusText: res.statusText,\n headers,\n });\n }\n\n headers.set(\"Content-Encoding\", encoding);\n // The length of the identity representation says nothing about the encoded\n // body, and a stale one would truncate the response.\n headers.delete(\"Content-Length\");\n\n return new Response(res.body!.pipeThrough(encoder), {\n status: res.status,\n statusText: res.statusText,\n headers,\n });\n}\n",
|
|
7
7
|
"import { readdir, stat } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { createUnsatisfiableResponse } from \"../http/createStreamResponse\";\nimport { formatContentRange, parseRangeHeader, resolveRange } from \"../http/range\";\nimport { SIBLING_EXTENSION } from \"../vite/precompressAssets\";\nimport { type ContentEncoding, negotiateEncoding } from \"./compression\";\nimport { generateETag } from \"./generateEtag\";\n\n/**\n * Every file in `dist/client` outside `/assets`, as absolute paths — what\n * Vite copied out of `public/` (#583).\n *\n * A root-level path may be an app route, so the static handler may only take\n * one that names a file the app really ships. This used to be guessed from the\n * extension, and every extension the list missed (`.mp4` on 0.58, `.wasm`,\n * `.csv` …) was answered by the router with a rendered 404 page. The build\n * output is fixed for the life of the process, so the exact set is read once\n * at boot instead.\n *\n * `assets/` is left out because the static handler takes everything under it\n * anyway. A name starting with a dot is left out at any depth: `.vite/` is\n * build metadata (both manifests and the asset base record), and a stray\n * `.DS_Store` or `.env` copied in from `public/` is nothing a request should\n * reach. `/.well-known` is served by its own rule.\n */\nexport async function listPublicFiles(clientDir: string): Promise<Set<string>> {\n const files = new Set<string>();\n\n async function walk(dir: string, topLevel: boolean) {\n let entries;\n try {\n entries = await readdir(dir, { withFileTypes: true });\n } catch {\n return;\n }\n for (const entry of entries) {\n if (entry.name.startsWith(\".\")) continue;\n if (topLevel && entry.name === \"assets\") continue;\n const path = join(dir, entry.name);\n let isDir = entry.isDirectory();\n let isFile = entry.isFile();\n if (entry.isSymbolicLink()) {\n try {\n const target = await stat(path);\n isDir = target.isDirectory();\n isFile = target.isFile();\n } catch {\n continue;\n }\n }\n if (isFile) files.add(path);\n // A symlinked directory is not followed: a link back up the tree would\n // never end, and `clientFilePath` only names paths inside `dist/client`.\n else if (isDir && !entry.isSymbolicLink()) await walk(path, false);\n }\n }\n\n await walk(clientDir, true);\n return files;\n}\n\nconst LONG_LIVED = \"public, max-age=31536000, must-revalidate\";\n\n/**\n * A content-hashed build asset never changes under its name: a new build\n * writes a new name. So a browser or edge may keep it a year without asking\n * again (#789).\n */\nconst IMMUTABLE = \"public, max-age=31536000, immutable\";\n\nexport interface StaticFileOptions {\n /**\n * The file is a content-hashed build asset (it is in the build's\n * `static-assets.json`), so it is sent as `immutable`.\n */\n immutable?: boolean;\n /**\n * The precompressed siblings the build wrote for it (`file.br`,\n * `file.gz`), in preference order. Offering any makes the response vary by\n * `Accept-Encoding`, so it says so even when it sends identity.\n */\n encodings?: ContentEncoding[];\n}\n\nasync function encodedSibling(\n req: Request,\n path: string,\n encodings: ContentEncoding[],\n): Promise<{ encoding: ContentEncoding; path: string; size: number } | null> {\n // A range is a byte range of the identity representation: an encoded\n // variant has no stable offsets to slice, so a range request gets identity.\n if (encodings.length === 0 || req.headers.has(\"Range\")) {\n return null;\n }\n const encoding = negotiateEncoding(req.headers.get(\"Accept-Encoding\"), encodings);\n if (!encoding) {\n return null;\n }\n const siblingPath = path + SIBLING_EXTENSION[encoding];\n try {\n // Checked per request, like the file itself: a sibling deleted under a\n // running server would otherwise fail mid-body after a committed 200.\n const info = await stat(siblingPath);\n return info.isFile() ? { encoding, path: siblingPath, size: info.size } : null;\n } catch {\n return null;\n }\n}\n\n/**\n * The response for a file in `dist/client` that is known to exist.\n *\n * Honours a single byte `Range`, so a `<video>` pointed at a public `.mp4`\n * can seek — Safari will not play one at all from a server that answers its\n * `bytes=0-1` probe with the whole file. Recent Bun releases slice a\n * file-backed 200 themselves; doing it here keeps that from depending on the\n * Bun version a deployment runs, and answers an unsatisfiable range with the\n * same 416 the `stream()` responses use.\n *\n * A build asset with precompressed siblings is sent as the one\n * `Accept-Encoding` prefers (brotli, then gzip, then identity), with the\n * original's `Content-Type`. Nothing is compressed here: a file without a\n * sibling is sent as is.\n *\n * `Bun.file().type` is the mime lookup, and is `application/octet-stream` for\n * an extension it does not know.\n */\nexport async function staticFileResponse(\n req: Request,\n path: string,\n options: StaticFileOptions = {},\n): Promise<Response> {\n const file = Bun.file(path);\n const encodings = options.encodings ?? [];\n\n const headers: Record<string, string> = {\n \"Content-Type\": file.type || \"application/octet-stream\",\n \"Cache-Control\": options.immutable ? IMMUTABLE : LONG_LIVED,\n \"Accept-Ranges\": \"bytes\",\n };\n if (encodings.length > 0) {\n headers.Vary = \"Accept-Encoding\";\n }\n\n const sibling = await encodedSibling(req, path, encodings);\n if (sibling) {\n const encoded = Bun.file(sibling.path);\n return new Response(encoded.stream(), {\n headers: {\n ...headers,\n \"Content-Encoding\": sibling.encoding,\n \"Content-Length\": String(sibling.size),\n // A different representation, so a different validator.\n ETag: `${generateETag(encoded.lastModified)}-${sibling.encoding}`,\n },\n });\n }\n\n const total = file.size;\n headers.ETag = generateETag(file.lastModified);\n\n const range = parseRangeHeader(req.headers.get(\"Range\"));\n\n if (range) {\n const resolved = resolveRange(range, total);\n if (!resolved) {\n return createUnsatisfiableResponse(total);\n }\n return new Response(file.slice(resolved.start, resolved.end + 1).stream(), {\n status: 206,\n headers: {\n ...headers,\n \"Content-Range\": formatContentRange(resolved.start, resolved.end, total),\n \"Content-Length\": String(resolved.length),\n },\n });\n }\n\n return new Response(file.stream(), {\n headers: { ...headers, \"Content-Length\": String(total) },\n });\n}\n",
|
|
8
8
|
"import { mkdir, readFile, stat, writeFile } from \"node:fs/promises\";\nimport { availableParallelism } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { promisify } from \"node:util\";\nimport { brotliCompress, constants, gzip } from \"node:zlib\";\n\nimport type { ContentEncoding } from \"../server/compression\";\n\n/**\n * Build-time compression of the client bundle (#789).\n *\n * `gemi start` compresses SSR HTML per request, but a hashed chunk is the same\n * bytes for the life of a release, so compressing it per request would spend\n * origin CPU on every visitor for a result that never changes. `gemi build`\n * writes `file.br` (brotli quality 11) and `file.gz` (gzip level 9) next to\n * every compressible build asset instead, once, and the server picks one by\n * `Accept-Encoding`.\n *\n * What the build emitted (and what it carried over from earlier releases, see\n * `previousAssets.ts`) is recorded in `dist/client/.vite/static-assets.json`,\n * with the encodings each file has. The server reads it at boot: a file in the\n * record is a content-hashed build asset, so it is sent as `immutable`, and\n * only the encodings listed for it are offered. A file not in the record (one\n * copied from `public/assets/`, or anything in a build made before this\n * existed) is served as before.\n */\n\n/** Relative to `dist/client`; beside Vite's own manifest, so never served. */\nexport const STATIC_ASSETS_RECORD = \".vite/static-assets.json\";\n\n/**\n * Below this, the encoded body plus its header is not worth a second request\n * representation: a 600-byte chunk saves a few hundred bytes at most, and\n * every file with siblings adds a `stat` per request.\n */\nexport const PRECOMPRESS_MIN_BYTES = 1024;\n\n/**\n * Text-like formats that compress well. Already-compressed formats (images,\n * `woff`/`woff2`, video) are left out: brotli shaves a percent or two off\n * them at best. Source maps are left out because a page never loads one, and\n * only devtools would gain from the extra build time.\n */\nconst COMPRESSIBLE = /\\.(?:m?js|cjs|css|svg|json|webmanifest|wasm|txt|xml|ttf|otf|eot)$/i;\n\nconst TEXT = /\\.(?:m?js|cjs|css|svg|json|webmanifest|txt|xml)$/i;\nconst FONT = /\\.(?:ttf|otf|eot)$/i;\n\nexport const SIBLING_EXTENSION: Record<ContentEncoding, string> = {\n br: \".br\",\n gzip: \".gz\",\n};\n\n/** Server preference: brotli is ~15% smaller than gzip -9 on a JS chunk. */\nconst ENCODINGS: ContentEncoding[] = [\"br\", \"gzip\"];\n\nexport interface StaticAssetsRecord {\n /**\n * Path relative to `dist/client` (`assets/client-DJhrQPW5.js`) → the\n * precompressed siblings it has, in preference order. An empty list is a\n * build asset that is served as identity only (an image, a small file).\n */\n files: Record<string, ContentEncoding[]>;\n}\n\nexport interface PrecompressStats {\n files: number;\n identityBytes: number;\n brBytes: number;\n gzipBytes: number;\n}\n\nconst brotli = promisify(brotliCompress);\nconst gzipAsync = promisify(gzip);\n\nexport function isPrecompressible(file: string): boolean {\n return COMPRESSIBLE.test(file);\n}\n\n/** `true` for `x.js.br` / `x.js.gz`: a sibling, not an asset of its own. */\nexport function isEncodedSibling(file: string): boolean {\n return file.endsWith(\".br\") || file.endsWith(\".gz\");\n}\n\nasync function encode(encoding: ContentEncoding, file: string, input: Buffer): Promise<Buffer> {\n if (encoding === \"gzip\") {\n return gzipAsync(input, { level: 9 });\n }\n return brotli(input, {\n params: {\n [constants.BROTLI_PARAM_QUALITY]: constants.BROTLI_MAX_QUALITY,\n [constants.BROTLI_PARAM_MODE]: TEXT.test(file)\n ? constants.BROTLI_MODE_TEXT\n : FONT.test(file)\n ? constants.BROTLI_MODE_FONT\n : constants.BROTLI_MODE_GENERIC,\n [constants.BROTLI_PARAM_SIZE_HINT]: input.length,\n },\n });\n}\n\nasync function fileSize(path: string): Promise<number | null> {\n try {\n const info = await stat(path);\n return info.isFile() ? info.size : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Writes the `.br`/`.gz` siblings of `files` (paths relative to `clientDir`)\n * and returns the encodings each one ended up with.\n *\n * A sibling is only kept when it is smaller than the file itself; one that is\n * not would cost bytes on the wire, so the file is served as identity for\n * that encoding. With `reuseExisting`, a sibling already on disk is trusted\n * rather than rebuilt — for files carried from an earlier release, whose\n * siblings were copied with them and match by content hash.\n */\nexport async function precompressAssets(\n clientDir: string,\n files: Iterable<string>,\n options: {\n reuseExisting?: boolean;\n minBytes?: number;\n concurrency?: number;\n } = {},\n): Promise<{ record: StaticAssetsRecord; stats: PrecompressStats }> {\n const minBytes = options.minBytes ?? PRECOMPRESS_MIN_BYTES;\n const record: StaticAssetsRecord = { files: {} };\n const stats: PrecompressStats = {\n files: 0,\n identityBytes: 0,\n brBytes: 0,\n gzipBytes: 0,\n };\n\n const queue = [...new Set(files)];\n for (const file of queue) {\n record.files[file] = [];\n }\n\n async function work(file: string) {\n if (!isPrecompressible(file)) {\n return;\n }\n const path = join(clientDir, file);\n const size = await fileSize(path);\n if (size === null || size < minBytes) {\n return;\n }\n\n let input: Buffer | null = null;\n const available: ContentEncoding[] = [];\n const sizes: Partial<Record<ContentEncoding, number>> = {};\n for (const encoding of ENCODINGS) {\n const siblingPath = path + SIBLING_EXTENSION[encoding];\n const existing = options.reuseExisting ? await fileSize(siblingPath) : null;\n if (existing !== null) {\n if (existing < size) {\n available.push(encoding);\n sizes[encoding] = existing;\n }\n continue;\n }\n input ??= await readFile(path);\n const output = await encode(encoding, file, input);\n if (output.length < size) {\n await writeFile(siblingPath, output);\n available.push(encoding);\n sizes[encoding] = output.length;\n }\n }\n\n record.files[file] = available;\n if (available.length > 0) {\n stats.files += 1;\n stats.identityBytes += size;\n stats.brBytes += sizes.br ?? size;\n stats.gzipBytes += sizes.gzip ?? size;\n }\n }\n\n // zlib's async calls run on the libuv thread pool; a few files in flight\n // keep it busy without reading the whole bundle into memory at once.\n const concurrency = Math.max(1, options.concurrency ?? Math.min(8, availableParallelism()));\n let next = 0;\n await Promise.all(\n Array.from({ length: Math.min(concurrency, queue.length) }, async () => {\n while (next < queue.length) {\n await work(queue[next++]);\n }\n }),\n );\n\n return { record, stats };\n}\n\nexport async function writeStaticAssetsRecord(clientDir: string, record: StaticAssetsRecord) {\n const path = join(clientDir, STATIC_ASSETS_RECORD);\n await mkdir(dirname(path), { recursive: true });\n // Sorted, so two builds of the same tree write the same file.\n const files = Object.fromEntries(\n Object.entries(record.files).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)),\n );\n await writeFile(path, `${JSON.stringify({ files }, null, 2)}\\n`);\n}\n\n/**\n * The record a build wrote, or `null` when there is none (a build made before\n * precompression, or an incomplete `dist/`): the server then serves every file\n * as before. A malformed entry is dropped rather than trusted.\n */\nexport async function readStaticAssetsRecord(\n clientDir: string,\n): Promise<StaticAssetsRecord | null> {\n let raw: string;\n try {\n raw = await readFile(join(clientDir, STATIC_ASSETS_RECORD), \"utf8\");\n } catch {\n return null;\n }\n let files: unknown;\n try {\n files = JSON.parse(raw)?.files;\n } catch {\n return null;\n }\n if (!files || typeof files !== \"object\") {\n return null;\n }\n const record: StaticAssetsRecord = { files: {} };\n for (const [file, encodings] of Object.entries(files)) {\n if (!Array.isArray(encodings)) continue;\n record.files[file] = ENCODINGS.filter((encoding) => encodings.includes(encoding));\n }\n return record;\n}\n",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"import { isReservedAssetPath, RESERVED_ROUTE_PREFIX } from \"../services/router/ViewRouteDispatcher\";\nimport { chunkReloadStubScript } from \"../client/chunkLoadRecovery\";\n\n/**\n * A JavaScript module under `/assets/` — the only shape a client build chunk\n * has. Vite writes every chunk into `assets/` with a `.js` extension (`.mjs`\n * is accepted for a build configured to emit it).\n *\n * The extension is matched at the end of the path, not anywhere in it:\n * `includes(\".js\")` also matched `app.js.map` and `data.json`, so a missing\n * source map was answered with a script that reloads the page.\n */\nconst BUILD_CHUNK = new RegExp(`^${RESERVED_ROUTE_PREFIX}/.+\\\\.m?js$`);\n\nexport function isBuildChunkPath(pathname: string): boolean {\n return BUILD_CHUNK.test(pathname);\n}\n\n/**\n * `/assets` or anything under it — the prefix the router refuses to mount a\n * route on at boot, so nothing there is ever the app's to answer. Defined\n * beside that check so the two cannot drift apart.\n */\nexport { isReservedAssetPath };\n\n/**\n * The answer to a static-looking request with no file behind it in\n * `dist/client`, or `undefined` to hand the request to the app.\n *\n * A missing build chunk is almost always a document rendered by another\n * release asking for its own chunks — a deploy between page load and a lazy\n * `import()`, or two releases serving at once. The page cannot continue with\n * the chunk it wanted, so it gets a module that reloads it onto this release's\n * document. Only for build chunks: outside `/assets/` a `.js` path may be an\n * app route, and a classic `<script src>` answered with a reload reloads\n * forever.\n *\n * `no-store` because the stub is a stand-in for one release's chunk, served\n * under that chunk's URL. Cached — by the browser or by an edge in front of\n * the origin — it would outlive the deploy that caused it and answer the\n * request after the real chunk is back.\n *\n * The body is only the reload. An earlier version opened with\n * `if(caches){caches?.delete(distPath)}`, which was wrong three times over:\n * `CacheStorage.delete` takes a cache *name*, not a URL, so a filesystem path\n * matched nothing; that path told anyone who asked for a missing chunk where\n * the server keeps its files; and `caches` is `[SecureContext]`, so on a\n * plain-http origin the bare identifier is not defined at all and reading it\n * threw a `ReferenceError` before the reload below could run — the recovery\n * failing exactly where it was needed.\n *\n * The reload is guarded by the same `sessionStorage` marker the client\n * router's chunk recovery uses (`client/chunkLoadRecovery.ts`), so the stub\n * and the router together reload once per cooldown. A chunk that is still\n * missing after that reload makes the stub throw a chunk-load error instead —\n * the importing `import()` rejects and the route's error boundary shows,\n * where an unguarded reload used to loop.\n */\nexport function staticAssetMiss(pathname: string): Response | undefined {\n if (isBuildChunkPath(pathname)) {\n return new Response(chunkReloadStubScript(pathname), {\n headers: {\n \"Content-Type\": \"application/javascript\",\n \"Cache-Control\": \"no-store\",\n },\n });\n }\n\n // `/assets` is reserved for build output (the router rejects routes there\n // at boot), so a miss is a miss — 404 without paying for an SSR render.\n if (isReservedAssetPath(pathname)) {\n return new Response(\"Not found\", { status: 404 });\n }\n\n // Anywhere else the public-file pattern matched on extension alone, so this\n // may well be an app route — a file route like\n // `this.file(() => Bun.file(...))` mounted at `/files/logo.svg` lands here\n // too. The app answers it rather than a 404 on its behalf.\n return undefined;\n}\n",
|
|
13
13
|
"import { stringEraRefusal } from \"../http/Error\";\nimport { isApiPath } from \"../services/router/apiPath\";\n\n// The production server's last resort: an error nothing on the way here turned\n// into a response. Request breakers (auth, 404s, 416s) are answered by the\n// dispatchers before this, so what arrives is a genuine failure — and its\n// message and stack are for the developer, not for whoever triggered it. They\n// can carry absolute paths, dependency versions, SQL fragments and column\n// names. The dev server renders them on purpose (`httpDev.ts`); this does not.\n\nconst GENERIC_500_HTML = `<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>Internal Server Error</title>\n <style>\n body { font-family: system-ui, sans-serif; margin: 0; min-height: 100vh; display: grid; place-items: center; }\n main { text-align: center; padding: 16px; }\n h1 { font-size: 1.5rem; margin: 0 0 0.5rem; }\n p { margin: 0; opacity: 0.7; }\n </style>\n </head>\n <body>\n <main>\n <h1>Something went wrong</h1>\n <p>Please try again later.</p>\n </main>\n </body>\n</html>`;\n\nexport function unhandledErrorResponse(\n err: unknown,\n pathname: string,\n onException?: (error: Error) => void,\n): Response {\n // `onException` is the report, and the default one `App` installs is a\n // `console.error`, so logging here as well printed every failure twice\n // (three times for `/api`, whose dispatcher logs before rethrowing). Only\n // without a reporter does this log itself.\n //\n // Reported for `/api` too: an app's error tracking should see an API failure\n // that got this far, not only a page one. `onRequestFail` is a different\n // hook (route config, called by the dispatchers), so this is not a second\n // report through the same channel.\n if (!onException) {\n console.error(err);\n } else {\n try {\n // The hook is typed to receive an `Error`; a thrown string or object is\n // wrapped so a reporter reading `.stack` or `.name` gets one.\n onException(err instanceof Error ? err : new Error(String(err), { cause: err }));\n } catch (reportError) {\n // A broken reporter must not turn a 500 into a leaked stack either.\n console.error(err);\n console.error(reportError);\n }\n }\n\n if (isApiPath(pathname)) {\n return new Response(\n JSON.stringify({ error: stringEraRefusal(\"server_error\", \"Internal Server Error\", 500) }),\n {\n status: 500,\n headers: { \"Content-Type\": \"application/json\" },\n },\n );\n }\n\n return new Response(GENERIC_500_HTML, {\n status: 500,\n headers: { \"Content-Type\": \"text/html; charset=utf-8\" },\n });\n}\n"
|
|
14
14
|
],
|
|
15
|
-
"mappings": "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",
|
|
16
|
-
"debugId": "
|
|
15
|
+
"mappings": ";gkCAAA,eAAS,UAET,SAAS,OAAM,IAAS,QCgBxB,WAAS,UACT,cAAS,oBAAW,WAAsB,QAW1C,FAAM,HAAuC,LAAC,DAAM,AAAM,JAQpD,HAAyB,DAYzB,HAAiB,JACjB,HAAqB,HAGrB,HAAa,EAEnB,SAAS,EAAW,CAAC,EAAiC,CACpD,GAAI,IAAQ,OACV,MAAO,GAKT,GAAI,CAAC,oBAAoB,KAAK,EAAI,KAAK,CAAC,EACtC,MAAO,GAET,IAAM,EAAQ,OAAO,EAAI,KAAK,CAAC,EAC/B,OAAO,OAAO,SAAS,CAAK,EAAI,KAAK,IAAI,EAAO,CAAC,EAAI,EAahD,SAAS,EAAmB,CAAC,EAA+D,CACjG,GAAI,IAAW,MAAQ,IAAW,OAChC,OAAO,KAGT,IAAM,EAAW,IAAI,IACrB,QAAW,KAAQ,EAAO,MAAM,GAAG,EAAG,CACpC,IAAO,KAAc,GAAU,EAAK,MAAM,GAAG,EACvC,EAAS,EAAU,KAAK,EAAE,YAAY,EAC5C,GAAI,IAAW,GACb,SAGF,IAAI,EAAI,EACR,QAAW,KAAS,EAAQ,CAC1B,IAAM,EAAY,EAAM,QAAQ,GAAG,EACnC,GAAI,IAAc,GAChB,SAEF,GAAI,EAAM,MAAM,EAAG,CAAS,EAAE,KAAK,EAAE,YAAY,IAAM,IAAK,CAC1D,EAAI,GAAY,EAAM,MAAM,EAAY,CAAC,CAAC,EAC1C,OAIJ,EAAS,IAAI,EAAQ,CAAC,EAGxB,OAAO,EAUF,SAAS,CAAiB,CAC/B,EACA,EAA+B,GACP,CACxB,IAAM,EAAW,GAAoB,CAAM,EAC3C,GAAI,CAAC,EACH,OAAO,KAGT,IAAM,EAAW,EAAS,IAAI,GAAG,EAC7B,EAA+B,KAC/B,EAAQ,EAEZ,QAAW,KAAU,EAAW,CAC9B,IAAM,EAAI,EAAS,IAAI,CAAM,GAAK,GAAY,EAC9C,GAAI,EAAI,EACN,EAAO,EACP,EAAQ,EAIZ,OAAO,EAGT,SAAS,EAAY,CAAC,EAA4B,EAA4B,CAC5E,GAAI,CAAC,EACH,MAAO,GAIT,OAAO,EACJ,MAAM,GAAG,EACT,KAAK,CAAC,IAAU,EAAM,KAAK,EAAE,YAAY,EAAE,MAAM,GAAG,EAAE,GAAG,KAAK,IAAM,CAAS,EAY3E,SAAS,EAAc,CAAC,EAAgB,EAAwB,CAGrE,GAAI,IAAW,OACb,MAAO,GAKT,GAAI,EAAI,OAAS,KAAO,EAAI,SAAW,KAAO,EAAI,SAAW,KAAO,EAAI,SAAW,IACjF,MAAO,GAGT,GAAI,EAAI,QAAQ,IAAI,eAAe,EACjC,MAAO,GAGT,IAAM,EAAc,EAAI,QAAQ,IAAI,cAAc,EAClD,GAAI,CAAC,GAAe,CAAC,0BAA0B,KAAK,CAAW,EAC7D,MAAO,GAOT,GAAI,CAAC,EAAI,KACP,MAAO,GAKT,IAAM,EAAkB,EAAI,QAAQ,IAAI,kBAAkB,EAC1D,GAAI,GAAmB,EAAgB,KAAK,EAAE,YAAY,IAAM,WAC9D,MAAO,GAKT,GAAI,GAAa,EAAI,QAAQ,IAAI,eAAe,EAAG,cAAc,EAC/D,MAAO,GAGT,IAAM,EAAgB,EAAI,QAAQ,IAAI,gBAAgB,EACtD,GAAI,IAAkB,MAAQ,OAAO,CAAa,EAAI,GACpD,MAAO,GAGT,MAAO,GAWF,SAAS,EAAmB,CAAC,EAA2B,CAC7D,IAAM,EACJ,IAAa,KACT,GAAqB,CACnB,OAAQ,EACL,EAAU,sBAAuB,IACjC,EAAU,oBAAqB,EAClC,EACA,MAAO,EAAU,sBACnB,CAAC,EACD,GAAW,CAAE,MAAO,GAAY,MAAO,EAAU,YAAa,CAAC,EAErE,OAAO,GAAO,MAAM,CAAU,EAGhC,SAAS,EAAsB,CAAC,EAAkB,CAChD,IAAM,EAAO,EAAQ,IAAI,MAAM,EAC/B,GAAI,CAAC,EAAM,CACT,EAAQ,IAAI,OAAQ,iBAAiB,EACrC,OAEF,IAAM,EAAS,EAAK,MAAM,GAAG,EAAE,IAAI,CAAC,IAAU,EAAM,KAAK,EAAE,YAAY,CAAC,EAGxE,GAAI,EAAO,SAAS,GAAG,GAAK,EAAO,SAAS,iBAAiB,EAC3D,OAEF,EAAQ,OAAO,OAAQ,iBAAiB,EAanC,SAAS,CAAgB,CAAC,EAAc,EAAyB,CACtE,GAAI,CAAC,GAAe,EAAI,OAAQ,CAAG,EACjC,OAAO,EAGT,IAAM,EAAU,IAAI,QAAQ,EAAI,OAAO,EACvC,GAAuB,CAAO,EAE9B,IAAM,EAAW,EAAkB,EAAI,QAAQ,IAAI,iBAAiB,CAAC,EACrE,GAAI,CAAC,EACH,OAAO,IAAI,SAAS,EAAI,KAAM,CAC5B,OAAQ,EAAI,OACZ,WAAY,EAAI,WAChB,SACF,CAAC,EAGH,IAAI,EACJ,GAAI,CACF,EAAU,GAAoB,CAAQ,EACtC,KAAM,CAGN,OAAO,IAAI,SAAS,EAAI,KAAM,CAC5B,OAAQ,EAAI,OACZ,WAAY,EAAI,WAChB,SACF,CAAC,EAQH,OALA,EAAQ,IAAI,mBAAoB,CAAQ,EAGxC,EAAQ,OAAO,gBAAgB,EAExB,IAAI,SAAS,EAAI,KAAM,YAAY,CAAO,EAAG,CAClD,OAAQ,EAAI,OACZ,WAAY,EAAI,WAChB,SACF,CAAC,EC5SH,kBAAS,WAAS,qBAClB,eAAS,cCDT,gBAAS,eAAO,WAAU,gBAAM,qBAEhC,kBAAS,WAAS,cAClB,oBAAS,aACT,yBAAS,gBAAgB,WAAW,cAwB7B,IAAM,GAAuB,2BAoB7B,IAAM,EAAqD,CAChE,GAAI,MACJ,KAAM,KACR,EAGM,GAA+B,CAAC,KAAM,MAAM,EAkB5C,GAAS,EAAU,EAAc,EACjC,GAAY,EAAU,EAAI,EA6IhC,eAAsB,CAAsB,CAC1C,EACoC,CACpC,IAAI,EACJ,GAAI,CACF,EAAM,MAAM,GAAS,GAAK,EAAW,EAAoB,EAAG,MAAM,EAClE,KAAM,CACN,OAAO,KAET,IAAI,EACJ,GAAI,CACF,EAAQ,KAAK,MAAM,CAAG,GAAG,MACzB,KAAM,CACN,OAAO,KAET,GAAI,CAAC,GAAS,OAAO,IAAU,SAC7B,OAAO,KAET,IAAM,EAA6B,CAAE,MAAO,CAAC,CAAE,EAC/C,QAAY,EAAM,KAAc,OAAO,QAAQ,CAAK,EAAG,CACrD,GAAI,CAAC,MAAM,QAAQ,CAAS,EAAG,SAC/B,EAAO,MAAM,GAAQ,GAAU,OAAO,CAAC,IAAa,EAAU,SAAS,CAAQ,CAAC,EAElF,OAAO,EC7OF,SAAS,CAAY,CAAC,EAA8B,CAEzD,IAAM,EAAkB,EAAa,SAAS,EAExC,EAAS,IAAI,IAAI,aAAa,MAAM,EAI1C,OAHA,EAAO,OAAO,CAAe,EAChB,EAAO,OAAO,KAAK,EFmBlC,eAAsB,EAAe,CAAC,EAAyC,CAC7E,IAAM,EAAQ,IAAI,IAElB,eAAe,CAAI,CAAC,EAAa,EAAmB,CAClD,IAAI,EACJ,GAAI,CACF,EAAU,MAAM,GAAQ,EAAK,CAAE,cAAe,EAAK,CAAC,EACpD,KAAM,CACN,OAEF,QAAW,KAAS,EAAS,CAC3B,GAAI,EAAM,KAAK,WAAW,GAAG,EAAG,SAChC,GAAI,GAAY,EAAM,OAAS,SAAU,SACzC,IAAM,EAAO,GAAK,EAAK,EAAM,IAAI,EAC7B,EAAQ,EAAM,YAAY,EAC1B,EAAS,EAAM,OAAO,EAC1B,GAAI,EAAM,eAAe,EACvB,GAAI,CACF,IAAM,EAAS,MAAM,GAAK,CAAI,EAC9B,EAAQ,EAAO,YAAY,EAC3B,EAAS,EAAO,OAAO,EACvB,KAAM,CACN,SAGJ,GAAI,EAAQ,EAAM,IAAI,CAAI,EAGrB,QAAI,GAAS,CAAC,EAAM,eAAe,EAAG,MAAM,EAAK,EAAM,EAAK,GAKrE,OADA,MAAM,EAAK,EAAW,EAAI,EACnB,EAGT,IAAM,GAAa,4CAOb,GAAY,sCAgBlB,eAAe,EAAc,CAC3B,EACA,EACA,EAC2E,CAG3E,GAAI,EAAU,SAAW,GAAK,EAAI,QAAQ,IAAI,OAAO,EACnD,OAAO,KAET,IAAM,EAAW,EAAkB,EAAI,QAAQ,IAAI,iBAAiB,EAAG,CAAS,EAChF,GAAI,CAAC,EACH,OAAO,KAET,IAAM,EAAc,EAAO,EAAkB,GAC7C,GAAI,CAGF,IAAM,EAAO,MAAM,GAAK,CAAW,EACnC,OAAO,EAAK,OAAO,EAAI,CAAE,WAAU,KAAM,EAAa,KAAM,EAAK,IAAK,EAAI,KAC1E,KAAM,CACN,OAAO,MAsBX,eAAsB,EAAkB,CACtC,EACA,EACA,EAA6B,CAAC,EACX,CACnB,IAAM,EAAO,IAAI,KAAK,CAAI,EACpB,EAAY,EAAQ,WAAa,CAAC,EAElC,EAAkC,CACtC,eAAgB,EAAK,MAAQ,2BAC7B,gBAAiB,EAAQ,UAAY,GAAY,GACjD,gBAAiB,OACnB,EACA,GAAI,EAAU,OAAS,EACrB,EAAQ,KAAO,kBAGjB,IAAM,EAAU,MAAM,GAAe,EAAK,EAAM,CAAS,EACzD,GAAI,EAAS,CACX,IAAM,EAAU,IAAI,KAAK,EAAQ,IAAI,EACrC,OAAO,IAAI,SAAS,EAAQ,OAAO,EAAG,CACpC,QAAS,IACJ,EACH,mBAAoB,EAAQ,SAC5B,iBAAkB,OAAO,EAAQ,IAAI,EAErC,KAAM,GAAG,EAAa,EAAQ,YAAY,KAAK,EAAQ,UACzD,CACF,CAAC,EAGH,IAAM,EAAQ,EAAK,KACnB,EAAQ,KAAO,EAAa,EAAK,YAAY,EAE7C,IAAM,EAAQ,EAAiB,EAAI,QAAQ,IAAI,OAAO,CAAC,EAEvD,GAAI,EAAO,CACT,IAAM,EAAW,GAAa,EAAO,CAAK,EAC1C,GAAI,CAAC,EACH,OAAO,GAA4B,CAAK,EAE1C,OAAO,IAAI,SAAS,EAAK,MAAM,EAAS,MAAO,EAAS,IAAM,CAAC,EAAE,OAAO,EAAG,CACzE,OAAQ,IACR,QAAS,IACJ,EACH,gBAAiB,GAAmB,EAAS,MAAO,EAAS,IAAK,CAAK,EACvE,iBAAkB,OAAO,EAAS,MAAM,CAC1C,CACF,CAAC,EAGH,OAAO,IAAI,SAAS,EAAK,OAAO,EAAG,CACjC,QAAS,IAAK,EAAS,iBAAkB,OAAO,CAAK,CAAE,CACzD,CAAC,EF/KH,eAAS,qBKQF,IAAM,EAAmB,iBAgBzB,SAAS,EAAiB,CAC/B,EACA,EAAoB,EAC+B,CACnD,IAAM,EAAO,EAAS,IAAmB,KACzC,GAAI,CAAC,EACH,OAEF,MAAO,CACL,OAAQ,EAAS,EAAM,CAAS,EAChC,QAAS,EAAsB,EAAU,EAAkB,CAAS,CACtE,EAgBK,SAAS,CAAqB,CACnC,EACA,EACA,EAAoB,EACV,CACV,IAAM,EAAiB,CAAC,EAGlB,EAAU,IAAI,IAEd,EAAO,CAAC,IAAgB,CAC5B,GAAI,EAAQ,IAAI,CAAG,EAAG,OACtB,EAAQ,IAAI,CAAG,EACf,IAAM,EAAQ,EAAS,GACvB,GAAI,CAAC,GAAO,KAAM,OAClB,EAAK,KAAK,EAAS,EAAM,KAAM,CAAS,CAAC,EACzC,QAAW,KAAY,EAAM,SAAW,CAAC,EACvC,EAAK,CAAQ,GAMjB,OAFA,EAAK,CAAQ,EAEN,EAYF,SAAS,EAAsB,CACpC,EACA,EACA,EAAoB,EACV,CACV,IAAM,EAAO,IAAI,IAAI,EAAsB,EAAU,EAAU,CAAS,CAAC,EACnE,EAAoB,CAAC,EACrB,EAAU,IAAI,IAEd,EAAO,CAAC,IAAgB,CAC5B,GAAI,EAAQ,IAAI,CAAG,EAAG,OACtB,EAAQ,IAAI,CAAG,EACf,IAAM,EAAQ,EAAS,GACvB,GAAI,CAAC,EAAO,OACZ,QAAW,KAAU,EAAM,gBAAkB,CAAC,EAC5C,GAAI,CAAC,uBAAuB,KAAK,CAAM,GAAK,CAAC,EAAQ,SAAS,CAAM,EAClE,EAAQ,KAAK,CAAM,EAGvB,QAAW,KAAY,EAAM,SAAW,CAAC,EACvC,EAAK,CAAQ,GAGjB,EAAK,CAAQ,EAEb,IAAM,EAAiB,CAAC,EACxB,QAAW,KAAU,EACnB,QAAW,KAAO,EAAsB,EAAU,EAAQ,CAAS,EACjE,GAAI,CAAC,EAAK,IAAI,CAAG,EACf,EAAK,IAAI,CAAG,EACZ,EAAK,KAAK,CAAG,EAInB,OAAO,EASF,SAAS,EAAU,CAAC,EAAwB,EAA4B,CAC7E,IAAM,EAAkB,CAAC,EACnB,EAAO,IAAI,IACX,EAAU,IAAI,IAEd,EAAO,CAAC,IAAgB,CAC5B,GAAI,EAAQ,IAAI,CAAG,EAAG,OACtB,EAAQ,IAAI,CAAG,EACf,IAAM,EAAQ,EAAS,GACvB,GAAI,CAAC,EAAO,OACZ,QAAW,KAAY,EAAM,SAAW,CAAC,EACvC,EAAK,CAAQ,EAEf,QAAW,KAAQ,EAAM,KAAO,CAAC,EAC/B,GAAI,CAAC,EAAK,IAAI,CAAI,EAChB,EAAK,IAAI,CAAI,EACb,EAAM,KAAK,CAAI,GAMrB,OADA,EAAK,CAAQ,EACN,EAWF,SAAS,EAAoB,CAClC,EACA,EAAoB,EAC6E,CACjG,IAAM,EAAQ,IAAI,IAIlB,MAAO,CAAC,IAAc,CACpB,GAAI,CAAC,EAAM,IAAI,CAAS,EAAG,CACzB,IAAM,EAAM,GAAG,IAAY,KACrB,EAAO,EAAS,IAAM,KAC5B,EAAM,IACJ,EACA,EACI,CACE,IAAK,EAAS,EAAM,CAAS,EAC7B,QAAS,EAAsB,EAAU,EAAK,CAAS,EACvD,eAAgB,GAAuB,EAAU,EAAK,CAAS,CACjE,EACA,MACN,EAEF,OAAO,EAAM,IAAI,CAAS,GCjKvB,IAAM,GAAe,eA8SrB,SAAS,EAAqB,CACnC,EACA,EA7S8C,MA8StC,CACR,IAAM,EAAM,KAAK,UAzTgB,mBAyTa,EACxC,EAAQ,KAAK,UAAU,gDAAgD,GAAU,EACvF,MACE,0FAA0F,uGAEnD,iCACV,2FACyB,eC1U1D,IAAM,GAAc,IAAI,OAAO,IAAI,eAAkC,EAE9D,SAAS,CAAgB,CAAC,EAA2B,CAC1D,OAAO,GAAY,KAAK,CAAQ,EA2C3B,SAAS,EAAe,CAAC,EAAwC,CACtE,GAAI,EAAiB,CAAQ,EAC3B,OAAO,IAAI,SAAS,GAAsB,CAAQ,EAAG,CACnD,QAAS,CACP,eAAgB,yBAChB,gBAAiB,UACnB,CACF,CAAC,EAKH,GAAI,EAAoB,CAAQ,EAC9B,OAAO,IAAI,SAAS,YAAa,CAAE,OAAQ,GAAI,CAAC,EAOlD,OCpEF,IAAM,GAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,SAqBlB,SAAS,CAAsB,CACpC,EACA,EACA,EACU,CAUV,GAAI,CAAC,EACH,QAAQ,MAAM,CAAG,EAEjB,QAAI,CAGF,EAAY,aAAe,MAAQ,EAAU,MAAM,OAAO,CAAG,EAAG,CAAE,MAAO,CAAI,CAAC,CAAC,EAC/E,MAAO,EAAa,CAEpB,QAAQ,MAAM,CAAG,EACjB,QAAQ,MAAM,CAAW,EAI7B,GAAI,EAAU,CAAQ,EACpB,OAAO,IAAI,SACT,KAAK,UAAU,CAAE,MAAO,GAAiB,eAAgB,wBAAyB,GAAG,CAAE,CAAC,EACxF,CACE,OAAQ,IACR,QAAS,CAAE,eAAgB,kBAAmB,CAChD,CACF,EAGF,OAAO,IAAI,SAAS,GAAkB,CACpC,OAAQ,IACR,QAAS,CAAE,eAAgB,0BAA2B,CACxD,CAAC,ERvCH,IAAM,EAAU,GAAY,EAEtB,GAAS,EAAK,EAAS,KAAK,EAC5B,EAAU,EAAK,EAAS,MAAM,EAE9B,EAAY,EAAK,EAAS,QAAQ,EAQxC,SAAS,EAAc,CAAC,EAAiC,CACvD,IAAI,EACJ,GAAI,CACF,EAAU,mBAAmB,CAAQ,EACrC,KAAM,CACN,OAAO,KAET,IAAM,EAAO,GAAQ,EAAW,IAAM,EAAQ,QAAQ,iBAAkB,SAAS,CAAC,EAClF,OAAO,EAAK,WAAW,EAAY,EAAG,EAAI,EAAO,KAGnD,eAAe,EAAM,CAAC,EAAc,CAClC,GAAI,CACF,OAAQ,MAAM,GAAK,CAAI,GAAG,OAAO,EACjC,KAAM,CACN,MAAO,IAQX,eAAsB,EAAQ,CAAC,EAAU,EAAkC,CACzE,IAAM,EAAW,MAAa,UAAG,gCAC3B,EAAiB,MAAa,UAAG,gCAIjC,EAAY,MAAM,GAAmB,GAAG,UAAgB,EAE9D,QAAQ,IAAI,SAAW,EACvB,QAAQ,IAAI,QAAU,GACtB,QAAQ,IAAI,SAAW,EAEvB,IAAM,EAAgB,CAAC,EACjB,EAAQ,CAAC,EACT,EAAc,CAAC,EACf,EAAc,CAAC,EAGf,EAAkD,CAAC,EACnD,EAAW,CAAC,EAAkB,IAAiB,IAAI,qBAA4B,MAC/E,EAAY,CAAC,EAEb,EAAgB,EAAI,iBAAiB,KAAK,CAAG,EACnD,QAAW,IAAY,CAAC,MAAO,GAAG,EAAI,qBAAqB,KAAK,CAAG,CAAC,EAAG,CACrE,IAAM,EAAa,EAAe,aAAa,SAC/C,GAAI,CAAC,GAAY,MAAQ,EAAc,GACrC,MAAM,GAAyB,EAAU,EAAc,EAAS,EAElE,GAAI,CAAC,GAAY,KAAM,CACrB,QAAQ,IAAI,6BAA6B,GAAU,EACnD,QAAQ,IAAI,CAAU,EACtB,IAAM,EAAQ,OAAO,KAAK,CAAc,EAClC,EAAO,aAAa,QAC1B,QAAQ,IAAI,GAAG,gCAAmC,EAClD,QAAQ,IAAI,CAAK,EAEnB,IAAM,EAAM,MAAa,UAAG,QAAQ,IAAI,mBAAmB,GAAY,QACvE,EAAc,GAAY,EAAI,QAC9B,EAAM,GAAY,EAAI,UAGtB,EAAY,GAAY,EACxB,IAAM,EAAa,EAAS,aAAa,SAEzC,GAAI,GAAY,KAAO,GAAY,IAAI,OAAS,EAC9C,EAAY,GAAY,GAAY,IAEtC,GAAI,EACF,EAAU,KAAK,EAAS,EAAU,EAAS,GAAY,KAAM,CAAS,CAAC,CAAC,EACxE,EAAsB,GAAY,EAChC,EACA,aAAa,QACb,CACF,EAIJ,IAAM,GAAU,IAAI,EAAU,KAAK,GAAG,KAKhC,EAAc,GAAkB,EAAU,CAAS,EACzD,GAAI,CAAC,EAGH,QAAQ,MACN,2BAA2B,wFAC7B,EAMF,IAAM,GAAwB,EAAS,mBAAmB,KAAO,CAAC,EAC5D,GACJ,MAAM,QAAQ,IAAI,GAAY,IAAI,CAAC,IAAY,IAAI,KAAK,GAAG,YAAkB,GAAS,EAAE,KAAK,CAAC,CAAC,GAC/F,KAAK;AAAA,CAAI,EAIL,GAAgB,GAAqB,EAAU,CAAS,EAMxD,EAAU,GAAW,QAAQ,EAChC,OAAO,MAAM,IAAI,KAAK,GAAG,8BAAoC,EAAE,KAAK,CAAC,EACrE,OAAO,WAAW,EAClB,MAAM,EAAG,EAAE,EAOR,EAAe,IAAI,IACnB,GAAU,CAAC,IAAiB,CAChC,IAAI,EAAO,EAAa,IAAI,CAAI,EAChC,GAAI,CAAC,EACH,EAAO,IAAI,KAAK,GAAG,YAAkB,GAAM,EAAE,KAAK,EAClD,EAAa,IAAI,EAAM,CAAI,EAE7B,OAAO,GAEH,GAAe,MAAO,EAAiB,IAA+B,CAC1E,IAAM,EAAQ,IAAI,IAClB,QAAW,KAAQ,EAAS,CAAC,EAAQ,GAAG,CAAK,EAAI,EAC/C,QAAW,KAAQ,GAAW,EAAU,aAAa,OAAU,EAAG,EAAM,IAAI,CAAI,EAElF,IAAM,EAAS,MAAM,QAAQ,IAC3B,CAAC,GAAG,CAAK,EAAE,IAAI,MAAO,KAAU,CAAE,GAAI,EAAM,QAAS,MAAM,GAAQ,CAAI,CAAE,EAAE,CAC7E,EACA,OAAO,EAAa,EAAS,EAAS,CAAC,CAAE,QAAS,CAAc,EAAG,GAAG,CAAM,CAAC,GAoBzE,GAAc,MAAM,GAAgB,CAAS,EAO7C,EAAe,IAAI,IACzB,QAAY,EAAM,KAAc,OAAO,SACpC,MAAM,EAAuB,CAAS,IAAI,OAAS,CAAC,CACvD,EAAG,CACD,IAAM,EAAO,GAAQ,EAAW,CAAI,EACpC,GAAI,EAAK,WAAW,EAAY,EAAG,EACjC,EAAa,IAAI,EAAM,CAAE,UAAW,GAAM,WAAU,CAAC,EAIzD,eAAe,EAAc,CAAC,EAAc,CAC1C,IAAQ,YAAa,IAAI,IAAI,EAAI,GAAG,EAEpC,GAAI,EAAU,CAAQ,EACpB,OAAO,MAAM,EAAc,EAAK,CAAQ,EAG1C,IAAM,EAAW,GAAe,CAAQ,EAClC,EAAc,EAAoB,CAAQ,EAC1C,EAAe,CAAC,GAAe,IAAa,MAAQ,GAAY,IAAI,CAAQ,EAC5E,EAAgB,GAAe,EAAS,WAAW,cAAc,GAAK,EAI5E,GAAI,GAAgB,EAAE,MAAM,EAAkB,CAAG,GAAG,YAClD,OAAO,MAAM,EAAc,EAAK,CAAQ,EAG1C,GAAI,EAAe,CAUjB,GAAI,CAAC,GAAY,CAAE,MAAM,GAAO,CAAQ,EAAI,CAG1C,GAAI,GAAe,CAAC,EAAiB,CAAQ,IAAM,MAAM,EAAkB,CAAG,GAAG,YAC/E,OAAO,MAAM,EAAc,EAAK,CAAQ,EAE1C,OAAO,GAAgB,CAAQ,GAAM,MAAM,EAAc,EAAK,CAAQ,EAGxE,GAAI,CACF,OAAO,MAAM,GAAmB,EAAK,EAAU,EAAa,IAAI,CAAQ,CAAC,EACzE,MAAO,EAAO,CAEd,OADA,EAAI,cAAc,CAAK,EAChB,IAAI,SAAS,YAAa,CAAE,OAAQ,GAAI,CAAC,GAIpD,IAAM,EAAW,MAAM,EAAc,EAAK,CAAQ,EAKlD,GAAI,EAAS,SAAS,OAAO,EAC3B,GAAI,CACF,EAAS,QAAQ,IAAI,GAAc,CAAO,EAC1C,KAAM,EAEV,OAAO,EAQT,eAAe,CAAiB,CAAC,EAAc,CAC7C,GAAI,CACF,OAAQ,MAAM,EAAI,cAAc,CAAG,GAAM,CAAE,YAAa,GAAM,YAAa,EAAM,EACjF,KAAM,CACN,MAAO,CAAE,YAAa,GAAO,YAAa,EAAK,GAInD,eAAe,CAAa,CAAC,EAAc,EAAkB,CAC3D,IAAM,EAAU,EAAI,MAAM,KAAK,CAAG,EAElC,GAAI,CACF,IAAM,EAAS,MAAM,EAAQ,CAAG,EAChC,GAAI,aAAkB,SACpB,OAAO,EACF,KACL,IAAM,EAAS,CAAC,EA8BhB,OA5BA,EAAO,KAAK,CACV,QAAS,CACX,CAAC,EA0BM,MAAM,EAAO,CAClB,UAzBgB,MAChB,EACA,IACG,CAIH,IAAM,EAAQ,GAAgB,CAAC,KAAK,EACpC,GAAI,GAAS,OACX,OAAO,GAAa,EAAO,EAAQ,MAAM,EAE3C,QAAW,MAAQ,EAAO,CACxB,IAAM,GAAa,EAAS,aAAa,UACzC,QAAW,KAAW,IAAY,KAAO,CAAC,EAAG,CAC3C,IAAM,GAAM,IAAI,KAAK,GAAG,QAAQ,IAAI,mBAAmB,GAAS,EAChE,EAAO,KAAK,CACV,GAAI,EACJ,QAAS,MAAM,GAAI,KAAK,CAC1B,CAAC,GAGL,OAAO,EAAa,CAAM,GAK1B,cACA,wBACA,WACA,gBACA,cACA,QACA,cACA,YACA,iBACA,SACF,CAAC,GAEH,MAAO,EAAK,CACZ,OAAO,EAAuB,EAAK,EAAU,EAAI,WAAW,GAOhE,IAAM,IAAsB,QAAQ,IAAI,kBAAoB,QAAQ,YAAY,IAAM,MAIhF,EAAiB,GAAgB,QAAQ,IAAI,gBAAgB,EAI7D,GAAgB,GAAyB,QAAQ,GAAG,EAGpD,EAAU,EAAI,QAAQ,EAC5B,GAAI,GAAW,EAAe,OAAS,QAAU,CAAC,GAChD,QAAQ,KACN,uSACF,EAGF,IAAM,EAAS,IAAI,MAAW,CAC5B,mBAAoB,YACpB,MAAO,MAAO,EAAK,IAAW,CAU5B,GAPA,GAAoB,EAAI,QAAS,EAAO,UAAU,CAAG,GAAG,SAAW,KAAM,CAAc,EAIvF,EAAI,qBAAqB,CAAG,EAGxB,GAAS,QAAQ,CAAG,EACtB,OAAO,EAAQ,QAAQ,EAAK,CAAM,EAIpC,IAAM,EAAM,MAAM,EAAgB,EAAK,CAAC,IACtC,EAAI,qBAAqB,EAAK,GAAgB,CAAC,IAC7C,EAAuB,EAAK,IAAI,IAAI,EAAI,GAAG,EAAE,SAAU,EAAI,WAAW,CACxE,CACF,EAKA,OAAO,GAAqB,EAAiB,EAAK,CAAG,EAAI,GAE3D,YAAa,GAAkB,EAC/B,KAAM,QAAQ,IAAI,MAAQ,KAC1B,UAAW,GAAS,SACtB,CAAC,EAKD,OAJA,MAAM,GAAS,MAAM,CAAM,EAE3B,GAAmB,CAAE,KAAM,EAAO,KAAM,SAAQ,CAAC,EAE1C",
|
|
16
|
+
"debugId": "6EA213211AF9E9CF64756E2164756E21",
|
|
17
17
|
"names": []
|
|
18
18
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
import{constants as M}from"os";var Y=Symbol.for("gemi.server.shutdown");function Q(){let j=globalThis;return j[Y]??={shuttingDown:!1,drained:!1},j[Y]}function R(){return Q().shuttingDown}function f(){Q().shuttingDown=!0}function x(){Q().drained=!0}function W(j=process.env){let z={timeoutMs:U(j.GEMI_SHUTDOWN_TIMEOUT,20),delayMs:U(j.GEMI_SHUTDOWN_DELAY,0),providerTimeoutMs:U(j.GEMI_SHUTDOWN_PROVIDER_TIMEOUT,5)};if(z.delayMs>0&&z.delayMs>=z.timeoutMs)console.warn(`[gemi] GEMI_SHUTDOWN_DELAY (${z.delayMs/1000}s) leaves no time to drain within GEMI_SHUTDOWN_TIMEOUT (${z.timeoutMs/1000}s); in-flight requests will be abandoned on every shutdown. Raise the timeout.`);return z}function U(j,z){let B=j===void 0||j.trim()===""?NaN:Number(j);if(Number.isFinite(B)&&B>=0)return B*1000;if(j!==void 0)console.warn(`[gemi] Ignoring shutdown setting "${j}": expected a number of seconds.`);return z*1000}async function E(j){let{server:z,settings:B}=j,J=Date.now()+B.timeoutMs;f(),console.log(`[gemi] Shutting down: draining in-flight requests (up to ${B.timeoutMs/1000}s).`);let H=!0;if(z){if(B.delayMs>0)await _(Math.min(B.delayMs,J-Date.now()));let O=z.stop(),V=Promise.resolve();try{V=Promise.resolve(j.closeSockets?.()).catch(()=>{})}catch(F){console.error("[gemi] Closing the broadcast sockets failed:",F)}let X=J-Date.now(),$=j.closeSockets?Promise.race([O,Promise.all([V,y(z)])]):O;if(H=X>0?await G($,X):z.pendingRequests===0,!H)console.error(`[gemi] Shutdown grace period elapsed with ${z.pendingRequests} request(s) still in flight; abandoning them.`),z.stop(!0).catch(()=>{})}x();let P=await j.shutdownProviders({timeoutMs:B.providerTimeoutMs}),K=H&&P.failed.length===0&&P.timedOut.length===0;return console.log(`[gemi] Shutdown ${K?"complete":"finished with errors"}.`),K?0:1}function y(j){return new Promise((z)=>{let B=()=>{if(j.pendingRequests===0){z();return}setTimeout(B,50).unref?.()};B()})}async function G(j,z){if(z<=0)return!1;let B,J=await Promise.race([j.then(()=>!0,()=>!0),new Promise((H)=>{B=setTimeout(()=>H(!1),z)})]);return clearTimeout(B),J}function _(j){return new Promise((z)=>setTimeout(z,Math.max(0,j)))}function q(j){if(!R())return j;try{return j.headers.set("Connection","close"),j}catch{let z=new Response(j.body,j);return z.headers.set("Connection","close"),z}}function N(j){return async(z,B)=>{if(Q().drained)return new Response("Service Unavailable",{status:503,headers:{Connection:"close"}});return q(await j(z,B))}}var Z=Symbol.for("gemi.server.signalsInstalled"),w=1000;function D(j,z={}){let B=globalThis;if(B[Z])return;B[Z]=!0;let J=z.repeatWindowMs??w,H,P=(K)=>{if(H!==void 0){if(Date.now()-H<J)return;console.error(`[gemi] ${K} received again; exiting without waiting for the drain.`),process.exit(128+M.signals[K])}H=Date.now(),console.log(`[gemi] ${K} received.`),j().then((O)=>process.exit(O),(O)=>{console.error("[gemi] Shutdown failed:",O),process.exit(1)})};process.on("SIGTERM",P),process.on("SIGINT",P)}
|
|
3
|
-
export{R as
|
|
3
|
+
export{R as ol,f as pl,W as ql,E as rl,q as sl,N as tl,D as ul};
|
|
4
4
|
|
|
5
|
-
//# debugId=
|
|
5
|
+
//# debugId=E9529CD9C155CBF964756E2164756E21
|
|
@@ -5,6 +5,6 @@
|
|
|
5
5
|
"import { constants } from \"node:os\";\nimport type { ShutdownReport } from \"../foundation/Application\";\nimport type { Instrumentation } from \"./types\";\n\n// On `globalThis` under a registry symbol rather than in a module variable, so\n// the flag an app's health route reads is the one the server set even when two\n// copies of this module are loaded — the built CLI bundles its own gemi (see\n// `app/App.ts`), and `bun --hot` re-evaluates every module on a reload.\nconst STATE = Symbol.for(\"gemi.server.shutdown\");\n\ntype ShutdownState = { shuttingDown: boolean; drained: boolean };\n\nfunction state(): ShutdownState {\n const global = globalThis as { [STATE]?: ShutdownState };\n global[STATE] ??= { shuttingDown: false, drained: false };\n return global[STATE];\n}\n\n/**\n * True from the moment this server process has been told to stop — before it\n * stops accepting connections — until it exits.\n *\n * gemi's built-in health route (`./health`) answers `503` while this is true,\n * and so should an app's own health route, so the platform's probe pulls the\n * instance while the in-flight requests are still draining. Every response sent in that window also carries\n * `Connection: close`, so a client that honours it opens its next request on a\n * new connection — one the load balancer can route elsewhere — instead of\n * reusing this one.\n */\nexport function isShuttingDown(): boolean {\n return state().shuttingDown;\n}\n\nexport function markShuttingDown() {\n state().shuttingDown = true;\n}\n\n/**\n * The request drain is over and the providers' `shutdown()` may be running:\n * from here on a request must not reach the app. See `serveForShutdown`.\n */\nexport function markDrained() {\n state().drained = true;\n}\n\n/** Test seam: a fresh process starts false, and so should each test. */\nexport function resetShuttingDown() {\n state().shuttingDown = false;\n state().drained = false;\n}\n\nexport type ShutdownSettings = {\n /**\n * From the signal to the end of the request drain, in milliseconds — the\n * delay below included. Requests still in flight when it runs out are\n * abandoned. `GEMI_SHUTDOWN_TIMEOUT`, in seconds; default 20. `0` means\n * \"close the listener and do not wait\": a shutdown with nothing in flight is\n * still a clean one.\n */\n timeoutMs: number;\n /**\n * How long to keep accepting new requests after `isShuttingDown()` turns\n * true, in milliseconds, so a health probe can see the `503` and the load\n * balancer stop routing here before the listener closes.\n * `GEMI_SHUTDOWN_DELAY`, in seconds; default 0.\n */\n delayMs: number;\n /**\n * The shared deadline for every provider's `shutdown()`, in milliseconds,\n * after the drain. `GEMI_SHUTDOWN_PROVIDER_TIMEOUT`, in seconds; default 5.\n * `0` skips the hooks — deliberately, so it is not reported as a failure.\n */\n providerTimeoutMs: number;\n};\n\n/**\n * 20s to drain plus 5s for providers is 25s: under the 30s that Kubernetes,\n * Azure Container Apps and ECS give by default between `SIGTERM` and `SIGKILL`,\n * with 5s left for the `gemi start` relay, the process exit and clock skew\n * between the platform's timer and ours. A platform with a shorter window\n * (Cloud Run's 10s, Fly's 5s) needs these lowered, not the other way round —\n * a drain that outlives the grace period ends in the same `SIGKILL` as no drain.\n *\n * The delay defaults to 0 because it only helps where a probe is watching, and\n * otherwise every shutdown would pay it for nothing.\n */\nexport function shutdownSettings(\n env: Record<string, string | undefined> = process.env,\n): ShutdownSettings {\n const settings = {\n timeoutMs: seconds(env.GEMI_SHUTDOWN_TIMEOUT, 20),\n delayMs: seconds(env.GEMI_SHUTDOWN_DELAY, 0),\n providerTimeoutMs: seconds(env.GEMI_SHUTDOWN_PROVIDER_TIMEOUT, 5),\n };\n // Warned, not clamped: the delay is sized to a probe, the timeout to the\n // platform's grace period, and which one is wrong only the operator knows.\n // Left alone, every shutdown spends the whole timeout in the delay and then\n // abandons whatever is in flight without draining it.\n if (settings.delayMs > 0 && settings.delayMs >= settings.timeoutMs) {\n console.warn(\n `[gemi] GEMI_SHUTDOWN_DELAY (${settings.delayMs / 1000}s) leaves no time to drain within GEMI_SHUTDOWN_TIMEOUT (${settings.timeoutMs / 1000}s); in-flight requests will be abandoned on every shutdown. Raise the timeout.`,\n );\n }\n return settings;\n}\n\nfunction seconds(value: string | undefined, fallback: number): number {\n const parsed = value === undefined || value.trim() === \"\" ? NaN : Number(value);\n if (Number.isFinite(parsed) && parsed >= 0) return parsed * 1000;\n if (value !== undefined) {\n console.warn(`[gemi] Ignoring shutdown setting \"${value}\": expected a number of seconds.`);\n }\n return fallback * 1000;\n}\n\n/** The part of `Bun.Server` a drain needs. */\nexport type Stoppable = {\n stop(closeActiveConnections?: boolean): Promise<void>;\n readonly pendingRequests: number;\n};\n\n/**\n * The shutdown sequence, in its order:\n *\n * 1. `isShuttingDown()` turns true — first, so the health route answers `503`\n * and responses carry `Connection: close` while the listener is still open.\n * 2. After `delayMs`, the listener closes (`server.stop()` without force):\n * new connections are refused, while every request in flight — a streamed\n * response included, to its last chunk — runs to completion.\n * Broadcast sockets are told `bye` and closed (`closeSockets`): a graceful\n * stop waits for open WebSockets, which never close on their own.\n * 3. That completion is awaited until `timeoutMs` from the start.\n * 4. From here a request is refused with a `503` before it reaches the app\n * (`serveForShutdown`), and every provider's `shutdown()` runs, reverse\n * order, `providerTimeoutMs`.\n *\n * Resolves with the exit code: 0 when every step finished in time, 1 when the\n * drain was cut short or a provider failed or overran.\n *\n * Two things `stop()` does not do, both measured on Bun 1.3.14, and both why\n * the process exit that follows is what actually ends the server:\n *\n * - It does not close a kept-alive connection. One that is idle when the\n * listener closes stays open, and a request sent down it afterwards is\n * served. That is what the `Connection: close` on every response in the\n * window is for: a client (or a proxy in front) that honours it opens its\n * next request on a new connection, which the closed listener refuses. A\n * client that does not honour it is what step 4's `503` is for — Bun does\n * not close the socket after a `Connection: close` response either (#566).\n * - `stop(true)` issued after a graceful `stop()` does not cut the remaining\n * requests off; it waits for the same ones. It is still called when the\n * drain times out, for a Bun that does, but nothing here depends on it. Nor\n * does it close an idle kept-alive connection. Only a `stop(true)` issued\n * *first* does that, and it cuts off the requests in flight with it, which\n * is the drain given up — so the idle ones are refused, not closed.\n */\nexport async function drain(params: {\n server: Stoppable | undefined;\n /**\n * Says `bye` to the broadcast sockets and closes them, right after the\n * listener closes, and resolves once they are closed or cut. A graceful\n * `stop()` waits for open WebSockets, which otherwise never close on their\n * own.\n */\n closeSockets?: () => void | Promise<void>;\n shutdownProviders: (options: { timeoutMs: number }) => Promise<ShutdownReport>;\n settings: ShutdownSettings;\n}): Promise<number> {\n const { server, settings } = params;\n const deadline = Date.now() + settings.timeoutMs;\n\n markShuttingDown();\n console.log(\n `[gemi] Shutting down: draining in-flight requests (up to ${settings.timeoutMs / 1000}s).`,\n );\n\n let drained = true;\n if (server) {\n if (settings.delayMs > 0) {\n await sleep(Math.min(settings.delayMs, deadline - Date.now()));\n }\n const stopped = server.stop();\n let socketsClosed: Promise<unknown> = Promise.resolve();\n try {\n socketsClosed = Promise.resolve(params.closeSockets?.()).catch(() => {});\n } catch (error) {\n console.error(\"[gemi] Closing the broadcast sockets failed:\", error);\n }\n // No time left to wait — a `GEMI_SHUTDOWN_TIMEOUT` of 0, or a delay that\n // spent all of it — is \"do not wait for what is in flight\", and that is\n // only a failure when something *is* in flight. Reading it as one made\n // every clean shutdown log an abandonment and exit 1 for the operator who\n // zeroed the budget to fit a 5s grace period, with nothing to abandon.\n const remaining = deadline - Date.now();\n // With broadcast sockets, the stop's own promise has been seen to stay\n // pending for good after every request finished and every socket was\n // closed or terminated (Bun 1.4.2 on a loaded Linux CI runner, with its\n // `pendingWebSockets` stuck above 0). So the drain also ends on its real\n // condition: no request in flight, and the hub's sockets closed or cut.\n const done = params.closeSockets\n ? Promise.race([stopped, Promise.all([socketsClosed, noRequestInFlight(server)])])\n : stopped;\n drained =\n remaining > 0 ? await settlesWithin(done, remaining) : server.pendingRequests === 0;\n if (!drained) {\n console.error(\n `[gemi] Shutdown grace period elapsed with ${server.pendingRequests} request(s) still in flight; abandoning them.`,\n );\n void server.stop(true).catch(() => {});\n }\n }\n\n // Whether or not the drain finished: a request that arrives now, down a\n // kept-alive connection the listener's close left open, would run against\n // providers that are being shut down underneath it.\n markDrained();\n const report = await params.shutdownProviders({ timeoutMs: settings.providerTimeoutMs });\n const clean = drained && report.failed.length === 0 && report.timedOut.length === 0;\n console.log(`[gemi] Shutdown ${clean ? \"complete\" : \"finished with errors\"}.`);\n return clean ? 0 : 1;\n}\n\n/**\n * Resolves once `server` has no request in flight. A streamed response counts\n * until its last chunk (measured on Bun 1.4, macOS and Linux).\n */\nfunction noRequestInFlight(server: Stoppable): Promise<void> {\n return new Promise((resolve) => {\n const check = () => {\n if (server.pendingRequests === 0) {\n resolve();\n return;\n }\n const timer = setTimeout(check, 50);\n (timer as { unref?: () => void }).unref?.();\n };\n check();\n });\n}\n\nasync function settlesWithin(promise: Promise<unknown>, ms: number): Promise<boolean> {\n // `drain` decides for itself what a budget that is already spent means, so\n // this is only a guard: a race against `setTimeout(0)` is a coin toss, and\n // \"did not settle within no time at all\" is the honest answer to it.\n if (ms <= 0) return false;\n let timer: ReturnType<typeof setTimeout> | undefined;\n const result = await Promise.race([\n promise.then(\n () => true,\n () => true,\n ),\n new Promise<false>((resolve) => {\n timer = setTimeout(() => resolve(false), ms);\n }),\n ]);\n clearTimeout(timer);\n return result;\n}\n\nfunction sleep(ms: number) {\n return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));\n}\n\n/**\n * `Connection: close` on a response sent while shutting down, so the client's\n * next request opens a connection — which the load balancer can send elsewhere\n * — instead of reusing this one.\n *\n * The spec makes some responses' headers immutable (a `Response.redirect`, a\n * proxied `fetch`), and setting one throws. Bun 1.3.14 lets both be set, so\n * the copy is for a Bun that follows the spec — a throw here would turn every\n * such response in the drain window into a 500.\n */\nexport function closeConnectionWhileShuttingDown(res: Response): Response {\n if (!isShuttingDown()) return res;\n try {\n res.headers.set(\"Connection\", \"close\");\n return res;\n } catch {\n const copy = new Response(res.body, res);\n copy.headers.set(\"Connection\", \"close\");\n return copy;\n }\n}\n\n/**\n * What every response goes through while this process serves: the app's\n * instrumentation, with the two changes a shutdown makes to it.\n *\n * - While the requests in flight drain, a response carries `Connection: close`\n * (`closeConnectionWhileShuttingDown`).\n * - Once they have drained, a request is answered `503` with\n * `Connection: close` and reaches neither the app nor its instrumentation.\n * The listener is closed by then, so only a kept-alive connection can still\n * deliver one, and `server.stop()` leaves those open. Serving it would run a\n * handler against providers whose `shutdown()` is closing the pool, the\n * queue or the cache it uses — until the process exits. The instrumentation\n * is skipped too because it is app code, and a tracer or error reporter is\n * exactly what a provider flushes and closes at this point.\n */\nexport function serveForShutdown(instrumentation: Instrumentation): Instrumentation {\n return async (req, next) => {\n if (state().drained) {\n return new Response(\"Service Unavailable\", {\n status: 503,\n headers: { Connection: \"close\" },\n });\n }\n return closeConnectionWhileShuttingDown(await instrumentation(req, next));\n };\n}\n\n// Installed once per process, not once per `Server`: a listener per instance\n// would each start a shutdown of its own, and read the other's signal as the\n// second one.\nconst SIGNALS_INSTALLED = Symbol.for(\"gemi.server.signalsInstalled\");\n\n/**\n * A repeat of the first signal inside this window is the same shutdown\n * delivered twice, not an operator asking to skip the drain. One shutdown\n * routinely arrives more than once, milliseconds apart: Ctrl+C on\n * `bun run start` reaches the server directly (the whole foreground group is\n * signalled), and again through `gemi start`'s relay — twice over, since\n * `bun run` forwards it to `gemi start` too. systemd's default\n * `KillMode=control-group` and `tini -g` do the same to every process in the\n * group. A person pressing Ctrl+C again is well outside a second.\n */\nconst REPEAT_WINDOW_MS = 1000;\n\n/**\n * `SIGTERM` or `SIGINT` runs `stop` and exits with the code it resolves with.\n * Another signal of either kind, more than `repeatWindowMs` after the first,\n * exits at once with `128 + n` — an operator pressing Ctrl+C again does not\n * want to wait out the grace period. Without a listener the first signal would\n * do that too: the default action for both is to terminate the process on the\n * spot.\n */\nexport function installShutdownSignals(\n stop: () => Promise<number>,\n options: { repeatWindowMs?: number } = {},\n) {\n const global = globalThis as { [SIGNALS_INSTALLED]?: boolean };\n if (global[SIGNALS_INSTALLED]) return;\n global[SIGNALS_INSTALLED] = true;\n\n const repeatWindowMs = options.repeatWindowMs ?? REPEAT_WINDOW_MS;\n let receivedAt: number | undefined;\n const onSignal = (signal: \"SIGTERM\" | \"SIGINT\") => {\n if (receivedAt !== undefined) {\n if (Date.now() - receivedAt < repeatWindowMs) return;\n console.error(`[gemi] ${signal} received again; exiting without waiting for the drain.`);\n process.exit(128 + constants.signals[signal]);\n }\n receivedAt = Date.now();\n console.log(`[gemi] ${signal} received.`);\n stop().then(\n (code) => process.exit(code),\n (error) => {\n console.error(\"[gemi] Shutdown failed:\", error);\n process.exit(1);\n },\n );\n };\n process.on(\"SIGTERM\", onSignal);\n process.on(\"SIGINT\", onSignal);\n}\n"
|
|
6
6
|
],
|
|
7
7
|
"mappings": "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",
|
|
8
|
-
"debugId": "
|
|
8
|
+
"debugId": "E9529CD9C155CBF964756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
import{Ld as R}from"./chunk-9aj6eavs.js";import{$d as S,Sd as Q,ee as E}from"./chunk-
|
|
2
|
+
import{Ld as R}from"./chunk-9aj6eavs.js";import{$d as S,Sd as Q,ee as E}from"./chunk-tbf76adx.js";import{fl as K}from"./chunk-bsnp7h5w.js";import{wl as A,xl as $,yl as D}from"./chunk-7gc1jp2z.js";class j extends Error{code;constructor(q,z=1){super(q);this.code=z;this.name="CommandFailed"}}class J{static commandName="unset";static description="";static args=[];static options=[];run(q){}}function p(q){return q}function C(){return{commands:[],commandsDir:"app/commands"}}var I=Symbol.for("gemi.console.builder");function k(q){if(q.trim()==="")throw Error("A command name cannot be empty.");if(/[{}]/.test(q)||/\s/.test(q))throw Error(`Invalid command name ${JSON.stringify(q)}. gemi does not parse a signature string; the name is the whole of it. Declare arguments with .arg() and flags with .option():
|
|
3
3
|
|
|
4
4
|
defineCommand("mail:send")
|
|
5
5
|
.arg("user", { required: true })
|
|
@@ -16,4 +16,4 @@ import{Ld as R}from"./chunk-9aj6eavs.js";import{$d as S,Sd as Q,ee as E}from"./c
|
|
|
16
16
|
If this is an abstract base its subclasses extend, move it out of the `+"listeners directory \u2014 the walk excludes gemi's `Listener` and "+"nothing else, so a base sitting beside its subclasses is registered and run like one of them.")}function v(q){for(let z of q){if(Object.getOwnPropertyDescriptor(z,"name")?.writable)continue;console.warn(`Event listener ${z.name} does not declare \`static name\`, so it is registered under its class name, which a production build renames. Nothing reads that name across a build boundary while the `+"listener is sync, which is why this is a warning \u2014 but adding "+`\`queued = true\` to it is refused at boot for the same reason, because the queue's key is that string. Add: static name = "${z.name}";`)}}function u(q){let z=new Set;for(let{event:F}of q){if(z.has(F))continue;if(Object.getOwnPropertyDescriptor(F,"name")?.writable)continue;z.add(F),console.warn(`Event ${F.name} does not declare \`static name\`, so it is dispatched under its class name. A production build minifies the code that dispatches it and renames the class, while discovery reads `+"the listener's copy from source and does not \u2014 so the dispatch would "+`reach no listener in production and nowhere else. Add: static name = "${F.name}";`)}}
|
|
17
17
|
export{j as vd,J as wd,p as xd,C as yd,c as zd,o as Ad,T as Bd,i as Cd,B as Dd,U as Ed,W as Fd,Sq as Gd,Zq as Hd,_q as Id,Iq as Jd,Uq as Kd};
|
|
18
18
|
|
|
19
|
-
//# debugId=
|
|
19
|
+
//# debugId=B641D321A010B3A864756E2164756E21
|
|
@@ -12,6 +12,6 @@
|
|
|
12
12
|
"import { existsSync } from \"node:fs\";\nimport { dirname, isAbsolute, join } from \"node:path\";\n\nimport {\n discoverClasses,\n DiscoveryError,\n projectRoot,\n} from \"../support/discover\";\nimport { Command, type CommandClass } from \"../console/Command\";\nimport { commandConfigDefaults } from \"../console/config\";\nimport { isUnfinishedBuilder } from \"../console/builder\";\nimport { CronJob } from \"./cron/CronJob\";\nimport { scheduleConfigDefaults } from \"./cron/config\";\nimport { eventConfigDefaults } from \"./events/config\";\nimport type { EventClass } from \"./events/Event\";\nimport { Listener, type ListenerClass } from \"./events/Listener\";\nimport { Job } from \"./queue/Job\";\nimport { queueConfigDefaults } from \"./queue/config\";\nimport { Workflow, type WorkflowClass } from \"./queue/workflow/Workflow\";\n\n/**\n * Reading an application's jobs off the filesystem, for the two subsystems that\n * were asking every app to write its job list twice.\n *\n * Both functions here are the same call to `discoverClasses` with a different\n * base class, which is deliberate: one walk, one set of skip rules, one answer\n * to \"what does this app have\". They are exported from `gemi/services` rather\n * than kept private to the providers because the list stopped being something\n * an app could import — a test that used to walk the exported `jobs` array\n * needs somewhere to ask the same question, and so does a script.\n *\n * What the providers add on top of these is the decision of *whether* to ask,\n * which is the config's to make and not this file's.\n */\n\n/**\n * A directory the app named, resolved the way the app meant it.\n *\n * Relative paths hang off the project root rather than `process.cwd()` at the\n * moment of the call, because a config slice saying `app/jobs` means the app's\n * `app/jobs` and not whatever directory somebody happened to launch a script\n * from. Absolute paths are taken as given, which is what makes these callable\n * from a test that has no project around it.\n */\nfunction resolveDir(dir: string): string {\n return isAbsolute(dir) ? dir : join(projectRoot(), dir);\n}\n\n/**\n * Says something when the directory is missing *and so is the tree it lives in*.\n *\n * A missing directory has two readings and they want opposite responses. An app\n * that has never written a cron job has no `app/cron`, and warning about that on\n * every boot for the rest of its life is noise — the answer \"you have no cron\n * jobs\" is correct and uninteresting. But a deploy that ships `dist/` and\n * `node_modules/` without the source has no `app/cron` *and no `app/`*, and\n * there discovery quietly returns nothing while the app very much does have\n * jobs: the exact silence #322 and #323 exist to remove, reintroduced one layer\n * down and in production only.\n *\n * The parent directory is what tells them apart. If neither the directory nor\n * the one above it is there, the tree this was pointed into is not there\n * either, and that is worth a sentence.\n */\nfunction warnIfSourceIsMissing(\n resolved: string,\n subject: string,\n slice: string,\n) {\n if (existsSync(resolved) || existsSync(dirname(resolved))) return;\n\n console.warn(\n `No ${subject} were discovered: neither ${resolved} nor the directory ` +\n `above it exists, so this process cannot see the application's source. ` +\n `If this is a deploy that ships only the build output, list the classes ` +\n `explicitly in app/config/${slice}.ts so they travel with it.`,\n );\n}\n\n/**\n * The `Job` subclasses under `dir`, defaulting to the config slice's `app/jobs`.\n *\n * This is what a `queue` config slice with no `jobs` key resolves to. Call it\n * directly to ask the question a test used to answer by importing the array:\n * every job this application would register, in the order it would register\n * them.\n *\n * Every file underneath is imported, so calling this runs the app's job modules\n * — the cost `discoverClasses` documents at length, paid once per call.\n */\nexport async function discoverJobs(\n dir: string = queueConfigDefaults().jobsDir,\n): Promise<Array<new () => Job>> {\n const resolved = resolveDir(dir);\n warnIfSourceIsMissing(resolved, \"queue jobs\", \"queue\");\n const jobs = await discoverClasses(resolved, Job);\n warnIfNameWillNotSurviveTheBuild(jobs);\n return jobs;\n}\n\n/**\n * The `Workflow` subclasses under `dir`, defaulting to the queue slice's\n * `app/workflows`: what a `queue` slice with no `workflows` key resolves to.\n * A missing directory is no workflows, said nothing about unless the tree\n * above it is missing too.\n */\nexport async function discoverWorkflows(\n dir: string = queueConfigDefaults().workflowsDir,\n): Promise<WorkflowClass[]> {\n const resolved = resolveDir(dir);\n warnIfSourceIsMissing(resolved, \"workflows\", \"queue\");\n const workflows = (await discoverClasses(resolved, Workflow)) as WorkflowClass[];\n for (const workflow of workflows) {\n if (Object.getOwnPropertyDescriptor(workflow, \"name\")?.writable) continue;\n console.warn(\n `Workflow ${workflow.name} does not declare \\`static name\\`. A workflow ` +\n `is stored under its name, and a production build renames the class. ` +\n `Add: static name = \"${workflow.name}\";`,\n );\n }\n return workflows;\n}\n\n/**\n * Says something about a discovered job that never declared `static name`.\n *\n * This is the one hazard discovery introduces rather than removes, and it is\n * production-only. `gemi build` minifies the server entry, and app code\n * reachable from it — a controller, and every job class that controller\n * imports to dispatch — is bundled and minified with it. Minification renames\n * the class binding, and a class's implicit `.name` is that binding: a\n * `TestJob` in the bundle reports `\"D\"`. Discovery, meanwhile, imports\n * `app/jobs/TestJob.ts` from source at runtime, where it is still `\"TestJob\"`.\n * The queue is keyed by that name, so the two halves stop agreeing and the\n * dispatch is dropped — in production, and only there.\n *\n * A declared `static name = \"TestJob\"` is a string literal, which survives\n * minification intact, and the two halves agree again. `docs/jobs-and-queues.md`\n * has always called it required; before discovery an app could get away without\n * it, because the explicit `jobs` array was bundled too and both sides were\n * wrong in the same way.\n *\n * The check is exact rather than a guess: a `static name = \"...\"` class field\n * defines a *writable* own property, while the implicit class name is\n * non-writable. Cron is unaffected — `CronJob.name` is an instance field\n * holding a string literal, not the class binding.\n */\nfunction warnIfNameWillNotSurviveTheBuild(jobs: Array<new () => Job>) {\n for (const job of jobs) {\n if (Object.getOwnPropertyDescriptor(job, \"name\")?.writable) continue;\n\n console.warn(\n `Queued job ${job.name} does not declare \\`static name\\`, so it is ` +\n `registered under its class name. A production build minifies the ` +\n `class that dispatches it and renames it, while discovery reads this ` +\n `file from source and does not — so the dispatch would be dropped in ` +\n `production and nowhere else. Add: static name = \"${job.name}\";`,\n );\n }\n}\n\n/**\n * The `CronJob` subclasses under `dir`, defaulting to the config slice's\n * `app/cron`.\n *\n * This is what a `schedule` config slice with no `jobs` key resolves to. Call it\n * directly to ask the question a test used to answer by importing the array —\n * though after boot `app(Scheduler).jobs` is the better one, because it is what\n * the scheduler actually took rather than what this would find if asked again.\n *\n * Every file underneath is imported, so calling this runs the app's cron\n * modules — the cost `discoverClasses` documents at length, paid once per call.\n */\nexport async function discoverCronJobs(\n dir: string = scheduleConfigDefaults().jobsDir,\n): Promise<Array<new () => CronJob>> {\n const resolved = resolveDir(dir);\n warnIfSourceIsMissing(resolved, \"cron jobs\", \"schedule\");\n return await discoverClasses(resolved, CronJob);\n}\n\n/**\n * The `Command` subclasses under `dir`, defaulting to the config slice's\n * `app/commands`.\n *\n * This is what a `commands`-less `command` slice resolves to, and what\n * `gemi run` lists. Unlike the two above it is not called during boot — a\n * command registry has one consumer, and importing every file under\n * `app/commands` into every server process would be a cost paid on every start\n * for code no request reaches.\n *\n * Every file underneath is imported, so calling this runs the app's command\n * modules — the cost `discoverClasses` documents at length, paid once per call.\n */\nexport async function discoverCommands(\n dir: string = commandConfigDefaults().commandsDir,\n): Promise<CommandClass[]> {\n const resolved = resolveDir(dir);\n warnIfSourceIsMissing(resolved, \"commands\", \"command\");\n\n const unfinished: string[] = [];\n\n const commands = await discoverClasses(\n resolved,\n Command,\n [],\n (value, file) => {\n if (isUnfinishedBuilder(value)) unfinished.push(file);\n },\n );\n\n refuseUnfinishedBuilders(unfinished);\n\n return commands as CommandClass[];\n}\n\n/**\n * Stops the walk for a builder chain that was never finished.\n *\n * This is the one silence the builder introduces, and it is the exact shape\n * #322 and #323 exist to remove. `defineCommand(...).arg(...)` without a\n * trailing `.handle(...)` is a plain object rather than a class, so\n * `discoverClasses` discards it along with every constant and helper a command\n * file happens to export — and the command disappears from `gemi run` with\n * nothing raised. The author sees a listing containing every command except the\n * one they just wrote.\n *\n * A throw rather than a warning, because there is no reading of an unfinished\n * builder under which the author meant it: the chain builds a description of a\n * command and then discards it.\n *\n * \"Unfinished\" is decided by `.handle()` clearing the brand, not by the export\n * still being a builder object. Those differ for the natural way to share\n * declarations —\n *\n * export const base = defineCommand(\"reindex\").option(\"verbose\", …);\n * export default base.handle(async () => {});\n *\n * — where `base` is a builder that *did* produce a command. Keying off the shape\n * alone made that file abort the whole of `gemi run`, taking every other command\n * in the project down with it, under a message that was untrue for it.\n */\nfunction refuseUnfinishedBuilders(files: string[]): void {\n if (files.length === 0) return;\n\n const subject =\n files.length === 1\n ? `${files[0]} exports a command builder that never called \\`.handle()\\``\n : `${files.length} files export a command builder that never called ` +\n `\\`.handle()\\`:\\n ${files.join(\"\\n \")}`;\n\n throw new DiscoveryError(\n `${subject}, so there is no command to run. \\`.handle()\\` is what turns ` +\n `the chain into a command — finish it:\\n\\n` +\n ` export default defineCommand(\"my-command\")\\n` +\n ` .handle(async ({ line }) => { line(\"hello\") })`,\n );\n}\n\n/**\n * The `Listener` subclasses under `dir`, defaulting to the config slice's\n * `app/listeners`.\n *\n * This is what an `events` config slice with no `listeners` key resolves to.\n * Call it directly to ask what an application would register: every listener,\n * in the order it would register them.\n *\n * Only listeners are walked. Event classes are never discovered — each is\n * imported by the listener that binds to it and by the code that dispatches it,\n * so there is nothing a walk over them would find that is not already in the\n * module graph.\n *\n * **Known residual, matching jobs and cron.** `discoverClasses` excludes the\n * `base` it is handed and not intermediates, so an abstract listener base\n * written in `listenersDir` and extended by its siblings is discovered and\n * registered alongside them — constructed, with its `handle` called, on every\n * dispatch of whatever event it declares. Keep shared bases outside the\n * directory.\n *\n * Every file underneath is imported, so calling this runs the app's listener\n * modules — the cost `discoverClasses` documents at length, paid once per call.\n *\n * @throws DiscoveryError if a discovered listener declares no `static event`.\n */\nexport async function discoverListeners(\n dir: string = eventConfigDefaults().listenersDir,\n): Promise<ListenerClass[]> {\n const resolved = resolveDir(dir);\n warnIfSourceIsMissing(resolved, \"event listeners\", \"events\");\n\n const listeners = (await discoverClasses(\n resolved,\n Listener,\n )) as ListenerClass[];\n\n refuseListenersWithNoEvent(listeners);\n warnIfListenerNameWillNotSurviveTheBuild(listeners);\n warnIfEventNameWillNotSurviveTheBuild(listeners);\n\n return listeners;\n}\n\n/**\n * Stops the walk for a listener that binds to nothing.\n *\n * `static event` is declared as required on the base and the compiler still\n * cannot see it missing: every subclass inherits the declaration whether or not\n * it assigns to one, so `typeof SomeListener` type-checks either way. What the\n * author gets instead is a file that is imported, registered under no name, and\n * never called — the silence this whole module is arranged against.\n *\n * A throw rather than a warning, because there is no reading of a listener with\n * no event under which the author meant it. The usual cause is the residual\n * above: an abstract base sharing a gate between two listeners, sitting in\n * `listenersDir` where the walk finds it. Moving it out is the fix, and the\n * message says so — the alternative, excluding it by some guess about\n * abstractness, is how a discovery pass ends up quietly covering less than the\n * directory holds.\n */\nfunction refuseListenersWithNoEvent(listeners: ListenerClass[]): void {\n const orphans = listeners.filter((listener) => !listener.event);\n if (orphans.length === 0) return;\n\n throw new DiscoveryError(\n `${orphans\n .map((listener) => listener.name)\n .join(\", \")} declare no \\`static event\\`, so there is no event name to ` +\n `register them under and their \\`handle\\` would never run. Every ` +\n `listener binds to exactly one event:\\n\\n` +\n ` static event = UserRegistered;\\n\\n` +\n `If this is an abstract base its subclasses extend, move it out of the ` +\n `listeners directory — the walk excludes gemi's \\`Listener\\` and ` +\n `nothing else, so a base sitting beside its subclasses is registered ` +\n `and run like one of them.`,\n );\n}\n\n/**\n * Says something about a discovered listener that never declared `static name`.\n *\n * The hazard is `warnIfNameWillNotSurviveTheBuild`'s, one subsystem over: a\n * `static name = \"...\"` class field defines a *writable* own property while the\n * implicit class binding does not, and a minifier renames the binding while\n * leaving the string literal alone. The two spellings are indistinguishable by\n * value — both read `\"SendWelcomeEmail\"` in development — so the descriptor is\n * the only thing that tells them apart, and the difference only becomes visible\n * in a production build.\n *\n * A warning rather than a refusal, because a *sync* listener's name has no\n * second half to disagree with: every reader of it comes from this same\n * source-side walk, so a minified build and this walk cannot produce two\n * different strings for it. A **queued** listener does have one — the name is\n * the queue's key and it crosses a JSON boundary — and that case is refused\n * outright, by `jobForListener`, at the point the listener is registered with\n * the queue rather than here. This walk cannot tell the two apart without\n * constructing every listener it found; the registry constructs them anyway.\n */\nfunction warnIfListenerNameWillNotSurviveTheBuild(listeners: ListenerClass[]) {\n for (const listener of listeners) {\n if (Object.getOwnPropertyDescriptor(listener, \"name\")?.writable) continue;\n\n console.warn(\n `Event listener ${listener.name} does not declare \\`static name\\`, so ` +\n `it is registered under its class name, which a production build ` +\n `renames. Nothing reads that name across a build boundary while the ` +\n `listener is sync, which is why this is a warning — but adding ` +\n `\\`queued = true\\` to it is refused at boot for the same reason, ` +\n `because the queue's key is that string. Add: static name = ` +\n `\"${listener.name}\";`,\n );\n }\n}\n\n/**\n * Says something about the *event* a discovered listener binds to, when that\n * event's name is the implicit class binding.\n *\n * This is the check that matters from the first dispatch, and it is the reason\n * `discoverListeners` reads `static event` at all. The registry is keyed by the\n * event's name; the dispatcher supplies that name from the class it constructs.\n * A production build minifies the server entry and the app code reachable from\n * it — the controller, and the event class it imports in order to dispatch —\n * while discovery imports `app/listeners/*.ts` from source, and those files\n * import `app/events/*.ts` from source too. Two module graphs, two class\n * objects, and an implicit `.name` that reads `\"UserRegistered\"` on one side\n * and `\"D\"` on the other. The dispatch then finds no listeners, which is legal,\n * normal, and completely silent.\n *\n * A declared `static name` is a string literal, which survives minification\n * intact, and the two halves agree again.\n *\n * Checked through the descriptor rather than the string, and not by comparing\n * `event.name` to anything: in development the two spellings produce the same\n * value, so a check on the string would be a tautology that passes forever.\n */\nfunction warnIfEventNameWillNotSurviveTheBuild(listeners: ListenerClass[]) {\n const warned = new Set<EventClass>();\n\n for (const { event } of listeners) {\n if (warned.has(event)) continue;\n if (Object.getOwnPropertyDescriptor(event, \"name\")?.writable) continue;\n warned.add(event);\n\n console.warn(\n `Event ${event.name} does not declare \\`static name\\`, so it is ` +\n `dispatched under its class name. A production build minifies the ` +\n `code that dispatches it and renames the class, while discovery reads ` +\n `the listener's copy from source and does not — so the dispatch would ` +\n `reach no listener in production and nowhere else. Add: ` +\n `static name = \"${event.name}\";`,\n );\n }\n}\n"
|
|
13
13
|
],
|
|
14
14
|
"mappings": "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",
|
|
15
|
-
"debugId": "
|
|
15
|
+
"debugId": "B641D321A010B3A864756E2164756E21",
|
|
16
16
|
"names": []
|
|
17
17
|
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import{W as P,Y as c,Z as f,_ as l}from"./chunk-psmwxtf5.js";import{na as u,oa as m}from"./chunk-x2e20w8g.js";import"./chunk-bwg7genv.js";import"./chunk-mvd4mc8p.js";import"./chunk-7txst143.js";import"./chunk-eykb93pv.js";import"./chunk-s0z61m9j.js";import"./chunk-r1n2fkfz.js";import{Ui as S}from"./chunk-4dnr1vg7.js";import{kl as T}from"./chunk-9pet8zmd.js";import{Al as y}from"./chunk-sjg5g92s.js";import"./chunk-xe126csw.js";import{join as YJ}from"path";import{createServer as AJ}from"vite";import{join as R,sep as o}from"path";import{existsSync as a,statSync as s}from"fs";import{IncomingMessage as t,ServerResponse as r}from"http";import{Socket as e}from"net";function i(J){return J.replace(/\x1b\[[0-9;]*m/g,"").replace(/\[[0-9;]*m/g,"").replace(/\n\s*\n/g,`
|
|
3
|
+
`).split(`
|
|
4
|
+
`).map((W)=>W.trim()).filter((W)=>W.length>0).join(`
|
|
5
|
+
`).trim()}function N(J){return`
|
|
6
|
+
<!DOCTYPE html>
|
|
7
|
+
<html lang="en">
|
|
8
|
+
<head>
|
|
9
|
+
<meta charset="UTF-8" />
|
|
10
|
+
<meta
|
|
11
|
+
name="viewport"
|
|
12
|
+
content="width=device-width, initial-scale=1.0"
|
|
13
|
+
/>
|
|
14
|
+
|
|
15
|
+
<title>Error</title>
|
|
16
|
+
<style>
|
|
17
|
+
body {
|
|
18
|
+
font-family: Arial, sans-serif;
|
|
19
|
+
padding: 20px;
|
|
20
|
+
}
|
|
21
|
+
h1 {
|
|
22
|
+
color: red;
|
|
23
|
+
}
|
|
24
|
+
</style>
|
|
25
|
+
</head>
|
|
26
|
+
<body>
|
|
27
|
+
<div id="overlay"></div>
|
|
28
|
+
<script type="module" src="/refresh.js"></script>
|
|
29
|
+
<script type="module" src="/@vite/client"></script>
|
|
30
|
+
<script>
|
|
31
|
+
${i.toString()}
|
|
32
|
+
const err = ${JSON.stringify(J,Object.getOwnPropertyNames(J),2)}
|
|
33
|
+
|
|
34
|
+
window.addEventListener('load', () => {
|
|
35
|
+
const container = document.getElementById('overlay')
|
|
36
|
+
const ErrorOverlay = customElements.get('vite-error-overlay')
|
|
37
|
+
if (ErrorOverlay) {
|
|
38
|
+
const overlay = new ErrorOverlay({ ...err, message: cleanupTerminalText(err.message) });
|
|
39
|
+
container.appendChild(overlay)
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
</script>
|
|
43
|
+
</body>
|
|
44
|
+
</html>
|
|
45
|
+
`}function JJ(J,W){let Z=new URL(W.url),$=new t(new e);$.url=Z.pathname+Z.search,$.method=W.method,$.headers=Object.fromEntries(W.headers);let z=new r($),Q=[];return new Promise((L)=>{z.write=(X)=>{if(X)Q.push(Buffer.from(X));return!0},z.end=(X)=>{if(X&&typeof X!=="function")Q.push(Buffer.from(X));let G=new Headers;for(let[O,Y]of Object.entries(z.getHeaders())){if(Y==null)continue;for(let K of Array.isArray(Y)?Y:[Y])G.append(O,String(K))}return L(new Response(Q.length?Buffer.concat(Q):null,{status:z.statusCode,headers:G})),z},J.middlewares($,z,()=>L(null))})}function WJ(J,W,Z){let $=String(W),z=" ".repeat($.length),Q=" ".repeat(Math.max(0,Z-1))+"^";return`${$} | ${J}
|
|
46
|
+
${z} | ${Q}
|
|
47
|
+
`}function I(J){if(J&&typeof J==="object"){let W=J.position;if(W&&typeof W==="object"&&typeof W.file==="string")return{message:typeof J.message==="string"?J.message:String(J),stack:"",frame:typeof W.lineText==="string"?WJ(W.lineText,W.line,W.column):void 0,loc:{file:W.file,line:W.line,column:W.column}};return{message:typeof J.message==="string"?J.message:String(J),stack:typeof J.stack==="string"?J.stack:""}}return{message:String(J),stack:""}}function x(J){let W=globalThis.__gemiVite;if(!W)return;W.ws.send({type:"error",err:I(J)}),globalThis.__gemiErrorActive=!0}function M(J){let W=J.environments.ssr;if(typeof W?.runner?.import!=="function")throw Error("gemi dev requires a runnable Vite SSR environment.");return W.runner}var k="/@gemi/dev-entry.js",ZJ=`const report = (error) => console.error(error);
|
|
48
|
+
await import("/refresh.js").catch(report);
|
|
49
|
+
await import("/@vite/client").catch(report);
|
|
50
|
+
await import("/app/client.tsx");
|
|
51
|
+
`,$J=`import RefreshRuntime from "/@react-refresh";
|
|
52
|
+
RefreshRuntime.injectIntoGlobalHook(window);
|
|
53
|
+
window.$RefreshReg$ = () => {};
|
|
54
|
+
window.$RefreshSig$ = () => (type) => type;
|
|
55
|
+
window.__vite_plugin_react_preamble_installed__ = true;
|
|
56
|
+
`;function zJ(J){let{pathname:W}=new URL(J.url),Z=($)=>new Response($,{headers:{"Content-Type":"application/javascript"}});if(W===k)return Z(ZJ);if(W.startsWith("/render-error.js"))return Z("window.render_error = true");if(W.startsWith("/refresh.js"))return Z($J);return null}function GJ(J,W){let Z=J.config?.publicDir;if(!Z||W==="/assets"||W.startsWith("/assets/"))return!1;let $;try{$=decodeURIComponent(W)}catch{return!1}if($.split("/").some((Q)=>Q.startsWith(".")))return!1;let z=R(Z,$);if(!z.startsWith(Z+o))return!1;try{return s(z).isFile()}catch{return!1}}function E(J,W,Z){let $=R(process.cwd(),"app");return async(z)=>{let{pathname:Q}=new URL(z.url),L=(G)=>{if(console.error(G),Z.ssrFixStacktrace?.(G),x(G),y(Q))return new Response(JSON.stringify({error:S("server_error",G?.message??String(G),500)}),{status:500,headers:{"Content-Type":"application/json"}});return globalThis.__gemiErrorPageServed=!0,new Response(N(I(G)),{status:500,headers:{"Content-Type":"text/html"}})},X=async(G)=>{let O=zJ(G);if(O)return O;try{let Y=J.fetch.bind(J),A=(GJ(Z,Q)&&!(await J.staticFiles(G)).publicFiles?null:await JJ(Z,G))??await Y(G);if(A instanceof Response)return A;let V={},_={},C={},d=(U,j)=>`"${U}": () => import("${j}")`,D=[],n=J.getRequiredViews.call(J);for(let U of["404",...J.getFlatComponentTree.call(J)]){if(Bun.env.NODE_ENV==="test")break;let j=`${process.env.APP_DIR}`,b=n[U];if(b&&!a(`${j}/views/${U}.tsx`))throw f(U,b);let B=await M(Z).import(`${j}/views/${U}.tsx`);V[U]=B.default,_[U]=B?.OpenGraph,C[U]=B,D.push(d(U,`/app/views/${U}.tsx`))}let p=`{${D.join(",")}}`;return await A({getStyles:async(U,j)=>await P($,Z,U,j?.layout),resolveIsland:(U)=>({src:`/${U}${T}`,preload:[]}),buildId:"dev",bootstrapModules:[k],viewImportMap:V,viewModules:C,ogMap:_,loaders:p,cssManifest:{}})}catch(Y){return L(Y)}};return await W(z,(G)=>J.withGlobalMiddleware(G,X,L))}}function QJ(J){try{return Bun.listen({hostname:"0.0.0.0",port:J,socket:{data(){}}}).stop(!0),!0}catch(W){return W?.code!=="EADDRINUSE"}}function g(J,W=QJ){let Z=24678+(J-5173);if(Z>65471)Z=24678;for(let $=Z;$<Z+64;$++)if(W($))return $;throw Error(`Could not find a free port for Vite's HMR websocket: tried ${Z}-${Z+64-1}. Set one explicitly in gemi.config.ts as \`vite: { server: { ws: { port } } }\`.`)}import{createServer as XJ}from"http";var KJ=new Set(["vite-hmr","vite-ping"]);async function q(J){let W=XJ((Z,$)=>{$.writeHead(426,{"Content-Type":"text/plain"}),$.end("Upgrade Required")});return await new Promise((Z,$)=>{W.once("error",$),W.listen(J,"127.0.0.1",()=>{W.off("error",$),Z()})}),W}function w(J){if(!J||typeof J!=="object")return null;let Z=J.server?.address?.();if(Z&&typeof Z==="object"&&"port"in Z)return Number(Z.port)||null;return null}function h(J){if(J.headers.get("upgrade")?.toLowerCase()!=="websocket")return!1;return KJ.has(J.headers.get("sec-websocket-protocol")??"")}var LJ=(J)=>typeof J==="string"?J:new Uint8Array(J);async function v(J,W,Z){let $=new URL(J.url),z=J.headers.get("sec-websocket-protocol")??"vite-hmr",Q={host:J.headers.get("host")??$.host},L=J.headers.get("origin");if(L)Q.origin=L;let X=new WebSocket(`ws://127.0.0.1:${Z}${$.pathname}${$.search}`,{protocols:[z],headers:Q});X.binaryType="arraybuffer";let G={upstream:X,early:[]};if(X.onmessage=(K)=>{let A=LJ(K.data);if(G.browser)G.browser.send(A);else G.early.push(A)},X.onclose=(K)=>{G.browser?.close(UJ(K.code),K.reason)},X.onerror=()=>{},!await new Promise((K)=>{X.addEventListener("open",()=>K(!0),{once:!0}),X.addEventListener("close",()=>K(!1),{once:!0})}))return new Response("Vite refused the HMR websocket",{status:403});if(!W.upgrade(J,{headers:{"Sec-WebSocket-Protocol":z},data:G}))return X.close(),new Response("WebSocket upgrade failed",{status:400});return}function UJ(J){if(J===1000||J>=1001&&J<=1003||J>=1007&&J<=1014)return J;if(J>=3000&&J<=4999)return J;return 1000}var F={open(J){let W=J.data;for(let Z of W.early)J.send(Z);if(W.early=[],W.browser=J,W.upstream.readyState!==WebSocket.OPEN)J.close(1000)},message(J,W){if(J.data.upstream.readyState===WebSocket.OPEN)J.data.upstream.send(W)},close(J){let{upstream:W}=J.data;if(W.readyState<=WebSocket.OPEN)W.close()}};function OJ(J,W){let Z=W.websocket,$=(z)=>z.data?.gemiSocket===!0;return{...W.socketOptions(),maxPayloadLength:16777216,closeOnBackpressureLimit:!1,open:(z)=>$(z)?Z.open?.(z):J.open?.(z),message:(z,Q)=>$(z)?Z.message(z,Q):J.message(z,Q),close:(z,Q,L)=>$(z)?Z.close?.(z,Q,L):J.close?.(z,Q,L)}}var H=process.cwd(),jJ=YJ(H,"app");if(!globalThis.__gemiErrorHooked)globalThis.__gemiErrorHooked=!0,process.on("unhandledRejection",(J)=>{console.error(J),x(J)}),process.on("uncaughtException",(J)=>{console.error(J),x(J)});async function vJ(J,W){let Z=Number(process.env.PORT)||5173,$=l(),z=Boolean(globalThis.__gemiVite),Q=J.devAllowedHosts.call(J);if(Q===!0&&!z)console.warn("gemi dev: `route.domains.devHosts` is `true`, so the dev server answers any Host. A web page you visit can then read the app's source through DNS rebinding. List the hosts you need instead.");let L=globalThis.__gemiVite??=await AJ({configFile:!1,plugins:[m()],server:{middlewareMode:!0,port:Z,ws:{server:await q(g(Z))},allowedHosts:Q,watch:{ignored:(K)=>{let A=K.split(/[/\\]/).pop()??"";return A===".env"||A.startsWith(".env.")}}},appType:"custom",resolve:{dedupe:["react","react-dom"]},optimizeDeps:{include:["react","react-dom","react-dom/client"]},ssr:{external:[...u]}});process.env.ROOT_DIR=H,process.env.APP_DIR=jJ;let X=E(J,W,L),G=w(L.config?.server?.ws),O=J.sockets(),Y=Bun.serve({port:Z,idleTimeout:$,fetch:(K,A)=>{if(G!==null&&h(K))return v(K,A,G);if(J.applyTrustedClientIp(K),O?.matches(K))return O.upgrade(K,A);return X(K)},websocket:O?OJ(F,O):F});if(await O?.start(Y),z)M(L).clearCache();if(z)if(globalThis.__gemiErrorActive&&globalThis.__gemiErrorPageServed)globalThis.__gemiErrorActive=!1,globalThis.__gemiErrorPageServed=!1,L.ws.send({type:"full-reload"});else globalThis.__gemiErrorActive=!1,L.ws.send({type:"custom",event:"http-reload"});else c({port:Y.port,rootDir:H});return Y}export{I as viteErrorPayload,vJ as httpDev};
|
|
57
|
+
|
|
58
|
+
//# debugId=560F47CBDDA6906764756E2164756E21
|