@ripplo/testing 0.4.7 → 0.5.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 +62 -19
- package/dist/actions.d.ts +10 -2
- package/dist/actions.js +24 -2
- package/dist/assert.d.ts +1 -1
- package/dist/{builder-BMjy83Iy.d.ts → builder-DiVz3t1D.d.ts} +10 -3
- package/dist/{chunk-V6LMXKGL.js → chunk-XO36IU66.js} +32 -38
- package/dist/{chunk-DL3HLCD7.js → chunk-YFOTJIVF.js} +1 -1
- package/dist/compiler.d.ts +8 -3
- package/dist/compiler.js +1 -1
- package/dist/elysia.d.ts +3 -3
- package/dist/elysia.js +16 -23
- package/dist/{engine-DMOkJdjd.d.ts → engine-DVbF4E5A.d.ts} +20 -6
- package/dist/express.d.ts +3 -3
- package/dist/express.js +16 -45
- package/dist/fastify.d.ts +3 -3
- package/dist/fastify.js +16 -24
- package/dist/hono.d.ts +3 -3
- package/dist/hono.js +17 -24
- package/dist/index.d.ts +32 -13
- package/dist/index.js +247 -88
- package/dist/koa.d.ts +3 -3
- package/dist/koa.js +17 -20
- package/dist/lockfile.d.ts +20 -5
- package/dist/lockfile.js +89 -3
- package/dist/nestjs.d.ts +3 -3
- package/dist/nestjs.js +15 -20
- package/dist/nextjs.d.ts +3 -3
- package/dist/nextjs.js +16 -23
- package/dist/{types-16SB7zjP.d.ts → types-BzZrl65Z.d.ts} +9 -2
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -77,6 +77,17 @@ export const preconditions = { authLoggedIn, dataProject };
|
|
|
77
77
|
|
|
78
78
|
`precondition(name)` → `.description()` → `.requires()` (optional) → `.contract<T>()`. Each field in `T` must be a primitive — `string`, `number`, or `boolean` (run-scoped value).
|
|
79
79
|
|
|
80
|
+
#### Preconditions are create-only
|
|
81
|
+
|
|
82
|
+
Precondition setups must be **additive**: insert new rows, don't `update` or `delete` existing ones. Concurrent runs share a database, and a `WHERE` clause that looks scoped to the current run can match rows that belong to another run in flight. Even when writes are reliably scoped, mutating a row another precondition produced creates ordering dependencies that break under composition.
|
|
83
|
+
|
|
84
|
+
If a test needs a non-default state, the precondition that **creates** the underlying row should accept that state as input — don't seed a default and mutate it from a downstream precondition.
|
|
85
|
+
|
|
86
|
+
Allowed exceptions:
|
|
87
|
+
|
|
88
|
+
- `upsert` on a row whose primary key makes it a 1:1 settings record for the current run (e.g. a per-`(userId, resourceId)` view). Treat it as create-with-default.
|
|
89
|
+
- Teardown may delete — but only data the precondition itself created.
|
|
90
|
+
|
|
80
91
|
### Observers
|
|
81
92
|
|
|
82
93
|
```typescript
|
|
@@ -158,6 +169,7 @@ import {
|
|
|
158
169
|
navigate,
|
|
159
170
|
scrollIntoView,
|
|
160
171
|
drag,
|
|
172
|
+
fixture,
|
|
161
173
|
upload,
|
|
162
174
|
handleDialog,
|
|
163
175
|
clipboard,
|
|
@@ -171,10 +183,22 @@ fill(role("textbox", "Email"), "test@x.com"); // clear + type
|
|
|
171
183
|
select(role("combobox", "Role"), "admin");
|
|
172
184
|
check(role("checkbox", "Terms"));
|
|
173
185
|
press("Enter");
|
|
174
|
-
upload(testId("file-input"), "
|
|
186
|
+
upload(testId("file-input"), fixture("logo.png"));
|
|
175
187
|
drag(role("row", "Item 1"), role("row", "Item 2"));
|
|
176
188
|
```
|
|
177
189
|
|
|
190
|
+
### Upload fixtures
|
|
191
|
+
|
|
192
|
+
`upload()` requires a `fixture()` reference. Fixture files live in `.ripplo/fixtures/` (committed). Limits: 10 MB per file, 50 MB total.
|
|
193
|
+
|
|
194
|
+
```ts
|
|
195
|
+
// .ripplo/fixtures/logo.png exists
|
|
196
|
+
upload(testId("logo-input"), fixture("logo.png"));
|
|
197
|
+
|
|
198
|
+
// Multiple files
|
|
199
|
+
upload(testId("attachments"), [fixture("a.pdf"), fixture("b.pdf")]);
|
|
200
|
+
```
|
|
201
|
+
|
|
178
202
|
### Assertions
|
|
179
203
|
|
|
180
204
|
```typescript
|
|
@@ -265,12 +289,21 @@ import { prisma } from "../lib/prisma.js";
|
|
|
265
289
|
export const engine = createEngine(ripplo, {
|
|
266
290
|
preconditions: {
|
|
267
291
|
authLoggedIn: {
|
|
268
|
-
setup
|
|
269
|
-
|
|
270
|
-
|
|
292
|
+
// setup receives a *batch* of items — one per concurrent run that requires this precondition.
|
|
293
|
+
// Issue one bulk write (createMany) and return one result per item, in input order.
|
|
294
|
+
setup: async (items) => {
|
|
295
|
+
const seeds = items.map(({ ctx }) => ({
|
|
296
|
+
id: ctx.uniqueId("user"),
|
|
297
|
+
email: ctx.uniqueEmail(),
|
|
298
|
+
}));
|
|
299
|
+
await prisma.user.createMany({ data: seeds });
|
|
300
|
+
// each item.ctx.setCookie(...) flows back to that run's browser
|
|
301
|
+
return seeds.map(({ id }) => ({ userId: id }));
|
|
271
302
|
},
|
|
272
|
-
teardown: async (
|
|
273
|
-
|
|
303
|
+
teardown: async (items) => {
|
|
304
|
+
await prisma.user.deleteMany({
|
|
305
|
+
where: { id: { in: items.map((it) => it.ctx.data.userId) } },
|
|
306
|
+
});
|
|
274
307
|
},
|
|
275
308
|
},
|
|
276
309
|
dataProject: notImplemented("awaiting prisma seed helper"), // stub for planning
|
|
@@ -289,15 +322,17 @@ export const engine = createEngine(ripplo, {
|
|
|
289
322
|
});
|
|
290
323
|
```
|
|
291
324
|
|
|
325
|
+
`setup` and `teardown` are **batched**: the runtime collects all concurrent runs that need a precondition within a 200ms window and calls the impl once with the full batch. Use `createMany` / `deleteMany` over per-item `create` / `delete` to keep DB load proportional to wall-clock time, not run count. The result array length must equal the input array length and order must be preserved (the engine zips by index back to runs).
|
|
326
|
+
|
|
292
327
|
### Setup context
|
|
293
328
|
|
|
294
|
-
`ctx`
|
|
329
|
+
`item.ctx` available inside each batched setup item:
|
|
295
330
|
|
|
296
331
|
- `ctx.runId` — unique 12-char id for this run
|
|
297
332
|
- `ctx.fixed<T extends string | number | boolean>(value: T)` — static test value (any primitive)
|
|
298
333
|
- `ctx.uniqueId(prefix)` — `ripplo-test-<prefix>-<runId>`
|
|
299
334
|
- `ctx.uniqueEmail()` — `ripplo-test-<runId>@test.ripplo.ai`
|
|
300
|
-
- `ctx.setCookie(name, value, options?)` —
|
|
335
|
+
- `ctx.setCookie(name, value, options?)` — applied to the run's browser context before the test starts
|
|
301
336
|
|
|
302
337
|
Helpers return plain primitives — interpolate, JSON.stringify, or pass through observer params directly. The return type is type-branded, so a hardcoded literal in a `setup` return fails at compile time.
|
|
303
338
|
|
|
@@ -434,10 +469,14 @@ const app = new Elysia().group("/ripplo", (g) => g.use(createElysiaHandler({ ena
|
|
|
434
469
|
For unsupported frameworks. The adapters above are thin wrappers over this API.
|
|
435
470
|
|
|
436
471
|
```ts
|
|
437
|
-
import {
|
|
472
|
+
import {
|
|
473
|
+
readAdapterWebhookSecret,
|
|
474
|
+
toBatchRunResults,
|
|
475
|
+
verifyWebhookSignature,
|
|
476
|
+
} from "@ripplo/testing";
|
|
438
477
|
import { engine } from "./test/engine.js";
|
|
439
478
|
|
|
440
|
-
const webhookSecret =
|
|
479
|
+
const webhookSecret = readAdapterWebhookSecret();
|
|
441
480
|
|
|
442
481
|
async function executePreconditions(req: Request): Promise<Response> {
|
|
443
482
|
const body = await req.text();
|
|
@@ -454,27 +493,31 @@ async function executePreconditions(req: Request): Promise<Response> {
|
|
|
454
493
|
return new Response(JSON.stringify({ error: "Invalid signature" }), { status: 401 });
|
|
455
494
|
}
|
|
456
495
|
|
|
457
|
-
|
|
496
|
+
// Request body is a batch: { batch: [{ runId, preconditions: [...names] }, ...] }
|
|
497
|
+
const { batch } = JSON.parse(body);
|
|
458
498
|
const appUrl = `${req.headers.get("x-forwarded-proto") ?? "http"}://${req.headers.get("host")}`;
|
|
459
|
-
const
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
result.cookies.forEach((c) =>
|
|
463
|
-
headers.append("Set-Cookie", buildSetCookieHeader(serializeCookie(c))),
|
|
499
|
+
const results = await engine.executePreconditions(
|
|
500
|
+
batch.map((b) => ({ runId: b.runId, names: b.preconditions })),
|
|
501
|
+
{ appUrl },
|
|
464
502
|
);
|
|
465
|
-
|
|
503
|
+
|
|
504
|
+
// Response body: { results: [{ runId, ok, cookies, data, executed } | { runId, ok: false, error }] }
|
|
505
|
+
return new Response(JSON.stringify({ results: toBatchRunResults(results) }), {
|
|
506
|
+
headers: { "content-type": "application/json" },
|
|
507
|
+
});
|
|
466
508
|
}
|
|
467
509
|
// teardown-preconditions and execute-observer follow the same verify-then-dispatch pattern.
|
|
510
|
+
// teardown's request shape is { batch: [{ runId, preconditions, data }, ...] }, response { results: [{ runId, ok, error? }] }.
|
|
468
511
|
```
|
|
469
512
|
|
|
470
|
-
You're responsible for: webhook verification (always before invoking the engine), routing the three endpoints,
|
|
513
|
+
You're responsible for: webhook verification (always before invoking the engine), routing the three endpoints, calling `toBatchRunResults` / `toTeardownResults` to shape the response body, and reading the raw body for signature verification before `JSON.parse`. Cookies travel inside the JSON response body — never as `Set-Cookie` headers; the runtime parses them out and applies them to the test browser context.
|
|
471
514
|
|
|
472
515
|
## Security & parallelism
|
|
473
516
|
|
|
474
517
|
- All requests signed via Standard Webhooks (HMAC-SHA256). Headers: `webhook-id`, `webhook-timestamp`, `webhook-signature`. **Always verify before executing.**
|
|
475
518
|
- `ENABLE_RIPPLO_TESTING` gates every adapter. Never expose in production.
|
|
476
519
|
- Use `ctx.uniqueId(prefix)` / `ctx.uniqueEmail()` so parallel runs don't collide.
|
|
477
|
-
- Return created entity IDs in the data contract; teardown deletes only
|
|
520
|
+
- Return created entity IDs in the data contract; teardown deletes only data this run created. Bulk operations are fine — and encouraged — as long as the `WHERE` is scoped to the batch's own ids (`deleteMany({ where: { id: { in: items.map((it) => it.ctx.data.userId) } } })`). Never write a `WHERE` that could match another run's rows.
|
|
478
521
|
|
|
479
522
|
## Lockfile
|
|
480
523
|
|
package/dist/actions.d.ts
CHANGED
|
@@ -3,6 +3,14 @@ import { U as UnlabeledStep } from './step-De52hTLd.js';
|
|
|
3
3
|
import { CheckLocator, InputLocator, AnyLocator, SelectLocator } from './locators.js';
|
|
4
4
|
import '@ripplo/spec';
|
|
5
5
|
|
|
6
|
+
declare class Fixture {
|
|
7
|
+
readonly kind: "fixture";
|
|
8
|
+
readonly name: string;
|
|
9
|
+
constructor(name: string);
|
|
10
|
+
}
|
|
11
|
+
declare function fixture(name: string): Fixture;
|
|
12
|
+
declare function isFixture(value: unknown): value is Fixture;
|
|
13
|
+
|
|
6
14
|
type StringOrVariable = string | Variable<string>;
|
|
7
15
|
declare function navigate(url: string): UnlabeledStep<{
|
|
8
16
|
type: "goto";
|
|
@@ -87,7 +95,7 @@ declare function press(key: string): UnlabeledStep<{
|
|
|
87
95
|
key: string;
|
|
88
96
|
type: "press";
|
|
89
97
|
}>;
|
|
90
|
-
declare function upload(locator: AnyLocator,
|
|
98
|
+
declare function upload(locator: AnyLocator, files: Fixture | ReadonlyArray<Fixture>, options?: StepOptions): UnlabeledStep<{
|
|
91
99
|
files: string[];
|
|
92
100
|
locator: {
|
|
93
101
|
by: "testId";
|
|
@@ -247,4 +255,4 @@ declare function setViewport({ height, width }: SetViewportOptions): UnlabeledSt
|
|
|
247
255
|
width: number;
|
|
248
256
|
}>;
|
|
249
257
|
|
|
250
|
-
export { check, clear, click, clipboard, dblclick, drag, fill, focus, handleDialog, hover, navigate, press, rightClick, scrollIntoView, select, setPermission, setViewport, typeText, uncheck, upload };
|
|
258
|
+
export { Fixture, check, clear, click, clipboard, dblclick, drag, fill, fixture, focus, handleDialog, hover, isFixture, navigate, press, rightClick, scrollIntoView, select, setPermission, setViewport, typeText, uncheck, upload };
|
package/dist/actions.js
CHANGED
|
@@ -9,6 +9,24 @@ import {
|
|
|
9
9
|
} from "./chunk-MGATMMCZ.js";
|
|
10
10
|
import "./chunk-4MGIQFAJ.js";
|
|
11
11
|
|
|
12
|
+
// src/steps/fixture.ts
|
|
13
|
+
var Fixture = class {
|
|
14
|
+
kind = "fixture";
|
|
15
|
+
name;
|
|
16
|
+
constructor(name) {
|
|
17
|
+
if (name.length === 0) {
|
|
18
|
+
throw new Error("fixture(name) requires a non-empty name");
|
|
19
|
+
}
|
|
20
|
+
this.name = name;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
function fixture(name) {
|
|
24
|
+
return new Fixture(name);
|
|
25
|
+
}
|
|
26
|
+
function isFixture(value) {
|
|
27
|
+
return value instanceof Fixture;
|
|
28
|
+
}
|
|
29
|
+
|
|
12
30
|
// src/steps/actions.ts
|
|
13
31
|
function navigate(url) {
|
|
14
32
|
return createStep({ type: "goto", url: { type: "static", value: url } });
|
|
@@ -46,9 +64,10 @@ function hover(locator) {
|
|
|
46
64
|
function press(key) {
|
|
47
65
|
return createStep({ key, type: "press" });
|
|
48
66
|
}
|
|
49
|
-
function upload(locator,
|
|
67
|
+
function upload(locator, files, options) {
|
|
68
|
+
const list = files instanceof Fixture ? [files] : [...files];
|
|
50
69
|
return createStep({
|
|
51
|
-
files:
|
|
70
|
+
files: list.map((f) => f.name),
|
|
52
71
|
locator: toSpecLocator(locator),
|
|
53
72
|
type: "upload",
|
|
54
73
|
uiOnly: options?.uiOnly
|
|
@@ -101,6 +120,7 @@ function setViewport({ height, width }) {
|
|
|
101
120
|
return createStep({ height, type: "setViewport", width });
|
|
102
121
|
}
|
|
103
122
|
export {
|
|
123
|
+
Fixture,
|
|
104
124
|
check,
|
|
105
125
|
clear,
|
|
106
126
|
click,
|
|
@@ -108,9 +128,11 @@ export {
|
|
|
108
128
|
dblclick,
|
|
109
129
|
drag,
|
|
110
130
|
fill,
|
|
131
|
+
fixture,
|
|
111
132
|
focus,
|
|
112
133
|
handleDialog,
|
|
113
134
|
hover,
|
|
135
|
+
isFixture,
|
|
114
136
|
navigate,
|
|
115
137
|
press,
|
|
116
138
|
rightClick,
|
package/dist/assert.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { O as ObserverHandle, a as ObserverInput, P as Primitive, b as ObserverBudgetTier } from './types-
|
|
1
|
+
import { O as ObserverHandle, a as ObserverInput, P as Primitive, b as ObserverBudgetTier } from './types-BzZrl65Z.js';
|
|
2
2
|
import { Variable, StaticStringRef, VariableRef } from './control.js';
|
|
3
3
|
import { U as UnlabeledStep } from './step-De52hTLd.js';
|
|
4
4
|
import { CheckLocator, AnyLocator } from './locators.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { P as Primitive, O as ObserverHandle, h as Precondition, m as PreconditionData, j as TestDefinition, f as ObserverDefinition, l as PreconditionDefinition, U as UnimplementedItems, e as ObserverContext, g as ObserverOutcome, S as SetupContext, k as TestValue, T as TeardownContext } from './types-
|
|
1
|
+
import { P as Primitive, O as ObserverHandle, h as Precondition, m as PreconditionData, j as TestDefinition, f as ObserverDefinition, l as PreconditionDefinition, U as UnimplementedItems, e as ObserverContext, g as ObserverOutcome, S as SetupContext, k as TestValue, T as TeardownContext } from './types-BzZrl65Z.js';
|
|
2
2
|
import { ObserverBudget } from '@ripplo/spec';
|
|
3
3
|
import { S as Step } from './step-De52hTLd.js';
|
|
4
4
|
|
|
@@ -90,9 +90,16 @@ interface TestNeedsCoverage {
|
|
|
90
90
|
type PreconditionSetupResult<TData extends Record<string, Primitive>> = {
|
|
91
91
|
readonly [K in keyof TData]: TestValue<TData[K]>;
|
|
92
92
|
};
|
|
93
|
+
interface PreconditionSetupItem<TDeps extends Record<string, Record<string, Primitive>>> {
|
|
94
|
+
readonly ctx: SetupContext;
|
|
95
|
+
readonly deps: TDeps;
|
|
96
|
+
}
|
|
97
|
+
interface PreconditionTeardownItem<TData extends Record<string, Primitive>> {
|
|
98
|
+
readonly ctx: TeardownContext<TData>;
|
|
99
|
+
}
|
|
93
100
|
interface PreconditionImpl<TData extends Record<string, Primitive>, TDeps extends Record<string, Record<string, Primitive>>> {
|
|
94
|
-
readonly setup: (
|
|
95
|
-
readonly teardown: (
|
|
101
|
+
readonly setup: (items: ReadonlyArray<PreconditionSetupItem<TDeps>>) => Promise<ReadonlyArray<PreconditionSetupResult<TData>>>;
|
|
102
|
+
readonly teardown: (items: ReadonlyArray<PreconditionTeardownItem<TData>>) => Promise<void>;
|
|
96
103
|
}
|
|
97
104
|
type ObserverImplFn<TInput extends Record<string, Primitive>> = (ctx: ObserverContext, params: TInput) => Promise<ObserverOutcome>;
|
|
98
105
|
interface TestOptions {
|
|
@@ -10,26 +10,28 @@ function readAdapterWebhookSecret() {
|
|
|
10
10
|
}
|
|
11
11
|
return value;
|
|
12
12
|
}
|
|
13
|
+
var primitiveSchema = z.union([z.string(), z.number(), z.boolean()]);
|
|
14
|
+
var dataSchema = z.record(z.string(), z.record(z.string(), primitiveSchema));
|
|
13
15
|
var batchRequestSchema = z.object({
|
|
14
|
-
|
|
16
|
+
batch: z.array(
|
|
17
|
+
z.object({
|
|
18
|
+
preconditions: z.array(z.string().min(1)),
|
|
19
|
+
runId: z.string().min(1)
|
|
20
|
+
})
|
|
21
|
+
).min(1)
|
|
15
22
|
});
|
|
16
23
|
var teardownRequestSchema = z.object({
|
|
17
|
-
|
|
18
|
-
|
|
24
|
+
batch: z.array(
|
|
25
|
+
z.object({
|
|
26
|
+
data: dataSchema,
|
|
27
|
+
preconditions: z.array(z.string().min(1)),
|
|
28
|
+
runId: z.string().min(1)
|
|
29
|
+
})
|
|
30
|
+
).min(1)
|
|
19
31
|
});
|
|
20
32
|
var observerRequestSchema = z.object({
|
|
21
33
|
observer: z.string().min(1).max(200),
|
|
22
|
-
params: z.record(z.string().max(200),
|
|
23
|
-
});
|
|
24
|
-
var observerOutcomeSchema = z.discriminatedUnion("kind", [
|
|
25
|
-
z.object({ kind: z.literal("pass") }),
|
|
26
|
-
z.object({ kind: z.literal("retry"), reason: z.string() }),
|
|
27
|
-
z.object({ kind: z.literal("fail"), reason: z.string() })
|
|
28
|
-
]);
|
|
29
|
-
var observerResponseSchema = z.object({
|
|
30
|
-
error: z.string().optional(),
|
|
31
|
-
outcome: observerOutcomeSchema.optional(),
|
|
32
|
-
success: z.boolean()
|
|
34
|
+
params: z.record(z.string().max(200), primitiveSchema)
|
|
33
35
|
});
|
|
34
36
|
function verifyWebhookSignature(payload, headers, secret) {
|
|
35
37
|
try {
|
|
@@ -50,7 +52,7 @@ function verifyWebhookSignature(payload, headers, secret) {
|
|
|
50
52
|
function serializeCookie(cookie) {
|
|
51
53
|
return {
|
|
52
54
|
domain: cookie.options?.domain,
|
|
53
|
-
expires: cookie.options?.expires
|
|
55
|
+
expires: cookie.options?.expires,
|
|
54
56
|
httpOnly: cookie.options?.httpOnly,
|
|
55
57
|
name: cookie.name,
|
|
56
58
|
path: cookie.options?.path,
|
|
@@ -59,28 +61,19 @@ function serializeCookie(cookie) {
|
|
|
59
61
|
value: cookie.value
|
|
60
62
|
};
|
|
61
63
|
}
|
|
62
|
-
function
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
if (cookie.secure === true) {
|
|
77
|
-
parts.push("Secure");
|
|
78
|
-
}
|
|
79
|
-
if (cookie.sameSite != null) {
|
|
80
|
-
const capitalized = cookie.sameSite.charAt(0).toUpperCase() + cookie.sameSite.slice(1);
|
|
81
|
-
parts.push(`SameSite=${capitalized}`);
|
|
82
|
-
}
|
|
83
|
-
return parts.join("; ");
|
|
64
|
+
function toBatchRunResults(results) {
|
|
65
|
+
return results.map(
|
|
66
|
+
(r) => r.success ? {
|
|
67
|
+
cookies: r.cookies.map((c) => serializeCookie(c)),
|
|
68
|
+
data: r.data,
|
|
69
|
+
executed: [...r.executed],
|
|
70
|
+
ok: true,
|
|
71
|
+
runId: r.runId
|
|
72
|
+
} : { error: r.error ?? "unknown error", ok: false, runId: r.runId }
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
function toTeardownResults(results) {
|
|
76
|
+
return results.map((r) => ({ error: r.error, ok: r.success, runId: r.runId }));
|
|
84
77
|
}
|
|
85
78
|
|
|
86
79
|
export {
|
|
@@ -90,5 +83,6 @@ export {
|
|
|
90
83
|
observerRequestSchema,
|
|
91
84
|
verifyWebhookSignature,
|
|
92
85
|
serializeCookie,
|
|
93
|
-
|
|
86
|
+
toBatchRunResults,
|
|
87
|
+
toTeardownResults
|
|
94
88
|
};
|
|
@@ -25,7 +25,7 @@ function compile(ripplo) {
|
|
|
25
25
|
};
|
|
26
26
|
});
|
|
27
27
|
const tests = testDefs.map((def) => compileTest(def, preconditionDefs));
|
|
28
|
-
return { observers, preconditions, tests };
|
|
28
|
+
return { fixtures: {}, observers, preconditions, tests };
|
|
29
29
|
}
|
|
30
30
|
function validateUniqueIds(defs) {
|
|
31
31
|
const seen = /* @__PURE__ */ new Map();
|
package/dist/compiler.d.ts
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { Observer, Precondition, WorkflowSpec } from '@ripplo/spec';
|
|
2
|
-
import { d as RipploBuilder } from './builder-
|
|
3
|
-
import './types-
|
|
2
|
+
import { d as RipploBuilder } from './builder-DiVz3t1D.js';
|
|
3
|
+
import './types-BzZrl65Z.js';
|
|
4
4
|
import './step-De52hTLd.js';
|
|
5
5
|
|
|
6
|
+
interface CompiledFixture {
|
|
7
|
+
readonly sha256: string;
|
|
8
|
+
readonly size: number;
|
|
9
|
+
}
|
|
6
10
|
interface CompileResult {
|
|
11
|
+
readonly fixtures: Record<string, CompiledFixture>;
|
|
7
12
|
readonly observers: Record<string, Observer>;
|
|
8
13
|
readonly preconditions: Record<string, Precondition>;
|
|
9
14
|
readonly tests: ReadonlyArray<CompiledTest>;
|
|
@@ -24,4 +29,4 @@ interface CompiledTest {
|
|
|
24
29
|
}
|
|
25
30
|
declare function compile(ripplo: RipploBuilder): CompileResult;
|
|
26
31
|
|
|
27
|
-
export { type CompileResult, type CompiledTest, compile };
|
|
32
|
+
export { type CompileResult, type CompiledFixture, type CompiledTest, compile };
|
package/dist/compiler.js
CHANGED
package/dist/elysia.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Elysia } from 'elysia';
|
|
2
|
-
import { R as RipploEngine } from './engine-
|
|
3
|
-
import './builder-
|
|
4
|
-
import './types-
|
|
2
|
+
import { R as RipploEngine } from './engine-DVbF4E5A.js';
|
|
3
|
+
import './builder-DiVz3t1D.js';
|
|
4
|
+
import './types-BzZrl65Z.js';
|
|
5
5
|
import './step-De52hTLd.js';
|
|
6
6
|
import '@ripplo/spec';
|
|
7
7
|
|
package/dist/elysia.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
batchRequestSchema,
|
|
3
|
-
buildSetCookieHeader,
|
|
4
3
|
observerRequestSchema,
|
|
5
4
|
readAdapterWebhookSecret,
|
|
6
|
-
serializeCookie,
|
|
7
5
|
teardownRequestSchema,
|
|
6
|
+
toBatchRunResults,
|
|
7
|
+
toTeardownResults,
|
|
8
8
|
verifyWebhookSignature
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-XO36IU66.js";
|
|
10
10
|
import "./chunk-4MGIQFAJ.js";
|
|
11
11
|
|
|
12
12
|
// src/adapters/elysia.ts
|
|
@@ -27,29 +27,17 @@ function build({ enabled, engine }) {
|
|
|
27
27
|
}
|
|
28
28
|
const parsed = parseWith(gate.body, batchRequestSchema);
|
|
29
29
|
if (parsed == null) {
|
|
30
|
-
return jsonResponse({ error: "Invalid request body"
|
|
30
|
+
return jsonResponse({ error: "Invalid request body" }, 400);
|
|
31
31
|
}
|
|
32
32
|
const host = request.headers.get("host");
|
|
33
33
|
if (host == null || host.length === 0) {
|
|
34
|
-
return jsonResponse({ error: "Missing host header"
|
|
34
|
+
return jsonResponse({ error: "Missing host header" }, 400);
|
|
35
35
|
}
|
|
36
36
|
const proto = request.headers.get("x-forwarded-proto") ?? "http";
|
|
37
37
|
const appUrl = `${proto}://${host}`;
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
headers.append("Set-Cookie", buildSetCookieHeader(serializeCookie(cookie)));
|
|
42
|
-
});
|
|
43
|
-
return new Response(
|
|
44
|
-
JSON.stringify({
|
|
45
|
-
data: result.data,
|
|
46
|
-
error: result.error,
|
|
47
|
-
executed: result.executed,
|
|
48
|
-
runId: result.runId,
|
|
49
|
-
success: result.success
|
|
50
|
-
}),
|
|
51
|
-
{ headers, status: 200 }
|
|
52
|
-
);
|
|
38
|
+
const items = parsed.batch.map((b) => ({ names: b.preconditions, runId: b.runId }));
|
|
39
|
+
const results = await engine.executePreconditions(items, { appUrl });
|
|
40
|
+
return jsonResponse({ results: toBatchRunResults(results) }, 200);
|
|
53
41
|
}).put("/execute-observer", async ({ request }) => {
|
|
54
42
|
const gate = await verifyAndReadBody(request, webhookSecret);
|
|
55
43
|
if ("response" in gate) {
|
|
@@ -71,10 +59,15 @@ function build({ enabled, engine }) {
|
|
|
71
59
|
}
|
|
72
60
|
const parsed = parseWith(gate.body, teardownRequestSchema);
|
|
73
61
|
if (parsed == null) {
|
|
74
|
-
return jsonResponse({ error: "Invalid request body"
|
|
62
|
+
return jsonResponse({ error: "Invalid request body" }, 400);
|
|
75
63
|
}
|
|
76
|
-
|
|
77
|
-
|
|
64
|
+
const items = parsed.batch.map((b) => ({
|
|
65
|
+
data: b.data,
|
|
66
|
+
names: b.preconditions,
|
|
67
|
+
runId: b.runId
|
|
68
|
+
}));
|
|
69
|
+
const results = await engine.teardown(items);
|
|
70
|
+
return jsonResponse({ results: toTeardownResults(results) }, 200);
|
|
78
71
|
});
|
|
79
72
|
}
|
|
80
73
|
async function verifyAndReadBody(req, webhookSecret) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { c as PreconditionRegistry, a as ObserverRegistry, O as ObserverImplFn, P as PreconditionImpl, e as RipploInstance } from './builder-
|
|
2
|
-
import { P as Primitive, g as ObserverOutcome, C as CookieEntry, f as ObserverDefinition, l as PreconditionDefinition, U as UnimplementedItems, O as ObserverHandle, h as Precondition } from './types-
|
|
1
|
+
import { c as PreconditionRegistry, a as ObserverRegistry, O as ObserverImplFn, P as PreconditionImpl, e as RipploInstance } from './builder-DiVz3t1D.js';
|
|
2
|
+
import { P as Primitive, g as ObserverOutcome, C as CookieEntry, f as ObserverDefinition, l as PreconditionDefinition, U as UnimplementedItems, O as ObserverHandle, h as Precondition } from './types-BzZrl65Z.js';
|
|
3
3
|
|
|
4
|
-
interface
|
|
4
|
+
interface EngineRunResult {
|
|
5
5
|
readonly cookies: ReadonlyArray<CookieEntry>;
|
|
6
6
|
readonly data: Record<string, Record<string, Primitive>>;
|
|
7
7
|
readonly error: string | undefined;
|
|
@@ -12,6 +12,20 @@ interface EngineResult {
|
|
|
12
12
|
interface ExecuteBatchOptions {
|
|
13
13
|
readonly appUrl: string | undefined;
|
|
14
14
|
}
|
|
15
|
+
interface ExecuteBatchItem {
|
|
16
|
+
readonly names: ReadonlyArray<string>;
|
|
17
|
+
readonly runId: string;
|
|
18
|
+
}
|
|
19
|
+
interface TeardownBatchItem {
|
|
20
|
+
readonly data: Record<string, Record<string, Primitive>>;
|
|
21
|
+
readonly names: ReadonlyArray<string>;
|
|
22
|
+
readonly runId: string;
|
|
23
|
+
}
|
|
24
|
+
interface TeardownRunResult {
|
|
25
|
+
readonly error: string | undefined;
|
|
26
|
+
readonly runId: string;
|
|
27
|
+
readonly success: boolean;
|
|
28
|
+
}
|
|
15
29
|
interface ObserverExecutionResult {
|
|
16
30
|
readonly error: string | undefined;
|
|
17
31
|
readonly outcome: ObserverOutcome | undefined;
|
|
@@ -19,11 +33,11 @@ interface ObserverExecutionResult {
|
|
|
19
33
|
}
|
|
20
34
|
interface RipploEngine {
|
|
21
35
|
readonly executeObserver: (name: string, params: Record<string, Primitive>) => Promise<ObserverExecutionResult>;
|
|
22
|
-
readonly executePreconditions: (
|
|
36
|
+
readonly executePreconditions: (items: ReadonlyArray<ExecuteBatchItem>, options?: ExecuteBatchOptions) => Promise<ReadonlyArray<EngineRunResult>>;
|
|
23
37
|
readonly getObservers: () => ReadonlyArray<ObserverDefinition>;
|
|
24
38
|
readonly getPreconditions: () => ReadonlyArray<PreconditionDefinition>;
|
|
25
39
|
readonly getUnimplemented: () => UnimplementedItems;
|
|
26
|
-
readonly teardown: (
|
|
40
|
+
readonly teardown: (items: ReadonlyArray<TeardownBatchItem>) => Promise<ReadonlyArray<TeardownRunResult>>;
|
|
27
41
|
}
|
|
28
42
|
declare const NOT_IMPLEMENTED_BRAND: unique symbol;
|
|
29
43
|
interface NotImplemented {
|
|
@@ -43,4 +57,4 @@ interface EngineImpls<P extends PreconditionRegistry, O extends ObserverRegistry
|
|
|
43
57
|
}
|
|
44
58
|
declare function createEngine<P extends PreconditionRegistry, O extends ObserverRegistry>(ripplo: RipploInstance<P, O>, impls: EngineImpls<P, O>): RipploEngine;
|
|
45
59
|
|
|
46
|
-
export { type
|
|
60
|
+
export { type EngineRunResult as E, type NotImplemented as N, type ObserverImplFnFor as O, type PreconditionImplFor as P, type RipploEngine as R, type TeardownRunResult as T, type EngineImpls as a, type ExecuteBatchItem as b, type ExecuteBatchOptions as c, type TeardownBatchItem as d, createEngine as e, notImplemented as n };
|
package/dist/express.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Router } from 'express';
|
|
2
|
-
import { R as RipploEngine } from './engine-
|
|
3
|
-
import './builder-
|
|
4
|
-
import './types-
|
|
2
|
+
import { R as RipploEngine } from './engine-DVbF4E5A.js';
|
|
3
|
+
import './builder-DiVz3t1D.js';
|
|
4
|
+
import './types-BzZrl65Z.js';
|
|
5
5
|
import './step-De52hTLd.js';
|
|
6
6
|
import '@ripplo/spec';
|
|
7
7
|
|