@ultimat3/action 30.3.0 → 30.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.
Files changed (2) hide show
  1. package/package.json +8 -8
  2. package/src/exercise.ts +24 -20
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ultimat3/action",
3
- "version": "30.3.0",
3
+ "version": "30.5.0",
4
4
  "description": "The action primitive: one declaration projected to route, OpenAPI, client, MCP tool, job handle, tests",
5
5
  "keywords": [
6
6
  "ultimate",
@@ -46,12 +46,12 @@
46
46
  "test": "bun test"
47
47
  },
48
48
  "dependencies": {
49
- "@ultimat3/cache": "30.3.0",
50
- "@ultimat3/core": "30.3.0",
51
- "@ultimat3/db": "30.3.0",
52
- "@ultimat3/entity": "30.3.0",
53
- "@ultimat3/http": "30.3.0",
54
- "@ultimat3/policy": "30.3.0",
55
- "@ultimat3/schema": "30.3.0"
49
+ "@ultimat3/cache": "30.5.0",
50
+ "@ultimat3/core": "30.5.0",
51
+ "@ultimat3/db": "30.5.0",
52
+ "@ultimat3/entity": "30.5.0",
53
+ "@ultimat3/http": "30.5.0",
54
+ "@ultimat3/policy": "30.5.0",
55
+ "@ultimat3/schema": "30.5.0"
56
56
  }
57
57
  }
package/src/exercise.ts CHANGED
@@ -13,7 +13,7 @@ import {
13
13
  runExercise,
14
14
  tryUseContext,
15
15
  } from '@ultimat3/core';
16
- import { statusFor } from '@ultimat3/http';
16
+ import { statusFor, withScratchRateLimitStore } from '@ultimat3/http';
17
17
  import { admitsAnonymous } from '@ultimat3/policy';
18
18
  import { arbitrary } from '@ultimat3/schema/sample';
19
19
  import type { AnyAction } from './action';
@@ -66,7 +66,8 @@ export const exerciseTestName = (name: string): string =>
66
66
  * transaction is open, the outbox stage. NOT rolled back: a write on a second client or
67
67
  * `outsideTransaction`, a job enqueued with `outbox: false`, every job enqueue when no SQL
68
68
  * transaction is open (the memory store: the jobs facade enqueues straight to the job driver),
69
- * storage puts, mail, outbound fetch (sealed under the test preload) and rate-limit counters.
69
+ * storage puts, mail and outbound fetch (sealed under the test preload). Rate-limit counters are
70
+ * spent in a scratch store of the exercise's own, so the installed one is left as it was.
70
71
  */
71
72
  export async function exerciseAction(
72
73
  target: AnyAction,
@@ -75,24 +76,27 @@ export async function exerciseAction(
75
76
  ): Promise<ExerciseReport> {
76
77
  const name = actionName(target);
77
78
  const base = ctx ?? tryUseContext() ?? ctxOf({});
78
- const verdict = await runExercise({
79
- name,
80
- runs: options.runs,
81
- stoppedLabel: 'stopped before the handler',
82
- scoped: rollbackAvailable(),
83
- draw: (seed, run) => arbitrary(target.input, { seed, run }),
84
- bind: bindDrawn(options.input),
85
- accepts: (input) => acceptedBy(target, input, name),
86
- attempt: async (input): Promise<ExerciseAttempt> => {
87
- const before = handlerReached(target);
88
- const settled = await inRolledBackScope(() =>
89
- invoke(target, input, { ctx: base, actor: options.actor, surface: 'http' }),
90
- );
91
- const reached = handlerReached(target) > before;
92
- return settled.ok ? { ok: true, reached } : { ok: false, reached, error: settled.error };
93
- },
94
- statusOf: statusFor,
95
- });
79
+ // Counted in a scratch store: a spent bucket would make the file's next real call a 429.
80
+ const verdict = await withScratchRateLimitStore(() =>
81
+ runExercise({
82
+ name,
83
+ runs: options.runs,
84
+ stoppedLabel: 'stopped before the handler',
85
+ scoped: rollbackAvailable(),
86
+ draw: (seed, run) => arbitrary(target.input, { seed, run }),
87
+ bind: bindDrawn(options.input),
88
+ accepts: (input) => acceptedBy(target, input, name),
89
+ attempt: async (input): Promise<ExerciseAttempt> => {
90
+ const before = handlerReached(target);
91
+ const settled = await inRolledBackScope(() =>
92
+ invoke(target, input, { ctx: base, actor: options.actor, surface: 'http' }),
93
+ );
94
+ const reached = handlerReached(target) > before;
95
+ return settled.ok ? { ok: true, reached } : { ok: false, reached, error: settled.error };
96
+ },
97
+ statusOf: statusFor,
98
+ }),
99
+ );
96
100
  switch (verdict.kind) {
97
101
  case 'pass':
98
102
  return verdict.report;