@ripplo/testing 0.4.8 → 0.5.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/dist/hono.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-V6LMXKGL.js";
9
+ } from "./chunk-XO36IU66.js";
10
10
  import "./chunk-4MGIQFAJ.js";
11
11
 
12
12
  // src/adapters/hono.ts
@@ -22,25 +22,17 @@ function createHonoHandler({ enabled, engine }) {
22
22
  const body = tryParseJson(c.get("rawBody"));
23
23
  const parsed = body == null ? null : batchRequestSchema.safeParse(body);
24
24
  if (parsed == null || !parsed.success) {
25
- return c.json({ error: "Invalid request body", success: false }, 400);
25
+ return c.json({ error: "Invalid request body" }, 400);
26
26
  }
27
27
  const host = c.req.header("host");
28
28
  if (host == null || host.length === 0) {
29
- return c.json({ error: "Missing host header", success: false }, 400);
29
+ return c.json({ error: "Missing host header" }, 400);
30
30
  }
31
31
  const proto = c.req.header("x-forwarded-proto") ?? "http";
32
32
  const appUrl = `${proto}://${host}`;
33
- const result = await engine.executePreconditions(parsed.data.preconditions, { appUrl });
34
- result.cookies.forEach((cookie) => {
35
- c.header("Set-Cookie", buildSetCookieHeader(serializeCookie(cookie)), { append: true });
36
- });
37
- return c.json({
38
- data: result.data,
39
- error: result.error,
40
- executed: result.executed,
41
- runId: result.runId,
42
- success: result.success
43
- });
33
+ const items = parsed.data.batch.map((b) => ({ names: b.preconditions, runId: b.runId }));
34
+ const results = await engine.executePreconditions(items, { appUrl });
35
+ return c.json({ results: toBatchRunResults(results) });
44
36
  });
45
37
  app.put("/execute-observer", async (c) => {
46
38
  const body = tryParseJson(c.get("rawBody"));
@@ -49,20 +41,21 @@ function createHonoHandler({ enabled, engine }) {
49
41
  return c.json({ error: "Invalid request body", success: false }, 400);
50
42
  }
51
43
  const result = await engine.executeObserver(parsed.data.observer, parsed.data.params);
52
- return c.json({
53
- error: result.error,
54
- outcome: result.outcome,
55
- success: result.success
56
- });
44
+ return c.json({ error: result.error, outcome: result.outcome, success: result.success });
57
45
  });
58
46
  app.put("/teardown-preconditions", async (c) => {
59
47
  const body = tryParseJson(c.get("rawBody"));
60
48
  const parsed = body == null ? null : teardownRequestSchema.safeParse(body);
61
49
  if (parsed == null || !parsed.success) {
62
- return c.json({ error: "Invalid request body", success: false }, 400);
50
+ return c.json({ error: "Invalid request body" }, 400);
63
51
  }
64
- await engine.teardown(parsed.data.preconditions, parsed.data.data);
65
- return c.json({ success: true });
52
+ const items = parsed.data.batch.map((b) => ({
53
+ data: b.data,
54
+ names: b.preconditions,
55
+ runId: b.runId
56
+ }));
57
+ const results = await engine.teardown(items);
58
+ return c.json({ results: toTeardownResults(results) });
66
59
  });
67
60
  return app;
68
61
  }
package/dist/index.d.ts CHANGED
@@ -1,9 +1,10 @@
1
- export { C as CoverageRegistry, O as ObserverImplFn, a as ObserverRegistry, P as PreconditionImpl, b as PreconditionRecord, c as PreconditionRegistry, R as ResolveDeps, d as RipploBuilder, e as RipploInstance, f as RipploRegistries, g as createRipplo, o as observer, p as precondition, t as test } from './builder-BMjy83Iy.js';
1
+ export { C as CoverageRegistry, O as ObserverImplFn, a as ObserverRegistry, P as PreconditionImpl, b as PreconditionRecord, c as PreconditionRegistry, R as ResolveDeps, d as RipploBuilder, e as RipploInstance, f as RipploRegistries, g as createRipplo, o as observer, p as precondition, t as test } from './builder-DiVz3t1D.js';
2
2
  import { CompileResult } from './compiler.js';
3
3
  export { CompiledTest, compile } from './compiler.js';
