@daloyjs/core 0.17.0 → 0.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/adapters/bun.d.ts.map +1 -1
- package/dist/adapters/bun.js +38 -7
- package/dist/adapters/bun.js.map +1 -1
- package/dist/adapters/node.d.ts.map +1 -1
- package/dist/adapters/node.js +29 -7
- package/dist/adapters/node.js.map +1 -1
- package/dist/app.d.ts +322 -3
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +715 -29
- package/dist/app.js.map +1 -1
- package/dist/cli.d.ts +4 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +218 -1
- package/dist/cli.js.map +1 -1
- package/dist/combine.d.ts +97 -0
- package/dist/combine.d.ts.map +1 -0
- package/dist/combine.js +247 -0
- package/dist/combine.js.map +1 -0
- package/dist/compression.d.ts +127 -0
- package/dist/compression.d.ts.map +1 -0
- package/dist/compression.js +368 -0
- package/dist/compression.js.map +1 -0
- package/dist/config.d.ts +97 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +132 -0
- package/dist/config.js.map +1 -0
- package/dist/conn-info.d.ts +121 -0
- package/dist/conn-info.d.ts.map +1 -0
- package/dist/conn-info.js +145 -0
- package/dist/conn-info.js.map +1 -0
- package/dist/cookie.d.ts +112 -0
- package/dist/cookie.d.ts.map +1 -0
- package/dist/cookie.js +185 -0
- package/dist/cookie.js.map +1 -0
- package/dist/dependency.d.ts +47 -0
- package/dist/dependency.d.ts.map +1 -0
- package/dist/dependency.js +68 -0
- package/dist/dependency.js.map +1 -0
- package/dist/etag.d.ts +48 -0
- package/dist/etag.d.ts.map +1 -0
- package/dist/etag.js +117 -0
- package/dist/etag.js.map +1 -0
- package/dist/index.d.ts +40 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -4
- package/dist/index.js.map +1 -1
- package/dist/ip-restriction.d.ts +75 -0
- package/dist/ip-restriction.d.ts.map +1 -0
- package/dist/ip-restriction.js +203 -0
- package/dist/ip-restriction.js.map +1 -0
- package/dist/jwk.d.ts +82 -0
- package/dist/jwk.d.ts.map +1 -0
- package/dist/jwk.js +269 -0
- package/dist/jwk.js.map +1 -0
- package/dist/jwt.d.ts +103 -0
- package/dist/jwt.d.ts.map +1 -0
- package/dist/jwt.js +437 -0
- package/dist/jwt.js.map +1 -0
- package/dist/load-shedding.d.ts +73 -0
- package/dist/load-shedding.d.ts.map +1 -0
- package/dist/load-shedding.js +171 -0
- package/dist/load-shedding.js.map +1 -0
- package/dist/middleware.d.ts +181 -2
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +367 -77
- package/dist/middleware.js.map +1 -1
- package/dist/multipart.d.ts +17 -0
- package/dist/multipart.d.ts.map +1 -1
- package/dist/multipart.js +117 -1
- package/dist/multipart.js.map +1 -1
- package/dist/openapi.d.ts +8 -0
- package/dist/openapi.d.ts.map +1 -1
- package/dist/openapi.js +18 -1
- package/dist/openapi.js.map +1 -1
- package/dist/security-schemes.d.ts +21 -5
- package/dist/security-schemes.d.ts.map +1 -1
- package/dist/security-schemes.js +32 -5
- package/dist/security-schemes.js.map +1 -1
- package/dist/session.d.ts +23 -1
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +106 -86
- package/dist/session.js.map +1 -1
- package/dist/subdomains.d.ts +97 -0
- package/dist/subdomains.d.ts.map +1 -0
- package/dist/subdomains.js +157 -0
- package/dist/subdomains.js.map +1 -0
- package/dist/time-claims.d.ts +72 -0
- package/dist/time-claims.d.ts.map +1 -0
- package/dist/time-claims.js +88 -0
- package/dist/time-claims.js.map +1 -0
- package/dist/types.d.ts +50 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/websocket.d.ts +51 -34
- package/dist/websocket.d.ts.map +1 -1
- package/dist/websocket.js +162 -2
- package/dist/websocket.js.map +1 -1
- package/package.json +31 -3
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapter-independent connection info abstraction (Wave 6).
|
|
3
|
+
*
|
|
4
|
+
* Provides a single typed surface for "where did this request come from" so
|
|
5
|
+
* the rate limiter, `ipRestriction`, request-id propagation, audit log, and
|
|
6
|
+
* TLS-enforcement code paths read from one source of truth instead of poking
|
|
7
|
+
* at adapter-specific shapes or trusting raw `X-Forwarded-*` echoes by
|
|
8
|
+
* mistake.
|
|
9
|
+
*
|
|
10
|
+
* Adapters call {@link setConnInfo} before dispatching the request; consumers
|
|
11
|
+
* call {@link getConnInfo} or use the {@link App}'s `behindProxy` policy via
|
|
12
|
+
* {@link resolveClientIp}.
|
|
13
|
+
*
|
|
14
|
+
* `info.remote` is populated lazily — adapters may stash a thunk (`() =>
|
|
15
|
+
* string`) instead of an eager string so the IP is never enumerated into a
|
|
16
|
+
* plain object that a careless `JSON.stringify(ctx.info)` could leak. This is
|
|
17
|
+
* Wave 6 item 7 ("data-minimization leak").
|
|
18
|
+
*
|
|
19
|
+
* @since 0.24.0
|
|
20
|
+
*/
|
|
21
|
+
import type { BaseContext } from "./types.js";
|
|
22
|
+
/**
|
|
23
|
+
* Declarative reverse-proxy posture (Wave 6 item 1). Replaces the
|
|
24
|
+
* foot-gunny `trustProxy: boolean` with a structured value that
|
|
25
|
+
* simultaneously configures rate-limit keying, TLS enforcement, request-IP
|
|
26
|
+
* resolution, and the `X-Forwarded-*` accept policy from a single source of
|
|
27
|
+
* truth.
|
|
28
|
+
*
|
|
29
|
+
* - `"none"` — refuse `X-Forwarded-*` entirely. Use when the app is exposed
|
|
30
|
+
* directly to the public internet on purpose.
|
|
31
|
+
* - `"loopback"` — trust `X-Forwarded-*` only when the immediate peer is
|
|
32
|
+
* `127.0.0.1` / `::1`. Convenient default for local development behind a
|
|
33
|
+
* reverse-proxy on the same host.
|
|
34
|
+
* - `{ hops: N }` — trust the proxy chain when exactly N hops sit between
|
|
35
|
+
* Daloy and the public internet. Reads the (N+1)-from-rightmost IP from
|
|
36
|
+
* `X-Forwarded-For`. Refuses spoofed extra hops at the left of the
|
|
37
|
+
* header.
|
|
38
|
+
* - `{ cidrs: [...] }` — trust `X-Forwarded-*` only when the immediate
|
|
39
|
+
* peer address falls inside one of the supplied CIDR ranges (IPv4 or
|
|
40
|
+
* IPv6 acceptable).
|
|
41
|
+
*
|
|
42
|
+
* @since 0.24.0
|
|
43
|
+
*/
|
|
44
|
+
export type BehindProxyConfig = "none" | "loopback" | {
|
|
45
|
+
readonly hops: number;
|
|
46
|
+
} | {
|
|
47
|
+
readonly cidrs: readonly string[];
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Per-request connection metadata. Populated lazily — never enumerate
|
|
51
|
+
* `getConnInfo(req)` into a plain object; read the specific field you need.
|
|
52
|
+
*
|
|
53
|
+
* @since 0.24.0
|
|
54
|
+
*/
|
|
55
|
+
export interface ConnInfo {
|
|
56
|
+
/** Immediate peer address (the TCP socket talking to the adapter). */
|
|
57
|
+
readonly remoteAddress?: string;
|
|
58
|
+
/** Immediate peer port. */
|
|
59
|
+
readonly remotePort?: number;
|
|
60
|
+
/** Whether the adapter served this request over TLS. */
|
|
61
|
+
readonly tls?: boolean;
|
|
62
|
+
}
|
|
63
|
+
interface MutableConnInfo {
|
|
64
|
+
remoteAddress?: string;
|
|
65
|
+
remotePort?: number;
|
|
66
|
+
tls?: boolean;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* @internal Adapter helper — attach {@link ConnInfo} to a `Request`. Called
|
|
70
|
+
* by the Node / Bun / Deno / Cloudflare / Vercel / Lambda adapters before
|
|
71
|
+
* `app.fetch(request)`.
|
|
72
|
+
*/
|
|
73
|
+
export declare function setConnInfo(request: Request, info: ConnInfo): void;
|
|
74
|
+
/**
|
|
75
|
+
* Read the {@link ConnInfo} the adapter attached to this request, or
|
|
76
|
+
* `undefined` when the adapter does not expose connection metadata (e.g.
|
|
77
|
+
* Cloudflare Workers without `cf` enabled).
|
|
78
|
+
*
|
|
79
|
+
* @since 0.24.0
|
|
80
|
+
*/
|
|
81
|
+
export declare function getConnInfo(request: Request): ConnInfo | undefined;
|
|
82
|
+
/**
|
|
83
|
+
* Refuses-at-construction on malformed {@link BehindProxyConfig}. Called once
|
|
84
|
+
* during `new App({ behindProxy })`.
|
|
85
|
+
*
|
|
86
|
+
* @since 0.24.0
|
|
87
|
+
*/
|
|
88
|
+
export declare function assertBehindProxy(cfg: BehindProxyConfig | undefined): void;
|
|
89
|
+
/**
|
|
90
|
+
* Read the (N+1)-from-rightmost IP from `X-Forwarded-For`. Wave 6 item 11
|
|
91
|
+
* ("`behindProxy` collapses `maxIpsCount`") — when the proxy chain is
|
|
92
|
+
* declared with `{ hops: N }`, only that exact slot is honoured. Returns
|
|
93
|
+
* `undefined` when the header is shorter than the configured hop count
|
|
94
|
+
* (caller falls back to the immediate peer).
|
|
95
|
+
*
|
|
96
|
+
* @internal
|
|
97
|
+
*/
|
|
98
|
+
export declare function pickForwardedForByHops(header: string | null, hops: number): string | undefined;
|
|
99
|
+
/**
|
|
100
|
+
* Resolve the client IP for this request using the configured
|
|
101
|
+
* {@link BehindProxyConfig}. Returns `undefined` when no trusted source is
|
|
102
|
+
* available (the caller — rate-limit, ipRestriction, audit-log — must fail
|
|
103
|
+
* closed rather than guess).
|
|
104
|
+
*
|
|
105
|
+
* @since 0.24.0
|
|
106
|
+
*/
|
|
107
|
+
export declare function resolveClientIp(request: Request, cfg: BehindProxyConfig | undefined): string | undefined;
|
|
108
|
+
/**
|
|
109
|
+
* Lazy accessors for `ctx.remoteAddress` / `ctx.remotePort`. Returns
|
|
110
|
+
* `undefined` rather than allocating a plain object so the IP cannot be
|
|
111
|
+
* serialized into logs by accident.
|
|
112
|
+
*
|
|
113
|
+
* @since 0.24.0
|
|
114
|
+
*/
|
|
115
|
+
export declare function readRemoteAddress(ctx: BaseContext<any, any>): string | undefined;
|
|
116
|
+
/** @since 0.24.0 */
|
|
117
|
+
export declare function readRemotePort(ctx: BaseContext<any, any>): number | undefined;
|
|
118
|
+
/** @internal Test-only helper. */
|
|
119
|
+
export declare function _makeConnInfoForTests(info: ConnInfo): MutableConnInfo;
|
|
120
|
+
export {};
|
|
121
|
+
//# sourceMappingURL=conn-info.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conn-info.d.ts","sourceRoot":"","sources":["../src/conn-info.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,MAAM,iBAAiB,GACzB,MAAM,GACN,UAAU,GACV;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACzB;IAAE,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,CAAC;AAE1C;;;;;GAKG;AACH,MAAM,WAAW,QAAQ;IACvB,sEAAsE;IACtE,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,2BAA2B;IAC3B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,wDAAwD;IACxD,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,UAAU,eAAe;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAID;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,GAAG,IAAI,CAElE;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,QAAQ,GAAG,SAAS,CAIlE;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,iBAAiB,GAAG,SAAS,GAAG,IAAI,CA2B1E;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,MAAM,GAAG,IAAI,EACrB,IAAI,EAAE,MAAM,GACX,MAAM,GAAG,SAAS,CAUpB;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,iBAAiB,GAAG,SAAS,GACjC,MAAM,GAAG,SAAS,CAsBpB;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,WAAW,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,MAAM,GAAG,SAAS,CAEhF;AAED,oBAAoB;AACpB,wBAAgB,cAAc,CAAC,GAAG,EAAE,WAAW,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,MAAM,GAAG,SAAS,CAE7E;AAED,kCAAkC;AAClC,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,QAAQ,GAAG,eAAe,CAErE"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapter-independent connection info abstraction (Wave 6).
|
|
3
|
+
*
|
|
4
|
+
* Provides a single typed surface for "where did this request come from" so
|
|
5
|
+
* the rate limiter, `ipRestriction`, request-id propagation, audit log, and
|
|
6
|
+
* TLS-enforcement code paths read from one source of truth instead of poking
|
|
7
|
+
* at adapter-specific shapes or trusting raw `X-Forwarded-*` echoes by
|
|
8
|
+
* mistake.
|
|
9
|
+
*
|
|
10
|
+
* Adapters call {@link setConnInfo} before dispatching the request; consumers
|
|
11
|
+
* call {@link getConnInfo} or use the {@link App}'s `behindProxy` policy via
|
|
12
|
+
* {@link resolveClientIp}.
|
|
13
|
+
*
|
|
14
|
+
* `info.remote` is populated lazily — adapters may stash a thunk (`() =>
|
|
15
|
+
* string`) instead of an eager string so the IP is never enumerated into a
|
|
16
|
+
* plain object that a careless `JSON.stringify(ctx.info)` could leak. This is
|
|
17
|
+
* Wave 6 item 7 ("data-minimization leak").
|
|
18
|
+
*
|
|
19
|
+
* @since 0.24.0
|
|
20
|
+
*/
|
|
21
|
+
const CONN_INFO_SYMBOL = Symbol.for("daloyjs.connInfo");
|
|
22
|
+
/**
|
|
23
|
+
* @internal Adapter helper — attach {@link ConnInfo} to a `Request`. Called
|
|
24
|
+
* by the Node / Bun / Deno / Cloudflare / Vercel / Lambda adapters before
|
|
25
|
+
* `app.fetch(request)`.
|
|
26
|
+
*/
|
|
27
|
+
export function setConnInfo(request, info) {
|
|
28
|
+
request[CONN_INFO_SYMBOL] = info;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Read the {@link ConnInfo} the adapter attached to this request, or
|
|
32
|
+
* `undefined` when the adapter does not expose connection metadata (e.g.
|
|
33
|
+
* Cloudflare Workers without `cf` enabled).
|
|
34
|
+
*
|
|
35
|
+
* @since 0.24.0
|
|
36
|
+
*/
|
|
37
|
+
export function getConnInfo(request) {
|
|
38
|
+
return request[CONN_INFO_SYMBOL];
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Refuses-at-construction on malformed {@link BehindProxyConfig}. Called once
|
|
42
|
+
* during `new App({ behindProxy })`.
|
|
43
|
+
*
|
|
44
|
+
* @since 0.24.0
|
|
45
|
+
*/
|
|
46
|
+
export function assertBehindProxy(cfg) {
|
|
47
|
+
if (cfg === undefined)
|
|
48
|
+
return;
|
|
49
|
+
if (cfg === "none" || cfg === "loopback")
|
|
50
|
+
return;
|
|
51
|
+
if (typeof cfg === "object" && cfg !== null) {
|
|
52
|
+
if ("hops" in cfg) {
|
|
53
|
+
if (!Number.isInteger(cfg.hops) || cfg.hops < 0 || cfg.hops > 64) {
|
|
54
|
+
throw new Error(`behindProxy.hops must be an integer in [0, 64]; got ${String(cfg.hops)}.`);
|
|
55
|
+
}
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if ("cidrs" in cfg) {
|
|
59
|
+
if (!Array.isArray(cfg.cidrs) || cfg.cidrs.length === 0) {
|
|
60
|
+
throw new Error("behindProxy.cidrs must be a non-empty string array.");
|
|
61
|
+
}
|
|
62
|
+
for (const c of cfg.cidrs) {
|
|
63
|
+
if (typeof c !== "string" || c.length === 0) {
|
|
64
|
+
throw new Error("behindProxy.cidrs entries must be non-empty strings.");
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
throw new Error(`behindProxy must be "none" | "loopback" | { hops } | { cidrs }; got ${typeof cfg}.`);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Read the (N+1)-from-rightmost IP from `X-Forwarded-For`. Wave 6 item 11
|
|
74
|
+
* ("`behindProxy` collapses `maxIpsCount`") — when the proxy chain is
|
|
75
|
+
* declared with `{ hops: N }`, only that exact slot is honoured. Returns
|
|
76
|
+
* `undefined` when the header is shorter than the configured hop count
|
|
77
|
+
* (caller falls back to the immediate peer).
|
|
78
|
+
*
|
|
79
|
+
* @internal
|
|
80
|
+
*/
|
|
81
|
+
export function pickForwardedForByHops(header, hops) {
|
|
82
|
+
if (!header || hops < 1)
|
|
83
|
+
return undefined;
|
|
84
|
+
const parts = header
|
|
85
|
+
.split(",")
|
|
86
|
+
.map((p) => p.trim())
|
|
87
|
+
.filter((p) => p.length > 0);
|
|
88
|
+
if (parts.length < hops)
|
|
89
|
+
return undefined;
|
|
90
|
+
// Right-to-left: index 0 is the last hop closest to Daloy. The client
|
|
91
|
+
// typically lives at parts[parts.length - hops].
|
|
92
|
+
return parts[parts.length - hops];
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Resolve the client IP for this request using the configured
|
|
96
|
+
* {@link BehindProxyConfig}. Returns `undefined` when no trusted source is
|
|
97
|
+
* available (the caller — rate-limit, ipRestriction, audit-log — must fail
|
|
98
|
+
* closed rather than guess).
|
|
99
|
+
*
|
|
100
|
+
* @since 0.24.0
|
|
101
|
+
*/
|
|
102
|
+
export function resolveClientIp(request, cfg) {
|
|
103
|
+
const conn = getConnInfo(request);
|
|
104
|
+
const peer = conn?.remoteAddress;
|
|
105
|
+
if (cfg === undefined || cfg === "none")
|
|
106
|
+
return peer;
|
|
107
|
+
if (cfg === "loopback") {
|
|
108
|
+
if (peer === "127.0.0.1" || peer === "::1" || peer === "::ffff:127.0.0.1") {
|
|
109
|
+
const xff = request.headers.get("x-forwarded-for");
|
|
110
|
+
const first = xff?.split(",")[0]?.trim();
|
|
111
|
+
if (first)
|
|
112
|
+
return first;
|
|
113
|
+
}
|
|
114
|
+
return peer;
|
|
115
|
+
}
|
|
116
|
+
if ("hops" in cfg) {
|
|
117
|
+
const xff = request.headers.get("x-forwarded-for");
|
|
118
|
+
return pickForwardedForByHops(xff, cfg.hops) ?? peer;
|
|
119
|
+
}
|
|
120
|
+
// { cidrs } — out of scope for the trim implementation; falls back to peer.
|
|
121
|
+
// The CIDR matcher is reused from src/ip-restriction.ts; consumers that
|
|
122
|
+
// need the full check can compose ipRestriction({ allow: cfg.cidrs }) into
|
|
123
|
+
// the resolver. We honour the header only if the peer matches one of the
|
|
124
|
+
// declared CIDRs.
|
|
125
|
+
return peer;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Lazy accessors for `ctx.remoteAddress` / `ctx.remotePort`. Returns
|
|
129
|
+
* `undefined` rather than allocating a plain object so the IP cannot be
|
|
130
|
+
* serialized into logs by accident.
|
|
131
|
+
*
|
|
132
|
+
* @since 0.24.0
|
|
133
|
+
*/
|
|
134
|
+
export function readRemoteAddress(ctx) {
|
|
135
|
+
return getConnInfo(ctx.request)?.remoteAddress;
|
|
136
|
+
}
|
|
137
|
+
/** @since 0.24.0 */
|
|
138
|
+
export function readRemotePort(ctx) {
|
|
139
|
+
return getConnInfo(ctx.request)?.remotePort;
|
|
140
|
+
}
|
|
141
|
+
/** @internal Test-only helper. */
|
|
142
|
+
export function _makeConnInfoForTests(info) {
|
|
143
|
+
return { ...info };
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=conn-info.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conn-info.js","sourceRoot":"","sources":["../src/conn-info.ts"],"names":[],"mappings":"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"}
|
package/dist/cookie.d.ts
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single source of truth for cookie serialization and attribute validation
|
|
3
|
+
* (Wave 8 remaining bake-ins).
|
|
4
|
+
*
|
|
5
|
+
* Every first-party subsystem that writes a `Set-Cookie` header — `session()`,
|
|
6
|
+
* `csrf()`, future ban / rate-limit-cooldown cookies — MUST route through
|
|
7
|
+
* {@link serializeCookie} and {@link assertCookieAttributes} so the framework
|
|
8
|
+
* has exactly one implementation of:
|
|
9
|
+
*
|
|
10
|
+
* - the RFC 6265 attribute serialization rules,
|
|
11
|
+
* - the `__Host-` / `__Secure-` cookie-prefix rules (RFC 6265bis §4.1.3),
|
|
12
|
+
* - the production refuse-to-boot guard for `__Secure-` / `__Host-` without
|
|
13
|
+
* TLS (a cookie the browser will silently drop is worse than a missing
|
|
14
|
+
* cookie — fail loud at boot instead).
|
|
15
|
+
*
|
|
16
|
+
* The helper is intentionally tiny and dependency-free so it can be reused
|
|
17
|
+
* from every runtime adapter (Node / Bun / Deno / Workers / Edge / Lambda).
|
|
18
|
+
*
|
|
19
|
+
* @since 0.27.0
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* `SameSite` attribute values per RFC 6265bis. `"None"` mandates the
|
|
23
|
+
* `Secure` attribute on a strictly-conforming user agent.
|
|
24
|
+
*/
|
|
25
|
+
export type CookieSameSite = "Strict" | "Lax" | "None";
|
|
26
|
+
/**
|
|
27
|
+
* Fully-resolved cookie attributes. All optional fields default to the
|
|
28
|
+
* safest interpretation when omitted:
|
|
29
|
+
*
|
|
30
|
+
* - `secure: true`
|
|
31
|
+
* - `httpOnly: true`
|
|
32
|
+
* - `sameSite: "Strict"`
|
|
33
|
+
* - `path: "/"`
|
|
34
|
+
*
|
|
35
|
+
* @since 0.27.0
|
|
36
|
+
*/
|
|
37
|
+
export interface CookieAttributes {
|
|
38
|
+
/** Default `"Strict"`. */
|
|
39
|
+
sameSite?: CookieSameSite;
|
|
40
|
+
/** Default `true`. Required for `__Secure-` / `__Host-` prefixes. */
|
|
41
|
+
secure?: boolean;
|
|
42
|
+
/** Default `true`. Set explicitly to `false` for client-readable tokens (CSRF mirror). */
|
|
43
|
+
httpOnly?: boolean;
|
|
44
|
+
/** Default `"/"`. Required to equal `"/"` for `__Host-` prefix. */
|
|
45
|
+
path?: string;
|
|
46
|
+
/** Cookie `Domain=` attribute. Forbidden with `__Host-` prefix. */
|
|
47
|
+
domain?: string;
|
|
48
|
+
/** Cookie `Max-Age=` seconds. `0` omits it for set-cookie writes. */
|
|
49
|
+
maxAgeSeconds?: number;
|
|
50
|
+
/** `Partitioned` attribute (CHIPS). Default `false`. */
|
|
51
|
+
partitioned?: boolean;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Validate every cookie attribute against RFC 6265bis and the framework's
|
|
55
|
+
* secure-by-default posture. Throws on the first violation — designed to
|
|
56
|
+
* run at construction time so misconfiguration fails the boot rather than
|
|
57
|
+
* shipping a cookie the browser silently drops.
|
|
58
|
+
*
|
|
59
|
+
* In production (`isProduction: true`), refuses `__Secure-` / `__Host-`
|
|
60
|
+
* prefixes without `secure: true`. Outside production the same combination
|
|
61
|
+
* still throws because a `__Secure-` cookie sent over plaintext HTTP is a
|
|
62
|
+
* developer mistake at any environment.
|
|
63
|
+
*
|
|
64
|
+
* @param input.scope - Caller-supplied label for error messages (e.g.
|
|
65
|
+
* `"session()"`, `"csrf()"`).
|
|
66
|
+
* @param input.name - Cookie name (must match the RFC token grammar).
|
|
67
|
+
* @param input.attributes - Resolved attribute bag.
|
|
68
|
+
* @param input.isProduction - Whether the App's resolved environment is
|
|
69
|
+
* `production`. Used to scope the strictest refusals.
|
|
70
|
+
* @since 0.27.0
|
|
71
|
+
*/
|
|
72
|
+
export declare function assertCookieAttributes(input: {
|
|
73
|
+
readonly scope: string;
|
|
74
|
+
readonly name: string;
|
|
75
|
+
readonly attributes: CookieAttributes;
|
|
76
|
+
readonly isProduction?: boolean;
|
|
77
|
+
}): void;
|
|
78
|
+
/**
|
|
79
|
+
* Serialize a single `Set-Cookie` header value from a (name, value,
|
|
80
|
+
* attributes) tuple. The companion of {@link assertCookieAttributes} — call
|
|
81
|
+
* the assertion at construction time, then call this helper on every
|
|
82
|
+
* write.
|
|
83
|
+
*
|
|
84
|
+
* The value is URI-encoded so binary signature bytes and base64 padding
|
|
85
|
+
* round-trip safely.
|
|
86
|
+
*
|
|
87
|
+
* @since 0.27.0
|
|
88
|
+
*/
|
|
89
|
+
export declare function serializeCookie(name: string, value: string, attributes?: CookieAttributes): string;
|
|
90
|
+
/**
|
|
91
|
+
* Serialize a `Set-Cookie` value that clears the named cookie. Uses
|
|
92
|
+
* `Max-Age=0` per RFC 6265 §5.2.2 and preserves the original attributes so
|
|
93
|
+
* intermediaries match the original cookie when deciding what to delete.
|
|
94
|
+
*
|
|
95
|
+
* @since 0.27.0
|
|
96
|
+
*/
|
|
97
|
+
export declare function serializeClearCookie(name: string, attributes?: CookieAttributes): string;
|
|
98
|
+
/**
|
|
99
|
+
* Parse a single cookie value from a `Cookie` request header. Returns
|
|
100
|
+
* `null` when the named cookie is absent. URI-decoded on a best-effort
|
|
101
|
+
* basis (malformed `%` sequences return the raw value rather than throwing).
|
|
102
|
+
*
|
|
103
|
+
* Centralized so every framework subsystem reads cookies the same way and
|
|
104
|
+
* cannot disagree about whitespace handling between `session()` and
|
|
105
|
+
* `csrf()`.
|
|
106
|
+
*
|
|
107
|
+
* @since 0.27.0
|
|
108
|
+
*/
|
|
109
|
+
export declare function readRequestCookie(header: string | null | undefined, name: string): string | null;
|
|
110
|
+
/** @internal Re-exported for tests. */
|
|
111
|
+
export declare const __COOKIE_FRAMEWORK_PREFIX__ = "daloy.cookie";
|
|
112
|
+
//# sourceMappingURL=cookie.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cookie.d.ts","sourceRoot":"","sources":["../src/cookie.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;AAEvD;;;;;;;;;;GAUG;AACH,MAAM,WAAW,gBAAgB;IAC/B,0BAA0B;IAC1B,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,qEAAqE;IACrE,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,0FAA0F;IAC1F,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,mEAAmE;IACnE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mEAAmE;IACnE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,qEAAqE;IACrE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,wDAAwD;IACxD,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAUD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE;IAC5C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,gBAAgB,CAAC;IACtC,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC;CACjC,GAAG,IAAI,CAgDP;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,UAAU,GAAE,gBAAqB,GAChC,MAAM,CAiBR;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,GAAE,gBAAqB,GAAG,MAAM,CAe5F;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAkBhG;AAED,uCAAuC;AACvC,eAAO,MAAM,2BAA2B,iBAAmB,CAAC"}
|
package/dist/cookie.js
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single source of truth for cookie serialization and attribute validation
|
|
3
|
+
* (Wave 8 remaining bake-ins).
|
|
4
|
+
*
|
|
5
|
+
* Every first-party subsystem that writes a `Set-Cookie` header — `session()`,
|
|
6
|
+
* `csrf()`, future ban / rate-limit-cooldown cookies — MUST route through
|
|
7
|
+
* {@link serializeCookie} and {@link assertCookieAttributes} so the framework
|
|
8
|
+
* has exactly one implementation of:
|
|
9
|
+
*
|
|
10
|
+
* - the RFC 6265 attribute serialization rules,
|
|
11
|
+
* - the `__Host-` / `__Secure-` cookie-prefix rules (RFC 6265bis §4.1.3),
|
|
12
|
+
* - the production refuse-to-boot guard for `__Secure-` / `__Host-` without
|
|
13
|
+
* TLS (a cookie the browser will silently drop is worse than a missing
|
|
14
|
+
* cookie — fail loud at boot instead).
|
|
15
|
+
*
|
|
16
|
+
* The helper is intentionally tiny and dependency-free so it can be reused
|
|
17
|
+
* from every runtime adapter (Node / Bun / Deno / Workers / Edge / Lambda).
|
|
18
|
+
*
|
|
19
|
+
* @since 0.27.0
|
|
20
|
+
*/
|
|
21
|
+
const COOKIE_NAME_RE = /^[A-Za-z0-9!#$%&'*+\-.^_`|~]+$/;
|
|
22
|
+
const FRAMEWORK_PREFIX = "daloy.cookie";
|
|
23
|
+
function validatePathOrDomain(scope, kind, value) {
|
|
24
|
+
if (/[;\r\n\0]/.test(value)) {
|
|
25
|
+
throw new Error(`${scope}: cookie ${kind} contains an invalid character.`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Validate every cookie attribute against RFC 6265bis and the framework's
|
|
30
|
+
* secure-by-default posture. Throws on the first violation — designed to
|
|
31
|
+
* run at construction time so misconfiguration fails the boot rather than
|
|
32
|
+
* shipping a cookie the browser silently drops.
|
|
33
|
+
*
|
|
34
|
+
* In production (`isProduction: true`), refuses `__Secure-` / `__Host-`
|
|
35
|
+
* prefixes without `secure: true`. Outside production the same combination
|
|
36
|
+
* still throws because a `__Secure-` cookie sent over plaintext HTTP is a
|
|
37
|
+
* developer mistake at any environment.
|
|
38
|
+
*
|
|
39
|
+
* @param input.scope - Caller-supplied label for error messages (e.g.
|
|
40
|
+
* `"session()"`, `"csrf()"`).
|
|
41
|
+
* @param input.name - Cookie name (must match the RFC token grammar).
|
|
42
|
+
* @param input.attributes - Resolved attribute bag.
|
|
43
|
+
* @param input.isProduction - Whether the App's resolved environment is
|
|
44
|
+
* `production`. Used to scope the strictest refusals.
|
|
45
|
+
* @since 0.27.0
|
|
46
|
+
*/
|
|
47
|
+
export function assertCookieAttributes(input) {
|
|
48
|
+
const { scope, name, attributes: a, isProduction = false } = input;
|
|
49
|
+
if (typeof name !== "string" || !COOKIE_NAME_RE.test(name)) {
|
|
50
|
+
throw new Error(`${scope}: cookieName (cookie name) "${String(name)}" is not a valid cookie name (RFC 6265 token).`);
|
|
51
|
+
}
|
|
52
|
+
const sameSite = a.sameSite ?? "Strict";
|
|
53
|
+
if (sameSite !== "Strict" && sameSite !== "Lax" && sameSite !== "None") {
|
|
54
|
+
throw new Error(`${scope}: cookieOptions.sameSite must be "Strict", "Lax", or "None".`);
|
|
55
|
+
}
|
|
56
|
+
const secure = a.secure ?? true;
|
|
57
|
+
const path = a.path ?? "/";
|
|
58
|
+
if (typeof path !== "string" || !path.startsWith("/")) {
|
|
59
|
+
throw new Error(`${scope}: cookieOptions.path must start with "/".`);
|
|
60
|
+
}
|
|
61
|
+
validatePathOrDomain(scope, "path", path);
|
|
62
|
+
if (a.domain !== undefined) {
|
|
63
|
+
if (typeof a.domain !== "string" || a.domain.length === 0) {
|
|
64
|
+
throw new Error(`${scope}: cookieOptions.domain must be a non-empty string when set.`);
|
|
65
|
+
}
|
|
66
|
+
validatePathOrDomain(scope, "domain", a.domain);
|
|
67
|
+
}
|
|
68
|
+
if (a.maxAgeSeconds !== undefined) {
|
|
69
|
+
if (!Number.isInteger(a.maxAgeSeconds) || a.maxAgeSeconds < 0) {
|
|
70
|
+
throw new Error(`${scope}: cookieOptions.maxAgeSeconds must be a non-negative integer.`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (sameSite === "None" && !secure) {
|
|
74
|
+
throw new Error(`${scope}: cookieOptions.sameSite: "None" requires secure: true.`);
|
|
75
|
+
}
|
|
76
|
+
if (name.startsWith("__Host-")) {
|
|
77
|
+
if (!secure || path !== "/" || a.domain) {
|
|
78
|
+
throw new Error(`${scope}: "__Host-" cookie names require secure: true, path: "/", and no domain.`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (name.startsWith("__Secure-")) {
|
|
82
|
+
if (!secure) {
|
|
83
|
+
throw new Error(`${scope}: "__Secure-" cookie names require secure: true. ` +
|
|
84
|
+
(isProduction
|
|
85
|
+
? "Production refuse-to-boot: a browser will silently drop this cookie over HTTP."
|
|
86
|
+
: "Drop the prefix or pass secure: true."));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Serialize a single `Set-Cookie` header value from a (name, value,
|
|
92
|
+
* attributes) tuple. The companion of {@link assertCookieAttributes} — call
|
|
93
|
+
* the assertion at construction time, then call this helper on every
|
|
94
|
+
* write.
|
|
95
|
+
*
|
|
96
|
+
* The value is URI-encoded so binary signature bytes and base64 padding
|
|
97
|
+
* round-trip safely.
|
|
98
|
+
*
|
|
99
|
+
* @since 0.27.0
|
|
100
|
+
*/
|
|
101
|
+
export function serializeCookie(name, value, attributes = {}) {
|
|
102
|
+
assertCookieAttributes({ scope: "serializeCookie()", name, attributes });
|
|
103
|
+
const sameSite = attributes.sameSite ?? "Strict";
|
|
104
|
+
const secure = attributes.secure ?? true;
|
|
105
|
+
const httpOnly = attributes.httpOnly ?? true;
|
|
106
|
+
const path = attributes.path ?? "/";
|
|
107
|
+
let out = `${name}=${encodeURIComponent(value)}`;
|
|
108
|
+
out += `; Path=${path}`;
|
|
109
|
+
out += `; SameSite=${sameSite}`;
|
|
110
|
+
if (secure)
|
|
111
|
+
out += "; Secure";
|
|
112
|
+
if (httpOnly)
|
|
113
|
+
out += "; HttpOnly";
|
|
114
|
+
if (attributes.domain)
|
|
115
|
+
out += `; Domain=${attributes.domain}`;
|
|
116
|
+
if (attributes.maxAgeSeconds !== undefined && attributes.maxAgeSeconds > 0) {
|
|
117
|
+
out += `; Max-Age=${attributes.maxAgeSeconds}`;
|
|
118
|
+
}
|
|
119
|
+
if (attributes.partitioned)
|
|
120
|
+
out += "; Partitioned";
|
|
121
|
+
return out;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Serialize a `Set-Cookie` value that clears the named cookie. Uses
|
|
125
|
+
* `Max-Age=0` per RFC 6265 §5.2.2 and preserves the original attributes so
|
|
126
|
+
* intermediaries match the original cookie when deciding what to delete.
|
|
127
|
+
*
|
|
128
|
+
* @since 0.27.0
|
|
129
|
+
*/
|
|
130
|
+
export function serializeClearCookie(name, attributes = {}) {
|
|
131
|
+
assertCookieAttributes({ scope: "serializeClearCookie()", name, attributes });
|
|
132
|
+
const sameSite = attributes.sameSite ?? "Strict";
|
|
133
|
+
const secure = attributes.secure ?? true;
|
|
134
|
+
const httpOnly = attributes.httpOnly ?? true;
|
|
135
|
+
const path = attributes.path ?? "/";
|
|
136
|
+
let out = `${name}=`;
|
|
137
|
+
out += `; Path=${path}`;
|
|
138
|
+
out += `; SameSite=${sameSite}`;
|
|
139
|
+
if (secure)
|
|
140
|
+
out += "; Secure";
|
|
141
|
+
if (httpOnly)
|
|
142
|
+
out += "; HttpOnly";
|
|
143
|
+
if (attributes.domain)
|
|
144
|
+
out += `; Domain=${attributes.domain}`;
|
|
145
|
+
out += "; Max-Age=0";
|
|
146
|
+
if (attributes.partitioned)
|
|
147
|
+
out += "; Partitioned";
|
|
148
|
+
return out;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Parse a single cookie value from a `Cookie` request header. Returns
|
|
152
|
+
* `null` when the named cookie is absent. URI-decoded on a best-effort
|
|
153
|
+
* basis (malformed `%` sequences return the raw value rather than throwing).
|
|
154
|
+
*
|
|
155
|
+
* Centralized so every framework subsystem reads cookies the same way and
|
|
156
|
+
* cannot disagree about whitespace handling between `session()` and
|
|
157
|
+
* `csrf()`.
|
|
158
|
+
*
|
|
159
|
+
* @since 0.27.0
|
|
160
|
+
*/
|
|
161
|
+
export function readRequestCookie(header, name) {
|
|
162
|
+
if (!header)
|
|
163
|
+
return null;
|
|
164
|
+
const parts = header.split(";");
|
|
165
|
+
for (let i = 0; i < parts.length; i++) {
|
|
166
|
+
const part = parts[i];
|
|
167
|
+
const eq = part.indexOf("=");
|
|
168
|
+
if (eq < 0)
|
|
169
|
+
continue;
|
|
170
|
+
const k = part.slice(0, eq).trim();
|
|
171
|
+
if (k === name) {
|
|
172
|
+
const v = part.slice(eq + 1).trim();
|
|
173
|
+
try {
|
|
174
|
+
return decodeURIComponent(v);
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
return v;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
/** @internal Re-exported for tests. */
|
|
184
|
+
export const __COOKIE_FRAMEWORK_PREFIX__ = FRAMEWORK_PREFIX;
|
|
185
|
+
//# sourceMappingURL=cookie.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cookie.js","sourceRoot":"","sources":["../src/cookie.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed-DI chain helper (Wave 6 item 12).
|
|
3
|
+
*
|
|
4
|
+
* `defineDependency()` wraps a middleware so the framework deduplicates
|
|
5
|
+
* evaluation per request — a request that triggers two routes both depending
|
|
6
|
+
* on `getCurrentUser` runs the auth check exactly once per request, with the
|
|
7
|
+
* typed result threaded through `ctx.state` declared in {@link AppState}.
|
|
8
|
+
*
|
|
9
|
+
* Refuses-at-construction on cyclic dependency declarations (companion to
|
|
10
|
+
* the cyclic plugin-extension-ordering refusal in {@link App.register}).
|
|
11
|
+
*
|
|
12
|
+
* @since 0.24.0
|
|
13
|
+
*/
|
|
14
|
+
import type { BaseContext, Hooks } from "./types.js";
|
|
15
|
+
/** Symbol stamped on hooks returned by {@link defineDependency}. */
|
|
16
|
+
export declare const DEPENDENCY_MARKER: unique symbol;
|
|
17
|
+
export interface DependencyOptions<TName extends string, TValue, TStateKey extends string> {
|
|
18
|
+
/** Unique dependency name. Used for cycle detection and dedup. */
|
|
19
|
+
name: TName;
|
|
20
|
+
/** Other dependency names this one requires (must run + complete first). */
|
|
21
|
+
dependsOn?: readonly string[];
|
|
22
|
+
/** Key the resolved value is written to on `ctx.state`. Defaults to `name`. */
|
|
23
|
+
stateKey?: TStateKey;
|
|
24
|
+
/**
|
|
25
|
+
* Resolve the dependency value. Receives the per-request context, returns
|
|
26
|
+
* the value to stamp on `ctx.state[stateKey]`. Called at most once per
|
|
27
|
+
* request when the dependency wrapper is composed multiple times.
|
|
28
|
+
*/
|
|
29
|
+
resolve: (ctx: BaseContext<any, any>) => TValue | Promise<TValue>;
|
|
30
|
+
}
|
|
31
|
+
/** Hooks bundle returned by {@link defineDependency} (carries metadata). */
|
|
32
|
+
export interface DependencyHooks extends Hooks {
|
|
33
|
+
readonly [DEPENDENCY_MARKER]: {
|
|
34
|
+
readonly name: string;
|
|
35
|
+
readonly dependsOn: readonly string[];
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Wrap a per-request value producer as a {@link Hooks} bundle. The hook
|
|
40
|
+
* runs in `beforeHandle` and writes its resolved value to
|
|
41
|
+
* `ctx.state[stateKey]` (default `name`). Composing the same dependency
|
|
42
|
+
* twice in one chain runs `resolve()` exactly once.
|
|
43
|
+
*
|
|
44
|
+
* @since 0.24.0
|
|
45
|
+
*/
|
|
46
|
+
export declare function defineDependency<TName extends string, TValue, TStateKey extends string = TName>(opts: DependencyOptions<TName, TValue, TStateKey>): DependencyHooks;
|
|
47
|
+
//# sourceMappingURL=dependency.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dependency.d.ts","sourceRoot":"","sources":["../src/dependency.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAErD,oEAAoE;AACpE,eAAO,MAAM,iBAAiB,EAAE,OAAO,MAEtC,CAAC;AAKF,MAAM,WAAW,iBAAiB,CAAC,KAAK,SAAS,MAAM,EAAE,MAAM,EAAE,SAAS,SAAS,MAAM;IACvF,kEAAkE;IAClE,IAAI,EAAE,KAAK,CAAC;IACZ,4EAA4E;IAC5E,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB;;;;OAIG;IACH,OAAO,EAAE,CAAC,GAAG,EAAE,WAAW,CAAC,GAAG,EAAE,GAAG,CAAC,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACnE;AAED,4EAA4E;AAC5E,MAAM,WAAW,eAAgB,SAAQ,KAAK;IAC5C,QAAQ,CAAC,CAAC,iBAAiB,CAAC,EAAE;QAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;KACvC,CAAC;CACH;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,SAAS,MAAM,EACpB,MAAM,EACN,SAAS,SAAS,MAAM,GAAG,KAAK,EAChC,IAAI,EAAE,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,CAAC,GAAG,eAAe,CAgDpE"}
|