@ultimat3/action 30.7.0 → 30.8.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/package.json +8 -8
- package/src/exercise-declarations.ts +6 -2
- package/src/exercise.ts +15 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ultimat3/action",
|
|
3
|
-
"version": "30.
|
|
3
|
+
"version": "30.8.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.
|
|
50
|
-
"@ultimat3/core": "30.
|
|
51
|
-
"@ultimat3/db": "30.
|
|
52
|
-
"@ultimat3/entity": "30.
|
|
53
|
-
"@ultimat3/http": "30.
|
|
54
|
-
"@ultimat3/policy": "30.
|
|
55
|
-
"@ultimat3/schema": "30.
|
|
49
|
+
"@ultimat3/cache": "30.8.0",
|
|
50
|
+
"@ultimat3/core": "30.8.0",
|
|
51
|
+
"@ultimat3/db": "30.8.0",
|
|
52
|
+
"@ultimat3/entity": "30.8.0",
|
|
53
|
+
"@ultimat3/http": "30.8.0",
|
|
54
|
+
"@ultimat3/policy": "30.8.0",
|
|
55
|
+
"@ultimat3/schema": "30.8.0"
|
|
56
56
|
}
|
|
57
57
|
}
|
|
@@ -23,11 +23,15 @@ export type ContractExercise = ExerciseOptions | ExerciseDeclarations;
|
|
|
23
23
|
export const isExerciseDeclarations = (value: ContractExercise): value is ExerciseDeclarations =>
|
|
24
24
|
'kind' in value && value.kind === 'exercise-overrides';
|
|
25
25
|
|
|
26
|
-
/**
|
|
26
|
+
/**
|
|
27
|
+
* The entry `name` reads: the declarations' own, or the inline one. `undefined` when none. An
|
|
28
|
+
* `'action:<name>'` key wins over the bare name — the one `x exercise` reads for an action whose
|
|
29
|
+
* name a job shares (`@ultimat3/testing`'s `overrideFor`; query's twin keeps the same rule).
|
|
30
|
+
*/
|
|
27
31
|
export function resolveExercise(
|
|
28
32
|
name: string,
|
|
29
33
|
exercise: ContractExercise | undefined,
|
|
30
34
|
): ExerciseOptions | undefined {
|
|
31
35
|
if (exercise === undefined || !isExerciseDeclarations(exercise)) return exercise;
|
|
32
|
-
return exercise.entries.get(name);
|
|
36
|
+
return exercise.entries.get(`action:${name}`) ?? exercise.entries.get(name);
|
|
33
37
|
}
|
package/src/exercise.ts
CHANGED
|
@@ -13,7 +13,8 @@ import {
|
|
|
13
13
|
runExercise,
|
|
14
14
|
tryUseContext,
|
|
15
15
|
} from '@ultimat3/core';
|
|
16
|
-
import {
|
|
16
|
+
import type { ExerciseRequestOption } from '@ultimat3/http';
|
|
17
|
+
import { statusFor, withRequestAs, withScratchRateLimitStore } from '@ultimat3/http';
|
|
17
18
|
import { admitsAnonymous } from '@ultimat3/policy';
|
|
18
19
|
import { arbitrary } from '@ultimat3/schema/sample';
|
|
19
20
|
import type { AnyAction } from './action';
|
|
@@ -48,6 +49,13 @@ export interface ExerciseActorOptions {
|
|
|
48
49
|
readonly input?: (drawn: DrawnInput) => unknown;
|
|
49
50
|
/** 1 to `EXERCISE_RUNS_MAX`; `EXERCISE_RUNS` when omitted. */
|
|
50
51
|
readonly runs?: number;
|
|
52
|
+
/**
|
|
53
|
+
* What the call's request carries in: the cookie a first step set (a pending sign-in, an OAuth
|
|
54
|
+
* state), a header. A function is awaited before EACH call, as that call's actor, so it can mint
|
|
55
|
+
* a session for whoever acts. Read by `x exercise` and the contract step alike: each run is then
|
|
56
|
+
* invoked inside that request (`@ultimat3/http`'s `withRequestAs`) instead of the bare context.
|
|
57
|
+
*/
|
|
58
|
+
readonly request?: ExerciseRequestOption;
|
|
51
59
|
}
|
|
52
60
|
|
|
53
61
|
/** No exercise, said out loud: the reason is listed in the assertion's name. */
|
|
@@ -95,8 +103,13 @@ export async function exerciseAction(
|
|
|
95
103
|
accepts: (input) => acceptedBy(target, input, name),
|
|
96
104
|
attempt: async (input): Promise<ExerciseAttempt> => {
|
|
97
105
|
const before = handlerReached(target);
|
|
106
|
+
// The actor is ON the declared request, so `invoke` takes none: its impersonation path
|
|
107
|
+
// would rebuild a plain child context and drop the request's headers.
|
|
108
|
+
const { request } = options;
|
|
98
109
|
const settled = await inRolledBackScope(() =>
|
|
99
|
-
|
|
110
|
+
request === undefined
|
|
111
|
+
? invoke(target, input, { ctx: base, actor, surface: 'http' })
|
|
112
|
+
: withRequestAs(name, actor, request, () => invoke(target, input, { surface: 'http' })),
|
|
100
113
|
);
|
|
101
114
|
const reached = handlerReached(target) > before;
|
|
102
115
|
return settled.ok ? { ok: true, reached } : { ok: false, reached, error: settled.error };
|