@ripplo/testing 0.6.1 → 0.7.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.
Files changed (44) hide show
  1. package/DSL.md +355 -0
  2. package/LICENSE.md +1 -0
  3. package/README.md +47 -273
  4. package/dist/engine-BT7hUouB.d.ts +1095 -0
  5. package/dist/express.d.ts +7 -9
  6. package/dist/express.js +422 -48
  7. package/dist/index.d.ts +122 -59
  8. package/dist/index.js +1630 -1126
  9. package/package.json +31 -113
  10. package/dist/actions.d.ts +0 -260
  11. package/dist/actions.js +0 -177
  12. package/dist/assert.d.ts +0 -188
  13. package/dist/assert.js +0 -111
  14. package/dist/builder-SsgqYqSC.d.ts +0 -156
  15. package/dist/chunk-2YLI7VD4.js +0 -65
  16. package/dist/chunk-4MGIQFAJ.js +0 -16
  17. package/dist/chunk-DCJBLS2U.js +0 -26
  18. package/dist/chunk-MGATMMCZ.js +0 -16
  19. package/dist/chunk-XO36IU66.js +0 -88
  20. package/dist/chunk-YFOTJIVF.js +0 -134
  21. package/dist/chunk-YQAEOH5W.js +0 -111
  22. package/dist/compiler.d.ts +0 -32
  23. package/dist/compiler.js +0 -8
  24. package/dist/control.d.ts +0 -45
  25. package/dist/control.js +0 -17
  26. package/dist/elysia.d.ts +0 -78
  27. package/dist/elysia.js +0 -114
  28. package/dist/engine-BOqzK_go.d.ts +0 -61
  29. package/dist/fastify.d.ts +0 -14
  30. package/dist/fastify.js +0 -79
  31. package/dist/hono.d.ts +0 -19
  32. package/dist/hono.js +0 -89
  33. package/dist/koa.d.ts +0 -14
  34. package/dist/koa.js +0 -135
  35. package/dist/locators.d.ts +0 -40
  36. package/dist/locators.js +0 -11
  37. package/dist/lockfile.d.ts +0 -722
  38. package/dist/lockfile.js +0 -707
  39. package/dist/nestjs.d.ts +0 -17
  40. package/dist/nestjs.js +0 -139
  41. package/dist/nextjs.d.ts +0 -14
  42. package/dist/nextjs.js +0 -137
  43. package/dist/step-De52hTLd.d.ts +0 -19
  44. package/dist/types-BzZrl65Z.d.ts +0 -115
package/dist/express.d.ts CHANGED
@@ -1,14 +1,12 @@
1
1
  import { Router } from 'express';
2
- import { R as RipploEngine } from './engine-BOqzK_go.js';
3
- import './builder-SsgqYqSC.js';
4
- import './types-BzZrl65Z.js';
5
- import './step-De52hTLd.js';
6
- import '@ripplo/spec';
2
+ import { E as Engine } from './engine-BT7hUouB.js';
3
+ import 'neverthrow';
4
+ import 'zod';
7
5
 
