@ripplo/testing 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,227 @@
1
+ // src/types.ts
2
+ import { z } from "zod";
3
+ var dslConfigSchema = z.object({
4
+ appUrl: z.string(),
5
+ preconditionsUrl: z.string(),
6
+ projectId: z.string(),
7
+ webhookSecret: z.string()
8
+ });
9
+ function readTestValue(value) {
10
+ return value.value;
11
+ }
12
+ function createTestValue(value) {
13
+ return { value };
14
+ }
15
+ function readPreconditionName(p) {
16
+ return p.name;
17
+ }
18
+
19
+ // src/engine.ts
20
+ function createEngine(ripplo) {
21
+ return {
22
+ executeBatch: (names, options) => executeBatch(ripplo, names, options),
23
+ teardown: (names, data) => teardown(ripplo, names, data)
24
+ };
25
+ }
26
+ async function executeBatch(ripplo, names, options) {
27
+ const runId = crypto.randomUUID().slice(0, 12);
28
+ const cookies = [];
29
+ const defaultDomain = deriveDefaultDomain(options?.appUrl);
30
+ const state = {
31
+ cookies,
32
+ ctx: createSetupContext({ cookies, defaultDomain, runId }),
33
+ data: {},
34
+ defsByName: buildDefMap(ripplo.getPreconditions()),
35
+ executed: [],
36
+ runId
37
+ };
38
+ return runBatchSequence(state, names);
39
+ }
40
+ async function runBatchSequence(state, names) {
41
+ let index = 0;
42
+ while (index < names.length) {
43
+ const name = names[index];
44
+ if (name == null) {
45
+ break;
46
+ }
47
+ const error = validatePrecondition(state.defsByName, name);
48
+ if (error != null) {
49
+ return fail(state, error);
50
+ }
51
+ const stepError = await executeOnePrecondition(state, name);
52
+ if (stepError != null) {
53
+ return fail(state, stepError);
54
+ }
55
+ index += 1;
56
+ }
57
+ return {
58
+ cookies: state.cookies,
59
+ data: state.data,
60
+ error: void 0,
61
+ executed: state.executed,
62
+ runId: state.runId,
63
+ success: true
64
+ };
65
+ }
66
+ function validatePrecondition(defsByName, name) {
67
+ const def = defsByName.get(name);
68
+ if (def == null) {
69
+ return `Unknown precondition: "${name}"`;
70
+ }
71
+ if (!def.implemented) {
72
+ return `Precondition "${name}" is not implemented`;
73
+ }
74
+ return void 0;
75
+ }
76
+ async function executeOnePrecondition(state, name) {
77
+ const def = state.defsByName.get(name);
78
+ if (def == null) {
79
+ return `Unknown precondition: "${name}"`;
80
+ }
81
+ try {
82
+ const result = await def.setup(state.ctx, state.data);
83
+ const resolved = {};
84
+ Object.entries(result).forEach(([key, value]) => {
85
+ resolved[key] = readTestValue(value);
86
+ });
87
+ state.data[name] = resolved;
88
+ state.executed.push(name);
89
+ return void 0;
90
+ } catch (error) {
91
+ return error instanceof Error ? error.message : String(error);
92
+ }
93
+ }
94
+ function fail(state, error) {
95
+ return {
96
+ cookies: state.cookies,
97
+ data: state.data,
98
+ error,
99
+ executed: state.executed,
100
+ runId: state.runId,
101
+ success: false
102
+ };
103
+ }
104
+ async function teardown(ripplo, names, data) {
105
+ const defsByName = buildDefMap(ripplo.getPreconditions());
106
+ const reversed = [...names].toReversed();
107
+ let index = 0;
108
+ while (index < reversed.length) {
109
+ const name = reversed[index];
110
+ if (name != null) {
111
+ await teardownOne(defsByName, name, data);
112
+ }
113
+ index += 1;
114
+ }
115
+ }
116
+ async function teardownOne(defsByName, name, data) {
117
+ const def = defsByName.get(name);
118
+ if (def?.teardown == null) {
119
+ return;
120
+ }
121
+ try {
122
+ await def.teardown({ data: data[name] ?? {} });
123
+ } catch {
124
+ }
125
+ }
126
+ function buildDefMap(defs) {
127
+ return new Map(defs.map((d) => [d.name, d]));
128
+ }
129
+ function createSetupContext({
130
+ cookies,
131
+ defaultDomain,
132
+ runId
133
+ }) {
134
+ return {
135
+ runId,
136
+ fixed: (value) => createTestValue(value),
137
+ setCookie: (name, value, options) => {
138
+ const resolvedOptions = options != null && options.domain == null && defaultDomain != null ? { ...options, domain: defaultDomain } : options ?? void 0;
139
+ cookies.push({ name, options: resolvedOptions, value });
140
+ },
141
+ uniqueEmail: () => createTestValue(`ripplo-test-${runId}@test.ripplo.ai`),
142
+ uniqueId: (prefix) => createTestValue(`ripplo-test-${prefix}-${runId}`)
143
+ };
144
+ }
145
+ function deriveDefaultDomain(baseUrl) {
146
+ if (baseUrl == null) {
147
+ return void 0;
148
+ }
149
+ try {
150
+ return new URL(baseUrl).hostname;
151
+ } catch {
152
+ return void 0;
153
+ }
154
+ }
155
+
156
+ // src/adapters/shared.ts
157
+ import { Webhook, WebhookVerificationError } from "standardwebhooks";
158
+ import { z as z2 } from "zod";
159
+ var batchRequestSchema = z2.object({
160
+ preconditions: z2.array(z2.string().min(1))
161
+ });
162
+ var teardownRequestSchema = z2.object({
163
+ data: z2.record(z2.string(), z2.record(z2.string(), z2.string())),
164
+ preconditions: z2.array(z2.string().min(1))
165
+ });
166
+ function verifyWebhookSignature(payload, headers, secret) {
167
+ try {
168
+ const wh = new Webhook(secret);
169
+ wh.verify(payload, {
170
+ "webhook-id": headers["webhook-id"] ?? "",
171
+ "webhook-signature": headers["webhook-signature"] ?? "",
172
+ "webhook-timestamp": headers["webhook-timestamp"] ?? ""
173
+ });
174
+ return true;
175
+ } catch (error) {
176
+ if (error instanceof WebhookVerificationError) {
177
+ return false;
178
+ }
179
+ throw error;
180
+ }
181
+ }
182
+ function serializeCookie(cookie) {
183
+ return {
184
+ domain: cookie.options?.domain,
185
+ expires: cookie.options?.expires == null ? void 0 : new Date(cookie.options.expires * 1e3),
186
+ httpOnly: cookie.options?.httpOnly,
187
+ name: cookie.name,
188
+ path: cookie.options?.path,
189
+ sameSite: cookie.options?.sameSite,
190
+ secure: cookie.options?.secure,
191
+ value: cookie.value
192
+ };
193
+ }
194
+ function buildSetCookieHeader(cookie) {
195
+ const parts = [`${cookie.name}=${cookie.value}`];
196
+ if (cookie.domain != null) {
197
+ parts.push(`Domain=${cookie.domain}`);
198
+ }
199
+ if (cookie.path != null) {
200
+ parts.push(`Path=${cookie.path}`);
201
+ }
202
+ if (cookie.expires != null) {
203
+ parts.push(`Expires=${cookie.expires.toUTCString()}`);
204
+ }
205
+ if (cookie.httpOnly === true) {
206
+ parts.push("HttpOnly");
207
+ }
208
+ if (cookie.secure === true) {
209
+ parts.push("Secure");
210
+ }
211
+ if (cookie.sameSite != null) {
212
+ const capitalized = cookie.sameSite.charAt(0).toUpperCase() + cookie.sameSite.slice(1);
213
+ parts.push(`SameSite=${capitalized}`);
214
+ }
215
+ return parts.join("; ");
216
+ }
217
+
218
+ export {
219
+ dslConfigSchema,
220
+ readPreconditionName,
221
+ createEngine,
222
+ batchRequestSchema,
223
+ teardownRequestSchema,
224
+ verifyWebhookSignature,
225
+ serializeCookie,
226
+ buildSetCookieHeader
227
+ };
@@ -0,0 +1,16 @@
1
+ // src/steps/step.ts
2
+ function readStep(step) {
3
+ return step.data;
4
+ }
5
+ function createStep(node) {
6
+ return {
7
+ as(label) {
8
+ return { data: { label, node } };
9
+ }
10
+ };
11
+ }
12
+
13
+ export {
14
+ readStep,
15
+ createStep
16
+ };
@@ -0,0 +1,149 @@
1
+ import {
2
+ createStep,
3
+ readStep
4
+ } from "./chunk-MGATMMCZ.js";
5
+
6
+ // src/compiler.ts
7
+ function compile(ripplo) {
8
+ const preconditionDefs = ripplo.getPreconditions();
9
+ const testDefs = ripplo.getTests();
10
+ validateUniqueIds(testDefs);
11
+ const tests = testDefs.map((def) => compileTest(def));
12
+ const graph = compileGraph(preconditionDefs, testDefs);
13
+ return { config: ripplo.getConfig(), graph, tests };
14
+ }
15
+ function validateUniqueIds(defs) {
16
+ const seen = /* @__PURE__ */ new Map();
17
+ defs.forEach((def) => {
18
+ const existing = seen.get(def.id);
19
+ if (existing != null) {
20
+ throw new Error(`Duplicate test id "${def.id}" used by "${existing}" and "${def.name}"`);
21
+ }
22
+ seen.set(def.id, def.name);
23
+ });
24
+ }
25
+ function compileTest(def) {
26
+ const slug = def.id;
27
+ const { accessedKeys, vars } = buildPlaceholderVars(def.requiresKeys);
28
+ const startsAtUrl = def.startsAtFn == null ? void 0 : def.startsAtFn(vars);
29
+ const userSteps = def.stepsFn == null ? [] : def.stepsFn(vars);
30
+ const allSteps = startsAtUrl == null ? userSteps : [createGotoStep(startsAtUrl), ...userSteps];
31
+ const spec = compileSteps(allSteps, accessedKeys, def.requiresKeys);
32
+ const warnings = [];
33
+ const hasRequires = Object.keys(def.requiresKeys).length > 0;
34
+ if (hasRequires && accessedKeys.size === 0 && def.implemented) {
35
+ warnings.push(
36
+ "Test requires preconditions but never references their data \u2014 destructure and use precondition data in steps()"
37
+ );
38
+ }
39
+ return {
40
+ additionalChecks: [],
41
+ description: def.description,
42
+ expectedOutcome: def.expectedOutcome,
43
+ name: def.name,
44
+ slug,
45
+ spec,
46
+ warnings
47
+ };
48
+ }
49
+ function createGotoStep(url) {
50
+ return createStep({ type: "goto", url: { type: "static", value: url } }).as(
51
+ `navigate to ${url}`
52
+ );
53
+ }
54
+ function buildPlaceholderVars(requiresKeys) {
55
+ const accessedKeys = /* @__PURE__ */ new Set();
56
+ const vars = {};
57
+ Object.keys(requiresKeys).forEach((ns) => {
58
+ vars[ns] = new Proxy(
59
+ {},
60
+ {
61
+ get(_target, prop) {
62
+ if (typeof prop === "string") {
63
+ const qualifiedKey = `${ns}.${prop}`;
64
+ accessedKeys.add(qualifiedKey);
65
+ return `{{${qualifiedKey}}}`;
66
+ }
67
+ }
68
+ }
69
+ );
70
+ });
71
+ return { accessedKeys, vars };
72
+ }
73
+ function compileSteps(steps, accessedKeys, requiresKeys) {
74
+ const nodes = {};
75
+ steps.forEach((step, index) => {
76
+ const id = `step-${String(index)}`;
77
+ const next = index < steps.length - 1 ? `step-${String(index + 1)}` : void 0;
78
+ nodes[id] = compileNode(step, id, next);
79
+ });
80
+ const variables = {};
81
+ accessedKeys.forEach((key) => {
82
+ variables[key] = { default: `test-${key}`, type: "string" };
83
+ });
84
+ const variableNamespaces = { ...requiresKeys };
85
+ return { entryNode: "step-0", nodes, variableNamespaces, variables, version: 2 };
86
+ }
87
+ function compileNode(step, id, next) {
88
+ const { label, node: raw } = readStep(step);
89
+ return { ...raw, id, label, next };
90
+ }
91
+ function compileGraph(preconditionDefs, testDefs) {
92
+ const preconditions = {};
93
+ preconditionDefs.forEach((def) => {
94
+ preconditions[def.name] = {
95
+ depends: [...def.dependsOn],
96
+ description: def.description,
97
+ returns: [...def.returns]
98
+ };
99
+ });
100
+ const states = {};
101
+ const edges = [];
102
+ testDefs.forEach((def) => {
103
+ if (!def.implemented || def.startsAtFn == null) {
104
+ return;
105
+ }
106
+ const resolved = resolveDependencyChain(def.requires, preconditionDefs);
107
+ const { vars } = buildPlaceholderVars(def.requiresKeys);
108
+ const route = def.startsAtFn(vars);
109
+ const stateId = deriveStateId(resolved, route);
110
+ states[stateId] = { preconditions: [...resolved], route };
111
+ edges.push({
112
+ from: stateId,
113
+ requiresKeys: def.requiresKeys,
114
+ to: stateId,
115
+ workflow: def.id
116
+ });
117
+ });
118
+ return { edges, preconditions, states, version: 3 };
119
+ }
120
+ function resolveDependencyChain(requires, preconditionDefs) {
121
+ const defsByName = new Map(preconditionDefs.map((d) => [d.name, d]));
122
+ const resolved = [];
123
+ const visited = /* @__PURE__ */ new Set();
124
+ function visit(name) {
125
+ if (visited.has(name)) {
126
+ return;
127
+ }
128
+ visited.add(name);
129
+ defsByName.get(name)?.dependsOn.forEach((dep) => {
130
+ visit(dep);
131
+ });
132
+ resolved.push(name);
133
+ }
134
+ requires.forEach((name) => {
135
+ visit(name);
136
+ });
137
+ return resolved;
138
+ }
139
+ function deriveStateId(preconditions, route) {
140
+ const sorted = preconditions.toSorted((a, b) => a.localeCompare(b));
141
+ return slugify(`${sorted.join("-")}-${route}`);
142
+ }
143
+ function slugify(input) {
144
+ return input.toLowerCase().replaceAll(/[^a-z0-9]+/g, "-").replaceAll(/^-|-$/g, "");
145
+ }
146
+
147
+ export {
148
+ compile
149
+ };
@@ -0,0 +1,22 @@
1
+ import { StateGraph, WorkflowSpec } from '@ripplo/spec';
2
+ import { D as DslConfig, R as RipploBuilder } from './builder-DTWMrbuv.js';
3
+ import 'zod';
4
+ import './step-DLfkKI3V.js';
5
+
6
+ interface CompileResult {
7
+ readonly config: DslConfig;
8
+ readonly graph: StateGraph;
9
+ readonly tests: ReadonlyArray<CompiledTest>;
10
+ }
11
+ interface CompiledTest {
12
+ readonly additionalChecks: ReadonlyArray<string>;
13
+ readonly description: string;
14
+ readonly expectedOutcome: string;
15
+ readonly name: string;
16
+ readonly slug: string;
17
+ readonly spec: WorkflowSpec;
18
+ readonly warnings: ReadonlyArray<string>;
19
+ }
20
+ declare function compile(ripplo: RipploBuilder): CompileResult;
21
+
22
+ export { type CompileResult, type CompiledTest, compile };
@@ -0,0 +1,7 @@
1
+ import {
2
+ compile
3
+ } from "./chunk-X2FROZPN.js";
4
+ import "./chunk-MGATMMCZ.js";
5
+ export {
6
+ compile
7
+ };
@@ -0,0 +1,24 @@
1
+ import { U as UnlabeledStep } from './step-DLfkKI3V.js';
2
+ import { AnyLocator } from './locators.js';
3
+ import '@ripplo/spec';
4
+
5
+ declare const VARIABLE_INTERNAL: unique symbol;
6
+ interface Variable<_TName extends string> {
7
+ readonly [VARIABLE_INTERNAL]: _TName;
8
+ }
9
+ declare function variable<TName extends string>(name: TName): Variable<TName>;
10
+ declare function readVariable(v: Variable<string>): string;
11
+ declare function extract(locator: AnyLocator, target: Variable<string>): UnlabeledStep<{
12
+ locator: {
13
+ by: "testId";
14
+ value: string;
15
+ } | {
16
+ by: "role";
17
+ role: string;
18
+ name?: string | undefined;
19
+ };
20
+ type: "extractText";
21
+ variable: string;
22
+ }>;
23
+
24
+ export { type Variable, extract, readVariable, variable };
@@ -0,0 +1,27 @@
1
+ import {
2
+ toSpecLocator
3
+ } from "./chunk-2VUWFRR5.js";
4
+ import {
5
+ createStep
6
+ } from "./chunk-MGATMMCZ.js";
7
+ import "./chunk-DCJBLS2U.js";
8
+
9
+ // src/steps/control.ts
10
+ function variable(name) {
11
+ return { name };
12
+ }
13
+ function readVariable(v) {
14
+ return v.name;
15
+ }
16
+ function extract(locator, target) {
17
+ return createStep({
18
+ locator: toSpecLocator(locator),
19
+ type: "extractText",
20
+ variable: readVariable(target)
21
+ });
22
+ }
23
+ export {
24
+ extract,
25
+ readVariable,
26
+ variable
27
+ };
@@ -0,0 +1,12 @@
1
+ import { Router } from 'express';
2
+ import { R as RipploBuilder } from './builder-DTWMrbuv.js';
3
+ import 'zod';
4
+ import './step-DLfkKI3V.js';
5
+ import '@ripplo/spec';
6
+
7
+ interface CreateExpressHandlerParams {
8
+ readonly ripplo: RipploBuilder;
9
+ }
10
+ declare function createExpressHandler({ ripplo }: CreateExpressHandlerParams): Router;
11
+
12
+ export { type CreateExpressHandlerParams, createExpressHandler };
@@ -0,0 +1,102 @@
1
+ import {
2
+ batchRequestSchema,
3
+ createEngine,
4
+ serializeCookie,
5
+ teardownRequestSchema,
6
+ verifyWebhookSignature
7
+ } from "./chunk-KWUKVAGI.js";
8
+
9
+ // src/adapters/express.ts
10
+ import { Router, json } from "express";
11
+ function createExpressHandler({ ripplo }) {
12
+ const engine = createEngine(ripplo);
13
+ const webhookSecret = ripplo.getConfig().webhookSecret;
14
+ const router = Router();
15
+ router.use(json());
16
+ router.use((req, res, next) => {
17
+ if (webhookSecret.length === 0) {
18
+ res.status(403).json({ error: "Webhook secret not configured" });
19
+ return;
20
+ }
21
+ const payload = JSON.stringify(req.body);
22
+ const headers = extractWebhookHeaders(req);
23
+ if (!verifyWebhookSignature(payload, headers, webhookSecret)) {
24
+ res.status(401).json({ error: "Invalid webhook signature" });
25
+ return;
26
+ }
27
+ next();
28
+ });
29
+ router.put("/execute-batch", (req, res) => {
30
+ const parsed = batchRequestSchema.safeParse(req.body);
31
+ if (!parsed.success) {
32
+ res.status(400).json({ error: "Invalid request body", success: false });
33
+ return;
34
+ }
35
+ const appUrl = `${req.protocol}://${req.get("host") ?? ""}`;
36
+ void engine.executeBatch(parsed.data.preconditions, { appUrl }).then((result) => {
37
+ result.cookies.forEach((cookie) => {
38
+ const s = serializeCookie(cookie);
39
+ res.cookie(s.name, s.value, buildExpressCookieOptions(s));
40
+ });
41
+ res.json({
42
+ data: result.data,
43
+ error: result.error,
44
+ executed: result.executed,
45
+ runId: result.runId,
46
+ success: result.success
47
+ });
48
+ });
49
+ });
50
+ router.put("/teardown", (req, res) => {
51
+ const parsed = teardownRequestSchema.safeParse(req.body);
52
+ if (!parsed.success) {
53
+ res.status(400).json({ error: "Invalid request body", success: false });
54
+ return;
55
+ }
56
+ void engine.teardown(parsed.data.preconditions, parsed.data.data).then(() => {
57
+ res.json({ success: true });
58
+ });
59
+ });
60
+ return router;
61
+ }
62
+ function extractWebhookHeaders(req) {
63
+ return {
64
+ "webhook-id": asString(req.headers["webhook-id"]),
65
+ "webhook-signature": asString(req.headers["webhook-signature"]),
66
+ "webhook-timestamp": asString(req.headers["webhook-timestamp"])
67
+ };
68
+ }
69
+ function asString(value) {
70
+ if (typeof value === "string") {
71
+ return value;
72
+ }
73
+ if (Array.isArray(value)) {
74
+ return value[0] ?? void 0;
75
+ }
76
+ return void 0;
77
+ }
78
+ function buildExpressCookieOptions(s) {
79
+ const opts = {};
80
+ if (s.domain != null) {
81
+ opts.domain = s.domain;
82
+ }
83
+ if (s.expires != null) {
84
+ opts.expires = s.expires;
85
+ }
86
+ if (s.httpOnly != null) {
87
+ opts.httpOnly = s.httpOnly;
88
+ }
89
+ if (s.path != null) {
90
+ opts.path = s.path;
91
+ }
92
+ if (s.sameSite != null) {
93
+ opts.sameSite = s.sameSite;
94
+ }
95
+ if (s.secure != null) {
96
+ opts.secure = s.secure;
97
+ }
98
+ return opts;
99
+ }
100
+ export {
101
+ createExpressHandler
102
+ };
@@ -0,0 +1,12 @@
1
+ import { FastifyInstance } from 'fastify';
2
+ import { R as RipploBuilder } from './builder-DTWMrbuv.js';
3
+ import 'zod';
4
+ import './step-DLfkKI3V.js';
5
+ import '@ripplo/spec';
6
+
7
+ interface RegisterFastifyHandlerParams {
8
+ readonly ripplo: RipploBuilder;
9
+ }
10
+ declare function registerFastifyHandler({ ripplo, }: RegisterFastifyHandlerParams): (fastify: FastifyInstance) => Promise<void>;
11
+
12
+ export { type RegisterFastifyHandlerParams, registerFastifyHandler };
@@ -0,0 +1,72 @@
1
+ import {
2
+ batchRequestSchema,
3
+ buildSetCookieHeader,
4
+ createEngine,
5
+ serializeCookie,
6
+ teardownRequestSchema,
7
+ verifyWebhookSignature
8
+ } from "./chunk-KWUKVAGI.js";
9
+
10
+ // src/adapters/fastify.ts
11
+ function registerFastifyHandler({
12
+ ripplo
13
+ }) {
14
+ const engine = createEngine(ripplo);
15
+ const webhookSecret = ripplo.getConfig().webhookSecret;
16
+ return async (fastify) => {
17
+ fastify.addHook("preHandler", async (req, reply) => {
18
+ if (webhookSecret.length === 0) {
19
+ return reply.code(403).send({ error: "Webhook secret not configured" });
20
+ }
21
+ const payload = JSON.stringify(req.body);
22
+ const headers = {
23
+ "webhook-id": extractHeader(req, "webhook-id"),
24
+ "webhook-signature": extractHeader(req, "webhook-signature"),
25
+ "webhook-timestamp": extractHeader(req, "webhook-timestamp")
26
+ };
27
+ if (!verifyWebhookSignature(payload, headers, webhookSecret)) {
28
+ return reply.code(401).send({ error: "Invalid webhook signature" });
29
+ }
30
+ });
31
+ fastify.put("/execute-batch", async (req, reply) => {
32
+ const parsed = batchRequestSchema.safeParse(req.body);
33
+ if (!parsed.success) {
34
+ return reply.code(400).send({ error: "Invalid request body", success: false });
35
+ }
36
+ const appUrl = `${req.protocol}://${req.hostname}`;
37
+ const result = await engine.executeBatch(parsed.data.preconditions, { appUrl });
38
+ result.cookies.forEach((cookie) => {
39
+ const s = serializeCookie(cookie);
40
+ reply.header("Set-Cookie", buildSetCookieHeader(s));
41
+ });
42
+ return reply.send({
43
+ data: result.data,
44
+ error: result.error,
45
+ executed: result.executed,
46
+ runId: result.runId,
47
+ success: result.success
48
+ });
49
+ });
50
+ fastify.put("/teardown", async (req, reply) => {
51
+ const parsed = teardownRequestSchema.safeParse(req.body);
52
+ if (!parsed.success) {
53
+ return reply.code(400).send({ error: "Invalid request body", success: false });
54
+ }
55
+ await engine.teardown(parsed.data.preconditions, parsed.data.data);
56
+ return reply.send({ success: true });
57
+ });
58
+ };
59
+ }
60
+ function extractHeader(req, name) {
61
+ const value = req.headers[name];
62
+ if (typeof value === "string") {
63
+ return value;
64
+ }
65
+ if (Array.isArray(value)) {
66
+ return value[0] ?? void 0;
67
+ }
68
+ return void 0;
69
+ }
70
+ export {
71
+ registerFastifyHandler
72
+ };