@mandujs/core 0.53.3 → 0.54.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +654 -654
- package/package.json +1 -1
- package/src/a11y/__tests__/run-audit.test.ts +333 -333
- package/src/a11y/fix-hints.ts +76 -76
- package/src/a11y/index.ts +18 -18
- package/src/a11y/run-audit.ts +394 -394
- package/src/a11y/types.ts +125 -125
- package/src/auth/__tests__/login.test.ts +1 -1
- package/src/auth/__tests__/password.test.ts +122 -122
- package/src/auth/__tests__/tokens.test.ts +274 -274
- package/src/auth/__tests__/verification.test.ts +274 -274
- package/src/auth/index.ts +76 -76
- package/src/auth/login.ts +225 -225
- package/src/auth/password.ts +120 -120
- package/src/auth/reset.ts +243 -243
- package/src/auth/tokens.ts +612 -612
- package/src/auth/verification.ts +253 -253
- package/src/brain/__tests__/redactor.test.ts +94 -94
- package/src/brain/adapters/__tests__/_helpers.ts +83 -83
- package/src/brain/adapters/__tests__/anthropic-oauth.test.ts +196 -196
- package/src/brain/adapters/__tests__/chatgpt-auth.test.ts +193 -193
- package/src/brain/adapters/__tests__/openai-oauth.test.ts +209 -209
- package/src/brain/adapters/__tests__/resolver.test.ts +143 -143
- package/src/brain/adapters/anthropic-oauth.ts +1 -1
- package/src/brain/adapters/chatgpt-auth.ts +300 -300
- package/src/brain/adapters/index.ts +319 -319
- package/src/brain/adapters/oauth-flow.ts +439 -439
- package/src/brain/consent.ts +240 -240
- package/src/brain/credentials.ts +396 -396
- package/src/bundler/__tests__/build-runner.ts +113 -113
- package/src/bundler/__tests__/dev-reliability.test.ts +619 -619
- package/src/bundler/__tests__/extended-watch.test.ts +711 -711
- package/src/bundler/__tests__/fast-refresh.test.ts +10 -10
- package/src/bundler/__tests__/hdr.test.ts +353 -353
- package/src/bundler/__tests__/hmr-client.test.ts +532 -532
- package/src/bundler/__tests__/manifest-schema.test.ts +266 -266
- package/src/bundler/__tests__/prod-smoke.test.ts +138 -138
- package/src/bundler/__tests__/reverse-import-graph.test.ts +519 -519
- package/src/bundler/__tests__/slot-dispatch.test.ts +573 -573
- package/src/bundler/__tests__/url-cap-and-slot-regex.test.ts +286 -286
- package/src/bundler/__tests__/vendor-cache.test.ts +455 -455
- package/src/bundler/budget.ts +404 -404
- package/src/bundler/build.test.ts +179 -179
- package/src/bundler/build.ts +55 -55
- package/src/bundler/dev.ts +42 -42
- package/src/bundler/manifest-schema.ts +301 -301
- package/src/bundler/plugins/__tests__/block-generated-imports.test.ts +263 -263
- package/src/bundler/plugins/__tests__/react-compiler-config.test.ts +83 -83
- package/src/bundler/plugins/__tests__/react-compiler-lint.test.ts +110 -110
- package/src/bundler/plugins/block-generated-imports.ts +155 -155
- package/src/bundler/plugins/index.ts +83 -83
- package/src/bundler/plugins/react-compiler-config.ts +108 -108
- package/src/bundler/plugins/react-compiler-lint.ts +253 -253
- package/src/bundler/plugins/react-compiler.ts +162 -162
- package/src/bundler/reverse-import-graph.ts +339 -339
- package/src/bundler/safe-build.test.ts +201 -201
- package/src/bundler/safe-build.ts +103 -103
- package/src/bundler/scenario-matrix.ts +229 -229
- package/src/bundler/types.ts +10 -10
- package/src/bundler/vendor-cache-types.ts +130 -130
- package/src/bundler/vendor-cache.ts +526 -526
- package/src/change/snapshot.ts +18 -18
- package/src/client/Form.tsx +105 -105
- package/src/client/Link.tsx +9 -9
- package/src/client/__tests__/use-sse.test.ts +153 -153
- package/src/client/globals.ts +1 -1
- package/src/client/hooks.ts +362 -362
- package/src/client/hydrate.ts +340 -340
- package/src/client/prefetch-helper.ts +55 -55
- package/src/client/router.ts +11 -11
- package/src/client/runtime.ts +47 -47
- package/src/client/serialize.ts +404 -404
- package/src/client/use-fetch.ts +6 -6
- package/src/client/use-head.ts +197 -197
- package/src/client/use-sse.ts +378 -378
- package/src/client/window-state.ts +101 -101
- package/src/components/Image.tsx +162 -162
- package/src/config/validate.ts +3 -3
- package/src/config/watcher.ts +311 -311
- package/src/constants.ts +40 -40
- package/src/content/collection.ts +8 -8
- package/src/content/content-layer.ts +7 -7
- package/src/content/data-store.ts +245 -245
- package/src/content/frontmatter.ts +189 -189
- package/src/content/loader-context.ts +171 -171
- package/src/content/loaders/api.ts +216 -216
- package/src/content/loaders/file.ts +172 -172
- package/src/content/loaders/glob.ts +253 -253
- package/src/content/loaders/index.ts +34 -34
- package/src/content/loaders/types.ts +137 -137
- package/src/content/meta-store.ts +209 -209
- package/src/content/prebuild.test.ts +571 -571
- package/src/content/prebuild.ts +636 -636
- package/src/content/schema.ts +20 -20
- package/src/content/sidebar.ts +630 -630
- package/src/content/slug.ts +110 -110
- package/src/content/types.ts +282 -282
- package/src/content/watcher.ts +135 -135
- package/src/contract/client-safe.test.ts +42 -42
- package/src/contract/client-safe.ts +114 -114
- package/src/contract/define.ts +11 -11
- package/src/contract/index.ts +1 -1
- package/src/contract/normalize.test.ts +276 -276
- package/src/contract/normalize.ts +410 -410
- package/src/contract/registry.test.ts +206 -206
- package/src/contract/route-helpers.ts +1 -1
- package/src/contract/rpc.ts +443 -443
- package/src/contract/schema.ts +48 -48
- package/src/contract/types.ts +58 -58
- package/src/db/__tests__/db.test.ts +482 -482
- package/src/db/index.ts +138 -138
- package/src/db/migrations/history-table.ts +345 -345
- package/src/db/migrations/index.ts +3 -3
- package/src/db/migrations/lock.ts +324 -324
- package/src/db/migrations/runner.ts +650 -650
- package/src/deploy/cache.ts +140 -140
- package/src/deploy/compile/vercel.ts +344 -344
- package/src/deploy/index.ts +87 -87
- package/src/deploy/inference/brain.ts +268 -268
- package/src/deploy/inference/context.ts +182 -182
- package/src/deploy/inference/filling-extract.ts +245 -245
- package/src/deploy/inference/heuristic.ts +182 -182
- package/src/deploy/intent.ts +173 -173
- package/src/deploy/plan.ts +178 -178
- package/src/design/__tests__/agents-link.test.ts +109 -109
- package/src/design/__tests__/extract-patch-diff.test.ts +265 -265
- package/src/design/__tests__/lint.test.ts +110 -110
- package/src/design/__tests__/parser.test.ts +195 -195
- package/src/design/__tests__/tailwind-theme.test.ts +229 -229
- package/src/design/agents-link.ts +165 -165
- package/src/design/diff.ts +138 -138
- package/src/design/extract.ts +285 -285
- package/src/design/index.ts +102 -102
- package/src/design/lint.ts +209 -209
- package/src/design/parser.ts +555 -555
- package/src/design/patch.ts +241 -241
- package/src/design/scaffold.ts +147 -147
- package/src/design/tailwind-theme.ts +441 -441
- package/src/design/types.ts +210 -210
- package/src/desktop/__tests__/webview-fallback.test.ts +254 -254
- package/src/desktop/__tests__/window.test.ts +248 -248
- package/src/desktop/__tests__/worker.test.ts +266 -266
- package/src/desktop/index.ts +43 -43
- package/src/desktop/types.ts +158 -158
- package/src/desktop/worker.ts +180 -180
- package/src/dev-error-overlay/__tests__/overlay-injector.test.ts +241 -241
- package/src/dev-error-overlay/index.ts +30 -30
- package/src/dev-error-overlay/overlay-injector.ts +243 -243
- package/src/dev-error-overlay/overlay-styles.ts +52 -52
- package/src/dev-error-overlay/types.ts +66 -66
- package/src/devtools/ai/context-builder.ts +375 -375
- package/src/devtools/ai/index.ts +25 -25
- package/src/devtools/ai/mcp-connector.ts +25 -25
- package/src/devtools/client/catchers/error-catcher.ts +344 -344
- package/src/devtools/client/catchers/index.ts +18 -18
- package/src/devtools/client/components/index.ts +39 -39
- package/src/devtools/client/components/mandu-character.tsx +331 -331
- package/src/devtools/client/components/overlay.tsx +368 -368
- package/src/devtools/client/components/panel/errors-panel.tsx +259 -259
- package/src/devtools/client/components/panel/guard-panel.tsx +30 -30
- package/src/devtools/client/components/panel/islands-panel.tsx +320 -320
- package/src/devtools/client/components/panel/network-panel.tsx +291 -291
- package/src/devtools/client/components/panel/panel-container.tsx +500 -500
- package/src/devtools/client/components/panel/preview-panel.tsx +46 -46
- package/src/devtools/client/filters/context-filters.ts +282 -282
- package/src/devtools/client/filters/index.ts +16 -16
- package/src/devtools/client/index.ts +63 -63
- package/src/devtools/client/persistence.ts +335 -335
- package/src/devtools/hook/create-hook.ts +207 -207
- package/src/devtools/hook/index.ts +13 -13
- package/src/devtools/index.ts +439 -439
- package/src/devtools/init.ts +265 -265
- package/src/devtools/protocol.ts +237 -237
- package/src/devtools/server/index.ts +17 -17
- package/src/devtools/server/source-context.ts +450 -450
- package/src/devtools/types.ts +35 -35
- package/src/devtools/worker/index.ts +25 -25
- package/src/devtools/worker/redaction-worker.ts +233 -233
- package/src/devtools/worker/worker-manager.ts +410 -410
- package/src/diagnose/__tests__/checks.test.ts +451 -451
- package/src/diagnose/checks.ts +832 -832
- package/src/diagnose/index.ts +17 -17
- package/src/diagnose/run.ts +93 -93
- package/src/diagnose/types.ts +53 -53
- package/src/email/__tests__/email.test.ts +355 -355
- package/src/email/index.ts +282 -282
- package/src/email/smtp.ts +64 -64
- package/src/error/domains.ts +265 -265
- package/src/error/result.ts +60 -60
- package/src/error/types.ts +6 -6
- package/src/errors/extractor.ts +409 -409
- package/src/errors/index.ts +19 -19
- package/src/filling/__tests__/session-sqlite.test.ts +454 -454
- package/src/filling/auth.ts +308 -308
- package/src/filling/cookie-codec.ts +299 -299
- package/src/filling/deps.ts +265 -265
- package/src/filling/session-sqlite.ts +617 -617
- package/src/filling/sse.ts +5 -5
- package/src/filling/ws.ts +78 -78
- package/src/generator/index.ts +3 -3
- package/src/guard/__tests__/design-inline-class.test.ts +219 -219
- package/src/guard/__tests__/tsgolint-bridge.test.ts +347 -347
- package/src/guard/analyzer.ts +360 -360
- package/src/guard/auto-correct.ts +1 -1
- package/src/guard/contract-guard.ts +9 -9
- package/src/guard/define-rule.ts +243 -243
- package/src/guard/design-inline-class.ts +393 -393
- package/src/guard/file-type.test.ts +24 -24
- package/src/guard/graph.ts +1 -1
- package/src/guard/healing.ts +36 -36
- package/src/guard/presets/atomic.ts +70 -70
- package/src/guard/presets/clean.ts +77 -77
- package/src/guard/presets/fsd.ts +79 -79
- package/src/guard/presets/hexagonal.ts +68 -68
- package/src/guard/reporter.ts +442 -442
- package/src/guard/rule-presets.ts +379 -379
- package/src/guard/semantic-slots.ts +1 -1
- package/src/guard/suggestions.ts +358 -358
- package/src/guard/tsgolint-bridge.ts +512 -512
- package/src/guard/types.ts +348 -348
- package/src/guard/watcher.ts +405 -405
- package/src/i18n/define.ts +126 -126
- package/src/i18n/index.ts +52 -52
- package/src/i18n/message-registry.ts +173 -173
- package/src/i18n/types.ts +112 -112
- package/src/id/index.ts +105 -105
- package/src/index.ts +2 -2
- package/src/kitchen/api/agent-devtools-api.ts +779 -544
- package/src/kitchen/api/errors-grouping.ts +126 -0
- package/src/kitchen/kitchen-handler.ts +192 -63
- package/src/kitchen/kitchen-ui.ts +842 -464
- package/src/logging/index.ts +22 -22
- package/src/logging/transports.ts +365 -365
- package/src/middleware/bridge.ts +147 -147
- package/src/middleware/compose.ts +134 -134
- package/src/middleware/compress.ts +62 -62
- package/src/middleware/cors.ts +47 -47
- package/src/middleware/csrf.ts +328 -328
- package/src/middleware/define.ts +132 -132
- package/src/middleware/jwt.ts +134 -134
- package/src/middleware/logger.ts +58 -58
- package/src/middleware/oauth/__tests__/oauth.test.ts +1 -1
- package/src/middleware/oauth/index.ts +505 -505
- package/src/middleware/oauth/providers.ts +115 -115
- package/src/middleware/rate-limit/index.ts +522 -522
- package/src/middleware/rate-limit/sqlite-store.ts +382 -382
- package/src/middleware/scheduler-cron.ts +96 -96
- package/src/middleware/secure/__tests__/secure.test.ts +360 -360
- package/src/middleware/secure/csp.ts +193 -193
- package/src/middleware/session.ts +174 -174
- package/src/middleware/timeout.ts +55 -55
- package/src/observability/logger-adapter.ts +36 -36
- package/src/observability/sqlite-store.ts +254 -254
- package/src/openapi/generator.ts +1 -1
- package/src/openapi/openapi.test.ts +43 -43
- package/src/perf/__tests__/user-marks.test.ts +354 -354
- package/src/perf/index.ts +133 -133
- package/src/plugins/__tests__/lifecycle-integration.test.ts +272 -272
- package/src/plugins/__tests__/runner.test.ts +409 -409
- package/src/plugins/define.ts +124 -124
- package/src/plugins/examples/dep-check-plugin.ts +80 -80
- package/src/plugins/examples/prerender-cache-plugin.ts +111 -111
- package/src/plugins/examples/sitemap-plugin.ts +65 -65
- package/src/plugins/runner.ts +361 -361
- package/src/plugins/types.ts +368 -368
- package/src/report/index.ts +1 -1
- package/src/resource/__tests__/generator.test.ts +32 -32
- package/src/resource/ddl/__tests__/diff.test.ts +639 -639
- package/src/resource/ddl/__tests__/emit.test.ts +823 -823
- package/src/resource/ddl/__tests__/snapshot.test.ts +499 -499
- package/src/resource/ddl/emit.ts +559 -559
- package/src/resource/ddl/persistence-types.ts +218 -218
- package/src/resource/ddl/type-map.ts +223 -223
- package/src/resource/ddl/types.ts +232 -232
- package/src/resource/generator-repo.ts +630 -630
- package/src/resource/schema.ts +1 -1
- package/src/router/fs-patterns.test.ts +96 -96
- package/src/router/fs-routes.ts +6 -6
- package/src/routes/index.ts +74 -74
- package/src/routes/metadata-routes.ts +427 -427
- package/src/routes/types.ts +341 -341
- package/src/runtime/__tests__/error-boundary-redaction.test.ts +141 -141
- package/src/runtime/__tests__/hdr-client.test.ts +223 -223
- package/src/runtime/__tests__/http-errors.test.ts +117 -117
- package/src/runtime/__tests__/not-found.test.ts +152 -152
- package/src/runtime/adapter.ts +47 -47
- package/src/runtime/boundary.tsx +252 -252
- package/src/runtime/cache.ts +494 -494
- package/src/runtime/compose.ts +222 -222
- package/src/runtime/fast-refresh-runtime.ts +322 -322
- package/src/runtime/handler.ts +65 -65
- package/src/runtime/handlers.ts +300 -300
- package/src/runtime/http-errors.ts +113 -113
- package/src/runtime/image-handler.ts +1 -1
- package/src/runtime/lifecycle.ts +381 -381
- package/src/runtime/logger.test.ts +345 -345
- package/src/runtime/middleware.ts +264 -264
- package/src/runtime/not-found.ts +93 -93
- package/src/runtime/openapi-endpoint.ts +236 -236
- package/src/runtime/ppr.ts +74 -74
- package/src/runtime/registry.ts +171 -171
- package/src/runtime/router.ts +105 -105
- package/src/runtime/server.ts +67 -67
- package/src/runtime/shims.ts +48 -48
- package/src/runtime/trace.ts +144 -144
- package/src/scheduler/validate.ts +169 -169
- package/src/seo/index.ts +219 -219
- package/src/seo/integration/ssr.ts +306 -306
- package/src/seo/render/basic.ts +435 -435
- package/src/seo/render/index.ts +143 -143
- package/src/seo/render/jsonld.ts +539 -539
- package/src/seo/render/opengraph.ts +197 -197
- package/src/seo/render/robots.ts +116 -116
- package/src/seo/render/sitemap.ts +137 -137
- package/src/seo/render/twitter.ts +127 -127
- package/src/seo/resolve/opengraph.ts +143 -143
- package/src/seo/resolve/robots.ts +73 -73
- package/src/seo/resolve/title.ts +94 -94
- package/src/seo/resolve/twitter.ts +73 -73
- package/src/seo/resolve/url.ts +104 -104
- package/src/seo/routes/index.ts +290 -290
- package/src/seo/types.ts +588 -588
- package/src/slot/validator.ts +39 -39
- package/src/storage/s3/__tests__/s3.test.ts +479 -479
- package/src/storage/s3/index.ts +412 -412
- package/src/testing/__tests__/assertions.test.ts +632 -632
- package/src/testing/__tests__/reporter.test.ts +454 -454
- package/src/testing/assertions.ts +986 -986
- package/src/testing/db.ts +157 -157
- package/src/testing/mocks.ts +203 -203
- package/src/testing/session.ts +190 -190
- package/src/types/branded.ts +56 -56
- package/src/types/index.ts +1 -1
- package/src/utils/safe-io.ts +188 -188
- package/src/utils/string-safe.ts +298 -298
- package/src/watcher/watcher.ts +18 -18
|
@@ -1,299 +1,299 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cookie codec abstraction.
|
|
3
|
-
*
|
|
4
|
-
* Two interchangeable implementations of the parse/serialize I/O layer used by
|
|
5
|
-
* `CookieManager`:
|
|
6
|
-
*
|
|
7
|
-
* - {@link LegacyCookieCodec}: pure-JS, runtime-neutral (works anywhere that
|
|
8
|
-
* has `Request`/`Response`/`encodeURIComponent`).
|
|
9
|
-
* - {@link BunCookieMapCodec}: delegates to `Bun.CookieMap` (Bun >= 1.3).
|
|
10
|
-
*
|
|
11
|
-
* The public `CookieManager` API is codec-agnostic. The codec only owns the
|
|
12
|
-
* translation between a wire-format `Cookie:` header string and in-memory
|
|
13
|
-
* entries, and between `(name, value, options)` and a `Set-Cookie:` string.
|
|
14
|
-
*
|
|
15
|
-
* Design notes:
|
|
16
|
-
* - Signed cookies (HMAC-SHA256) and `getParsed` live on `CookieManager` and do
|
|
17
|
-
* not go through the codec. Their format (`encodeURIComponent(value).sig`)
|
|
18
|
-
* must stay stable regardless of codec.
|
|
19
|
-
* - `Bun.CookieMap` auto-injects `SameSite=Lax` when `sameSite` is omitted and
|
|
20
|
-
* reorders attributes relative to the legacy serializer. The Bun codec
|
|
21
|
-
* post-processes output to strip the implicit `SameSite=Lax` so behavior
|
|
22
|
-
* matches the legacy codec exactly. Attribute order differs between codecs
|
|
23
|
-
* but is semantically identical (RFC 6265 treats attributes as an unordered
|
|
24
|
-
* set).
|
|
25
|
-
*/
|
|
26
|
-
|
|
27
|
-
import type { CookieOptions } from "./context";
|
|
28
|
-
|
|
29
|
-
// ========== Interface ==========
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Low-level cookie wire-format codec.
|
|
33
|
-
*
|
|
34
|
-
* Implementations translate between `Cookie:` / `Set-Cookie:` string forms and
|
|
35
|
-
* in-memory values. They carry no state beyond the call and must be safe to
|
|
36
|
-
* share across concurrent requests.
|
|
37
|
-
*/
|
|
38
|
-
export interface CookieCodec {
|
|
39
|
-
/** Human-readable codec name for diagnostics and tests. */
|
|
40
|
-
readonly name: string;
|
|
41
|
-
/**
|
|
42
|
-
* Parse a raw `Cookie:` request header (RFC 6265) into a name→value map.
|
|
43
|
-
* Returns an empty map when `header` is null, empty, or unparseable.
|
|
44
|
-
*
|
|
45
|
-
* Values are URL-decoded. On decode failure the raw value is retained
|
|
46
|
-
* (matches legacy behavior so existing cookies in the wild keep working).
|
|
47
|
-
*
|
|
48
|
-
* For duplicate names the **first** occurrence wins; this matches RFC 6265
|
|
49
|
-
* §5.4's "the user agent SHOULD serve... the first match" guidance.
|
|
50
|
-
*/
|
|
51
|
-
parseRequestHeader(header: string | null): Map<string, string>;
|
|
52
|
-
/**
|
|
53
|
-
* Serialize a single cookie into a `Set-Cookie:` header value.
|
|
54
|
-
*
|
|
55
|
-
* The returned string must not contain a leading `Set-Cookie:` prefix. Both
|
|
56
|
-
* name and value are URL-encoded to survive the header transport layer.
|
|
57
|
-
*/
|
|
58
|
-
serializeSetCookie(name: string, value: string, options: CookieOptions): string;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// ========== Legacy (pure-JS) implementation ==========
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Runtime-neutral codec. Used when `Bun.CookieMap` is unavailable (Node.js,
|
|
65
|
-
* Deno, browsers, edge runtimes without Bun).
|
|
66
|
-
*/
|
|
67
|
-
export const LegacyCookieCodec: CookieCodec = {
|
|
68
|
-
name: "legacy",
|
|
69
|
-
|
|
70
|
-
parseRequestHeader(header: string | null): Map<string, string> {
|
|
71
|
-
const cookies = new Map<string, string>();
|
|
72
|
-
if (!header) return cookies;
|
|
73
|
-
|
|
74
|
-
const pairs = header.split(";");
|
|
75
|
-
for (const pair of pairs) {
|
|
76
|
-
const trimmed = pair.trim();
|
|
77
|
-
if (!trimmed) continue;
|
|
78
|
-
const eqIdx = trimmed.indexOf("=");
|
|
79
|
-
// RFC 6265 §4.2.1 requires cookie-pair = cookie-name "=" cookie-value.
|
|
80
|
-
// Bareword tokens (no '=') are not valid cookies; skip them.
|
|
81
|
-
if (eqIdx === -1) continue;
|
|
82
|
-
const rawName = trimmed.slice(0, eqIdx);
|
|
83
|
-
const rawValue = trimmed.slice(eqIdx + 1);
|
|
84
|
-
if (!rawName) continue;
|
|
85
|
-
const name = safeDecode(rawName);
|
|
86
|
-
// RFC 6265: first cookie wins on duplicate name.
|
|
87
|
-
if (cookies.has(name)) continue;
|
|
88
|
-
cookies.set(name, safeDecode(rawValue));
|
|
89
|
-
}
|
|
90
|
-
return cookies;
|
|
91
|
-
},
|
|
92
|
-
|
|
93
|
-
serializeSetCookie(name: string, value: string, options: CookieOptions): string {
|
|
94
|
-
const parts: string[] = [`${encodeURIComponent(name)}=${encodeURIComponent(value)}`];
|
|
95
|
-
|
|
96
|
-
if (options.maxAge !== undefined) {
|
|
97
|
-
parts.push(`Max-Age=${options.maxAge}`);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
if (options.expires) {
|
|
101
|
-
const expires =
|
|
102
|
-
options.expires instanceof Date
|
|
103
|
-
? options.expires.toUTCString()
|
|
104
|
-
: options.expires;
|
|
105
|
-
parts.push(`Expires=${expires}`);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (options.domain) {
|
|
109
|
-
parts.push(`Domain=${options.domain}`);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Default path matches legacy behavior and most browser defaults.
|
|
113
|
-
parts.push(options.path ? `Path=${options.path}` : "Path=/");
|
|
114
|
-
|
|
115
|
-
if (options.secure) parts.push("Secure");
|
|
116
|
-
if (options.httpOnly) parts.push("HttpOnly");
|
|
117
|
-
|
|
118
|
-
if (options.sameSite) {
|
|
119
|
-
parts.push(`SameSite=${capitalize(options.sameSite)}`);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
if (options.partitioned) parts.push("Partitioned");
|
|
123
|
-
|
|
124
|
-
return parts.join("; ");
|
|
125
|
-
},
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
// ========== Bun.CookieMap-backed implementation ==========
|
|
129
|
-
|
|
130
|
-
interface BunCookieInit {
|
|
131
|
-
name: string;
|
|
132
|
-
value: string;
|
|
133
|
-
domain?: string;
|
|
134
|
-
path?: string;
|
|
135
|
-
expires?: number | Date;
|
|
136
|
-
secure?: boolean;
|
|
137
|
-
httpOnly?: boolean;
|
|
138
|
-
partitioned?: boolean;
|
|
139
|
-
sameSite?: "strict" | "lax" | "none" | "Strict" | "Lax" | "None";
|
|
140
|
-
maxAge?: number;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
interface BunCookieMapCtor {
|
|
144
|
-
new (init?: string): {
|
|
145
|
-
get(name: string): string | null;
|
|
146
|
-
entries(): IterableIterator<[string, string]>;
|
|
147
|
-
set(init: BunCookieInit): void;
|
|
148
|
-
toSetCookieHeaders(): string[];
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Attempt to resolve `Bun.CookieMap` from the ambient runtime. Returns the
|
|
154
|
-
* constructor when available, or `null` on non-Bun runtimes or older Bun
|
|
155
|
-
* builds that lack the API.
|
|
156
|
-
*/
|
|
157
|
-
function resolveBunCookieMap(): BunCookieMapCtor | null {
|
|
158
|
-
// `Bun` is only present in Bun's runtime. Access via `globalThis` to avoid
|
|
159
|
-
// ReferenceError under other runtimes while still allowing bundlers to
|
|
160
|
-
// tree-shake based on the dynamic lookup.
|
|
161
|
-
const bun = (globalThis as unknown as { Bun?: { CookieMap?: unknown } }).Bun;
|
|
162
|
-
const Ctor = bun?.CookieMap;
|
|
163
|
-
if (typeof Ctor !== "function") return null;
|
|
164
|
-
return Ctor as BunCookieMapCtor;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Factory for the Bun-native codec. Returns `null` if `Bun.CookieMap` is not
|
|
169
|
-
* available, allowing callers to fall back to the legacy codec.
|
|
170
|
-
*/
|
|
171
|
-
export function createBunCookieMapCodec(): CookieCodec | null {
|
|
172
|
-
const CookieMap = resolveBunCookieMap();
|
|
173
|
-
if (!CookieMap) return null;
|
|
174
|
-
|
|
175
|
-
return {
|
|
176
|
-
name: "bun-cookiemap",
|
|
177
|
-
|
|
178
|
-
parseRequestHeader(header: string | null): Map<string, string> {
|
|
179
|
-
const cookies = new Map<string, string>();
|
|
180
|
-
if (!header) return cookies;
|
|
181
|
-
// Bun.CookieMap handles URL-decoding and whitespace, and iterates
|
|
182
|
-
// entries in insertion order.
|
|
183
|
-
const map = new CookieMap(header);
|
|
184
|
-
for (const [name, value] of map.entries()) {
|
|
185
|
-
if (!name) continue;
|
|
186
|
-
// First-wins semantics to match LegacyCookieCodec and RFC 6265.
|
|
187
|
-
if (!cookies.has(name)) cookies.set(name, value);
|
|
188
|
-
}
|
|
189
|
-
return cookies;
|
|
190
|
-
},
|
|
191
|
-
|
|
192
|
-
serializeSetCookie(name: string, value: string, options: CookieOptions): string {
|
|
193
|
-
const map = new CookieMap();
|
|
194
|
-
const init: BunCookieInit = { name, value };
|
|
195
|
-
|
|
196
|
-
if (options.maxAge !== undefined) init.maxAge = options.maxAge;
|
|
197
|
-
// NOTE: we deliberately omit `expires` from the Bun.CookieMap init and
|
|
198
|
-
// append our own Expires attribute below. Bun 1.3.10's CookieMap emits
|
|
199
|
-
// `Expires` in a non-standard form (e.g. `Tue, 15 Jun 2026 ... -0000`
|
|
200
|
-
// for a Monday — day-of-week mismatch) that breaks byte-parity with the
|
|
201
|
-
// legacy codec and is technically not RFC 7231 IMF-fixdate. Using
|
|
202
|
-
// `Date.toUTCString()` matches the legacy impl exactly.
|
|
203
|
-
if (options.domain) init.domain = options.domain;
|
|
204
|
-
init.path = options.path ?? "/";
|
|
205
|
-
if (options.secure) init.secure = true;
|
|
206
|
-
if (options.httpOnly) init.httpOnly = true;
|
|
207
|
-
if (options.partitioned) init.partitioned = true;
|
|
208
|
-
if (options.sameSite) init.sameSite = options.sameSite;
|
|
209
|
-
|
|
210
|
-
map.set(init);
|
|
211
|
-
const headers = map.toSetCookieHeaders();
|
|
212
|
-
if (headers.length === 0) {
|
|
213
|
-
// Defensive: Bun.CookieMap should always yield one header after set().
|
|
214
|
-
return LegacyCookieCodec.serializeSetCookie(name, value, options);
|
|
215
|
-
}
|
|
216
|
-
let header = headers[0];
|
|
217
|
-
|
|
218
|
-
// Bun.CookieMap auto-injects `SameSite=Lax` when sameSite is unspecified.
|
|
219
|
-
// The legacy codec omits SameSite in that case; preserve that behavior.
|
|
220
|
-
if (!options.sameSite) {
|
|
221
|
-
header = stripDefaultSameSite(header);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// Append RFC 7231-compliant Expires after Bun's other attributes.
|
|
225
|
-
if (options.expires !== undefined) {
|
|
226
|
-
const expiresValue =
|
|
227
|
-
options.expires instanceof Date
|
|
228
|
-
? options.expires.toUTCString()
|
|
229
|
-
: options.expires;
|
|
230
|
-
header += `; Expires=${expiresValue}`;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
return header;
|
|
234
|
-
},
|
|
235
|
-
};
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* Remove the trailing `; SameSite=Lax` that Bun injects when sameSite is not
|
|
240
|
-
* specified. Only strips an **exact, isolated** attribute so explicit
|
|
241
|
-
* `SameSite=Lax` from the caller survives (caller would have set
|
|
242
|
-
* `options.sameSite='lax'` and this function is skipped anyway).
|
|
243
|
-
*/
|
|
244
|
-
function stripDefaultSameSite(header: string): string {
|
|
245
|
-
// Prefer trailing position (Bun's current emit order).
|
|
246
|
-
if (header.endsWith("; SameSite=Lax")) {
|
|
247
|
-
return header.slice(0, -"; SameSite=Lax".length);
|
|
248
|
-
}
|
|
249
|
-
// Defensive: handle middle-position insertion if Bun reorders attributes.
|
|
250
|
-
const middle = header.indexOf("; SameSite=Lax;");
|
|
251
|
-
if (middle !== -1) {
|
|
252
|
-
return header.slice(0, middle) + header.slice(middle + "; SameSite=Lax".length);
|
|
253
|
-
}
|
|
254
|
-
return header;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// ========== Codec selection ==========
|
|
258
|
-
|
|
259
|
-
function capitalize(s: string): string {
|
|
260
|
-
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
/**
|
|
264
|
-
* URL-decode a cookie token, preserving the raw value if the encoding is
|
|
265
|
-
* malformed. Matches the forgiving behavior of most cookie parsers (browsers,
|
|
266
|
-
* `cookie` npm package, Bun.CookieMap) so malformed cookies from clients do
|
|
267
|
-
* not crash server-side code.
|
|
268
|
-
*/
|
|
269
|
-
function safeDecode(s: string): string {
|
|
270
|
-
try {
|
|
271
|
-
return decodeURIComponent(s);
|
|
272
|
-
} catch {
|
|
273
|
-
return s;
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
let activeCodec: CookieCodec = resolveDefaultCodec();
|
|
278
|
-
|
|
279
|
-
function resolveDefaultCodec(): CookieCodec {
|
|
280
|
-
return createBunCookieMapCodec() ?? LegacyCookieCodec;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
/**
|
|
284
|
-
* Return the active codec. Resolved once at module load and overridable via
|
|
285
|
-
* {@link _setCodecForTesting}.
|
|
286
|
-
*/
|
|
287
|
-
export function getCookieCodec(): CookieCodec {
|
|
288
|
-
return activeCodec;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
/**
|
|
292
|
-
* Test-only escape hatch for forcing a specific codec. Pass a codec to
|
|
293
|
-
* override, or omit to restore the runtime-detected default.
|
|
294
|
-
*
|
|
295
|
-
* @internal
|
|
296
|
-
*/
|
|
297
|
-
export function _setCodecForTesting(codec?: CookieCodec): void {
|
|
298
|
-
activeCodec = codec ?? resolveDefaultCodec();
|
|
299
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Cookie codec abstraction.
|
|
3
|
+
*
|
|
4
|
+
* Two interchangeable implementations of the parse/serialize I/O layer used by
|
|
5
|
+
* `CookieManager`:
|
|
6
|
+
*
|
|
7
|
+
* - {@link LegacyCookieCodec}: pure-JS, runtime-neutral (works anywhere that
|
|
8
|
+
* has `Request`/`Response`/`encodeURIComponent`).
|
|
9
|
+
* - {@link BunCookieMapCodec}: delegates to `Bun.CookieMap` (Bun >= 1.3).
|
|
10
|
+
*
|
|
11
|
+
* The public `CookieManager` API is codec-agnostic. The codec only owns the
|
|
12
|
+
* translation between a wire-format `Cookie:` header string and in-memory
|
|
13
|
+
* entries, and between `(name, value, options)` and a `Set-Cookie:` string.
|
|
14
|
+
*
|
|
15
|
+
* Design notes:
|
|
16
|
+
* - Signed cookies (HMAC-SHA256) and `getParsed` live on `CookieManager` and do
|
|
17
|
+
* not go through the codec. Their format (`encodeURIComponent(value).sig`)
|
|
18
|
+
* must stay stable regardless of codec.
|
|
19
|
+
* - `Bun.CookieMap` auto-injects `SameSite=Lax` when `sameSite` is omitted and
|
|
20
|
+
* reorders attributes relative to the legacy serializer. The Bun codec
|
|
21
|
+
* post-processes output to strip the implicit `SameSite=Lax` so behavior
|
|
22
|
+
* matches the legacy codec exactly. Attribute order differs between codecs
|
|
23
|
+
* but is semantically identical (RFC 6265 treats attributes as an unordered
|
|
24
|
+
* set).
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import type { CookieOptions } from "./context";
|
|
28
|
+
|
|
29
|
+
// ========== Interface ==========
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Low-level cookie wire-format codec.
|
|
33
|
+
*
|
|
34
|
+
* Implementations translate between `Cookie:` / `Set-Cookie:` string forms and
|
|
35
|
+
* in-memory values. They carry no state beyond the call and must be safe to
|
|
36
|
+
* share across concurrent requests.
|
|
37
|
+
*/
|
|
38
|
+
export interface CookieCodec {
|
|
39
|
+
/** Human-readable codec name for diagnostics and tests. */
|
|
40
|
+
readonly name: string;
|
|
41
|
+
/**
|
|
42
|
+
* Parse a raw `Cookie:` request header (RFC 6265) into a name→value map.
|
|
43
|
+
* Returns an empty map when `header` is null, empty, or unparseable.
|
|
44
|
+
*
|
|
45
|
+
* Values are URL-decoded. On decode failure the raw value is retained
|
|
46
|
+
* (matches legacy behavior so existing cookies in the wild keep working).
|
|
47
|
+
*
|
|
48
|
+
* For duplicate names the **first** occurrence wins; this matches RFC 6265
|
|
49
|
+
* §5.4's "the user agent SHOULD serve... the first match" guidance.
|
|
50
|
+
*/
|
|
51
|
+
parseRequestHeader(header: string | null): Map<string, string>;
|
|
52
|
+
/**
|
|
53
|
+
* Serialize a single cookie into a `Set-Cookie:` header value.
|
|
54
|
+
*
|
|
55
|
+
* The returned string must not contain a leading `Set-Cookie:` prefix. Both
|
|
56
|
+
* name and value are URL-encoded to survive the header transport layer.
|
|
57
|
+
*/
|
|
58
|
+
serializeSetCookie(name: string, value: string, options: CookieOptions): string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ========== Legacy (pure-JS) implementation ==========
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Runtime-neutral codec. Used when `Bun.CookieMap` is unavailable (Node.js,
|
|
65
|
+
* Deno, browsers, edge runtimes without Bun).
|
|
66
|
+
*/
|
|
67
|
+
export const LegacyCookieCodec: CookieCodec = {
|
|
68
|
+
name: "legacy",
|
|
69
|
+
|
|
70
|
+
parseRequestHeader(header: string | null): Map<string, string> {
|
|
71
|
+
const cookies = new Map<string, string>();
|
|
72
|
+
if (!header) return cookies;
|
|
73
|
+
|
|
74
|
+
const pairs = header.split(";");
|
|
75
|
+
for (const pair of pairs) {
|
|
76
|
+
const trimmed = pair.trim();
|
|
77
|
+
if (!trimmed) continue;
|
|
78
|
+
const eqIdx = trimmed.indexOf("=");
|
|
79
|
+
// RFC 6265 §4.2.1 requires cookie-pair = cookie-name "=" cookie-value.
|
|
80
|
+
// Bareword tokens (no '=') are not valid cookies; skip them.
|
|
81
|
+
if (eqIdx === -1) continue;
|
|
82
|
+
const rawName = trimmed.slice(0, eqIdx);
|
|
83
|
+
const rawValue = trimmed.slice(eqIdx + 1);
|
|
84
|
+
if (!rawName) continue;
|
|
85
|
+
const name = safeDecode(rawName);
|
|
86
|
+
// RFC 6265: first cookie wins on duplicate name.
|
|
87
|
+
if (cookies.has(name)) continue;
|
|
88
|
+
cookies.set(name, safeDecode(rawValue));
|
|
89
|
+
}
|
|
90
|
+
return cookies;
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
serializeSetCookie(name: string, value: string, options: CookieOptions): string {
|
|
94
|
+
const parts: string[] = [`${encodeURIComponent(name)}=${encodeURIComponent(value)}`];
|
|
95
|
+
|
|
96
|
+
if (options.maxAge !== undefined) {
|
|
97
|
+
parts.push(`Max-Age=${options.maxAge}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (options.expires) {
|
|
101
|
+
const expires =
|
|
102
|
+
options.expires instanceof Date
|
|
103
|
+
? options.expires.toUTCString()
|
|
104
|
+
: options.expires;
|
|
105
|
+
parts.push(`Expires=${expires}`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (options.domain) {
|
|
109
|
+
parts.push(`Domain=${options.domain}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Default path matches legacy behavior and most browser defaults.
|
|
113
|
+
parts.push(options.path ? `Path=${options.path}` : "Path=/");
|
|
114
|
+
|
|
115
|
+
if (options.secure) parts.push("Secure");
|
|
116
|
+
if (options.httpOnly) parts.push("HttpOnly");
|
|
117
|
+
|
|
118
|
+
if (options.sameSite) {
|
|
119
|
+
parts.push(`SameSite=${capitalize(options.sameSite)}`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (options.partitioned) parts.push("Partitioned");
|
|
123
|
+
|
|
124
|
+
return parts.join("; ");
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// ========== Bun.CookieMap-backed implementation ==========
|
|
129
|
+
|
|
130
|
+
interface BunCookieInit {
|
|
131
|
+
name: string;
|
|
132
|
+
value: string;
|
|
133
|
+
domain?: string;
|
|
134
|
+
path?: string;
|
|
135
|
+
expires?: number | Date;
|
|
136
|
+
secure?: boolean;
|
|
137
|
+
httpOnly?: boolean;
|
|
138
|
+
partitioned?: boolean;
|
|
139
|
+
sameSite?: "strict" | "lax" | "none" | "Strict" | "Lax" | "None";
|
|
140
|
+
maxAge?: number;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
interface BunCookieMapCtor {
|
|
144
|
+
new (init?: string): {
|
|
145
|
+
get(name: string): string | null;
|
|
146
|
+
entries(): IterableIterator<[string, string]>;
|
|
147
|
+
set(init: BunCookieInit): void;
|
|
148
|
+
toSetCookieHeaders(): string[];
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Attempt to resolve `Bun.CookieMap` from the ambient runtime. Returns the
|
|
154
|
+
* constructor when available, or `null` on non-Bun runtimes or older Bun
|
|
155
|
+
* builds that lack the API.
|
|
156
|
+
*/
|
|
157
|
+
function resolveBunCookieMap(): BunCookieMapCtor | null {
|
|
158
|
+
// `Bun` is only present in Bun's runtime. Access via `globalThis` to avoid
|
|
159
|
+
// ReferenceError under other runtimes while still allowing bundlers to
|
|
160
|
+
// tree-shake based on the dynamic lookup.
|
|
161
|
+
const bun = (globalThis as unknown as { Bun?: { CookieMap?: unknown } }).Bun;
|
|
162
|
+
const Ctor = bun?.CookieMap;
|
|
163
|
+
if (typeof Ctor !== "function") return null;
|
|
164
|
+
return Ctor as BunCookieMapCtor;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Factory for the Bun-native codec. Returns `null` if `Bun.CookieMap` is not
|
|
169
|
+
* available, allowing callers to fall back to the legacy codec.
|
|
170
|
+
*/
|
|
171
|
+
export function createBunCookieMapCodec(): CookieCodec | null {
|
|
172
|
+
const CookieMap = resolveBunCookieMap();
|
|
173
|
+
if (!CookieMap) return null;
|
|
174
|
+
|
|
175
|
+
return {
|
|
176
|
+
name: "bun-cookiemap",
|
|
177
|
+
|
|
178
|
+
parseRequestHeader(header: string | null): Map<string, string> {
|
|
179
|
+
const cookies = new Map<string, string>();
|
|
180
|
+
if (!header) return cookies;
|
|
181
|
+
// Bun.CookieMap handles URL-decoding and whitespace, and iterates
|
|
182
|
+
// entries in insertion order.
|
|
183
|
+
const map = new CookieMap(header);
|
|
184
|
+
for (const [name, value] of map.entries()) {
|
|
185
|
+
if (!name) continue;
|
|
186
|
+
// First-wins semantics to match LegacyCookieCodec and RFC 6265.
|
|
187
|
+
if (!cookies.has(name)) cookies.set(name, value);
|
|
188
|
+
}
|
|
189
|
+
return cookies;
|
|
190
|
+
},
|
|
191
|
+
|
|
192
|
+
serializeSetCookie(name: string, value: string, options: CookieOptions): string {
|
|
193
|
+
const map = new CookieMap();
|
|
194
|
+
const init: BunCookieInit = { name, value };
|
|
195
|
+
|
|
196
|
+
if (options.maxAge !== undefined) init.maxAge = options.maxAge;
|
|
197
|
+
// NOTE: we deliberately omit `expires` from the Bun.CookieMap init and
|
|
198
|
+
// append our own Expires attribute below. Bun 1.3.10's CookieMap emits
|
|
199
|
+
// `Expires` in a non-standard form (e.g. `Tue, 15 Jun 2026 ... -0000`
|
|
200
|
+
// for a Monday — day-of-week mismatch) that breaks byte-parity with the
|
|
201
|
+
// legacy codec and is technically not RFC 7231 IMF-fixdate. Using
|
|
202
|
+
// `Date.toUTCString()` matches the legacy impl exactly.
|
|
203
|
+
if (options.domain) init.domain = options.domain;
|
|
204
|
+
init.path = options.path ?? "/";
|
|
205
|
+
if (options.secure) init.secure = true;
|
|
206
|
+
if (options.httpOnly) init.httpOnly = true;
|
|
207
|
+
if (options.partitioned) init.partitioned = true;
|
|
208
|
+
if (options.sameSite) init.sameSite = options.sameSite;
|
|
209
|
+
|
|
210
|
+
map.set(init);
|
|
211
|
+
const headers = map.toSetCookieHeaders();
|
|
212
|
+
if (headers.length === 0) {
|
|
213
|
+
// Defensive: Bun.CookieMap should always yield one header after set().
|
|
214
|
+
return LegacyCookieCodec.serializeSetCookie(name, value, options);
|
|
215
|
+
}
|
|
216
|
+
let header = headers[0];
|
|
217
|
+
|
|
218
|
+
// Bun.CookieMap auto-injects `SameSite=Lax` when sameSite is unspecified.
|
|
219
|
+
// The legacy codec omits SameSite in that case; preserve that behavior.
|
|
220
|
+
if (!options.sameSite) {
|
|
221
|
+
header = stripDefaultSameSite(header);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Append RFC 7231-compliant Expires after Bun's other attributes.
|
|
225
|
+
if (options.expires !== undefined) {
|
|
226
|
+
const expiresValue =
|
|
227
|
+
options.expires instanceof Date
|
|
228
|
+
? options.expires.toUTCString()
|
|
229
|
+
: options.expires;
|
|
230
|
+
header += `; Expires=${expiresValue}`;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return header;
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Remove the trailing `; SameSite=Lax` that Bun injects when sameSite is not
|
|
240
|
+
* specified. Only strips an **exact, isolated** attribute so explicit
|
|
241
|
+
* `SameSite=Lax` from the caller survives (caller would have set
|
|
242
|
+
* `options.sameSite='lax'` and this function is skipped anyway).
|
|
243
|
+
*/
|
|
244
|
+
function stripDefaultSameSite(header: string): string {
|
|
245
|
+
// Prefer trailing position (Bun's current emit order).
|
|
246
|
+
if (header.endsWith("; SameSite=Lax")) {
|
|
247
|
+
return header.slice(0, -"; SameSite=Lax".length);
|
|
248
|
+
}
|
|
249
|
+
// Defensive: handle middle-position insertion if Bun reorders attributes.
|
|
250
|
+
const middle = header.indexOf("; SameSite=Lax;");
|
|
251
|
+
if (middle !== -1) {
|
|
252
|
+
return header.slice(0, middle) + header.slice(middle + "; SameSite=Lax".length);
|
|
253
|
+
}
|
|
254
|
+
return header;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// ========== Codec selection ==========
|
|
258
|
+
|
|
259
|
+
function capitalize(s: string): string {
|
|
260
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* URL-decode a cookie token, preserving the raw value if the encoding is
|
|
265
|
+
* malformed. Matches the forgiving behavior of most cookie parsers (browsers,
|
|
266
|
+
* `cookie` npm package, Bun.CookieMap) so malformed cookies from clients do
|
|
267
|
+
* not crash server-side code.
|
|
268
|
+
*/
|
|
269
|
+
function safeDecode(s: string): string {
|
|
270
|
+
try {
|
|
271
|
+
return decodeURIComponent(s);
|
|
272
|
+
} catch {
|
|
273
|
+
return s;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
let activeCodec: CookieCodec = resolveDefaultCodec();
|
|
278
|
+
|
|
279
|
+
function resolveDefaultCodec(): CookieCodec {
|
|
280
|
+
return createBunCookieMapCodec() ?? LegacyCookieCodec;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Return the active codec. Resolved once at module load and overridable via
|
|
285
|
+
* {@link _setCodecForTesting}.
|
|
286
|
+
*/
|
|
287
|
+
export function getCookieCodec(): CookieCodec {
|
|
288
|
+
return activeCodec;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Test-only escape hatch for forcing a specific codec. Pass a codec to
|
|
293
|
+
* override, or omit to restore the runtime-detected default.
|
|
294
|
+
*
|
|
295
|
+
* @internal
|
|
296
|
+
*/
|
|
297
|
+
export function _setCodecForTesting(codec?: CookieCodec): void {
|
|
298
|
+
activeCodec = codec ?? resolveDefaultCodec();
|
|
299
|
+
}
|