8
- interface CreateExpressHandlerParams {
6
+ interface CreateEngineHandlerParams {
9
7
  readonly enabled: boolean;
10
- readonly engine: RipploEngine;
8
+ readonly engine: Engine;
11
9
  }
12
- declare function createExpressHandler({ enabled, engine }: CreateExpressHandlerParams): Router;
10
+ declare function createEngineHandler({ enabled, engine }: CreateEngineHandlerParams): Router;
13
11
 
14
- export { type CreateExpressHandlerParams, createExpressHandler };
12
+ export { type CreateEngineHandlerParams, createEngineHandler };
package/dist/express.js CHANGED
@@ -1,87 +1,461 @@
1
- import {
2
- batchRequestSchema,
3
- observerRequestSchema,
4
- readAdapterWebhookSecret,
5
- teardownRequestSchema,
6
- toBatchRunResults,
7
- toTeardownResults,
8
- verifyWebhookSignature
9
- } from "./chunk-XO36IU66.js";
10
- import "./chunk-4MGIQFAJ.js";
1
+ // src/adapters/express.ts
2
+ import { json, Router } from "express";
3
+
4
+ // ../spec/src/leaves.ts
5
+ import { z } from "zod";
6
+ var budgetSchema = z.enum(["fast", "slow", "async"]);
7
+ var valueRefSchema = z.object({ ref: z.string().min(1) });
8
+ var primitiveSchema = z.union([z.string(), z.number(), z.boolean()]);
9
+ var templateSchema = z.object({
10
+ template: z.array(z.union([z.string(), valueRefSchema])).min(1)
11
+ });
12
+ var setValueSchema = z.union([valueRefSchema, primitiveSchema, templateSchema, z.null()]);
13
+ var changedSchema = z.object({ kind: z.literal("changed") });
14
+ var updateValueSchema = z.union([setValueSchema, changedSchema]);
15
+ var stringValueSchema = z.union([z.string(), valueRefSchema, templateSchema]);
16
+ var roleLocatorSchema = z.object({
17
+ by: z.literal("role"),
18
+ name: z.union([stringValueSchema, z.undefined()]).optional().transform((value) => value),
19
+ role: z.string().min(1)
20
+ });
21
+ var testIdLocatorSchema = z.object({ by: z.literal("testId"), value: stringValueSchema });
22
+ var insideLocatorSchema = z.object({
23
+ by: z.literal("inside"),
24
+ scope: z.lazy(() => locatorSchema),
25
+ target: z.lazy(() => locatorSchema)
26
+ });
27
+ var locatorSchema = z.discriminatedUnion("by", [
28
+ roleLocatorSchema,
29
+ testIdLocatorSchema,
30
+ insideLocatorSchema
31
+ ]);
32
+ var primitiveTypeSchema = z.enum(["string", "number", "boolean"]);
33
+ var consistencyClassSchema = z.enum(["strict", "eventual"]);
34
+ var stringConstraintsSchema = z.object({
35
+ kind: z.literal("string"),
36
+ maxLength: z.number().int().positive().optional(),
37
+ minLength: z.number().int().nonnegative().optional(),
38
+ pattern: z.string().optional()
39
+ });
40
+ var numberConstraintsSchema = z.object({
41
+ kind: z.literal("number"),
42
+ max: z.number().int().optional(),
43
+ min: z.number().int().optional()
44
+ });
45
+ var datetimeConstraintsSchema = z.object({
46
+ kind: z.literal("datetime"),
47
+ maxOffsetDays: z.number().int(),
48
+ minOffsetDays: z.number().int()
49
+ });
50
+ var constraintsSchema = z.discriminatedUnion("kind", [
51
+ stringConstraintsSchema,
52
+ numberConstraintsSchema,
53
+ datetimeConstraintsSchema
54
+ ]);
55
+ var generatorSchema = z.enum([
56
+ "company.name",
57
+ "date.iso",
58
+ "internet.email",
59
+ "internet.url",
60
+ "person.fullName",
61
+ "lorem.slug",
62
+ "lorem.word"
63
+ ]);
64
+ var valueSpaceSchema = z.object({
65
+ constraints: constraintsSchema.optional(),
66
+ generator: generatorSchema,
67
+ name: z.string().min(1),
68
+ type: primitiveTypeSchema,
69
+ values: z.array(primitiveSchema).min(1).optional()
70
+ });
71
+ var propSpecSchema = z.object({
72
+ consistency: consistencyClassSchema.default("strict"),
73
+ optional: z.boolean(),
74
+ stable: z.boolean(),
75
+ type: primitiveTypeSchema,
76
+ valueSpace: z.string().min(1).optional()
77
+ });
78
+ var sourceSchema = z.enum(["backend", "client"]);
79
+ var entitySchemaSchema = z.object({
80
+ description: z.string().optional(),
81
+ identity: z.array(z.string().min(1)).min(1),
82
+ identityKind: z.enum(["surrogate", "natural"]),
83
+ name: z.string().min(1),
84
+ props: z.record(z.string().min(1), propSpecSchema),
85
+ source: sourceSchema.default("backend")
86
+ });
87
+ var singletonSchemaSchema = z.object({
88
+ consistency: consistencyClassSchema.default("strict"),
89
+ default: primitiveSchema,
90
+ description: z.string().optional(),
91
+ name: z.string().min(1),
92
+ source: sourceSchema.default("backend"),
93
+ type: primitiveTypeSchema,
94
+ valueSpace: z.string().min(1).optional()
95
+ });
96
+ var browserSingletonSchema = z.enum(["url", "title", "viewport"]);
97
+
98
+ // ../spec/src/predicate.ts
99
+ import { z as z2 } from "zod";
100
+ var stateAssertionSchema = z2.discriminatedUnion("kind", [
101
+ z2.object({
102
+ as: z2.string().min(1),
103
+ kind: z2.literal("created"),
104
+ props: z2.record(z2.string().min(1), setValueSchema)
105
+ }),
106
+ z2.object({
107
+ as: z2.string().min(1),
108
+ kind: z2.literal("updated"),
109
+ props: z2.record(z2.string().min(1), updateValueSchema)
110
+ }),
111
+ z2.object({ kind: z2.literal("deleted") })
112
+ ]);
113
+ var singletonAssertionSchema = z2.object({ kind: z2.literal("is"), value: setValueSchema });
114
+ var whereValueSchema = z2.lazy(
115
+ () => z2.union([setValueSchema, withinSchema])
116
+ );
117
+ var selectionSchema = z2.object({
118
+ entity: z2.string().min(1),
119
+ where: z2.record(z2.string().min(1), whereValueSchema)
120
+ });
121
+ var withinSchema = z2.object({
122
+ field: z2.string().min(1),
123
+ kind: z2.literal("within"),
124
+ selection: selectionSchema
125
+ });
126
+ var wait = z2.union([budgetSchema, z2.undefined()]).optional().transform((value) => value);
127
+ var predicateSchema = z2.lazy(
128
+ () => z2.discriminatedUnion("kind", [
129
+ z2.object({ kind: z2.literal("visible"), locator: locatorSchema, wait }),
130
+ z2.object({ kind: z2.literal("disabled"), locator: locatorSchema, wait }),
131
+ z2.object({ kind: z2.literal("enabled"), locator: locatorSchema, wait }),
132
+ z2.object({ kind: z2.literal("focused"), locator: locatorSchema, wait }),
133
+ z2.object({ kind: z2.literal("value"), locator: locatorSchema, value: stringValueSchema, wait }),
134
+ z2.object({ kind: z2.literal("text"), locator: locatorSchema, value: stringValueSchema, wait }),
135
+ z2.object({
136
+ assertion: singletonAssertionSchema,
137
+ kind: z2.literal("singleton"),
138
+ singleton: z2.string().min(1),
139
+ wait
140
+ }),
141
+ z2.object({
142
+ kind: z2.literal("browser"),
143
+ name: browserSingletonSchema,
144
+ value: stringValueSchema,
145
+ wait
146
+ }),
147
+ z2.object({
148
+ assertion: stateAssertionSchema,
149
+ entity: z2.string().min(1),
150
+ key: z2.record(z2.string().min(1), whereValueSchema),
151
+ kind: z2.literal("state"),
152
+ wait
153
+ }),
154
+ z2.object({ kind: z2.literal("not"), predicate: predicateSchema }),
155
+ z2.object({ kind: z2.literal("and"), predicates: z2.array(predicateSchema) }),
156
+ z2.object({
157
+ entity: z2.string().min(1),
158
+ kind: z2.literal("count"),
159
+ value: z2.number().int().nonnegative()
160
+ }),
161
+ z2.object({
162
+ condition: predicateSchema,
163
+ consequence: predicateSchema,
164
+ kind: z2.literal("when"),
165
+ otherwise: z2.union([predicateSchema, z2.undefined()]).optional().transform((value) => value)
166
+ })
167
+ ])
168
+ );
169
+
170
+ // ../spec/src/codec.ts
171
+ import { z as z3 } from "zod";
172
+ var envelopeSchema = z3.object({
173
+ __codec: z3.string().min(1),
174
+ data: z3.unknown(),
175
+ version: z3.number().int().positive()
176
+ });
177
+ var CodecVersionError = class extends Error {
178
+ codec;
179
+ currentVersion;
180
+ gotVersion;
181
+ constructor(params) {
182
+ super(
183
+ `Unsupported ${params.codec} version ${String(params.gotVersion)} (current ${String(params.currentVersion)}). Upgrade Ripplo or rebuild with a compatible CLI.`
184
+ );
185
+ this.name = "CodecVersionError";
186
+ this.codec = params.codec;
187
+ this.currentVersion = params.currentVersion;
188
+ this.gotVersion = params.gotVersion;
189
+ }
190
+ };
191
+ var CodecMismatchError = class extends Error {
192
+ constructor(params) {
193
+ super(`Codec mismatch: expected "${params.expected}", got "${params.got}"`);
194
+ this.name = "CodecMismatchError";
195
+ }
196
+ };
197
+ function defineCodec({
198
+ name,
199
+ schema
200
+ }) {
201
+ return {
202
+ currentVersion: 1,
203
+ name,
204
+ decode: (raw) => decode({ name, raw, schema }),
205
+ encode: (value) => ({
206
+ __codec: name,
207
+ data: value,
208
+ version: 1
209
+ })
210
+ };
211
+ }
212
+ function decode({
213
+ name,
214
+ raw,
215
+ schema
216
+ }) {
217
+ const envelope = envelopeSchema.parse(raw);
218
+ if (envelope.__codec !== name) {
219
+ throw new CodecMismatchError({ expected: name, got: envelope.__codec });
220
+ }
221
+ if (envelope.version !== 1) {
222
+ throw new CodecVersionError({ codec: name, currentVersion: 1, gotVersion: envelope.version });
223
+ }
224
+ return schema.parse(envelope.data);
225
+ }
226
+
227
+ // ../spec/src/lockfile.ts
228
+ import { z as z4 } from "zod";
229
+ var actionSchema = z4.discriminatedUnion("kind", [
230
+ z4.object({ kind: z4.literal("goto"), url: stringValueSchema }),
231
+ z4.object({ kind: z4.literal("fill"), locator: locatorSchema, value: setValueSchema }),
232
+ z4.object({ kind: z4.literal("clear"), locator: locatorSchema }),
233
+ z4.object({ kind: z4.literal("click"), locator: locatorSchema }),
234
+ z4.object({ kind: z4.literal("dblclick"), locator: locatorSchema }),
235
+ z4.object({ kind: z4.literal("select"), locator: locatorSchema, value: setValueSchema }),
236
+ z4.object({ kind: z4.literal("check"), locator: locatorSchema }),
237
+ z4.object({ kind: z4.literal("uncheck"), locator: locatorSchema }),
238
+ z4.object({ kind: z4.literal("hover"), locator: locatorSchema }),
239
+ z4.object({
240
+ files: z4.array(z4.string().min(1)).min(1),
241
+ kind: z4.literal("upload"),
242
+ locator: locatorSchema
243
+ }),
244
+ z4.object({ key: z4.string().min(1), kind: z4.literal("press"), locator: locatorSchema.optional() })
245
+ ]);
246
+ var stepSchema = z4.object({
247
+ action: actionSchema,
248
+ expect: z4.array(predicateSchema).default([])
249
+ });
250
+ var paramSchema = z4.object({
251
+ example: primitiveSchema.optional(),
252
+ valueSpace: z4.string().min(1)
253
+ });
254
+ var setupSchema = z4.object({
255
+ as: z4.string().min(1),
256
+ entity: z4.string().min(1),
257
+ set: z4.record(z4.string().min(1), setValueSchema)
258
+ });
259
+ var absenceSchema = z4.object({
260
+ entity: z4.string().min(1),
261
+ where: z4.record(z4.string().min(1), setValueSchema)
262
+ });
263
+ var testSchema = z4.object({
264
+ absent: z4.array(absenceSchema).default([]),
265
+ exclusive: z4.array(z4.string().min(1)).default([]),
266
+ intent: z4.string().min(1),
267
+ maybe: z4.array(setupSchema).default([]),
268
+ name: z4.string().min(1),
269
+ params: z4.record(z4.string().min(1), paramSchema),
270
+ singletons: z4.record(z4.string().min(1), setValueSchema).default({}),
271
+ sourcePath: z4.string().min(1).optional(),
272
+ steps: z4.array(stepSchema).default([]),
273
+ stub: z4.boolean().default(false),
274
+ world: z4.array(setupSchema).default([])
275
+ });
276
+ var fixtureEntrySchema = z4.object({
277
+ sha256: z4.string().regex(/^[0-9a-f]{64}$/u),
278
+ size: z4.number().int().nonnegative()
279
+ });
280
+ var lockfileSchema = z4.object({
281
+ entities: z4.array(entitySchemaSchema),
282
+ fixtures: z4.record(z4.string().min(1), fixtureEntrySchema).default({}),
283
+ singletons: z4.array(singletonSchemaSchema).default([]),
284
+ tests: z4.array(testSchema),
285
+ valueSpaces: z4.array(valueSpaceSchema)
286
+ });
287
+ var lockfileCodec = defineCodec({ name: "ripplo-lockfile", schema: lockfileSchema });
288
+
289
+ // ../spec/src/sync-payload.ts
290
+ import { z as z5 } from "zod";
291
+ var stepDescriptorSchema = z5.object({
292
+ index: z5.number().int().nonnegative(),
293
+ kind: z5.string(),
294
+ target: z5.string(),
295
+ value: z5.string()
296
+ });
297
+ var workflowSpecSchema = z5.object({
298
+ steps: z5.array(stepDescriptorSchema),
299
+ stub: z5.boolean().default(false)
300
+ });
301
+
302
+ // ../spec/src/session.ts
303
+ import { z as z6 } from "zod";
304
+ var sameSiteSchema = z6.enum(["Strict", "Lax", "None"]);
305
+ var cookieSchema = z6.object({
306
+ domain: z6.string().min(1),
307
+ expires: z6.number(),
308
+ httpOnly: z6.boolean(),
309
+ name: z6.string().min(1),
310
+ path: z6.string().min(1),
311
+ sameSite: sameSiteSchema,
312
+ secure: z6.boolean(),
313
+ value: z6.string()
314
+ });
315
+ var originSchema = z6.object({
316
+ localStorage: z6.array(z6.object({ name: z6.string().min(1), value: z6.string() })),
317
+ origin: z6.string().min(1)
318
+ });
319
+ var sessionSchema = z6.object({
320
+ cookies: z6.array(cookieSchema),
321
+ headers: z6.record(z6.string().min(1), z6.string()).optional(),
322
+ origins: z6.array(originSchema)
323
+ });
324
+
325
+ // ../spec/src/engine.ts
326
+ import { z as z7 } from "zod";
327
+ var cellSchema = z7.union([primitiveSchema, z7.null()]);
328
+ var rowSchema = z7.record(z7.string().min(1), cellSchema);
329
+ var setupSpecSchema = z7.object({
330
+ as: z7.string().min(1),
331
+ entity: z7.string().min(1),
332
+ fields: z7.record(z7.string().min(1), setValueSchema)
333
+ });
334
+ var setupRequestSchema = z7.object({
335
+ entities: z7.array(setupSpecSchema),
336
+ runId: z7.string().min(1),
337
+ singletons: z7.record(z7.string().min(1), cellSchema).default({})
338
+ });
339
+ var setupRowSchema = z7.object({
340
+ as: z7.string().min(1),
341
+ row: rowSchema,
342
+ session: sessionSchema.optional()
343
+ });
344
+ var setupResponseSchema = z7.object({
345
+ rows: z7.array(setupRowSchema)
346
+ });
347
+ var stateRequestSchema = z7.object({
348
+ entities: z7.array(z7.string().min(1)),
349
+ runId: z7.string().min(1),
350
+ singletons: z7.array(z7.string().min(1)).default([])
351
+ });
352
+ var stateResponseSchema = z7.object({
353
+ entities: z7.record(z7.string().min(1), z7.array(rowSchema)),
354
+ singletons: z7.record(z7.string().min(1), cellSchema).default({})
355
+ });
356
+ var teardownRequestSchema = z7.object({
357
+ runId: z7.string().min(1)
358
+ });
359
+ var teardownResponseSchema = z7.object({
360
+ ok: z7.literal(true)
361
+ });
11
362
 
12
363
  // src/adapters/express.ts
13
- import { Router, json } from "express";
14
- function createExpressHandler({ enabled, engine }) {
364
+ import { Webhook, WebhookVerificationError } from "standardwebhooks";
365
+ function createEngineHandler({ enabled, engine }) {
15
366
  const router = Router();
16
367
  if (!enabled) {
17
368
  return router;
18
369
  }
19
- const webhookSecret = readAdapterWebhookSecret();
370
+ const secret = readWebhookSecret();
20
371
  router.use(json());
21
372
  router.use((req, res, next) => {
22
- const payload = JSON.stringify(req.body);
23
- const headers = extractWebhookHeaders(req);
24
- if (!verifyWebhookSignature(payload, headers, webhookSecret)) {
373
+ if (!verify(JSON.stringify(req.body), req, secret)) {
25
374
  res.status(401).json({ error: "Invalid webhook signature" });
26
375
  return;
27
376
  }
28
377
  next();
29
378
  });
30
- router.put("/execute-preconditions", (req, res) => {
31
- const parsed = batchRequestSchema.safeParse(req.body);
379
+ router.put("/setup", (req, res) => {
380
+ const parsed = setupRequestSchema.safeParse(req.body);
32
381
  if (!parsed.success) {
33
382
  res.status(400).json({ error: "Invalid request body" });
34
383
  return;
35
384
  }
36
- const appUrl = `${req.protocol}://${req.get("host") ?? ""}`;
37
- const items = parsed.data.batch.map((b) => ({ names: b.preconditions, runId: b.runId }));
38
- void engine.executePreconditions(items, { appUrl }).then((results) => {
39
- res.json({ results: toBatchRunResults(results) });
40
- });
385
+ const { entities, runId, singletons } = parsed.data;
386
+ void engine.seed({ entities, singletons }, runId).match(
387
+ (rows) => res.json({ rows }),
388
+ (error) => {
389
+ fail(res, error);
390
+ }
391
+ );
41
392
  });
42
- router.put("/execute-observer", (req, res) => {
43
- const parsed = observerRequestSchema.safeParse(req.body);
393
+ router.put("/state", (req, res) => {
394
+ const parsed = stateRequestSchema.safeParse(req.body);
44
395
  if (!parsed.success) {
45
- res.status(400).json({ error: "Invalid request body", success: false });
396
+ res.status(400).json({ error: "Invalid request body" });
46
397
  return;
47
398
  }
48
- void engine.executeObserver(parsed.data.observer, parsed.data.params).then((result) => {
49
- res.json({ error: result.error, outcome: result.outcome, success: result.success });
50
- });
399
+ const { entities, runId, singletons } = parsed.data;
400
+ void engine.read({ entities, singletons }, runId).match(
401
+ (read) => res.json(read),
402
+ (error) => {
403
+ fail(res, error);
404
+ }
405
+ );
51
406
  });
52
- router.put("/teardown-preconditions", (req, res) => {
407
+ router.put("/teardown", (req, res) => {
53
408
  const parsed = teardownRequestSchema.safeParse(req.body);
54
409
  if (!parsed.success) {
55
410
  res.status(400).json({ error: "Invalid request body" });
56
411
  return;
57
412
  }
58
- const items = parsed.data.batch.map((b) => ({
59
- data: b.data,
60
- names: b.preconditions,
61
- runId: b.runId
62
- }));
63
- void engine.teardown(items).then((results) => {
64
- res.json({ results: toTeardownResults(results) });
65
- });
413
+ void engine.teardown(parsed.data.runId).match(
414
+ () => res.json({ ok: true }),
415
+ (error) => {
416
+ fail(res, error);
417
+ }
418
+ );
66
419
  });
67
420
  return router;
68
421
  }
69
- function extractWebhookHeaders(req) {
70
- return {
71
- "webhook-id": asString(req.headers["webhook-id"]),
72
- "webhook-signature": asString(req.headers["webhook-signature"]),
73
- "webhook-timestamp": asString(req.headers["webhook-timestamp"])
74
- };
422
+ function readWebhookSecret() {
423
+ const value = process.env["RIPPLO_WEBHOOK_SECRET"];
424
+ if (value == null || value.length === 0) {
425
+ throw new Error(
426
+ "ripplo: RIPPLO_WEBHOOK_SECRET is not set. The engine adapter needs it to verify calls."
427
+ );
428
+ }
429
+ return value;
75
430
  }
76
- function asString(value) {
431
+ function verify(payload, req, secret) {
432
+ try {
433
+ new Webhook(secret).verify(payload, {
434
+ "webhook-id": header(req, "webhook-id"),
435
+ "webhook-signature": header(req, "webhook-signature"),
436
+ "webhook-timestamp": header(req, "webhook-timestamp")
437
+ });
438
+ return true;
439
+ } catch (error) {
440
+ if (error instanceof WebhookVerificationError) {
441
+ return false;
442
+ }
443
+ throw error;
444
+ }
445
+ }
446
+ function header(req, name) {
447
+ const value = req.headers[name];
77
448
  if (typeof value === "string") {
78
449
  return value;
79
450
  }
80
451
  if (Array.isArray(value)) {
81
- return value[0] ?? void 0;
452
+ return value[0] ?? "";
82
453
  }
83
- return void 0;
454
+ return "";
455
+ }
456
+ function fail(res, error) {
457
+ res.status(500).json({ error: error.message });
84
458
  }
85
459
  export {
86
- createExpressHandler
460
+ createEngineHandler
87
461
  };