4
- export { E as EngineImpls, a as EngineResult, b as ExecuteBatchOptions, N as NotImplemented, O as ObserverImplFnFor, P as PreconditionImplFor, R as RipploEngine, c as createEngine, n as notImplemented } from './engine-DMOkJdjd.js';
5
- import { C as CookieEntry } from './types-16SB7zjP.js';
6
- export { c as CookieOptions, D as DEFAULT_IGNORE_PATHS, d as DEFAULT_WATCH_PATHS, e as ObserverContext, f as ObserverDefinition, O as ObserverHandle, a as ObserverInput, g as ObserverOutcome, h as Precondition, i as PreconditionDeps, P as Primitive, S as SetupContext, T as TeardownContext, j as TestDefinition, k as TestValue } from './types-16SB7zjP.js';
4
+ import { E as EngineRunResult, T as TeardownRunResult$1 } from './engine-DVbF4E5A.js';
5
+ export { a as EngineImpls, b as ExecuteBatchItem, c as ExecuteBatchOptions, N as NotImplemented, O as ObserverImplFnFor, P as PreconditionImplFor, R as RipploEngine, d as TeardownBatchItem, e as createEngine, n as notImplemented } from './engine-DVbF4E5A.js';
6
+ import { C as CookieEntry } from './types-BzZrl65Z.js';
7
+ export { c as CookieOptions, D as DEFAULT_IGNORE_PATHS, d as DEFAULT_WATCH_PATHS, e as ObserverContext, f as ObserverDefinition, O as ObserverHandle, a as ObserverInput, g as ObserverOutcome, h as Precondition, i as PreconditionDeps, P as Primitive, S as SetupContext, T as TeardownContext, j as TestDefinition, k as TestValue } from './types-BzZrl65Z.js';
7
8
  export { D as DslNodeInput } from './step-De52hTLd.js';
8
9
  import '@ripplo/spec';
9
10
 
@@ -18,15 +19,10 @@ interface LintResult {
18
19
  }
19
20
  declare function lint(result: CompileResult): LintResult;
20
21
 
21
- interface WebhookHeaders {
22
- readonly "webhook-id": string | undefined;
23
- readonly "webhook-signature": string | undefined;
24
- readonly "webhook-timestamp": string | undefined;
25
- }
26
- declare function verifyWebhookSignature(payload: string, headers: WebhookHeaders, secret: string): boolean;
22
+ declare function readAdapterWebhookSecret(): string;
27
23
  interface SerializedCookie {
28
24
  readonly domain: string | undefined;
29
- readonly expires: Date | undefined;
25
+ readonly expires: number | undefined;
30
26
  readonly httpOnly: boolean | undefined;
31
27
  readonly name: string;
32
28
  readonly path: string | undefined;
@@ -34,7 +30,30 @@ interface SerializedCookie {
34
30
  readonly secure: boolean | undefined;
35
31
  readonly value: string;
36
32
  }
33
+ type BatchRunResult = {
34
+ readonly cookies: ReadonlyArray<SerializedCookie>;
35
+ readonly data: Record<string, Record<string, string | number | boolean>>;
36
+ readonly executed: ReadonlyArray<string>;
37
+ readonly ok: true;
38
+ readonly runId: string;
39
+ } | {
40
+ readonly error: string;
41
+ readonly ok: false;
42
+ readonly runId: string;
43
+ };
44
+ type TeardownRunResult = {
45
+ readonly error: string | undefined;
46
+ readonly ok: boolean;
47
+ readonly runId: string;
48
+ };
49
+ interface WebhookHeaders {
50
+ readonly "webhook-id": string | undefined;
51
+ readonly "webhook-signature": string | undefined;
52
+ readonly "webhook-timestamp": string | undefined;
53
+ }
54
+ declare function verifyWebhookSignature(payload: string, headers: WebhookHeaders, secret: string): boolean;
37
55
  declare function serializeCookie(cookie: CookieEntry): SerializedCookie;
38
- declare function buildSetCookieHeader(cookie: SerializedCookie): string;
56
+ declare function toBatchRunResults(results: ReadonlyArray<EngineRunResult>): ReadonlyArray<BatchRunResult>;
57
+ declare function toTeardownResults(results: ReadonlyArray<TeardownRunResult$1>): ReadonlyArray<TeardownRunResult>;
39
58
 
40
- export { CompileResult, CookieEntry, type LintDiagnostic, type LintResult, type SerializedCookie, buildSetCookieHeader, lint, serializeCookie, verifyWebhookSignature };
59
+ export { CompileResult, CookieEntry, EngineRunResult, type LintDiagnostic, type LintResult, type SerializedCookie, TeardownRunResult$1 as TeardownRunResult, lint, readAdapterWebhookSecret, serializeCookie, toBatchRunResults, toTeardownResults, verifyWebhookSignature };
package/dist/index.js CHANGED
@@ -17,10 +17,12 @@ import {
17
17
  } from "./chunk-YFOTJIVF.js";
18
18
  import "./chunk-MGATMMCZ.js";
19
19
  import {
20
- buildSetCookieHeader,
20
+ readAdapterWebhookSecret,
21
21
  serializeCookie,
22
+ toBatchRunResults,
23
+ toTeardownResults,
22
24
  verifyWebhookSignature
23
- } from "./chunk-V6LMXKGL.js";
25
+ } from "./chunk-XO36IU66.js";
24
26
  import "./chunk-4MGIQFAJ.js";
