@rotorsoft/act-http 1.2.0 → 1.3.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 +44 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/@types/api/errors.d.ts.map +1 -1
- package/dist/@types/receiver/start.d.ts +1 -1
- package/dist/@types/sse/apply-patch.d.ts +3 -3
- package/dist/@types/sse/broadcast.d.ts +6 -6
- package/dist/@types/sse/broadcast.d.ts.map +1 -1
- package/dist/@types/sse/presence.d.ts +7 -7
- package/dist/@types/sse/presence.d.ts.map +1 -1
- package/dist/@types/trpc/index.d.ts +168 -0
- package/dist/@types/trpc/index.d.ts.map +1 -0
- package/dist/@types/webhook/classify.d.ts +6 -6
- package/dist/@types/webhook/classify.d.ts.map +1 -1
- package/dist/@types/webhook/index.d.ts +1 -1
- package/dist/@types/webhook/index.d.ts.map +1 -1
- package/dist/@types/webhook/sign.d.ts +1 -1
- package/dist/@types/webhook/sign.d.ts.map +1 -1
- package/dist/api/index.cjs +2 -2
- package/dist/api/index.cjs.map +1 -1
- package/dist/api/index.js +4 -55
- package/dist/api/index.js.map +1 -1
- package/dist/{chunk-NOIXOF2I.js → chunk-4CGAUB5H.js} +13 -13
- package/dist/chunk-4CGAUB5H.js.map +1 -0
- package/dist/{chunk-F7VWYZ37.js → chunk-K4HAOBRF.js} +4 -4
- package/dist/{chunk-F7VWYZ37.js.map → chunk-K4HAOBRF.js.map} +1 -1
- package/dist/chunk-Z6FVXDDU.js +63 -0
- package/dist/chunk-Z6FVXDDU.js.map +1 -0
- package/dist/receiver/express/index.cjs +14 -14
- package/dist/receiver/express/index.cjs.map +1 -1
- package/dist/receiver/express/index.js +3 -3
- package/dist/receiver/express/index.js.map +1 -1
- package/dist/receiver/fastify/index.cjs +12 -12
- package/dist/receiver/fastify/index.cjs.map +1 -1
- package/dist/receiver/fastify/index.js +1 -1
- package/dist/receiver/hono/index.cjs +14 -14
- package/dist/receiver/hono/index.cjs.map +1 -1
- package/dist/receiver/hono/index.js +2 -2
- package/dist/receiver/index.cjs +19 -2746
- package/dist/receiver/index.cjs.map +1 -1
- package/dist/receiver/index.js +5 -2077
- package/dist/receiver/index.js.map +1 -1
- package/dist/receiver/trpc/index.cjs +12 -12
- package/dist/receiver/trpc/index.cjs.map +1 -1
- package/dist/receiver/trpc/index.js +1 -1
- package/dist/sse/index.cjs +21 -21
- package/dist/sse/index.cjs.map +1 -1
- package/dist/sse/index.js +24 -24
- package/dist/sse/index.js.map +1 -1
- package/dist/trpc/index.cjs +169 -0
- package/dist/trpc/index.cjs.map +1 -0
- package/dist/trpc/index.js +95 -0
- package/dist/trpc/index.js.map +1 -0
- package/dist/webhook/index.cjs +14 -34
- package/dist/webhook/index.cjs.map +1 -1
- package/dist/webhook/index.js +14 -32
- package/dist/webhook/index.js.map +1 -1
- package/package.json +52 -30
- package/dist/chunk-NOIXOF2I.js.map +0 -1
- package/dist/dist-NWMJQI4E.js +0 -647
- package/dist/dist-NWMJQI4E.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../src/api/errors.ts"],"names":[],"mappings":"AAQA;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,QAAQ,GAAG;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;CAM4B,CAAC;
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../src/api/errors.ts"],"names":[],"mappings":"AAQA;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,QAAQ,GAAG;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;CAM4B,CAAC;AAanD;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,OAAO,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,CAuB3E"}
|
|
@@ -19,7 +19,7 @@ import type { ReceiverBuilder, ReceiverOptions } from "@rotorsoft/act-ops/receiv
|
|
|
19
19
|
* }), async (event, ctx) => {
|
|
20
20
|
* // event.orderId and event.total are typed
|
|
21
21
|
* // ctx.key is the deduplicated Idempotency-Key
|
|
22
|
-
* await
|
|
22
|
+
* await process_order(event.orderId, event.total);
|
|
23
23
|
* })
|
|
24
24
|
* .build();
|
|
25
25
|
*
|
|
@@ -22,12 +22,12 @@ export type ApplyResult<S extends BroadcastState = BroadcastState> = {
|
|
|
22
22
|
*
|
|
23
23
|
* ```typescript
|
|
24
24
|
* onData: (msg) => {
|
|
25
|
-
* const cached = utils.
|
|
25
|
+
* const cached = utils.get_state.get_data({ streamId });
|
|
26
26
|
* const result = applyPatchMessage(msg, cached);
|
|
27
27
|
* if (result.ok) {
|
|
28
|
-
* utils.
|
|
28
|
+
* utils.get_state.setData({ streamId }, result.state);
|
|
29
29
|
* } else if (result.reason === "behind") {
|
|
30
|
-
* utils.
|
|
30
|
+
* utils.get_state.invalidate({ streamId }); // trigger full refetch
|
|
31
31
|
* }
|
|
32
32
|
* // "stale" → no-op, client already has newer state
|
|
33
33
|
* }
|
|
@@ -25,7 +25,7 @@ import type { BroadcastState, PatchMessage, Subscriber } from "./types.js";
|
|
|
25
25
|
* });
|
|
26
26
|
*
|
|
27
27
|
* // Initial state for reconnects:
|
|
28
|
-
* const cached = broadcast.
|
|
28
|
+
* const cached = broadcast.get_state(streamId);
|
|
29
29
|
* ```
|
|
30
30
|
*
|
|
31
31
|
* ## Version Contract
|
|
@@ -36,9 +36,9 @@ import type { BroadcastState, PatchMessage, Subscriber } from "./types.js";
|
|
|
36
36
|
*/
|
|
37
37
|
export declare class BroadcastChannel<S extends BroadcastState = BroadcastState> {
|
|
38
38
|
private channels;
|
|
39
|
-
private
|
|
39
|
+
private state_cache;
|
|
40
40
|
constructor(options?: {
|
|
41
|
-
|
|
41
|
+
cache_size?: number;
|
|
42
42
|
});
|
|
43
43
|
/**
|
|
44
44
|
* Publish domain patches from a commit.
|
|
@@ -54,16 +54,16 @@ export declare class BroadcastChannel<S extends BroadcastState = BroadcastState>
|
|
|
54
54
|
* (e.g. presence overlay, computed field refresh).
|
|
55
55
|
* Uses the same version as the cached state, single entry.
|
|
56
56
|
*/
|
|
57
|
-
|
|
57
|
+
publish_overlay(streamId: string, overlay_patch: Partial<S>): PatchMessage<S> | undefined;
|
|
58
58
|
/**
|
|
59
59
|
* Subscribe to broadcast messages for a stream.
|
|
60
60
|
* Returns a cleanup function that removes the subscription.
|
|
61
61
|
*/
|
|
62
62
|
subscribe(streamId: string, cb: Subscriber<S>): () => void;
|
|
63
63
|
/** Get the number of subscribers for a stream. */
|
|
64
|
-
|
|
64
|
+
get_subscriber_count(streamId: string): number;
|
|
65
65
|
/** Get the cached state for a stream (for reconnects / initial SSE yield). */
|
|
66
|
-
|
|
66
|
+
get_state(streamId: string): S | undefined;
|
|
67
67
|
/** Direct access to the state cache (for app-specific reads like presence). */
|
|
68
68
|
get cache(): StateCache<S>;
|
|
69
69
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"broadcast.d.ts","sourceRoot":"","sources":["../../../src/sse/broadcast.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE3E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,qBAAa,gBAAgB,CAAC,CAAC,SAAS,cAAc,GAAG,cAAc;IACrE,OAAO,CAAC,QAAQ,CAAyC;IACzD,OAAO,CAAC,
|
|
1
|
+
{"version":3,"file":"broadcast.d.ts","sourceRoot":"","sources":["../../../src/sse/broadcast.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE3E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,qBAAa,gBAAgB,CAAC,CAAC,SAAS,cAAc,GAAG,cAAc;IACrE,OAAO,CAAC,QAAQ,CAAyC;IACzD,OAAO,CAAC,WAAW,CAAgB;gBAEvB,OAAO,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE;IAI7C;;;;;;;OAOG;IACH,OAAO,CACL,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,CAAC,EACR,OAAO,GAAE,OAAO,CAAC,CAAC,CAAC,EAAO,GACzB,YAAY,CAAC,CAAC,CAAC;IAgBlB;;;;OAIG;IACH,eAAe,CACb,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,OAAO,CAAC,CAAC,CAAC,GACxB,YAAY,CAAC,CAAC,CAAC,GAAG,SAAS;IAe9B;;;OAGG;IACH,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI;IAW1D,kDAAkD;IAClD,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM;IAI9C,8EAA8E;IAC9E,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS;IAI1C,+EAA+E;IAC/E,IAAI,KAAK,IAAI,UAAU,CAAC,CAAC,CAAC,CAEzB;CACF"}
|
|
@@ -11,24 +11,24 @@
|
|
|
11
11
|
* const presence = new PresenceTracker();
|
|
12
12
|
*
|
|
13
13
|
* // On SSE connect:
|
|
14
|
-
* presence.add(
|
|
14
|
+
* presence.add(game_id, player_id);
|
|
15
15
|
*
|
|
16
16
|
* // On SSE disconnect:
|
|
17
|
-
* presence.remove(
|
|
17
|
+
* presence.remove(game_id, player_id);
|
|
18
18
|
*
|
|
19
19
|
* // Query:
|
|
20
|
-
* presence.
|
|
20
|
+
* presence.get_online(game_id); // Set<string>
|
|
21
21
|
* ```
|
|
22
22
|
*/
|
|
23
23
|
export declare class PresenceTracker {
|
|
24
24
|
private streams;
|
|
25
25
|
/** Increment ref count for an identity on a stream. */
|
|
26
|
-
add(streamId: string,
|
|
26
|
+
add(streamId: string, identity_id: string): void;
|
|
27
27
|
/** Decrement ref count. Removes the identity when count reaches 0. */
|
|
28
|
-
remove(streamId: string,
|
|
28
|
+
remove(streamId: string, identity_id: string): void;
|
|
29
29
|
/** Get the set of online identity IDs for a stream. */
|
|
30
|
-
|
|
30
|
+
get_online(streamId: string): Set<string>;
|
|
31
31
|
/** Check if a specific identity is online for a stream. */
|
|
32
|
-
|
|
32
|
+
is_online(streamId: string, identity_id: string): boolean;
|
|
33
33
|
}
|
|
34
34
|
//# sourceMappingURL=presence.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"presence.d.ts","sourceRoot":"","sources":["../../../src/sse/presence.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,OAAO,CAA0C;IAEzD,uDAAuD;IACvD,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"presence.d.ts","sourceRoot":"","sources":["../../../src/sse/presence.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,OAAO,CAA0C;IAEzD,uDAAuD;IACvD,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI;IAMhD,sEAAsE;IACtE,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI;IASnD,uDAAuD;IACvD,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC;IAKzC,2DAA2D;IAC3D,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO;CAG1D"}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @packageDocumentation
|
|
3
|
+
* @module act-http/trpc
|
|
4
|
+
*
|
|
5
|
+
* tRPC subpath for the auto-generated API epic (#835). Walks the
|
|
6
|
+
* registry of a built `Act` once at function call and emits a typed
|
|
7
|
+
* router that exposes every registered action as a flat `mutation`,
|
|
8
|
+
* matching the shape hand-written tRPC routers in the Act ecosystem
|
|
9
|
+
* already use.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
*
|
|
13
|
+
* ```ts
|
|
14
|
+
* import { trpc } from "@rotorsoft/act-http/trpc";
|
|
15
|
+
*
|
|
16
|
+
* const router = trpc(app, {
|
|
17
|
+
* actor: (ctx) => ({ id: ctx.user.id, name: ctx.user.name }),
|
|
18
|
+
* stream: (action, input, ctx) => input.stream ?? ctx.tenant,
|
|
19
|
+
* });
|
|
20
|
+
*
|
|
21
|
+
* // Client sees:
|
|
22
|
+
* // appRouter.PressKey.mutate({ key: "5" })
|
|
23
|
+
* // appRouter.OpenTicket.mutate({ title: "..." })
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* The route shape is flat: one procedure per registered action at
|
|
27
|
+
* the top level. Action names are the unique identifiers across
|
|
28
|
+
* the registry (the framework already enforces no duplicates), so
|
|
29
|
+
* an extra namespace tier would be overhead in the common case.
|
|
30
|
+
* Pure mutations for this slice; query/projection wiring lives in
|
|
31
|
+
* a follow-up. See `libs/act-http/README.md` for the full surface
|
|
32
|
+
* walkthrough.
|
|
33
|
+
*
|
|
34
|
+
* **Actor lives on the context.** The generator wires an internal
|
|
35
|
+
* tRPC middleware that runs the host's
|
|
36
|
+
* {@link TrpcOptions.actor | actor extractor} once per call and
|
|
37
|
+
* injects the resolved {@link Actor} onto the downstream context as
|
|
38
|
+
* `ctx.actor`. Every generated mutation reads from there — auth
|
|
39
|
+
* resolves at the procedure boundary, not inside each action body,
|
|
40
|
+
* and any composed middleware (logging, tracing) sees the same
|
|
41
|
+
* `ctx.actor`. The middleware factory is also exported as
|
|
42
|
+
* {@link authenticated} for hosts that need to compose it into
|
|
43
|
+
* their own procedure chain.
|
|
44
|
+
*
|
|
45
|
+
* Composes the shared utilities at `@rotorsoft/act-http/api`:
|
|
46
|
+
* {@link ActorExtractor} for the auth seam, {@link toApiError} for
|
|
47
|
+
* the uniform error→status/code mapping, {@link withIdempotency}
|
|
48
|
+
* for `Idempotency-Key` dedup. Three transports (this one, Hono,
|
|
49
|
+
* OpenAPI) ride the same utilities so a client talking to two
|
|
50
|
+
* transports never sees two error shapes for the same framework
|
|
51
|
+
* error.
|
|
52
|
+
*/
|
|
53
|
+
import type { Act, Schema, Schemas } from "@rotorsoft/act";
|
|
54
|
+
import type { IdempotencyStore } from "@rotorsoft/act-ops/idempotency";
|
|
55
|
+
import { type ActorExtractor } from "../api/index.js";
|
|
56
|
+
/**
|
|
57
|
+
* Per-call options for {@link trpc}. The host supplies the three
|
|
58
|
+
* seams the package can't make decisions on:
|
|
59
|
+
*
|
|
60
|
+
* - `actor` — auth resolver. Receives the tRPC context, returns the
|
|
61
|
+
* `Actor` that flows onto `ctx.actor` for every generated
|
|
62
|
+
* procedure (via an internal {@link authenticated}).
|
|
63
|
+
* - `stream` — stream-id resolver. Receives the action name, the
|
|
64
|
+
* validated action input, and the original tRPC context; returns
|
|
65
|
+
* the target stream. Singleton aggregates return a constant;
|
|
66
|
+
* per-tenant aggregates pull from input or context.
|
|
67
|
+
* - `expectedVersion` (optional) — optimistic-concurrency resolver.
|
|
68
|
+
* When set, the procedure threads the resolved value through
|
|
69
|
+
* `target.expectedVersion` so `app.do` refuses to commit if the
|
|
70
|
+
* stream has moved. Hosts typically read it from an `If-Match`
|
|
71
|
+
* header on the underlying HTTP request, or pull it from the
|
|
72
|
+
* client's last-known snapshot. Returning `undefined` skips the
|
|
73
|
+
* check for that call — handy when only some actions are
|
|
74
|
+
* concurrency-sensitive.
|
|
75
|
+
* - `idempotency` (optional) — when set, the procedure honors
|
|
76
|
+
* `Idempotency-Key` via the shared
|
|
77
|
+
* {@link withIdempotency} helper. The host supplies the
|
|
78
|
+
* `IdempotencyStore` and a `keyFrom` extractor that reads the key
|
|
79
|
+
* out of the tRPC context (typically a header). On a duplicate
|
|
80
|
+
* claim, the procedure throws a `CONFLICT` `TRPCError` — the
|
|
81
|
+
* receiver-side convention of "ack the duplicate" doesn't carry
|
|
82
|
+
* over because the contract intentionally does not cache the
|
|
83
|
+
* original handler's result.
|
|
84
|
+
*
|
|
85
|
+
* @template Ctx The host's tRPC context shape. Flows end-to-end —
|
|
86
|
+
* procedures see this exact context type (plus `actor`) at
|
|
87
|
+
* runtime.
|
|
88
|
+
*/
|
|
89
|
+
export type TrpcOptions<Ctx> = {
|
|
90
|
+
readonly actor: ActorExtractor;
|
|
91
|
+
readonly stream: (action_name: string, input: unknown, ctx: Ctx) => string | Promise<string>;
|
|
92
|
+
readonly expectedVersion?: (action_name: string, input: unknown, ctx: Ctx) => number | undefined | Promise<number | undefined>;
|
|
93
|
+
readonly idempotency?: {
|
|
94
|
+
readonly store: IdempotencyStore;
|
|
95
|
+
readonly keyFrom: (ctx: Ctx) => string | undefined;
|
|
96
|
+
};
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* Build a tRPC middleware that runs an {@link ActorExtractor} once
|
|
100
|
+
* per call and forwards the resolved {@link Actor} on the
|
|
101
|
+
* downstream context as `ctx.actor`. The generator at
|
|
102
|
+
* {@link trpc} uses this internally; it's also exported so hosts
|
|
103
|
+
* that want to compose their own procedure chain (logging,
|
|
104
|
+
* tracing, custom auth flavors) can wire it directly:
|
|
105
|
+
*
|
|
106
|
+
* ```ts
|
|
107
|
+
* import { initTRPC } from "@trpc/server";
|
|
108
|
+
* import { authenticated } from "@rotorsoft/act-http/trpc";
|
|
109
|
+
*
|
|
110
|
+
* type Ctx = { user?: { id: string; name: string } };
|
|
111
|
+
* const t = initTRPC.context<Ctx>().create();
|
|
112
|
+
*
|
|
113
|
+
* const authed = t.procedure.use(
|
|
114
|
+
* authenticated((ctx) => {
|
|
115
|
+
* if (!ctx.user) throw new Error("not authenticated");
|
|
116
|
+
* return { id: ctx.user.id, name: ctx.user.name };
|
|
117
|
+
* })
|
|
118
|
+
* );
|
|
119
|
+
*
|
|
120
|
+
* // Inside any procedure built off `authed`, `ctx.actor: Actor`.
|
|
121
|
+
* ```
|
|
122
|
+
*
|
|
123
|
+
* The middleware is unaware of the host's `t` instance — it returns
|
|
124
|
+
* a structurally-typed function that any tRPC `procedure.use(...)`
|
|
125
|
+
* accepts. Errors thrown by the extractor surface unchanged
|
|
126
|
+
* (typically wrap them as `TRPCError({ code: "UNAUTHORIZED" })`
|
|
127
|
+
* in the extractor body).
|
|
128
|
+
*/
|
|
129
|
+
export declare function authenticated(extractor: ActorExtractor): any;
|
|
130
|
+
/**
|
|
131
|
+
* Build a typed tRPC router from a built `Act` instance.
|
|
132
|
+
*
|
|
133
|
+
* Walks `app.registry.actions` once and emits one flat mutation per
|
|
134
|
+
* action under `router.<actionName>`. The framework already enforces
|
|
135
|
+
* no duplicate action names across states, so a flat tier reads
|
|
136
|
+
* cleanly at the client and matches the hand-written router shape
|
|
137
|
+
* adopters already have. Each mutation:
|
|
138
|
+
*
|
|
139
|
+
* 1. Runs the internal {@link authenticated} — `options.actor`
|
|
140
|
+
* resolves the {@link Actor} once and injects it onto
|
|
141
|
+
* `ctx.actor`.
|
|
142
|
+
* 2. Resolves the target stream via `options.stream(name, input, ctx)`.
|
|
143
|
+
* 3. (Optionally) claims the `Idempotency-Key` via
|
|
144
|
+
* {@link withIdempotency} — throws `CONFLICT` on duplicate.
|
|
145
|
+
* 4. Calls `app.do(name, { stream, actor: ctx.actor }, input)`.
|
|
146
|
+
* 5. Maps any thrown framework error onto a `TRPCError` via
|
|
147
|
+
* {@link toApiError}.
|
|
148
|
+
*
|
|
149
|
+
* The returned router's procedure inputs come straight from each
|
|
150
|
+
* action's Zod schema; outputs are the framework's
|
|
151
|
+
* `Snapshot<TState, TEvents>[]` shape. Strong typing flows
|
|
152
|
+
* end-to-end via tRPC's normal inference path — the client sees
|
|
153
|
+
* `appRouter.Calculator.PressKey.mutate({ key: "5" })` as a typed
|
|
154
|
+
* call without any codegen.
|
|
155
|
+
*
|
|
156
|
+
* @template Ctx The tRPC context shape the host's runtime feeds in.
|
|
157
|
+
* Flows through `actor`, `stream`, and `idempotency.keyFrom` so
|
|
158
|
+
* the operator's existing context types just work. The internal
|
|
159
|
+
* middleware augments `Ctx` with `{ actor: Actor }` on the
|
|
160
|
+
* downstream procedure context.
|
|
161
|
+
*
|
|
162
|
+
* @param app A built `Act` orchestrator.
|
|
163
|
+
* @param options Auth, stream, and (optional) idempotency seams.
|
|
164
|
+
* @returns A tRPC router covering every registered action, grouped
|
|
165
|
+
* by owning state.
|
|
166
|
+
*/
|
|
167
|
+
export declare function trpc<Ctx extends object = object>(app: Act<any, Schemas, Schemas, Record<string, Schema>>, options: TrpcOptions<Ctx>): never;
|
|
168
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/trpc/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AACH,OAAO,KAAK,EAAE,GAAG,EAAS,MAAM,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAClE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAEvE,OAAO,EACL,KAAK,cAAc,EAGpB,MAAM,iBAAiB,CAAC;AAEzB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,MAAM,MAAM,WAAW,CAAC,GAAG,IAAI;IAC7B,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,CACf,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,OAAO,EACd,GAAG,EAAE,GAAG,KACL,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9B,QAAQ,CAAC,eAAe,CAAC,EAAE,CACzB,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,OAAO,EACd,GAAG,EAAE,GAAG,KACL,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACtD,QAAQ,CAAC,WAAW,CAAC,EAAE;QACrB,QAAQ,CAAC,KAAK,EAAE,gBAAgB,CAAC;QACjC,QAAQ,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,MAAM,GAAG,SAAS,CAAC;KACpD,CAAC;CACH,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,wBAAgB,aAAa,CAAC,SAAS,EAAE,cAAc,GAAG,GAAG,CAW5D;AA+CD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,wBAAgB,IAAI,CAAC,GAAG,SAAS,MAAM,GAAG,MAAM,EAE9C,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EACvD,OAAO,EAAE,WAAW,CAAC,GAAG,CAAC,SAkE1B"}
|
|
@@ -22,14 +22,14 @@ export type HttpDisposition = "ok" | "retry" | "block";
|
|
|
22
22
|
* SDK-based reactions, etc.) can apply the same retry semantics
|
|
23
23
|
* without inventing a parallel rule.
|
|
24
24
|
*/
|
|
25
|
-
export declare function
|
|
26
|
-
/** Options for {@link
|
|
25
|
+
export declare function classify_http_response(response: Response): HttpDisposition;
|
|
26
|
+
/** Options for {@link try_ok}. */
|
|
27
27
|
export type TryOkOptions = {
|
|
28
28
|
/** The endpoint that received the request. Surfaced on the thrown error and in its message. */
|
|
29
29
|
url: string;
|
|
30
30
|
/**
|
|
31
31
|
* Label prefixed onto the error message — typically the
|
|
32
|
-
* integration's identity (`"webhook"`, `"
|
|
32
|
+
* integration's identity (`"webhook"`, `"my_sdk"`, `"grpc"`).
|
|
33
33
|
* Default: `"request"`.
|
|
34
34
|
*/
|
|
35
35
|
label?: string;
|
|
@@ -43,8 +43,8 @@ export type TryOkOptions = {
|
|
|
43
43
|
*
|
|
44
44
|
* ```ts
|
|
45
45
|
* .on("OrderConfirmed").do(async (event) => {
|
|
46
|
-
* const response = await
|
|
47
|
-
* await
|
|
46
|
+
* const response = await my_sdk.deliver(event);
|
|
47
|
+
* await try_ok(response, { url: my_sdk.url, label: "my_sdk" });
|
|
48
48
|
* // ...response was 2xx; continue with downstream work...
|
|
49
49
|
* });
|
|
50
50
|
* ```
|
|
@@ -55,5 +55,5 @@ export type TryOkOptions = {
|
|
|
55
55
|
* here, so `instanceof RetryableHttpError` matches both webhook and
|
|
56
56
|
* custom-integration errors uniformly.
|
|
57
57
|
*/
|
|
58
|
-
export declare function
|
|
58
|
+
export declare function try_ok(response: Response, options: TryOkOptions): Promise<void>;
|
|
59
59
|
//# sourceMappingURL=classify.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"classify.d.ts","sourceRoot":"","sources":["../../../src/webhook/classify.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,eAAe,GAAG,IAAI,GAAG,OAAO,GAAG,OAAO,CAAC;AAEvD;;;;;;GAMG;AACH,wBAAgB,
|
|
1
|
+
{"version":3,"file":"classify.d.ts","sourceRoot":"","sources":["../../../src/webhook/classify.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,eAAe,GAAG,IAAI,GAAG,OAAO,GAAG,OAAO,CAAC;AAEvD;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,QAAQ,GAAG,eAAe,CAI1E;AAED,kCAAkC;AAClC,MAAM,MAAM,YAAY,GAAG;IACzB,+FAA+F;IAC/F,GAAG,EAAE,MAAM,CAAC;IACZ;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,MAAM,CAC1B,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC,IAAI,CAAC,CAmBf"}
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
*/
|
|
27
27
|
import type { ReactionHandler, Schemas } from "@rotorsoft/act";
|
|
28
28
|
import { type WebhookConfig } from "./types.js";
|
|
29
|
-
export {
|
|
29
|
+
export type { HttpDisposition } from "./classify.js";
|
|
30
30
|
export type { HttpDeliveryErrorInit, WebhookBody, WebhookConfig, WebhookResolver, } from "./types.js";
|
|
31
31
|
export { NonRetryableHttpError, NonRetryableWebhookError, RetryableHttpError, WebhookError, } from "./types.js";
|
|
32
32
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/webhook/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAAa,eAAe,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAG1E,OAAO,EAEL,KAAK,aAAa,EAEnB,MAAM,YAAY,CAAC;AAEpB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/webhook/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAAa,eAAe,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAG1E,OAAO,EAEL,KAAK,aAAa,EAEnB,MAAM,YAAY,CAAC;AAEpB,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,YAAY,EACV,qBAAqB,EACrB,WAAW,EACX,aAAa,EACb,eAAe,GAChB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,qBAAqB,EACrB,wBAAwB,EACxB,kBAAkB,EAClB,YAAY,GACb,MAAM,YAAY,CAAC;AAsBpB;;;;;;;;;;;;;GAaG;AACH,wBAAgB,OAAO,CAAC,OAAO,SAAS,OAAO,GAAG,OAAO,EACvD,MAAM,EAAE,aAAa,CAAC,OAAO,CAAC,GAC7B,eAAe,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC,CA+EzC"}
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* from the package's `./webhook` entry — the webhook helper calls
|
|
19
19
|
* it internally, and operators don't need it directly.
|
|
20
20
|
*/
|
|
21
|
-
export declare function
|
|
21
|
+
export declare function sign_request(body: string, secret: string, now?: number): {
|
|
22
22
|
signature: string;
|
|
23
23
|
timestamp: string;
|
|
24
24
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sign.d.ts","sourceRoot":"","sources":["../../../src/webhook/sign.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,
|
|
1
|
+
{"version":3,"file":"sign.d.ts","sourceRoot":"","sources":["../../../src/webhook/sign.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,GAAG,GAAE,MAAsC,GAC1C;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAK1C"}
|
package/dist/api/index.cjs
CHANGED
|
@@ -35,7 +35,7 @@ var ERROR_MAP = {
|
|
|
35
35
|
StreamClosedError: { status: 410, code: "STREAM_CLOSED" },
|
|
36
36
|
NonRetryableError: { status: 400, code: "NON_RETRYABLE" }
|
|
37
37
|
};
|
|
38
|
-
var
|
|
38
|
+
var lookup_known = (err) => {
|
|
39
39
|
if (err instanceof import_act.ValidationError) return { name: "ValidationError" };
|
|
40
40
|
if (err instanceof import_act.InvariantError) return { name: "InvariantError" };
|
|
41
41
|
if (err instanceof import_act.ConcurrencyError) return { name: "ConcurrencyError" };
|
|
@@ -44,7 +44,7 @@ var lookupKnown = (err) => {
|
|
|
44
44
|
return null;
|
|
45
45
|
};
|
|
46
46
|
function toApiError(err) {
|
|
47
|
-
const known =
|
|
47
|
+
const known = lookup_known(err);
|
|
48
48
|
if (known) {
|
|
49
49
|
const entry = ERROR_MAP[known.name];
|
|
50
50
|
return {
|
package/dist/api/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/api/index.ts","../../src/api/errors.ts","../../src/api/idempotency.ts"],"sourcesContent":["/**\n * @packageDocumentation\n * @module act-http/api\n *\n * Shared utilities for the act-http auto-generated API surfaces.\n * Three concerns that every transport (tRPC, Hono, OpenAPI) has to\n * address — actor extraction, error envelope mapping,\n * `Idempotency-Key` wiring — defined once here and composed by each\n * transport sibling subpath.\n *\n * - {@link ActorExtractor} — the host-supplied closure that resolves\n * an `Actor` from an incoming request. Auth (JWT, session, API\n * key) stays in the host; the package only asks for this one\n * function.\n * - {@link ApiError}, {@link ERROR_MAP}, {@link toApiError} — the\n * uniform error envelope and the status/code mapping every\n * transport uses. Cross-transport consistency by construction.\n * - {@link withIdempotency} — the helper that wraps action handlers\n * in an `Idempotency-Key` claim. Reuses the\n * `@rotorsoft/act-ops/idempotency` contract that\n * `@rotorsoft/act-http/receiver` already speaks, so receivers and\n * generated APIs share one `IdempotencyStore` implementation.\n *\n * Sibling subpaths in the same package consume the utilities here:\n *\n * - `@rotorsoft/act-http/trpc` — tRPC adapter (#843).\n * - `@rotorsoft/act-http/hono` — Hono adapter (#844).\n * - `@rotorsoft/act-http/openapi` — OpenAPI emitter (#845).\n *\n * Existing siblings unrelated to the generated-API work:\n *\n * - `@rotorsoft/act-http/webhook` — outbound POST delivery.\n * - `@rotorsoft/act-http/sse` — incremental state broadcast.\n * - `@rotorsoft/act-http/receiver` — inbound webhook ingestion.\n */\n\nexport type { ActorExtractor } from \"./actor.js\";\nexport {\n type ApiError,\n ERROR_MAP,\n type ErrorMapEntry,\n toApiError,\n} from \"./errors.js\";\nexport { type IdempotencyResult, withIdempotency } from \"./idempotency.js\";\n","import {\n ConcurrencyError,\n InvariantError,\n NonRetryableError,\n StreamClosedError,\n ValidationError,\n} from \"@rotorsoft/act\";\n\n/**\n * Uniform error envelope shipped over the wire by every act-http\n * transport. Hosts get the same shape from REST, tRPC, and OpenAPI —\n * a client that talks to two transports doesn't have to invent two\n * error parsers.\n *\n * - `error` — the framework error name (`\"ValidationError\"`,\n * `\"InvariantError\"`, …). Stable identifier, safe to switch on.\n * - `detail` — the framework's message text. Human-readable; not\n * parsed by clients.\n * - `code` — a machine-readable status code from {@link ERROR_MAP}\n * for clients that prefer enum-style branching over name strings.\n */\nexport type ApiError = {\n error: string;\n detail?: string;\n code?: string;\n};\n\n/**\n * Status + code pair for one known framework error.\n */\nexport type ErrorMapEntry = {\n status: number;\n code: string;\n};\n\n/**\n * The single table that maps framework error types to HTTP status\n * codes and machine-readable codes. One table, three consumers\n * (Hono, tRPC, OpenAPI) — cross-transport consistency by\n * construction.\n *\n * Operators wanting different mappings wrap the generated transport\n * rather than mutating this — the consistency is the load-bearing\n * property, not the specific status codes.\n */\nexport const ERROR_MAP = {\n ValidationError: { status: 422, code: \"VALIDATION\" },\n InvariantError: { status: 409, code: \"INVARIANT\" },\n ConcurrencyError: { status: 412, code: \"CONCURRENCY\" },\n StreamClosedError: { status: 410, code: \"STREAM_CLOSED\" },\n NonRetryableError: { status: 400, code: \"NON_RETRYABLE\" },\n} as const satisfies Record<string, ErrorMapEntry>;\n\nconst
|
|
1
|
+
{"version":3,"sources":["../../src/api/index.ts","../../src/api/errors.ts","../../src/api/idempotency.ts"],"sourcesContent":["/**\n * @packageDocumentation\n * @module act-http/api\n *\n * Shared utilities for the act-http auto-generated API surfaces.\n * Three concerns that every transport (tRPC, Hono, OpenAPI) has to\n * address — actor extraction, error envelope mapping,\n * `Idempotency-Key` wiring — defined once here and composed by each\n * transport sibling subpath.\n *\n * - {@link ActorExtractor} — the host-supplied closure that resolves\n * an `Actor` from an incoming request. Auth (JWT, session, API\n * key) stays in the host; the package only asks for this one\n * function.\n * - {@link ApiError}, {@link ERROR_MAP}, {@link toApiError} — the\n * uniform error envelope and the status/code mapping every\n * transport uses. Cross-transport consistency by construction.\n * - {@link withIdempotency} — the helper that wraps action handlers\n * in an `Idempotency-Key` claim. Reuses the\n * `@rotorsoft/act-ops/idempotency` contract that\n * `@rotorsoft/act-http/receiver` already speaks, so receivers and\n * generated APIs share one `IdempotencyStore` implementation.\n *\n * Sibling subpaths in the same package consume the utilities here:\n *\n * - `@rotorsoft/act-http/trpc` — tRPC adapter (#843).\n * - `@rotorsoft/act-http/hono` — Hono adapter (#844).\n * - `@rotorsoft/act-http/openapi` — OpenAPI emitter (#845).\n *\n * Existing siblings unrelated to the generated-API work:\n *\n * - `@rotorsoft/act-http/webhook` — outbound POST delivery.\n * - `@rotorsoft/act-http/sse` — incremental state broadcast.\n * - `@rotorsoft/act-http/receiver` — inbound webhook ingestion.\n */\n\nexport type { ActorExtractor } from \"./actor.js\";\nexport {\n type ApiError,\n ERROR_MAP,\n type ErrorMapEntry,\n toApiError,\n} from \"./errors.js\";\nexport { type IdempotencyResult, withIdempotency } from \"./idempotency.js\";\n","import {\n ConcurrencyError,\n InvariantError,\n NonRetryableError,\n StreamClosedError,\n ValidationError,\n} from \"@rotorsoft/act\";\n\n/**\n * Uniform error envelope shipped over the wire by every act-http\n * transport. Hosts get the same shape from REST, tRPC, and OpenAPI —\n * a client that talks to two transports doesn't have to invent two\n * error parsers.\n *\n * - `error` — the framework error name (`\"ValidationError\"`,\n * `\"InvariantError\"`, …). Stable identifier, safe to switch on.\n * - `detail` — the framework's message text. Human-readable; not\n * parsed by clients.\n * - `code` — a machine-readable status code from {@link ERROR_MAP}\n * for clients that prefer enum-style branching over name strings.\n */\nexport type ApiError = {\n error: string;\n detail?: string;\n code?: string;\n};\n\n/**\n * Status + code pair for one known framework error.\n */\nexport type ErrorMapEntry = {\n status: number;\n code: string;\n};\n\n/**\n * The single table that maps framework error types to HTTP status\n * codes and machine-readable codes. One table, three consumers\n * (Hono, tRPC, OpenAPI) — cross-transport consistency by\n * construction.\n *\n * Operators wanting different mappings wrap the generated transport\n * rather than mutating this — the consistency is the load-bearing\n * property, not the specific status codes.\n */\nexport const ERROR_MAP = {\n ValidationError: { status: 422, code: \"VALIDATION\" },\n InvariantError: { status: 409, code: \"INVARIANT\" },\n ConcurrencyError: { status: 412, code: \"CONCURRENCY\" },\n StreamClosedError: { status: 410, code: \"STREAM_CLOSED\" },\n NonRetryableError: { status: 400, code: \"NON_RETRYABLE\" },\n} as const satisfies Record<string, ErrorMapEntry>;\n\nconst lookup_known = (\n err: unknown\n): { name: keyof typeof ERROR_MAP } | null => {\n if (err instanceof ValidationError) return { name: \"ValidationError\" };\n if (err instanceof InvariantError) return { name: \"InvariantError\" };\n if (err instanceof ConcurrencyError) return { name: \"ConcurrencyError\" };\n if (err instanceof StreamClosedError) return { name: \"StreamClosedError\" };\n if (err instanceof NonRetryableError) return { name: \"NonRetryableError\" };\n return null;\n};\n\n/**\n * Translate an unknown thrown value into the canonical\n * {@link ApiError} envelope plus HTTP status. Each transport's error\n * boundary calls this once and forwards the result to the wire.\n *\n * Known framework errors map per {@link ERROR_MAP}. Everything else\n * surfaces as a 500 with `code: \"INTERNAL\"`; the `detail` field is\n * populated when the throw was an `Error` instance, omitted\n * otherwise (a thrown string or object doesn't get to leak its\n * payload to the client).\n */\nexport function toApiError(err: unknown): { status: number; body: ApiError } {\n const known = lookup_known(err);\n if (known) {\n const entry = ERROR_MAP[known.name];\n return {\n status: entry.status,\n body: {\n error: known.name,\n detail: (err as Error).message,\n code: entry.code,\n },\n };\n }\n if (err instanceof Error) {\n return {\n status: 500,\n body: { error: \"InternalError\", detail: err.message, code: \"INTERNAL\" },\n };\n }\n return {\n status: 500,\n body: { error: \"InternalError\", code: \"INTERNAL\" },\n };\n}\n","import type { IdempotencyStore } from \"@rotorsoft/act-ops/idempotency\";\n\n/**\n * Result of a {@link withIdempotency} call.\n *\n * - `{ deduped: false, result }` — the claim was fresh; the handler\n * ran and produced `result`.\n * - `{ deduped: true }` — the claim was already taken; the handler\n * was *not* invoked. The caller decides how to respond (typically\n * a 2xx with no body, matching the receiver-side convention).\n *\n * Note: the contract does not cache the previous response. A\n * duplicate call returns the deduped marker only — replaying the\n * original handler's output would require a response-caching\n * adapter, which is out of scope here. The receiver-side convention\n * (and the convention the generated transports follow) is \"ack the\n * duplicate; do nothing else.\"\n */\nexport type IdempotencyResult<T> =\n | { deduped: false; result: T }\n | { deduped: true };\n\n/**\n * Wrap an action handler so the framework honors `Idempotency-Key`\n * dedup. Acquires the key via {@link IdempotencyStore.claim}, runs\n * the handler exactly when the claim was fresh, and skips the\n * handler entirely on a duplicate.\n *\n * Reuses the contract `@rotorsoft/act-ops/idempotency` already\n * defines for the receiver-side `Idempotency-Key` story. A single\n * `IdempotencyStore` implementation covers both halves of the \"Act\n * over the wire\" surface — receiver and generated API.\n */\nexport async function withIdempotency<T>(\n store: IdempotencyStore,\n key: string,\n handler: () => Promise<T>\n): Promise<IdempotencyResult<T>> {\n const fresh = await store.claim(key);\n if (!fresh) {\n return { deduped: true };\n }\n return { deduped: false, result: await handler() };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,iBAMO;AAuCA,IAAM,YAAY;AAAA,EACvB,iBAAiB,EAAE,QAAQ,KAAK,MAAM,aAAa;AAAA,EACnD,gBAAgB,EAAE,QAAQ,KAAK,MAAM,YAAY;AAAA,EACjD,kBAAkB,EAAE,QAAQ,KAAK,MAAM,cAAc;AAAA,EACrD,mBAAmB,EAAE,QAAQ,KAAK,MAAM,gBAAgB;AAAA,EACxD,mBAAmB,EAAE,QAAQ,KAAK,MAAM,gBAAgB;AAC1D;AAEA,IAAM,eAAe,CACnB,QAC4C;AAC5C,MAAI,eAAe,2BAAiB,QAAO,EAAE,MAAM,kBAAkB;AACrE,MAAI,eAAe,0BAAgB,QAAO,EAAE,MAAM,iBAAiB;AACnE,MAAI,eAAe,4BAAkB,QAAO,EAAE,MAAM,mBAAmB;AACvE,MAAI,eAAe,6BAAmB,QAAO,EAAE,MAAM,oBAAoB;AACzE,MAAI,eAAe,6BAAmB,QAAO,EAAE,MAAM,oBAAoB;AACzE,SAAO;AACT;AAaO,SAAS,WAAW,KAAkD;AAC3E,QAAM,QAAQ,aAAa,GAAG;AAC9B,MAAI,OAAO;AACT,UAAM,QAAQ,UAAU,MAAM,IAAI;AAClC,WAAO;AAAA,MACL,QAAQ,MAAM;AAAA,MACd,MAAM;AAAA,QACJ,OAAO,MAAM;AAAA,QACb,QAAS,IAAc;AAAA,QACvB,MAAM,MAAM;AAAA,MACd;AAAA,IACF;AAAA,EACF;AACA,MAAI,eAAe,OAAO;AACxB,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,MAAM,EAAE,OAAO,iBAAiB,QAAQ,IAAI,SAAS,MAAM,WAAW;AAAA,IACxE;AAAA,EACF;AACA,SAAO;AAAA,IACL,QAAQ;AAAA,IACR,MAAM,EAAE,OAAO,iBAAiB,MAAM,WAAW;AAAA,EACnD;AACF;;;ACjEA,eAAsB,gBACpB,OACA,KACA,SAC+B;AAC/B,QAAM,QAAQ,MAAM,MAAM,MAAM,GAAG;AACnC,MAAI,CAAC,OAAO;AACV,WAAO,EAAE,SAAS,KAAK;AAAA,EACzB;AACA,SAAO,EAAE,SAAS,OAAO,QAAQ,MAAM,QAAQ,EAAE;AACnD;","names":[]}
|
package/dist/api/index.js
CHANGED
|
@@ -1,59 +1,8 @@
|
|
|
1
|
-
// src/api/errors.ts
|
|
2
1
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
ValidationError
|
|
8
|
-
} from "@rotorsoft/act";
|
|
9
|
-
var ERROR_MAP = {
|
|
10
|
-
ValidationError: { status: 422, code: "VALIDATION" },
|
|
11
|
-
InvariantError: { status: 409, code: "INVARIANT" },
|
|
12
|
-
ConcurrencyError: { status: 412, code: "CONCURRENCY" },
|
|
13
|
-
StreamClosedError: { status: 410, code: "STREAM_CLOSED" },
|
|
14
|
-
NonRetryableError: { status: 400, code: "NON_RETRYABLE" }
|
|
15
|
-
};
|
|
16
|
-
var lookupKnown = (err) => {
|
|
17
|
-
if (err instanceof ValidationError) return { name: "ValidationError" };
|
|
18
|
-
if (err instanceof InvariantError) return { name: "InvariantError" };
|
|
19
|
-
if (err instanceof ConcurrencyError) return { name: "ConcurrencyError" };
|
|
20
|
-
if (err instanceof StreamClosedError) return { name: "StreamClosedError" };
|
|
21
|
-
if (err instanceof NonRetryableError) return { name: "NonRetryableError" };
|
|
22
|
-
return null;
|
|
23
|
-
};
|
|
24
|
-
function toApiError(err) {
|
|
25
|
-
const known = lookupKnown(err);
|
|
26
|
-
if (known) {
|
|
27
|
-
const entry = ERROR_MAP[known.name];
|
|
28
|
-
return {
|
|
29
|
-
status: entry.status,
|
|
30
|
-
body: {
|
|
31
|
-
error: known.name,
|
|
32
|
-
detail: err.message,
|
|
33
|
-
code: entry.code
|
|
34
|
-
}
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
if (err instanceof Error) {
|
|
38
|
-
return {
|
|
39
|
-
status: 500,
|
|
40
|
-
body: { error: "InternalError", detail: err.message, code: "INTERNAL" }
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
return {
|
|
44
|
-
status: 500,
|
|
45
|
-
body: { error: "InternalError", code: "INTERNAL" }
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// src/api/idempotency.ts
|
|
50
|
-
async function withIdempotency(store, key, handler) {
|
|
51
|
-
const fresh = await store.claim(key);
|
|
52
|
-
if (!fresh) {
|
|
53
|
-
return { deduped: true };
|
|
54
|
-
}
|
|
55
|
-
return { deduped: false, result: await handler() };
|
|
56
|
-
}
|
|
2
|
+
ERROR_MAP,
|
|
3
|
+
toApiError,
|
|
4
|
+
withIdempotency
|
|
5
|
+
} from "../chunk-Z6FVXDDU.js";
|
|
57
6
|
export {
|
|
58
7
|
ERROR_MAP,
|
|
59
8
|
toApiError,
|
package/dist/api/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -14,12 +14,12 @@ import { createHmac, timingSafeEqual } from "crypto";
|
|
|
14
14
|
function verifyWebhook(headers, body, secret, options) {
|
|
15
15
|
const maxAgeSeconds = options?.maxAgeSeconds ?? 300;
|
|
16
16
|
const now = options?.now ?? Math.floor(Date.now() / 1e3);
|
|
17
|
-
const signature =
|
|
17
|
+
const signature = pick_header(headers, "x-webhook-signature");
|
|
18
18
|
if (!signature) return { ok: false, reason: "missing-signature" };
|
|
19
|
-
const
|
|
20
|
-
if (!
|
|
21
|
-
const timestamp = Number.parseInt(
|
|
22
|
-
if (Number.isNaN(timestamp) || String(timestamp) !==
|
|
19
|
+
const timestamp_str = pick_header(headers, "x-webhook-timestamp");
|
|
20
|
+
if (!timestamp_str) return { ok: false, reason: "missing-timestamp" };
|
|
21
|
+
const timestamp = Number.parseInt(timestamp_str, 10);
|
|
22
|
+
if (Number.isNaN(timestamp) || String(timestamp) !== timestamp_str) {
|
|
23
23
|
return { ok: false, reason: "missing-timestamp" };
|
|
24
24
|
}
|
|
25
25
|
const delta = now - timestamp;
|
|
@@ -28,21 +28,21 @@ function verifyWebhook(headers, body, secret, options) {
|
|
|
28
28
|
if (!signature.startsWith("sha256=")) {
|
|
29
29
|
return { ok: false, reason: "bad-signature" };
|
|
30
30
|
}
|
|
31
|
-
const
|
|
32
|
-
if (!/^[0-9a-fA-F]{64}$/.test(
|
|
31
|
+
const provided_hex = signature.slice("sha256=".length);
|
|
32
|
+
if (!/^[0-9a-fA-F]{64}$/.test(provided_hex)) {
|
|
33
33
|
return { ok: false, reason: "bad-signature" };
|
|
34
34
|
}
|
|
35
|
-
const
|
|
36
|
-
const a = Buffer.from(
|
|
37
|
-
const b = Buffer.from(
|
|
35
|
+
const expected_hex = createHmac("sha256", secret).update(`${timestamp_str}.${body}`).digest("hex");
|
|
36
|
+
const a = Buffer.from(provided_hex, "hex");
|
|
37
|
+
const b = Buffer.from(expected_hex, "hex");
|
|
38
38
|
if (!timingSafeEqual(a, b)) {
|
|
39
39
|
return { ok: false, reason: "bad-signature" };
|
|
40
40
|
}
|
|
41
41
|
return { ok: true };
|
|
42
42
|
}
|
|
43
|
-
function
|
|
43
|
+
function pick_header(headers, lower_name) {
|
|
44
44
|
for (const [name, value] of Object.entries(headers)) {
|
|
45
|
-
if (name.toLowerCase() !==
|
|
45
|
+
if (name.toLowerCase() !== lower_name) continue;
|
|
46
46
|
if (Array.isArray(value) || value === void 0 || value === "") {
|
|
47
47
|
return void 0;
|
|
48
48
|
}
|
|
@@ -75,4 +75,4 @@ export {
|
|
|
75
75
|
verifyWebhook,
|
|
76
76
|
checkWebhook
|
|
77
77
|
};
|
|
78
|
-
//# sourceMappingURL=chunk-
|
|
78
|
+
//# sourceMappingURL=chunk-4CGAUB5H.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/receiver/extract.ts","../src/receiver/verify.ts","../src/receiver/check.ts"],"sourcesContent":["/**\n * Pull the `Idempotency-Key` header from a Node-style headers bag,\n * case-insensitive. Returns `undefined` when any of the following\n * carries no usable key:\n *\n * - the header is missing\n * - its value is an array (ambiguous — can't pick one without a\n * policy the receiver hasn't declared)\n * - its value is the empty string (carries no idempotency\n * information; structurally equivalent to \"no header at all\")\n *\n * Pair with `IdempotencyStore.claim` from\n * `@rotorsoft/act-ops/idempotency`: extract the key from the inbound\n * request, claim it on the store, return a `deduped` marker when the\n * claim fails. The framework-agnostic middleware that wires these\n * together lands in #744.\n *\n * Validation beyond \"is there a usable key?\" (length bounds, format\n * checks, normalization) is intentionally out of scope. Receivers\n * picking a policy can layer it on top — or, when #744 ships, opt\n * into the middleware's opinionated defaults.\n */\nexport function extractIdempotencyKey(\n headers: Record<string, string | string[] | undefined>\n): string | undefined {\n for (const [name, value] of Object.entries(headers)) {\n if (name.toLowerCase() !== \"idempotency-key\") continue;\n if (Array.isArray(value)) return undefined;\n if (value === \"\") return undefined;\n return value;\n }\n return undefined;\n}\n","import { createHmac, timingSafeEqual } from \"node:crypto\";\n\n/**\n * Outcome of {@link verifyWebhook}. Either the request signature\n * checks out, or one of five distinct failure reasons applies. Each\n * reason maps to an operator-meaningful telemetry bucket — separated\n * deliberately so dashboards can distinguish \"client lost its secret\"\n * from \"client clock is wrong\" from \"this is a replay attack.\"\n */\nexport type VerifyResult =\n | { ok: true }\n | {\n ok: false;\n reason:\n | \"missing-signature\"\n | \"missing-timestamp\"\n | \"stale\"\n | \"future\"\n | \"bad-signature\";\n };\n\n/** Options for {@link verifyWebhook}. */\nexport type VerifyOptions = {\n /**\n * Maximum acceptable timestamp drift in either direction, in\n * seconds. Default: 300 (±5 minutes) — matches Stripe / GitHub /\n * Slack conventions. Tightening narrows the replay window;\n * loosening accommodates clients with worse clock sync.\n */\n maxAgeSeconds?: number;\n /**\n * Current Unix-seconds time. Exposed for tests; production\n * callers should leave it undefined so wall-clock is used.\n */\n now?: number;\n};\n\n/**\n * Verify an inbound webhook's signature and timestamp against the\n * shared secret. Pair with the sender side: configure\n * `webhook({ secret })` from `@rotorsoft/act-http/webhook`.\n *\n * Returns `{ ok: true }` on success or `{ ok: false; reason }` on\n * failure. The reasons are:\n *\n * - `missing-signature` — no `X-Webhook-Signature` header, value\n * was an array, or value was empty.\n * - `missing-timestamp` — no `X-Webhook-Timestamp` header, value\n * was empty, or value isn't a parseable integer.\n * - `stale` — timestamp older than `maxAgeSeconds` from `now`.\n * - `future` — timestamp more than `maxAgeSeconds` ahead of `now`.\n * - `bad-signature` — signature header didn't start with `sha256=`,\n * wasn't 64 hex chars, or the recomputed HMAC didn't match\n * (constant-time compare).\n *\n * The signed payload is `${timestamp}.${body}`, so `body` must be\n * the **raw request body bytes**. Any pre-parse normalization\n * (whitespace trimming, JSON re-stringification) would change the\n * hash and reject every otherwise-valid request. Framework adapters\n * in #744 will provide the raw body alongside the parsed one.\n *\n * Uses Node's `crypto.timingSafeEqual` for the final comparison to\n * avoid signature-equality timing attacks.\n */\nexport function verifyWebhook(\n headers: Record<string, string | string[] | undefined>,\n body: string,\n secret: string,\n options?: VerifyOptions\n): VerifyResult {\n const maxAgeSeconds = options?.maxAgeSeconds ?? 300;\n const now = options?.now ?? Math.floor(Date.now() / 1000);\n\n const signature = pick_header(headers, \"x-webhook-signature\");\n if (!signature) return { ok: false, reason: \"missing-signature\" };\n\n const timestamp_str = pick_header(headers, \"x-webhook-timestamp\");\n if (!timestamp_str) return { ok: false, reason: \"missing-timestamp\" };\n const timestamp = Number.parseInt(timestamp_str, 10);\n if (Number.isNaN(timestamp) || String(timestamp) !== timestamp_str) {\n return { ok: false, reason: \"missing-timestamp\" };\n }\n\n const delta = now - timestamp;\n if (delta > maxAgeSeconds) return { ok: false, reason: \"stale\" };\n if (delta < -maxAgeSeconds) return { ok: false, reason: \"future\" };\n\n if (!signature.startsWith(\"sha256=\")) {\n return { ok: false, reason: \"bad-signature\" };\n }\n const provided_hex = signature.slice(\"sha256=\".length);\n if (!/^[0-9a-fA-F]{64}$/.test(provided_hex)) {\n return { ok: false, reason: \"bad-signature\" };\n }\n\n const expected_hex = createHmac(\"sha256\", secret)\n .update(`${timestamp_str}.${body}`)\n .digest(\"hex\");\n\n const a = Buffer.from(provided_hex, \"hex\");\n const b = Buffer.from(expected_hex, \"hex\");\n if (!timingSafeEqual(a, b)) {\n return { ok: false, reason: \"bad-signature\" };\n }\n\n return { ok: true };\n}\n\nfunction pick_header(\n headers: Record<string, string | string[] | undefined>,\n lower_name: string\n): string | undefined {\n for (const [name, value] of Object.entries(headers)) {\n if (name.toLowerCase() !== lower_name) continue;\n if (Array.isArray(value) || value === undefined || value === \"\") {\n return undefined;\n }\n return value;\n }\n return undefined;\n}\n","import type { IdempotencyStore } from \"@rotorsoft/act-ops/idempotency\";\nimport { extractIdempotencyKey } from \"./extract.js\";\nimport { type VerifyOptions, verifyWebhook } from \"./verify.js\";\n\n/**\n * Failure reasons returned by {@link checkWebhook}. The shape splits\n * `missing-key` (a client error, mapped to HTTP 400) from the five\n * verification failures (authentication errors, HTTP 401) so each\n * maps to its own telemetry bucket.\n */\nexport type CheckFailureReason =\n | \"missing-key\"\n | \"missing-signature\"\n | \"missing-timestamp\"\n | \"stale\"\n | \"future\"\n | \"bad-signature\";\n\n/**\n * Outcome of {@link checkWebhook}. Either the request passed every\n * configured check and carries a usable idempotency key, or it\n * failed one of them and the framework adapter should reply with the\n * corresponding HTTP status.\n */\nexport type CheckResult =\n | { ok: false; status: 400 | 401; reason: CheckFailureReason }\n | { ok: true; key: string; deduped: boolean };\n\n/** Options for {@link checkWebhook}. */\nexport type CheckWebhookOptions = {\n /** Idempotency store the framework-agnostic core claims the key on. */\n store: IdempotencyStore;\n /**\n * Optional HMAC-SHA256 secret. When set, the request's\n * `X-Webhook-Signature` and `X-Webhook-Timestamp` headers are\n * verified before the dedup claim. When omitted, signature\n * verification is skipped (unsigned receivers).\n */\n secret?: string;\n /**\n * Verification options forwarded to {@link verifyWebhook}. Only\n * meaningful when `secret` is set. Defaults to a ±300-second\n * timestamp window.\n */\n verify?: VerifyOptions;\n};\n\n/**\n * Framework-agnostic receiver check: verify the signature (when a\n * secret is configured), extract the `Idempotency-Key`, and claim\n * it on the store. Returns the request's fate as a discriminated\n * union the per-framework adapter translates into the framework's\n * idiomatic 4xx response or context injection.\n *\n * **Order of checks** (matters):\n *\n * 1. Verify signature + timestamp window (when `secret` is set).\n * Rejecting bad signatures *before* extracting and claiming the\n * key keeps attacker-supplied keys out of the dedup store —\n * otherwise a flood of spoofed POSTs would pollute the LRU.\n * 2. Extract the `Idempotency-Key`. Missing → reject with 400.\n * 3. Claim the key on the store. If already seen, return\n * `{ ok: true; deduped: true }` so the framework adapter can\n * short-circuit the handler without re-running side effects.\n *\n * The dedup store may be sync (`InMemoryIdempotencyStore`) or async\n * (durable adapters like a future `PostgresIdempotencyStore`); the\n * core awaits unconditionally so both shapes compose cleanly.\n */\nexport async function checkWebhook(\n headers: Record<string, string | string[] | undefined>,\n body: string,\n options: CheckWebhookOptions\n): Promise<CheckResult> {\n if (options.secret !== undefined) {\n const verification = verifyWebhook(\n headers,\n body,\n options.secret,\n options.verify\n );\n if (!verification.ok) {\n return { ok: false, status: 401, reason: verification.reason };\n }\n }\n\n const key = extractIdempotencyKey(headers);\n if (!key) return { ok: false, status: 400, reason: \"missing-key\" };\n\n const claimed = await options.store.claim(key);\n return { ok: true, key, deduped: !claimed };\n}\n"],"mappings":";AAsBO,SAAS,sBACd,SACoB;AACpB,aAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,OAAO,GAAG;AACnD,QAAI,KAAK,YAAY,MAAM,kBAAmB;AAC9C,QAAI,MAAM,QAAQ,KAAK,EAAG,QAAO;AACjC,QAAI,UAAU,GAAI,QAAO;AACzB,WAAO;AAAA,EACT;AACA,SAAO;AACT;;;AChCA,SAAS,YAAY,uBAAuB;AAgErC,SAAS,cACd,SACA,MACA,QACA,SACc;AACd,QAAM,gBAAgB,SAAS,iBAAiB;AAChD,QAAM,MAAM,SAAS,OAAO,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AAExD,QAAM,YAAY,YAAY,SAAS,qBAAqB;AAC5D,MAAI,CAAC,UAAW,QAAO,EAAE,IAAI,OAAO,QAAQ,oBAAoB;AAEhE,QAAM,gBAAgB,YAAY,SAAS,qBAAqB;AAChE,MAAI,CAAC,cAAe,QAAO,EAAE,IAAI,OAAO,QAAQ,oBAAoB;AACpE,QAAM,YAAY,OAAO,SAAS,eAAe,EAAE;AACnD,MAAI,OAAO,MAAM,SAAS,KAAK,OAAO,SAAS,MAAM,eAAe;AAClE,WAAO,EAAE,IAAI,OAAO,QAAQ,oBAAoB;AAAA,EAClD;AAEA,QAAM,QAAQ,MAAM;AACpB,MAAI,QAAQ,cAAe,QAAO,EAAE,IAAI,OAAO,QAAQ,QAAQ;AAC/D,MAAI,QAAQ,CAAC,cAAe,QAAO,EAAE,IAAI,OAAO,QAAQ,SAAS;AAEjE,MAAI,CAAC,UAAU,WAAW,SAAS,GAAG;AACpC,WAAO,EAAE,IAAI,OAAO,QAAQ,gBAAgB;AAAA,EAC9C;AACA,QAAM,eAAe,UAAU,MAAM,UAAU,MAAM;AACrD,MAAI,CAAC,oBAAoB,KAAK,YAAY,GAAG;AAC3C,WAAO,EAAE,IAAI,OAAO,QAAQ,gBAAgB;AAAA,EAC9C;AAEA,QAAM,eAAe,WAAW,UAAU,MAAM,EAC7C,OAAO,GAAG,aAAa,IAAI,IAAI,EAAE,EACjC,OAAO,KAAK;AAEf,QAAM,IAAI,OAAO,KAAK,cAAc,KAAK;AACzC,QAAM,IAAI,OAAO,KAAK,cAAc,KAAK;AACzC,MAAI,CAAC,gBAAgB,GAAG,CAAC,GAAG;AAC1B,WAAO,EAAE,IAAI,OAAO,QAAQ,gBAAgB;AAAA,EAC9C;AAEA,SAAO,EAAE,IAAI,KAAK;AACpB;AAEA,SAAS,YACP,SACA,YACoB;AACpB,aAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,OAAO,GAAG;AACnD,QAAI,KAAK,YAAY,MAAM,WAAY;AACvC,QAAI,MAAM,QAAQ,KAAK,KAAK,UAAU,UAAa,UAAU,IAAI;AAC/D,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;;;ACnDA,eAAsB,aACpB,SACA,MACA,SACsB;AACtB,MAAI,QAAQ,WAAW,QAAW;AAChC,UAAM,eAAe;AAAA,MACnB;AAAA,MACA;AAAA,MACA,QAAQ;AAAA,MACR,QAAQ;AAAA,IACV;AACA,QAAI,CAAC,aAAa,IAAI;AACpB,aAAO,EAAE,IAAI,OAAO,QAAQ,KAAK,QAAQ,aAAa,OAAO;AAAA,IAC/D;AAAA,EACF;AAEA,QAAM,MAAM,sBAAsB,OAAO;AACzC,MAAI,CAAC,IAAK,QAAO,EAAE,IAAI,OAAO,QAAQ,KAAK,QAAQ,cAAc;AAEjE,QAAM,UAAU,MAAM,QAAQ,MAAM,MAAM,GAAG;AAC7C,SAAO,EAAE,IAAI,MAAM,KAAK,SAAS,CAAC,QAAQ;AAC5C;","names":[]}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
checkWebhook
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-4CGAUB5H.js";
|
|
4
4
|
|
|
5
5
|
// src/receiver/hono/index.ts
|
|
6
6
|
function webhookMiddleware(options) {
|
|
7
7
|
return async function check(c, next) {
|
|
8
|
-
const headers =
|
|
8
|
+
const headers = headers_bag(c.req.raw.headers);
|
|
9
9
|
const rawBody = await c.req.text();
|
|
10
10
|
const result = await checkWebhook(headers, rawBody, options);
|
|
11
11
|
if (!result.ok) {
|
|
@@ -15,7 +15,7 @@ function webhookMiddleware(options) {
|
|
|
15
15
|
await next();
|
|
16
16
|
};
|
|
17
17
|
}
|
|
18
|
-
function
|
|
18
|
+
function headers_bag(headers) {
|
|
19
19
|
const out = {};
|
|
20
20
|
headers.forEach((value, key) => {
|
|
21
21
|
out[key] = value;
|
|
@@ -26,4 +26,4 @@ function headersBag(headers) {
|
|
|
26
26
|
export {
|
|
27
27
|
webhookMiddleware
|
|
28
28
|
};
|
|
29
|
-
//# sourceMappingURL=chunk-
|
|
29
|
+
//# sourceMappingURL=chunk-K4HAOBRF.js.map
|