@ripplo/testing 0.6.1 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DSL.md +357 -0
- package/LICENSE.md +1 -0
- package/README.md +47 -273
- package/dist/engine-BfvzXgLg.d.ts +1091 -0
- package/dist/express.d.ts +7 -9
- package/dist/express.js +422 -48
- package/dist/index.d.ts +134 -59
- package/dist/index.js +1654 -1126
- package/package.json +31 -113
- package/dist/actions.d.ts +0 -260
- package/dist/actions.js +0 -177
- package/dist/assert.d.ts +0 -188
- package/dist/assert.js +0 -111
- package/dist/builder-SsgqYqSC.d.ts +0 -156
- package/dist/chunk-2YLI7VD4.js +0 -65
- package/dist/chunk-4MGIQFAJ.js +0 -16
- package/dist/chunk-DCJBLS2U.js +0 -26
- package/dist/chunk-MGATMMCZ.js +0 -16
- package/dist/chunk-XO36IU66.js +0 -88
- package/dist/chunk-YFOTJIVF.js +0 -134
- package/dist/chunk-YQAEOH5W.js +0 -111
- package/dist/compiler.d.ts +0 -32
- package/dist/compiler.js +0 -8
- package/dist/control.d.ts +0 -45
- package/dist/control.js +0 -17
- package/dist/elysia.d.ts +0 -78
- package/dist/elysia.js +0 -114
- package/dist/engine-BOqzK_go.d.ts +0 -61
- package/dist/fastify.d.ts +0 -14
- package/dist/fastify.js +0 -79
- package/dist/hono.d.ts +0 -19
- package/dist/hono.js +0 -89
- package/dist/koa.d.ts +0 -14
- package/dist/koa.js +0 -135
- package/dist/locators.d.ts +0 -40
- package/dist/locators.js +0 -11
- package/dist/lockfile.d.ts +0 -722
- package/dist/lockfile.js +0 -707
- package/dist/nestjs.d.ts +0 -17
- package/dist/nestjs.js +0 -139
- package/dist/nextjs.d.ts +0 -14
- package/dist/nextjs.js +0 -137
- package/dist/step-De52hTLd.d.ts +0 -19
- 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 {
|
|
3
|
-
import '
|
|
4
|
-
import '
|
|
5
|
-
import './step-De52hTLd.js';
|
|
6
|
-
import '@ripplo/spec';
|
|
2
|
+
import { E as Engine } from './engine-BfvzXgLg.js';
|
|
3
|
+
import 'neverthrow';
|
|
4
|
+
import 'zod';
|
|
7
5
|
|
|
8
|
-
interface
|
|
6
|
+
interface CreateEngineHandlerParams {
|
|
9
7
|
readonly enabled: boolean;
|
|
10
|
-
readonly engine:
|
|
8
|
+
readonly engine: Engine;
|
|
11
9
|
}
|
|
12
|
-
declare function
|
|
10
|
+
declare function createEngineHandler({ enabled, engine }: CreateEngineHandlerParams): Router;
|
|
13
11
|
|
|
14
|
-
export { type
|
|
12
|
+
export { type CreateEngineHandlerParams, createEngineHandler };
|
package/dist/express.js
CHANGED
|
@@ -1,87 +1,461 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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 {
|
|
14
|
-
function
|
|
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
|
|
370
|
+
const secret = readWebhookSecret();
|
|
20
371
|
router.use(json());
|
|
21
372
|
router.use((req, res, next) => {
|
|
22
|
-
|
|
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("/
|
|
31
|
-
const parsed =
|
|
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
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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("/
|
|
43
|
-
const parsed =
|
|
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"
|
|
396
|
+
res.status(400).json({ error: "Invalid request body" });
|
|
46
397
|
return;
|
|
47
398
|
}
|
|
48
|
-
|
|
49
|
-
|
|
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
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
|
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] ??
|
|
452
|
+
return value[0] ?? "";
|
|
82
453
|
}
|
|
83
|
-
return
|
|
454
|
+
return "";
|
|
455
|
+
}
|
|
456
|
+
function fail(res, error) {
|
|
457
|
+
res.status(500).json({ error: error.message });
|
|
84
458
|
}
|
|
85
459
|
export {
|
|
86
|
-
|
|
460
|
+
createEngineHandler
|
|
87
461
|
};
|