25
27
 
26
28
  // src/chainable.ts
@@ -149,7 +151,7 @@ function stubPreconditionDef(p) {
149
151
  name,
150
152
  returns: [],
151
153
  teardown: void 0,
152
- setup: () => Promise.resolve({})
154
+ setup: () => Promise.resolve([])
153
155
  };
154
156
  }
155
157
  function stubObserverDef(o) {
@@ -860,7 +862,7 @@ function createEngine(ripplo, impls) {
860
862
  const observersByName = new Map(observerDefs.map((d) => [d.name, d]));
861
863
  return {
862
864
  executeObserver: (name, params) => executeObserver(observersByName, name, params),
863
- executePreconditions: (names, options) => executePreconditions({ defsByName: preconditionsByName, names, options }),
865
+ executePreconditions: (items, options) => executePreconditions({ defsByName: preconditionsByName, items, options }),
864
866
  getObservers: () => observerDefs,
865
867
  getPreconditions: () => preconditionDefs,
866
868
  getUnimplemented: () => ({
@@ -868,7 +870,7 @@ function createEngine(ripplo, impls) {
868
870
  preconditions: preconditionDefs.filter((p) => !p.implemented).map((p) => p.name),
869
871
  tests: ripplo.tests.filter((t) => !t.implemented).map((t) => t.id)
870
872
  }),
871
- teardown: (names, data) => teardown(preconditionsByName, names, data)
873
+ teardown: (items) => teardown({ defsByName: preconditionsByName, items })
872
874
  };
873
875
  }
874
876
  function wirePreconditions(registry, impls) {
@@ -913,17 +915,34 @@ function makeWiredPreconditionDef(handle, impl) {
913
915
  implemented: true,
914
916
  name,
915
917
  returns: [],
916
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- typedImpl.teardown has narrower TData, wider context at engine boundary
917
- teardown: typedImpl.teardown,
918
- setup: async (ctx, allDeps) => {
919
- const resolved = {};
920
- mapping.forEach(([key, depName]) => {
921
- const data = allDeps[depName];
922
- if (data != null) {
923
- resolved[key] = data;
924
- }
918
+ setup: async (items) => {
919
+ const resolvedItems = items.map((item) => {
920
+ const resolved = {};
921
+ mapping.forEach(([key, depName]) => {
922
+ const data = item.deps[depName];
923
+ if (data != null) {
924
+ resolved[key] = data;
925
+ }
926
+ });
927
+ return { ctx: item.ctx, deps: resolved };
928
+ });
929
+ const results = await typedImpl.setup(resolvedItems);
930
+ return results.map((r) => {
931
+ const out = {};
932
+ Object.entries(r).forEach(([key, value]) => {
933
+ if (!isPrimitive(value)) {
934
+ throw new TypeError(
935
+ `Precondition "${name}" returned non-primitive value at key "${key}". Setup return values must be string, number, or boolean \u2014 produced via ctx.fixed/uniqueEmail/uniqueId.`
936
+ );
937
+ }
938
+ out[key] = value;
939
+ });
940
+ return out;
925
941
  });
926
- return typedImpl.setup(ctx, resolved);
942
+ },
943
+ teardown: async (items) => {
944
+ const typed = items;
945
+ await typedImpl.teardown(typed);
927
946
  }
928
947
  };
929
948
  }
@@ -964,88 +983,170 @@ function readBudgetOf(handle) {
964
983
  }
965
984
  async function executePreconditions({
966
985
  defsByName,
967
- names,
986
+ items,
968
987
  options
969
988
  }) {
970
- const runId = crypto.randomUUID().slice(0, 12);
971
- const cookies = [];
972
989
  const defaultDomain = deriveDefaultDomain(options?.appUrl);
973
- const state = {
974
- cookies,
975
- ctx: createSetupContext({ cookies, defaultDomain, runId }),
976
- data: {},
977
- defsByName,
978
- executed: [],
979
- runId
980
- };
981
- return runBatchSequence(state, names);
990
+ const initial = items.map((item) => {
991
+ const cookiesRef = [];
992
+ return {
993
+ cookies: cookiesRef,
994
+ cookiesRef,
995
+ ctx: createSetupContext({ cookies: cookiesRef, defaultDomain, runId: item.runId }),
996
+ data: {},
997
+ executed: [],
998
+ failure: void 0,
999
+ names: item.names,
1000
+ runId: item.runId
1001
+ };
1002
+ });
1003
+ const order = topoOrder(defsByName, items);
1004
+ const orderError = order.error;
1005
+ if (orderError != null) {
1006
+ return initial.map((s) => failResult(s, orderError));
1007
+ }
1008
+ const sequence = order.names ?? [];
1009
+ let states = initial;
1010
+ let i = 0;
1011
+ while (i < sequence.length) {
1012
+ const name = sequence[i];
1013
+ if (name != null) {
1014
+ states = await executeStep({ defsByName, name, states });
1015
+ }
1016
+ i += 1;
1017
+ }
1018
+ return states.map(
1019
+ (s) => s.failure == null ? {
1020
+ cookies: s.cookiesRef,
1021
+ data: s.data,
1022
+ error: void 0,
1023
+ executed: s.executed,
1024
+ runId: s.runId,
1025
+ success: true
1026
+ } : failResult(s, s.failure)
1027
+ );
982
1028
  }
983
- async function runBatchSequence(state, names) {
984
- let index = 0;
985
- while (index < names.length) {
986
- const name = names[index];
987
- if (name == null) {
988
- break;
1029
+ function topoOrder(defsByName, items) {
1030
+ const seen = /* @__PURE__ */ new Set();
1031
+ const order = [];
1032
+ const visiting = /* @__PURE__ */ new Set();
1033
+ const visitDeps = (deps) => {
1034
+ const found = deps.find((d) => visit(d) != null);
1035
+ return found == null ? void 0 : visit(found);
1036
+ };
1037
+ const visit = (name) => {
1038
+ if (seen.has(name)) {
1039
+ return void 0;
989
1040
  }
990
- const error = validatePrecondition(state.defsByName, name);
991
- if (error != null) {
992
- return fail(state, error);
1041
+ if (visiting.has(name)) {
1042
+ return `Cycle detected at precondition "${name}"`;
993
1043
  }
994
- const stepError = await executeOnePrecondition(state, name);
995
- if (stepError != null) {
996
- return fail(state, stepError);
1044
+ const def = defsByName.get(name);
1045
+ if (def == null) {
1046
+ return `Unknown precondition: "${name}"`;
997
1047
  }
998
- index += 1;
999
- }
1000
- return {
1001
- cookies: state.cookies,
1002
- data: state.data,
1003
- error: void 0,
1004
- executed: state.executed,
1005
- runId: state.runId,
1006
- success: true
1048
+ visiting.add(name);
1049
+ const deps = def.depMapping.map(([, depName]) => depName);
1050
+ const cycleError = visitDeps(deps);
1051
+ visiting.delete(name);
1052
+ if (cycleError != null) {
1053
+ return cycleError;
1054
+ }
1055
+ seen.add(name);
1056
+ order.push(name);
1057
+ return void 0;
1007
1058
  };
1059
+ let err;
1060
+ let k = 0;
1061
+ const allNames = items.flatMap((item) => item.names);
1062
+ while (k < allNames.length && err == null) {
1063
+ const n = allNames[k];
1064
+ if (n != null) {
1065
+ err = visit(n);
1066
+ }
1067
+ k += 1;
1068
+ }
1069
+ if (err != null) {
1070
+ return { error: err, names: void 0 };
1071
+ }
1072
+ return { error: void 0, names: order };
1008
1073
  }
1009
- function validatePrecondition(defsByName, name) {
1074
+ async function executeStep({
1075
+ defsByName,
1076
+ name,
1077
+ states
1078
+ }) {
1079
+ const isActive = (s) => s.failure == null && s.names.includes(name);
1080
+ const active = states.filter((s) => isActive(s));
1081
+ if (active.length === 0) {
1082
+ return states;
1083
+ }
1010
1084
  const def = defsByName.get(name);
1011
1085
  if (def == null) {
1012
- return `Unknown precondition: "${name}"`;
1086
+ return states.map(
1087
+ (s) => isActive(s) ? { ...s, failure: `Unknown precondition: "${name}"` } : s
1088
+ );
1013
1089
  }
1014
1090
  if (!def.implemented) {
1015
- return `Precondition "${name}" is not implemented`;
1091
+ return states.map(
1092
+ (s) => isActive(s) ? { ...s, failure: `Precondition "${name}" is not implemented` } : s
1093
+ );
1016
1094
  }
1017
- return void 0;
1095
+ const stepResult = await runSetup({ active, def, name });
1096
+ return states.map((s) => isActive(s) ? mergeStepResult(s, name, stepResult) : s);
1018
1097
  }
1019
- async function executeOnePrecondition(state, name) {
1020
- const def = state.defsByName.get(name);
1021
- if (def == null) {
1022
- return `Unknown precondition: "${name}"`;
1023
- }
1098
+ async function runSetup({ active, def, name }) {
1099
+ const itemsForCall = active.map((s) => ({ ctx: s.ctx, deps: s.data }));
1024
1100
  try {
1025
- const result = await def.setup(state.ctx, state.data);
1026
- const resolved = {};
1027
- Object.entries(result).forEach(([key, value]) => {
1028
- if (!isPrimitive(value)) {
1029
- throw new TypeError(
1030
- `Precondition "${name}" returned non-primitive value at key "${key}". Setup return values must be string, number, or boolean \u2014 produced via ctx.fixed/uniqueEmail/uniqueId.`
1031
- );
1032
- }
1033
- resolved[key] = value;
1101
+ const results = await def.setup(itemsForCall);
1102
+ if (results.length !== active.length) {
1103
+ return {
1104
+ batchError: `Precondition "${name}" returned ${String(results.length)} results for ${String(active.length)} items`,
1105
+ perItem: /* @__PURE__ */ new Map()
1106
+ };
1107
+ }
1108
+ const entries = active.map((s, idx) => {
1109
+ const r = results[idx];
1110
+ return r == null ? void 0 : [s.runId, r];
1034
1111
  });
1035
- state.data[name] = resolved;
1036
- state.executed.push(name);
1037
- return void 0;
1112
+ const valid = entries.filter(
1113
+ (e) => e != null
1114
+ );
1115
+ if (valid.length !== active.length) {
1116
+ return {
1117
+ batchError: `Precondition "${name}" returned a null result for at least one item`,
1118
+ perItem: /* @__PURE__ */ new Map()
1119
+ };
1120
+ }
1121
+ return { batchError: void 0, perItem: new Map(valid) };
1038
1122
  } catch (error) {
1039
- return error instanceof Error ? error.message : String(error);
1123
+ return {
1124
+ batchError: error instanceof Error ? error.message : String(error),
1125
+ perItem: /* @__PURE__ */ new Map()
1126
+ };
1040
1127
  }
1041
1128
  }
1042
- function fail(state, error) {
1129
+ function mergeStepResult(s, name, result) {
1130
+ if (result.batchError != null) {
1131
+ return { ...s, failure: result.batchError };
1132
+ }
1133
+ const data = result.perItem.get(s.runId);
1134
+ if (data == null) {
1135
+ return { ...s, failure: `Precondition "${name}" produced no result for run "${s.runId}"` };
1136
+ }
1043
1137
  return {
1044
- cookies: state.cookies,
1045
- data: state.data,
1138
+ ...s,
1139
+ data: { ...s.data, [name]: data },
1140
+ executed: [...s.executed, name]
1141
+ };
1142
+ }
1143
+ function failResult(s, error) {
1144
+ return {
1145
+ cookies: s.cookies,
1146
+ data: s.data,
1046
1147
  error,
1047
- executed: state.executed,
1048
- runId: state.runId,
1148
+ executed: s.executed,
1149
+ runId: s.runId,
1049
1150
  success: false
1050
1151
  };
1051
1152
  }
@@ -1074,25 +1175,56 @@ function createObserverContext(runId) {
1074
1175
  retry: (reason) => createRetryOutcome(reason)
1075
1176
  };
1076
1177
  }
1077
- async function teardown(defsByName, names, data) {
1078
- const reversed = [...names].toReversed();
1079
- let index = 0;
1080
- while (index < reversed.length) {
1081
- const name = reversed[index];
1178
+ async function teardown({
1179
+ defsByName,
1180
+ items
1181
+ }) {
1182
+ const errors = /* @__PURE__ */ new Map();
1183
+ const allNamesUnique = [...new Set(items.flatMap((i2) => i2.names))];
1184
+ const order = topoOrder(
1185
+ defsByName,
1186
+ allNamesUnique.map((n) => ({ names: [n], runId: "" }))
1187
+ );
1188
+ const reversed = (order.names ?? []).toReversed();
1189
+ let i = 0;
1190
+ while (i < reversed.length) {
1191
+ const name = reversed[i];
1082
1192
  if (name != null) {
1083
- await teardownOne(defsByName, name, data);
1193
+ await teardownStep({ defsByName, errors, items, name });
1084
1194
  }
1085
- index += 1;
1195
+ i += 1;
1086
1196
  }
1197
+ return items.map((it) => {
1198
+ const err = errors.get(it.runId);
1199
+ return { error: err, runId: it.runId, success: err == null };
1200
+ });
1087
1201
  }
1088
- async function teardownOne(defsByName, name, data) {
1202
+ async function teardownStep({
1203
+ defsByName,
1204
+ errors,
1205
+ items,
1206
+ name
1207
+ }) {
1089
1208
  const def = defsByName.get(name);
1090
1209
  if (def?.teardown == null) {
1091
1210
  return;
1092
1211
  }
1212
+ const active = items.filter((i) => i.names.includes(name));
1213
+ if (active.length === 0) {
1214
+ return;
1215
+ }
1216
+ const teardownItems = active.map((it) => ({
1217
+ ctx: { data: it.data[name] ?? {} }
1218
+ }));
1093
1219
  try {
1094
- await def.teardown({ data: data[name] ?? {} });
1095
- } catch {
1220
+ await def.teardown(teardownItems);
1221
+ } catch (error) {
1222
+ const message = error instanceof Error ? error.message : String(error);
1223
+ active.forEach((it) => {
1224
+ if (!errors.has(it.runId)) {
1225
+ errors.set(it.runId, message);
1226
+ }
1227
+ });
1096
1228
  }
1097
1229
  }
1098
1230
  function createSetupContext({
@@ -1124,7 +1256,6 @@ function deriveDefaultDomain(baseUrl) {
1124
1256
  export {
1125
1257
  DEFAULT_IGNORE_PATHS,
1126
1258
  DEFAULT_WATCH_PATHS,
1127
- buildSetCookieHeader,
1128
1259
  compile,
1129
1260
  createEngine,
1130
1261
  createRipplo,
@@ -1132,7 +1263,10 @@ export {
1132
1263
  notImplemented,
1133
1264
  observer,
1134
1265
  precondition,
1266
+ readAdapterWebhookSecret,
1135
1267
  serializeCookie,
1136
1268
  test,
1269
+ toBatchRunResults,
1270
+ toTeardownResults,
1137
1271
  verifyWebhookSignature
1138
1272
  };
package/dist/koa.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Middleware } from 'koa';
2
- import { R as RipploEngine } from './engine-DMOkJdjd.js';
3
- import './builder-BMjy83Iy.js';
4
- import './types-16SB7zjP.js';
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/koa.js CHANGED
@@ -1,18 +1,18 @@
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-V6LMXKGL.js";
9
+ } from "./chunk-XO36IU66.js";
10
10
  import "./chunk-4MGIQFAJ.js";
11
11
 
12
12
  // src/adapters/koa.ts
13
13
  function createKoaHandler({ enabled, engine }) {
14
14
  if (!enabled) {
15
- return async (ctx, next) => {
15
+ return async (_ctx, next) => {
16
16
  await next();
17
17
  };
18
18
  }
@@ -57,29 +57,21 @@ async function handleExecutePreconditions({ body, ctx, engine }) {
57
57
  const parsed = json == null ? null : batchRequestSchema.safeParse(json);
58
58
  if (parsed == null || !parsed.success) {
59
59
  ctx.status = 400;
60
- ctx.body = { error: "Invalid request body", success: false };
60
+ ctx.body = { error: "Invalid request body" };
61
61
  return;
62
62
  }
63
63
  const host = ctx.get("host");
64
64
  if (host.length === 0) {
65
65
  ctx.status = 400;
66
- ctx.body = { error: "Missing host header", success: false };
66
+ ctx.body = { error: "Missing host header" };
67
67
  return;
68
68
  }
69
69
  const proto = ctx.get("x-forwarded-proto").length > 0 ? ctx.get("x-forwarded-proto") : "http";
70
70
  const appUrl = `${proto}://${host}`;
71
- const result = await engine.executePreconditions(parsed.data.preconditions, { appUrl });
72
- result.cookies.forEach((cookie) => {
73
- ctx.append("Set-Cookie", buildSetCookieHeader(serializeCookie(cookie)));
74
- });
71
+ const items = parsed.data.batch.map((b) => ({ names: b.preconditions, runId: b.runId }));
72
+ const results = await engine.executePreconditions(items, { appUrl });
75
73
  ctx.status = 200;
76
- ctx.body = {
77
- data: result.data,
78
- error: result.error,
79
- executed: result.executed,
80
- runId: result.runId,
81
- success: result.success
82
- };
74
+ ctx.body = { results: toBatchRunResults(results) };
83
75
  }
84
76
  async function handleExecuteObserver({ body, ctx, engine }) {
85
77
  const json = tryParseJson(body);
@@ -98,12 +90,17 @@ async function handleTeardown({ body, ctx, engine }) {
98
90
  const parsed = json == null ? null : teardownRequestSchema.safeParse(json);
99
91
  if (parsed == null || !parsed.success) {
100
92
  ctx.status = 400;
101
- ctx.body = { error: "Invalid request body", success: false };
93
+ ctx.body = { error: "Invalid request body" };
102
94
  return;
103
95
  }
104
- await engine.teardown(parsed.data.preconditions, parsed.data.data);
96
+ const items = parsed.data.batch.map((b) => ({
97
+ data: b.data,
98
+ names: b.preconditions,
99
+ runId: b.runId
100
+ }));
101
+ const results = await engine.teardown(items);
105
102
  ctx.status = 200;
106
- ctx.body = { success: true };
103
+ ctx.body = { results: toTeardownResults(results) };
107
104
  }
108
105
  function lastPathSegment(path) {
109
106
  const segments = path.split("/").filter((s) => s.length > 0);
@@ -1,8 +1,8 @@
1
1
  import { Codec } from '@ripplo/spec';
2
2
  import { z } from 'zod';
3
3
  import { CompileResult } from './compiler.js';
4
- import './builder-BMjy83Iy.js';
5
- import './types-16SB7zjP.js';
4
+ import './builder-DiVz3t1D.js';
5
+ import './types-BzZrl65Z.js';
6
6
  import './step-De52hTLd.js';
7
7
 
8
8
  declare const LOCKFILE_RELATIVE_PATH = ".ripplo/ripplo.lock";
@@ -62,6 +62,12 @@ declare const lockfileBodyV2Schema: z.ZodObject<{
62
62
  name: z.ZodOptional<z.ZodString>;
63
63
  role: z.ZodString;
64
64
  }, z.core.$strip>], "by">;
65
+ modifier: z.ZodOptional<z.ZodEnum<{
66
+ Alt: "Alt";
67
+ Control: "Control";
68
+ Meta: "Meta";
69
+ Shift: "Shift";
70
+ }>>;
65
71
  type: z.ZodLiteral<"click">;
66
72
  id: z.ZodString;
67
73
  label: z.ZodOptional<z.ZodString>;
package/dist/lockfile.js CHANGED
@@ -237,7 +237,12 @@ var gotoNode = z7.object({
237
237
  type: z7.literal("goto"),
238
238
  url: stringValueRefSchema
239
239
  });
240
- var clickNode = z7.object({ ...nodeBase, locator: locatorSchema, type: z7.literal("click") });
240
+ var clickNode = z7.object({
241
+ ...nodeBase,
242
+ locator: locatorSchema,
243
+ modifier: z7.enum(["Alt", "Control", "Meta", "Shift"]).optional(),
244
+ type: z7.literal("click")
245
+ });
241
246
  var fillNode = z7.object({
242
247
  ...nodeBase,
243
248
  locator: locatorSchema,