@mapples/request 1.6.0-beta.20260921105308 → 1.6.0-beta.20260929074057
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/CHANGELOG.md +10 -4
- package/README.md +96 -0
- package/dist/RequestActionHandler.d.ts +1 -1
- package/dist/RequestActionHandler.d.ts.map +1 -1
- package/dist/RequestActionHandler.js +56 -27
- package/dist/RequestActionHandler.js.map +1 -1
- package/dist/RequestProvider.d.ts +37 -8
- package/dist/RequestProvider.d.ts.map +1 -1
- package/dist/RequestProvider.js +55 -16
- package/dist/RequestProvider.js.map +1 -1
- package/dist/actionCommands.d.ts +23 -6
- package/dist/actionCommands.d.ts.map +1 -1
- package/dist/actionCommands.js +75 -40
- package/dist/actionCommands.js.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/transport.d.ts +43 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +41 -0
- package/dist/transport.js.map +1 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
# @mapples/request
|
|
2
2
|
|
|
3
|
-
## 1.6.0-beta.
|
|
3
|
+
## 1.6.0-beta.20260929074057
|
|
4
4
|
|
|
5
5
|
### Minor Changes
|
|
6
6
|
|
|
7
|
+
- 517b1d4: API responses move to their own `@mapples/state` context (`ApiStateContextProvider`, `useApiState`), so `api.*` bindings resolve inside a nested `StateProvider` — a list row, a scoped data state — the way `vars.*` does; `validateApiDocument` now rejects a state key outside the `api.` namespace (`STATE_KEY_NAMESPACE`, `API_NAMESPACE`) and no longer reads an environment variable's `{{name}}` as the path token `{name`; it also rejects a request name that can't be a binding-path segment (`REQUEST_NAME_INVALID`, `API_REQUEST_NAME_PATTERN`) and an empty path (`PATH_EMPTY`). `@mapples/request` performs calls through a pluggable `RequestTransport` (`transport` prop, `createFetchTransport`), which receives `auth` unresolved so a forwarding transport never holds a secret. The runtime no longer fails silently: an unknown `requestId`, a path token that resolves to nothing, an undefined `{{variable}}`, a relative path without a base URL and an unconfigured secret all throw (`onRequestError` receives the resolution errors), an unresolved `state` source is no longer sent as the string `N/A`, and `event` sources read the event's first argument instead of the `staticData`-merged payload. `RequestProvider` drops its `state` prop.
|
|
8
|
+
- 4a8f1d8: `RequestProvider` takes `previewSamples` and `autoRun`. With `previewSamples`, every request that has a `responseSample` publishes it as a settled response (`status: 200`) until a real one arrives, so an editor preview shows bound data before anything has run. With `autoRun`, entering `fetch` mode emits a `mapples:request` for every GET that needs nothing from a trigger — `isAutoRunnable` (new export): params are literals or app state, never `event` / `data` / `route.*` — once per switch, through the action bus like a pressed element. Responses now belong to the mode that produced them: changing `mode` clears them, so leaving `fetch` returns to the samples. Both props default to off; a shipped app behaves as before.
|
|
9
|
+
- 132e4a8: Routes carry a flow of their own: a `path` with `:token` params (`/product/:id`), an `onEnter` action emitted on every focus, and an access `guard` with a fallback route. Params travel on the navigate action as literals or value sources (`data` reads the pressed list row, `state` any binding), land in a new `route.*` state namespace the screen binds, and reach root-mounted handlers through `ActiveRouteProvider` — so a request bound to `route.id` loads the product page. `resolveValueSource` is shared by the request and navigation runtimes, and the new `data` source kind reads an action's resolved data. The navigate action form gets one source field per route param, the AI catalog `set_route_path` / `set_route_guard` / `set_route_on_enter` and `add_connection` params.
|
|
7
10
|
- a6c5872: All `@mapples/*` runtime packages now share one version and are versioned and published together from `mapplesorg/mapples-kit`; `mapples-open-kit` is a read-only mirror of the source.
|
|
8
11
|
|
|
9
12
|
### Patch Changes
|
|
10
13
|
|
|
14
|
+
- Updated dependencies [132e4a8]
|
|
15
|
+
- Updated dependencies [517b1d4]
|
|
16
|
+
- Updated dependencies [132e4a8]
|
|
11
17
|
- Updated dependencies [a6c5872]
|
|
12
|
-
- @mapples/
|
|
13
|
-
- @mapples/state@1.6.0-beta.
|
|
14
|
-
- @mapples/
|
|
18
|
+
- @mapples/types@1.6.0-beta.20260929074057
|
|
19
|
+
- @mapples/state@1.6.0-beta.20260929074057
|
|
20
|
+
- @mapples/action@1.6.0-beta.20260929074057
|
|
15
21
|
|
|
16
22
|
## 0.1.0
|
|
17
23
|
|
package/README.md
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# @mapples/request
|
|
2
|
+
|
|
3
|
+
Runtime for the Mapples `ApiDocument`: executes `mapples:request` actions, publishes each response as bindable state, and chains follow-up actions on the action bus.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @mapples/request
|
|
9
|
+
# or
|
|
10
|
+
yarn add @mapples/request
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Peer dependencies: `@mapples/action`, `@mapples/state`, `@mapples/types`, `lodash`, `react`, `rxjs`.
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
Mount the provider inside an `ActionProvider`, with the handler as a child:
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
import ActionProvider from '@mapples/action';
|
|
21
|
+
import { RequestActionHandler, RequestProvider } from '@mapples/request';
|
|
22
|
+
|
|
23
|
+
const App = () => (
|
|
24
|
+
<ActionProvider>
|
|
25
|
+
<RequestProvider document={apiDocument} mode="fetch" secrets={secrets}>
|
|
26
|
+
<RequestActionHandler />
|
|
27
|
+
{/* Your app components */}
|
|
28
|
+
</RequestProvider>
|
|
29
|
+
</ActionProvider>
|
|
30
|
+
);
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
An element runs a request by emitting a `mapples:request` action:
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
{
|
|
37
|
+
type: 'mapples:request',
|
|
38
|
+
staticData: {
|
|
39
|
+
requestId: '<id from the ApiDocument>',
|
|
40
|
+
onSuccess: { type: 'mapples:navigate', data: { routeName: 'Home' } },
|
|
41
|
+
onError: { type: 'mapples:navigate', data: { routeName: 'Error' } },
|
|
42
|
+
},
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Response state
|
|
47
|
+
|
|
48
|
+
A request's response lands under its state key — `api.<name>` by default, always inside the `api.` namespace:
|
|
49
|
+
|
|
50
|
+
| Binding | Value |
|
|
51
|
+
| -------------------- | ------------------------------------------- |
|
|
52
|
+
| `api.<name>.loading` | `true` while the request is in flight |
|
|
53
|
+
| `api.<name>.data` | The parsed JSON body (`null` when not JSON) |
|
|
54
|
+
| `api.<name>.error` | `HTTP <status>` or the failure message |
|
|
55
|
+
| `api.<name>.status` | The HTTP status |
|
|
56
|
+
|
|
57
|
+
Response state lives in its own context, so `api.*` bindings resolve the same anywhere in the tree — a nested `StateProvider` (a list row, a scoped data state) doesn't shadow them.
|
|
58
|
+
|
|
59
|
+
## Value sources
|
|
60
|
+
|
|
61
|
+
Path tokens, query parameters and JSON body leaves take an `ApiValueSource`:
|
|
62
|
+
|
|
63
|
+
- `{ kind: 'static', value }` — a literal.
|
|
64
|
+
- `{ kind: 'state', dataProp }` — a `@mapples/state` path, resolved where the handler is mounted (`vars.*`, `api.*`, root data state).
|
|
65
|
+
- `{ kind: 'event', path? }` — the triggering event's first argument (form values, input text); `path` narrows into it.
|
|
66
|
+
- `{ kind: 'data', path? }` — the action's resolved data (`Action.payload.data`): the pressed list row when the element's action binds `(this)`, or the route's params when a screen's `onEnter` runs the request; `path` narrows into it. The way a row-scoped value reaches a request — a `state` source resolves where the handler is mounted, above the list.
|
|
67
|
+
|
|
68
|
+
A request a screen runs on enter (`RouteNode.onEnter`) binds its path token to the route param: `{ kind: 'state', dataProp: 'route.id' }` for `/product/:id` → `GET /products/{id}`. The resolver is `resolveValueSource` from `@mapples/state`, shared with the navigation runtime's route params.
|
|
69
|
+
|
|
70
|
+
## Modes
|
|
71
|
+
|
|
72
|
+
- `sample` (default) resolves the request's `responseSample` after a short delay. No call is made and the url is not resolved.
|
|
73
|
+
- `fetch` performs the call through the transport.
|
|
74
|
+
|
|
75
|
+
## Transport
|
|
76
|
+
|
|
77
|
+
`fetch` mode calls a `RequestTransport`. The default is a direct `fetch` that resolves `auth.secretRef` against the `secrets` prop. Supply your own to route calls elsewhere — a backend proxy, for example — without the runtime knowing about it:
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
const transport: RequestTransport = async (request) => {
|
|
81
|
+
// request: { requestId, method, url, headers, body?, auth? }
|
|
82
|
+
return { status, ok, headers, data };
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
<RequestProvider document={apiDocument} mode="fetch" transport={transport} />;
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`auth` reaches the transport unresolved (it carries the secret's name), so a forwarding transport never holds the secret's value.
|
|
89
|
+
|
|
90
|
+
## Errors
|
|
91
|
+
|
|
92
|
+
Nothing fails silently:
|
|
93
|
+
|
|
94
|
+
- A `mapples:request` action with an unknown `requestId`, a path token that resolves to nothing, an undefined `{{variable}}`, or a relative path with no environment base URL throws. Pass `onRequestError` to receive those; without it they surface as uncaught errors.
|
|
95
|
+
- A secret that isn't configured rejects the call — the request never goes out unauthenticated.
|
|
96
|
+
- A rejected transport sets `api.<name>.error`, emits `onError`, and never emits `onSuccess`.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Bridges the `@mapples/action` store to request execution: subscribes to
|
|
3
3
|
* emitted actions, resolves `mapples:request` actions into commands, executes
|
|
4
|
-
* them (sample or
|
|
4
|
+
* them (sample, or the runtime's transport), stores responses via the surrounding
|
|
5
5
|
* `RequestProvider`, and re-emits `onSuccess`/`onError` follow-up actions
|
|
6
6
|
* (e.g. `mapples:navigate`) onto the same bus. Renders nothing.
|
|
7
7
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RequestActionHandler.d.ts","sourceRoot":"","sources":["../src/RequestActionHandler.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"RequestActionHandler.d.ts","sourceRoot":"","sources":["../src/RequestActionHandler.tsx"],"names":[],"mappings":"AAiBA;;;;;;;;GAQG;AACH,QAAA,MAAM,oBAAoB,YA8GzB,CAAC;AAEF,eAAe,oBAAoB,CAAC"}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import { useEffect } from 'react';
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
2
|
import { useActionStore } from '@mapples/action';
|
|
3
3
|
import { useDataState } from '@mapples/state';
|
|
4
|
-
import { toRequestCommand } from './actionCommands';
|
|
4
|
+
import { isAutoRunnable, toRequestCommand, toTransportRequest, } from './actionCommands';
|
|
5
|
+
import { RequestActionType } from './constants';
|
|
5
6
|
import { useRequestRuntime } from './RequestProvider';
|
|
6
7
|
/** Artificial latency for sample mode so loading states are visible. */
|
|
7
8
|
const SAMPLE_DELAY_MS = 500;
|
|
8
9
|
/**
|
|
9
10
|
* Bridges the `@mapples/action` store to request execution: subscribes to
|
|
10
11
|
* emitted actions, resolves `mapples:request` actions into commands, executes
|
|
11
|
-
* them (sample or
|
|
12
|
+
* them (sample, or the runtime's transport), stores responses via the surrounding
|
|
12
13
|
* `RequestProvider`, and re-emits `onSuccess`/`onError` follow-up actions
|
|
13
14
|
* (e.g. `mapples:navigate`) onto the same bus. Renders nothing.
|
|
14
15
|
*
|
|
@@ -21,7 +22,7 @@ const RequestActionHandler = () => {
|
|
|
21
22
|
useEffect(() => {
|
|
22
23
|
if (!runtime?.document)
|
|
23
24
|
return;
|
|
24
|
-
const { document, mode,
|
|
25
|
+
const { document, mode, transport, onRequestError, setResponse } = runtime;
|
|
25
26
|
const emitFollowUp = (followUp) => {
|
|
26
27
|
if (!followUp?.type)
|
|
27
28
|
return;
|
|
@@ -45,33 +46,21 @@ const RequestActionHandler = () => {
|
|
|
45
46
|
return;
|
|
46
47
|
}
|
|
47
48
|
try {
|
|
48
|
-
const response = await
|
|
49
|
-
method: command.method,
|
|
50
|
-
headers: command.headers,
|
|
51
|
-
body: command.body !== undefined
|
|
52
|
-
? JSON.stringify(command.body)
|
|
53
|
-
: undefined,
|
|
54
|
-
});
|
|
55
|
-
// Only bodies that declare JSON are parsed. A body that claims JSON and
|
|
56
|
-
// fails to parse is a broken server contract, so the parse error is left
|
|
57
|
-
// to reach the handler below instead of quietly becoming `null` data.
|
|
58
|
-
const contentType = response.headers.get('content-type') ?? '';
|
|
59
|
-
const data = contentType.includes('json')
|
|
60
|
-
? await response.json()
|
|
61
|
-
: null;
|
|
49
|
+
const response = await transport(toTransportRequest(command));
|
|
62
50
|
setResponse(command.stateKey, {
|
|
63
51
|
loading: false,
|
|
64
|
-
data,
|
|
52
|
+
data: response.data,
|
|
65
53
|
status: response.status,
|
|
66
54
|
error: response.ok ? undefined : `HTTP ${response.status}`,
|
|
67
55
|
});
|
|
68
56
|
emitFollowUp(response.ok ? command.onSuccess : command.onError);
|
|
69
57
|
}
|
|
70
58
|
catch (error) {
|
|
71
|
-
// A
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
//
|
|
59
|
+
// A rejected transport (network, CORS, parse, an unconfigured secret)
|
|
60
|
+
// is a failed request. It is reported as one: no sample data is
|
|
61
|
+
// substituted for the real response, and only the configured error
|
|
62
|
+
// follow-up runs — never `onSuccess`, which would advance the flow as
|
|
63
|
+
// if the request had worked.
|
|
75
64
|
setResponse(command.stateKey, {
|
|
76
65
|
loading: false,
|
|
77
66
|
data: null,
|
|
@@ -81,16 +70,56 @@ const RequestActionHandler = () => {
|
|
|
81
70
|
}
|
|
82
71
|
};
|
|
83
72
|
const subscription = actionStore.subscribe((action) => {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
73
|
+
let command;
|
|
74
|
+
try {
|
|
75
|
+
command = toRequestCommand(action, document, {
|
|
76
|
+
// `null`, not `getValue`'s display default: an unresolved source
|
|
77
|
+
// must not go out as the literal string "N/A".
|
|
78
|
+
getStateValue: (path) => getValue({ key: path, active: true }, null),
|
|
79
|
+
sample: mode === 'sample',
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
// The action named a request that can't be built. The host surfaces
|
|
84
|
+
// it; with no host handler it stays an uncaught error.
|
|
85
|
+
if (!onRequestError)
|
|
86
|
+
throw error;
|
|
87
|
+
onRequestError(error, action);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
88
90
|
if (!command)
|
|
89
91
|
return;
|
|
90
92
|
void execute(command);
|
|
91
93
|
});
|
|
92
94
|
return () => subscription.unsubscribe();
|
|
93
95
|
}, [actionStore, getValue, runtime]);
|
|
96
|
+
// Entering `fetch` mode runs the auto-runnable GETs once, through the bus
|
|
97
|
+
// so they take the same path as a pressed element. Declared after the
|
|
98
|
+
// subscription so it is in place when these are emitted; the ref keeps a
|
|
99
|
+
// document edit (a new runtime value) from running them again.
|
|
100
|
+
const mode = runtime?.mode;
|
|
101
|
+
const autoRun = !!runtime?.autoRun && !!runtime.document;
|
|
102
|
+
const ranForMode = useRef(null);
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
if (!autoRun || mode !== 'fetch') {
|
|
105
|
+
ranForMode.current = null;
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (ranForMode.current === mode)
|
|
109
|
+
return;
|
|
110
|
+
ranForMode.current = mode;
|
|
111
|
+
for (const request of Object.values(runtime.document.requests)) {
|
|
112
|
+
if (!isAutoRunnable(request))
|
|
113
|
+
continue;
|
|
114
|
+
actionStore.next({
|
|
115
|
+
time: Date.now(),
|
|
116
|
+
type: RequestActionType.Execute,
|
|
117
|
+
payload: { data: { requestId: request.id } },
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
// Only a mode change (or the runtime becoming able to run) re-runs them.
|
|
121
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
122
|
+
}, [actionStore, autoRun, mode]);
|
|
94
123
|
return null;
|
|
95
124
|
};
|
|
96
125
|
export default RequestActionHandler;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RequestActionHandler.js","sourceRoot":"","sources":["../src/RequestActionHandler.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"RequestActionHandler.js","sourceRoot":"","sources":["../src/RequestActionHandler.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { FC, PropsWithChildren } from 'react';
|
|
2
|
-
import { ApiDocument } from '@mapples/types';
|
|
2
|
+
import { Action, ApiDocument } from '@mapples/types';
|
|
3
|
+
import { RequestTransport } from './transport';
|
|
3
4
|
/** Per-request response state, exposed to bindings under the state key. */
|
|
4
5
|
export interface ResponseState {
|
|
5
6
|
loading: boolean;
|
|
@@ -11,23 +12,51 @@ export type RequestExecutionMode = 'sample' | 'fetch';
|
|
|
11
12
|
export interface RequestRuntimeContextType {
|
|
12
13
|
document: ApiDocument | null;
|
|
13
14
|
mode: RequestExecutionMode;
|
|
14
|
-
|
|
15
|
+
transport: RequestTransport;
|
|
16
|
+
onRequestError?: (error: Error, action: Action) => void;
|
|
17
|
+
/** See {@link RequestProviderProps.autoRun}. */
|
|
18
|
+
autoRun: boolean;
|
|
15
19
|
setResponse: (stateKey: string, state: ResponseState) => void;
|
|
16
20
|
}
|
|
17
21
|
export declare const useRequestRuntime: () => RequestRuntimeContextType | null;
|
|
18
22
|
export interface RequestProviderProps {
|
|
19
23
|
document?: ApiDocument | null;
|
|
20
|
-
/** Base state the responses are merged over. */
|
|
21
|
-
state?: object;
|
|
22
24
|
/** `sample` resolves `responseSample`s; `fetch` performs real requests. */
|
|
23
25
|
mode?: RequestExecutionMode;
|
|
26
|
+
/**
|
|
27
|
+
* Performs the call in `fetch` mode. Defaults to a direct `fetch` resolving
|
|
28
|
+
* auth against {@link secrets}; a host that must not call third parties
|
|
29
|
+
* itself (a browser editor behind CORS) routes through its own backend here.
|
|
30
|
+
*/
|
|
31
|
+
transport?: RequestTransport;
|
|
32
|
+
/** Secrets by `secretRef` for the default transport — never stored. */
|
|
24
33
|
secrets?: Record<string, string>;
|
|
34
|
+
/**
|
|
35
|
+
* Receives a `mapples:request` action that could not become a request (an
|
|
36
|
+
* unknown request id, an unbound path token). Without it the error is
|
|
37
|
+
* thrown from the action subscription.
|
|
38
|
+
*/
|
|
39
|
+
onRequestError?: (error: Error, action: Action) => void;
|
|
40
|
+
/**
|
|
41
|
+
* Seed every request that has a `responseSample` with it, as a settled
|
|
42
|
+
* response, until a real one arrives — so an editor preview shows bound
|
|
43
|
+
* data before any request has run. Off in a shipped app: a sample is
|
|
44
|
+
* design-time data, not a response.
|
|
45
|
+
*/
|
|
46
|
+
previewSamples?: boolean;
|
|
47
|
+
/**
|
|
48
|
+
* In `fetch` mode, run every GET request that needs nothing from a
|
|
49
|
+
* triggering action once when the runtime enters `fetch` mode (see
|
|
50
|
+
* `isAutoRunnable`) — the editor's "Live data" switch.
|
|
51
|
+
*/
|
|
52
|
+
autoRun?: boolean;
|
|
25
53
|
}
|
|
26
54
|
/**
|
|
27
|
-
* Holds API response state and
|
|
28
|
-
* bindings like `api.login.data.user.name` and conditions like
|
|
29
|
-
* `api.login.loading::eq::true` resolve
|
|
30
|
-
*
|
|
55
|
+
* Holds API response state and publishes it through the `@mapples/state` API
|
|
56
|
+
* context, so bindings like `api.login.data.user.name` and conditions like
|
|
57
|
+
* `api.login.loading::eq::true` resolve anywhere in the tree — a nested
|
|
58
|
+
* `StateProvider` (a frame's data state, a list row) doesn't shadow them.
|
|
59
|
+
* Pair with `RequestActionHandler` (rendered as a child) to execute
|
|
31
60
|
* `mapples:request` actions.
|
|
32
61
|
*/
|
|
33
62
|
declare const RequestProvider: FC<PropsWithChildren<RequestProviderProps>>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RequestProvider.d.ts","sourceRoot":"","sources":["../src/RequestProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,EAEL,EAAE,EACF,iBAAiB,EAKlB,MAAM,OAAO,CAAC;AAGf,OAAO,
|
|
1
|
+
{"version":3,"file":"RequestProvider.d.ts","sourceRoot":"","sources":["../src/RequestProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,EAEL,EAAE,EACF,iBAAiB,EAKlB,MAAM,OAAO,CAAC;AAGf,OAAO,EACL,MAAM,EAEN,WAAW,EAEZ,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAwB,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAErE,2EAA2E;AAC3E,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,oBAAoB,GAAG,QAAQ,GAAG,OAAO,CAAC;AAEtD,MAAM,WAAW,yBAAyB;IACxC,QAAQ,EAAE,WAAW,GAAG,IAAI,CAAC;IAC7B,IAAI,EAAE,oBAAoB,CAAC;IAC3B,SAAS,EAAE,gBAAgB,CAAC;IAC5B,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACxD,gDAAgD;IAChD,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;CAC/D;AAMD,eAAO,MAAM,iBAAiB,wCAA0C,CAAC;AAEzE,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IAC9B,2EAA2E;IAC3E,IAAI,CAAC,EAAE,oBAAoB,CAAC;IAC5B;;;;OAIG;IACH,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAC7B,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC;;;;OAIG;IACH,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACxD;;;;;OAKG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAID;;;;;;;GAOG;AACH,QAAA,MAAM,eAAe,EAAE,EAAE,CAAC,iBAAiB,CAAC,oBAAoB,CAAC,CAgFhE,CAAC;AAEF,eAAe,eAAe,CAAC"}
|
package/dist/RequestProvider.js
CHANGED
|
@@ -1,32 +1,71 @@
|
|
|
1
1
|
import { createContext, useCallback, useContext, useMemo, useState, } from 'react';
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
2
|
+
import { ApiStateContextProvider } from '@mapples/state';
|
|
3
|
+
import { API_NAMESPACE, apiRequestStateKey, } from '@mapples/types';
|
|
4
|
+
import { set } from 'lodash';
|
|
5
|
+
import { createFetchTransport } from './transport';
|
|
4
6
|
const RequestRuntimeContext = createContext(null);
|
|
5
7
|
export const useRequestRuntime = () => useContext(RequestRuntimeContext);
|
|
8
|
+
const STATE_KEY_PREFIX = `${API_NAMESPACE}.`;
|
|
6
9
|
/**
|
|
7
|
-
* Holds API response state and
|
|
8
|
-
* bindings like `api.login.data.user.name` and conditions like
|
|
9
|
-
* `api.login.loading::eq::true` resolve
|
|
10
|
-
*
|
|
10
|
+
* Holds API response state and publishes it through the `@mapples/state` API
|
|
11
|
+
* context, so bindings like `api.login.data.user.name` and conditions like
|
|
12
|
+
* `api.login.loading::eq::true` resolve anywhere in the tree — a nested
|
|
13
|
+
* `StateProvider` (a frame's data state, a list row) doesn't shadow them.
|
|
14
|
+
* Pair with `RequestActionHandler` (rendered as a child) to execute
|
|
11
15
|
* `mapples:request` actions.
|
|
12
16
|
*/
|
|
13
|
-
const RequestProvider = ({ children, document = null,
|
|
17
|
+
const RequestProvider = ({ children, document = null, mode = 'sample', transport, secrets, onRequestError, previewSamples = false, autoRun = false, }) => {
|
|
14
18
|
const [responses, setResponses] = useState({});
|
|
19
|
+
// Responses belong to the mode that produced them: leaving `fetch` must not
|
|
20
|
+
// keep showing live data as if it were the preview, nor the reverse.
|
|
21
|
+
const [responsesMode, setResponsesMode] = useState(mode);
|
|
22
|
+
if (mode !== responsesMode) {
|
|
23
|
+
setResponsesMode(mode);
|
|
24
|
+
setResponses({});
|
|
25
|
+
}
|
|
15
26
|
const setResponse = useCallback((stateKey, response) => {
|
|
16
27
|
setResponses((prev) => ({ ...prev, [stateKey]: response }));
|
|
17
28
|
}, []);
|
|
18
|
-
// State keys are dot paths (`api.login`) — expand
|
|
19
|
-
// object shape
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
29
|
+
// State keys are dot paths under the namespace (`api.login`) — expand the
|
|
30
|
+
// remainder into the nested object shape bindings resolve against.
|
|
31
|
+
const apiState = useMemo(() => {
|
|
32
|
+
const samples = {};
|
|
33
|
+
if (previewSamples && document) {
|
|
34
|
+
for (const request of Object.values(document.requests)) {
|
|
35
|
+
if (request.responseSample === undefined)
|
|
36
|
+
continue;
|
|
37
|
+
samples[apiRequestStateKey(request)] = {
|
|
38
|
+
loading: false,
|
|
39
|
+
data: request.responseSample,
|
|
40
|
+
status: 200,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const result = {};
|
|
45
|
+
for (const [stateKey, response] of Object.entries({
|
|
46
|
+
...samples,
|
|
47
|
+
...responses,
|
|
48
|
+
})) {
|
|
49
|
+
if (!stateKey.startsWith(STATE_KEY_PREFIX)) {
|
|
50
|
+
throw new Error(`State key "${stateKey}" is outside the "${API_NAMESPACE}" namespace`);
|
|
51
|
+
}
|
|
52
|
+
set(result, stateKey.slice(STATE_KEY_PREFIX.length), response);
|
|
24
53
|
}
|
|
25
54
|
return result;
|
|
26
|
-
}, [
|
|
27
|
-
const
|
|
55
|
+
}, [document, previewSamples, responses]);
|
|
56
|
+
const resolvedTransport = useMemo(() => transport ?? createFetchTransport(secrets), [transport, secrets]);
|
|
57
|
+
const contextValue = useMemo(() => ({
|
|
58
|
+
document,
|
|
59
|
+
mode,
|
|
60
|
+
transport: resolvedTransport,
|
|
61
|
+
onRequestError,
|
|
62
|
+
autoRun,
|
|
63
|
+
setResponse,
|
|
64
|
+
}), [document, mode, resolvedTransport, onRequestError, autoRun, setResponse]);
|
|
28
65
|
return (<RequestRuntimeContext value={contextValue}>
|
|
29
|
-
<
|
|
66
|
+
<ApiStateContextProvider value={apiState}>
|
|
67
|
+
{children}
|
|
68
|
+
</ApiStateContextProvider>
|
|
30
69
|
</RequestRuntimeContext>);
|
|
31
70
|
};
|
|
32
71
|
export default RequestProvider;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RequestProvider.js","sourceRoot":"","sources":["../src/RequestProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EAGb,WAAW,EACX,UAAU,EACV,OAAO,EACP,QAAQ,GACT,MAAM,OAAO,CAAC;AAEf,OAAO,
|
|
1
|
+
{"version":3,"file":"RequestProvider.js","sourceRoot":"","sources":["../src/RequestProvider.tsx"],"names":[],"mappings":"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"}
|
package/dist/actionCommands.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { Action, ApiDocument, HttpMethod } from '@mapples/types';
|
|
1
|
+
import { Action, ApiDocument, ApiRequest, HttpMethod } from '@mapples/types';
|
|
2
|
+
import { TransportAuth, TransportRequest } from './transport';
|
|
2
3
|
/** A follow-up action re-emitted after the request settles. */
|
|
3
4
|
export interface FollowUpAction {
|
|
4
5
|
type: string;
|
|
@@ -12,6 +13,8 @@ export interface RequestCommand {
|
|
|
12
13
|
method: HttpMethod;
|
|
13
14
|
headers: Record<string, string>;
|
|
14
15
|
body?: unknown;
|
|
16
|
+
/** Left unresolved: the transport that performs the call owns the secret. */
|
|
17
|
+
auth?: TransportAuth;
|
|
15
18
|
responseSample?: unknown;
|
|
16
19
|
onSuccess?: FollowUpAction;
|
|
17
20
|
onError?: FollowUpAction;
|
|
@@ -20,13 +23,27 @@ export interface RequestCommand {
|
|
|
20
23
|
export interface RequestResolutionContext {
|
|
21
24
|
/** Resolves `state`-kind value sources (a `@mapples/state` lookup). */
|
|
22
25
|
getStateValue?: (path: string) => unknown;
|
|
23
|
-
/**
|
|
24
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Sample execution never performs the call, so the url is left as the
|
|
28
|
+
* request's unresolved path: a preview must not fail on a value that only
|
|
29
|
+
* exists at runtime (a path token bound to a list row, say).
|
|
30
|
+
*/
|
|
31
|
+
sample?: boolean;
|
|
25
32
|
}
|
|
26
33
|
/**
|
|
27
|
-
* Maps a `mapples:request` action to an executable {@link RequestCommand}
|
|
28
|
-
*
|
|
29
|
-
*
|
|
34
|
+
* Maps a `mapples:request` action to an executable {@link RequestCommand}.
|
|
35
|
+
* Pure. Returns null for any other action type; a `mapples:request` action
|
|
36
|
+
* that names no request, or one the document doesn't hold, throws — a press
|
|
37
|
+
* that silently does nothing is a wiring defect nobody can see.
|
|
30
38
|
*/
|
|
31
39
|
export declare const toRequestCommand: (action: Action, document: ApiDocument, context?: RequestResolutionContext) => RequestCommand | null;
|
|
40
|
+
/** The part of a {@link RequestCommand} a transport performs. */
|
|
41
|
+
export declare const toTransportRequest: (command: RequestCommand) => TransportRequest;
|
|
42
|
+
/**
|
|
43
|
+
* Whether a request can run with no triggering action: a GET whose params
|
|
44
|
+
* are all literals or app state. An `event` / `data` source only exists when
|
|
45
|
+
* an element fires the request, and `route.*` only once a screen is entered,
|
|
46
|
+
* so those wait for their trigger.
|
|
47
|
+
*/
|
|
48
|
+
export declare const isAutoRunnable: (request: ApiRequest) => boolean;
|
|
32
49
|
//# sourceMappingURL=actionCommands.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"actionCommands.d.ts","sourceRoot":"","sources":["../src/actionCommands.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"actionCommands.d.ts","sourceRoot":"","sources":["../src/actionCommands.ts"],"names":[],"mappings":"AACA,OAAO,EACL,MAAM,EACN,WAAW,EACX,UAAU,EAGV,UAAU,EACX,MAAM,gBAAgB,CAAC;AAIxB,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE9D,+DAA+D;AAC/D,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,kDAAkD;AAClD,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,UAAU,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,6EAA6E;IAC7E,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B,OAAO,CAAC,EAAE,cAAc,CAAC;CAC1B;AAED,yDAAyD;AACzD,MAAM,WAAW,wBAAwB;IACvC,uEAAuE;IACvE,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IAC1C;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAsGD;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,GAC3B,QAAQ,MAAM,EACd,UAAU,WAAW,EACrB,UAAS,wBAA6B,KACrC,cAAc,GAAG,IA2EnB,CAAC;AAEF,iEAAiE;AACjE,eAAO,MAAM,kBAAkB,GAC7B,SAAS,cAAc,KACtB,gBAOD,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,cAAc,GAAI,SAAS,UAAU,KAAG,OAMlD,CAAC"}
|
package/dist/actionCommands.js
CHANGED
|
@@ -1,20 +1,8 @@
|
|
|
1
|
+
import { isValueSource, resolveValueSource } from '@mapples/state';
|
|
1
2
|
import { apiRequestStateKey, } from '@mapples/types';
|
|
2
|
-
import {
|
|
3
|
+
import { isPlainObject } from 'lodash';
|
|
3
4
|
import { RequestActionType } from './constants';
|
|
4
|
-
const
|
|
5
|
-
['static', 'state', 'event'].includes(value.kind ?? '');
|
|
6
|
-
const resolveSource = (source, action, context) => {
|
|
7
|
-
switch (source.kind) {
|
|
8
|
-
case 'static':
|
|
9
|
-
return source.value;
|
|
10
|
-
case 'state':
|
|
11
|
-
return context.getStateValue?.(source.dataProp);
|
|
12
|
-
case 'event': {
|
|
13
|
-
const payload = action.payload?.data;
|
|
14
|
-
return source.path ? get(payload, source.path) : payload;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
};
|
|
5
|
+
const resolveSource = (source, action, context) => resolveValueSource(source, action, context.getStateValue);
|
|
18
6
|
const resolveTemplate = (template, action, context) => {
|
|
19
7
|
if (isValueSource(template)) {
|
|
20
8
|
return resolveSource(template, action, context);
|
|
@@ -31,25 +19,46 @@ const resolveTemplate = (template, action, context) => {
|
|
|
31
19
|
}
|
|
32
20
|
return template;
|
|
33
21
|
};
|
|
34
|
-
/**
|
|
35
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Interpolates `{{variable}}` templates from environment variables. A
|
|
24
|
+
* variable the environment doesn't define throws: left in place it would go
|
|
25
|
+
* out as a literal `{{name}}` in the url or a header.
|
|
26
|
+
*/
|
|
27
|
+
const interpolate = (input, variables) => input.replace(/\{\{([^}]+)\}\}/g, (_match, name) => {
|
|
36
28
|
const value = variables?.[name.trim()];
|
|
37
|
-
|
|
29
|
+
if (value === undefined) {
|
|
30
|
+
throw new Error(`Environment variable "${name.trim()}" is not defined`);
|
|
31
|
+
}
|
|
32
|
+
return value;
|
|
38
33
|
});
|
|
39
34
|
const buildUrl = (request, baseUrl, variables, action, context) => {
|
|
40
35
|
let path = interpolate(request.path, variables);
|
|
41
36
|
const query = [];
|
|
42
37
|
for (const [key, source] of Object.entries(request.params ?? {})) {
|
|
43
38
|
const value = resolveSource(source, action, context);
|
|
44
|
-
const
|
|
45
|
-
if (
|
|
39
|
+
const isPathToken = path.includes(`{${key}}`);
|
|
40
|
+
if (value === undefined || value === null) {
|
|
41
|
+
// `/users/` is a different endpoint from `/users/{id}`: a path token
|
|
42
|
+
// that resolves to nothing fails the request. A query parameter with no
|
|
43
|
+
// value is simply left out.
|
|
44
|
+
if (isPathToken) {
|
|
45
|
+
throw new Error(`Path token "{${key}}" of API request "${request.name}" resolved to nothing`);
|
|
46
|
+
}
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
const encoded = encodeURIComponent(String(value));
|
|
50
|
+
if (isPathToken) {
|
|
46
51
|
path = path.replaceAll(`{${key}}`, encoded);
|
|
47
52
|
}
|
|
48
53
|
else {
|
|
49
54
|
query.push(`${encodeURIComponent(key)}=${encoded}`);
|
|
50
55
|
}
|
|
51
56
|
}
|
|
52
|
-
const
|
|
57
|
+
const isAbsolute = /^https?:\/\//i.test(path);
|
|
58
|
+
if (!isAbsolute && !baseUrl) {
|
|
59
|
+
throw new Error(`API request "${request.name}" has a relative path and no environment base URL`);
|
|
60
|
+
}
|
|
61
|
+
const absolute = isAbsolute
|
|
53
62
|
? path
|
|
54
63
|
: `${interpolate(baseUrl, variables).replace(/\/$/, '')}${path.startsWith('/') ? '' : '/'}${path}`;
|
|
55
64
|
return query.length > 0
|
|
@@ -57,19 +66,22 @@ const buildUrl = (request, baseUrl, variables, action, context) => {
|
|
|
57
66
|
: absolute;
|
|
58
67
|
};
|
|
59
68
|
/**
|
|
60
|
-
* Maps a `mapples:request` action to an executable {@link RequestCommand}
|
|
61
|
-
*
|
|
62
|
-
*
|
|
69
|
+
* Maps a `mapples:request` action to an executable {@link RequestCommand}.
|
|
70
|
+
* Pure. Returns null for any other action type; a `mapples:request` action
|
|
71
|
+
* that names no request, or one the document doesn't hold, throws — a press
|
|
72
|
+
* that silently does nothing is a wiring defect nobody can see.
|
|
63
73
|
*/
|
|
64
74
|
export const toRequestCommand = (action, document, context = {}) => {
|
|
65
75
|
if (action.type !== RequestActionType.Execute)
|
|
66
76
|
return null;
|
|
67
77
|
const data = (action.payload?.data ?? {});
|
|
68
|
-
if (!data.requestId)
|
|
69
|
-
|
|
78
|
+
if (!data.requestId) {
|
|
79
|
+
throw new Error(`A "${RequestActionType.Execute}" action has no requestId`);
|
|
80
|
+
}
|
|
70
81
|
const request = document.requests[data.requestId];
|
|
71
|
-
if (!request)
|
|
72
|
-
|
|
82
|
+
if (!request) {
|
|
83
|
+
throw new Error(`API request "${data.requestId}" is not in the document`);
|
|
84
|
+
}
|
|
73
85
|
const environment = (document.activeEnvironmentId
|
|
74
86
|
? document.environments[document.activeEnvironmentId]
|
|
75
87
|
: undefined) ?? Object.values(document.environments)[0];
|
|
@@ -78,19 +90,21 @@ export const toRequestCommand = (action, document, context = {}) => {
|
|
|
78
90
|
...environment?.headers,
|
|
79
91
|
...request.headers,
|
|
80
92
|
};
|
|
81
|
-
|
|
82
|
-
|
|
93
|
+
if (!context.sample) {
|
|
94
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
95
|
+
headers[key] = interpolate(value, variables);
|
|
96
|
+
}
|
|
83
97
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if (
|
|
87
|
-
|
|
88
|
-
headers.Authorization = `Bearer ${secret}`;
|
|
89
|
-
}
|
|
90
|
-
else {
|
|
91
|
-
headers[request.auth.header ?? 'Authorization'] = secret;
|
|
92
|
-
}
|
|
98
|
+
let auth;
|
|
99
|
+
if (request.auth && request.auth.kind !== 'none') {
|
|
100
|
+
if (!request.auth.secretRef) {
|
|
101
|
+
throw new Error(`API request "${request.name}" auth has no secretRef`);
|
|
93
102
|
}
|
|
103
|
+
auth = {
|
|
104
|
+
kind: request.auth.kind,
|
|
105
|
+
header: request.auth.header,
|
|
106
|
+
secretRef: request.auth.secretRef,
|
|
107
|
+
};
|
|
94
108
|
}
|
|
95
109
|
const body = request.body
|
|
96
110
|
? resolveTemplate(request.body.template, action, context)
|
|
@@ -101,13 +115,34 @@ export const toRequestCommand = (action, document, context = {}) => {
|
|
|
101
115
|
return {
|
|
102
116
|
requestId: request.id,
|
|
103
117
|
stateKey: apiRequestStateKey(request),
|
|
104
|
-
url:
|
|
118
|
+
url: context.sample
|
|
119
|
+
? request.path
|
|
120
|
+
: buildUrl(request, environment?.baseUrl ?? '', variables, action, context),
|
|
105
121
|
method: request.method,
|
|
106
122
|
headers,
|
|
107
123
|
body,
|
|
124
|
+
auth,
|
|
108
125
|
responseSample: request.responseSample,
|
|
109
126
|
onSuccess: data.onSuccess,
|
|
110
127
|
onError: data.onError,
|
|
111
128
|
};
|
|
112
129
|
};
|
|
130
|
+
/** The part of a {@link RequestCommand} a transport performs. */
|
|
131
|
+
export const toTransportRequest = (command) => ({
|
|
132
|
+
requestId: command.requestId,
|
|
133
|
+
method: command.method,
|
|
134
|
+
url: command.url,
|
|
135
|
+
headers: command.headers,
|
|
136
|
+
body: command.body,
|
|
137
|
+
auth: command.auth,
|
|
138
|
+
});
|
|
139
|
+
/**
|
|
140
|
+
* Whether a request can run with no triggering action: a GET whose params
|
|
141
|
+
* are all literals or app state. An `event` / `data` source only exists when
|
|
142
|
+
* an element fires the request, and `route.*` only once a screen is entered,
|
|
143
|
+
* so those wait for their trigger.
|
|
144
|
+
*/
|
|
145
|
+
export const isAutoRunnable = (request) => request.method === 'GET' &&
|
|
146
|
+
Object.values(request.params ?? {}).every((source) => source.kind === 'static' ||
|
|
147
|
+
(source.kind === 'state' && !source.dataProp.startsWith('route.')));
|
|
113
148
|
//# sourceMappingURL=actionCommands.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"actionCommands.js","sourceRoot":"","sources":["../src/actionCommands.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"actionCommands.js","sourceRoot":"","sources":["../src/actionCommands.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview Runtime for the Mapples `ApiDocument`: executes
|
|
3
|
-
* `mapples:request` actions (sample or real
|
|
4
|
-
*
|
|
3
|
+
* `mapples:request` actions (sample, or a real call through a pluggable
|
|
4
|
+
* transport), publishes responses into the `@mapples/state` API context, and
|
|
5
|
+
* chains follow-up actions on the action bus.
|
|
5
6
|
*/
|
|
6
7
|
export { default as RequestProvider } from './RequestProvider';
|
|
7
8
|
export { useRequestRuntime } from './RequestProvider';
|
|
8
9
|
export type { RequestExecutionMode, RequestProviderProps, RequestRuntimeContextType, ResponseState, } from './RequestProvider';
|
|
9
10
|
export { default as RequestActionHandler } from './RequestActionHandler';
|
|
11
|
+
export { createFetchTransport } from './transport';
|
|
12
|
+
export type { RequestTransport, TransportAuth, TransportRequest, TransportResponse, } from './transport';
|
|
10
13
|
export * from './actionCommands';
|
|
11
14
|
export * from './constants';
|
|
12
15
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAC/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,YAAY,EACV,oBAAoB,EACpB,oBAAoB,EACpB,yBAAyB,EACzB,aAAa,GACd,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,OAAO,IAAI,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAEzE,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnD,YAAY,EACV,gBAAgB,EAChB,aAAa,EACb,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,aAAa,CAAC;AAErB,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview Runtime for the Mapples `ApiDocument`: executes
|
|
3
|
-
* `mapples:request` actions (sample or real
|
|
4
|
-
*
|
|
3
|
+
* `mapples:request` actions (sample, or a real call through a pluggable
|
|
4
|
+
* transport), publishes responses into the `@mapples/state` API context, and
|
|
5
|
+
* chains follow-up actions on the action bus.
|
|
5
6
|
*/
|
|
6
7
|
export { default as RequestProvider } from './RequestProvider';
|
|
7
8
|
export { useRequestRuntime } from './RequestProvider';
|
|
8
9
|
export { default as RequestActionHandler } from './RequestActionHandler';
|
|
10
|
+
export { createFetchTransport } from './transport';
|
|
9
11
|
export * from './actionCommands';
|
|
10
12
|
export * from './constants';
|
|
11
13
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAC/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAOtD,OAAO,EAAE,OAAO,IAAI,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAEzE,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAQnD,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { HttpMethod } from '@mapples/types';
|
|
2
|
+
/** Auth a transport applies to the outgoing request. */
|
|
3
|
+
export interface TransportAuth {
|
|
4
|
+
kind: 'bearer' | 'header';
|
|
5
|
+
/** Header name for `kind: 'header'` (defaults to `Authorization`). */
|
|
6
|
+
header?: string;
|
|
7
|
+
/** Name of the secret; the transport owns the lookup. */
|
|
8
|
+
secretRef: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* A resolved request as a transport receives it. `auth` still carries the
|
|
12
|
+
* secret's *name*: whoever performs the call resolves it, so a transport that
|
|
13
|
+
* forwards to a server never holds the secret's value.
|
|
14
|
+
*/
|
|
15
|
+
export interface TransportRequest {
|
|
16
|
+
requestId: string;
|
|
17
|
+
method: HttpMethod;
|
|
18
|
+
url: string;
|
|
19
|
+
headers: Record<string, string>;
|
|
20
|
+
body?: unknown;
|
|
21
|
+
auth?: TransportAuth;
|
|
22
|
+
}
|
|
23
|
+
export interface TransportResponse {
|
|
24
|
+
status: number;
|
|
25
|
+
/** Whether the status is in the 2xx range. */
|
|
26
|
+
ok: boolean;
|
|
27
|
+
headers: Record<string, string>;
|
|
28
|
+
/** The parsed body; `null` when the response declares no JSON. */
|
|
29
|
+
data: unknown;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Performs one request. A rejection is a failed request (network, CORS, an
|
|
33
|
+
* unconfigured secret, an unparsable JSON body) — an HTTP error status is
|
|
34
|
+
* not: it resolves with `ok: false`.
|
|
35
|
+
*/
|
|
36
|
+
export type RequestTransport = (request: TransportRequest) => Promise<TransportResponse>;
|
|
37
|
+
/**
|
|
38
|
+
* The default transport: a direct `fetch`, with `auth` resolved against the
|
|
39
|
+
* host-supplied `secrets`. A request whose secret is not configured throws
|
|
40
|
+
* rather than going out unauthenticated.
|
|
41
|
+
*/
|
|
42
|
+
export declare const createFetchTransport: (secrets?: Record<string, string>) => RequestTransport;
|
|
43
|
+
//# sourceMappingURL=transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAE5C,wDAAwD;AACxD,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC1B,sEAAsE;IACtE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yDAAyD;IACzD,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,UAAU,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,aAAa,CAAC;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,8CAA8C;IAC9C,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,kEAAkE;IAClE,IAAI,EAAE,OAAO,CAAC;CACf;AAED;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAC7B,OAAO,EAAE,gBAAgB,KACtB,OAAO,CAAC,iBAAiB,CAAC,CAAC;AAEhC;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,GAC9B,UAAU,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAG,gBAwCnC,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The default transport: a direct `fetch`, with `auth` resolved against the
|
|
3
|
+
* host-supplied `secrets`. A request whose secret is not configured throws
|
|
4
|
+
* rather than going out unauthenticated.
|
|
5
|
+
*/
|
|
6
|
+
export const createFetchTransport = (secrets) => async (request) => {
|
|
7
|
+
const headers = { ...request.headers };
|
|
8
|
+
if (request.auth) {
|
|
9
|
+
const secret = secrets?.[request.auth.secretRef];
|
|
10
|
+
if (secret === undefined) {
|
|
11
|
+
throw new Error(`Secret "${request.auth.secretRef}" is not configured`);
|
|
12
|
+
}
|
|
13
|
+
if (request.auth.kind === 'bearer') {
|
|
14
|
+
headers.Authorization = `Bearer ${secret}`;
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
headers[request.auth.header ?? 'Authorization'] = secret;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
const response = await fetch(request.url, {
|
|
21
|
+
method: request.method,
|
|
22
|
+
headers,
|
|
23
|
+
body: request.body !== undefined ? JSON.stringify(request.body) : undefined,
|
|
24
|
+
});
|
|
25
|
+
// Only bodies that declare JSON are parsed. A body that claims JSON and
|
|
26
|
+
// fails to parse is a broken server contract, so the parse error is left
|
|
27
|
+
// to reject instead of quietly becoming `null` data.
|
|
28
|
+
const contentType = response.headers.get('content-type') ?? '';
|
|
29
|
+
const data = contentType.includes('json') ? await response.json() : null;
|
|
30
|
+
const responseHeaders = {};
|
|
31
|
+
response.headers.forEach((value, key) => {
|
|
32
|
+
responseHeaders[key] = value;
|
|
33
|
+
});
|
|
34
|
+
return {
|
|
35
|
+
status: response.status,
|
|
36
|
+
ok: response.ok,
|
|
37
|
+
headers: responseHeaders,
|
|
38
|
+
data,
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
//# sourceMappingURL=transport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.js","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AA2CA;;;;GAIG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAC/B,CAAC,OAAgC,EAAoB,EAAE,CACvD,KAAK,EAAE,OAAO,EAAE,EAAE;IAChB,MAAM,OAAO,GAAG,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IAEvC,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;QACjB,MAAM,MAAM,GAAG,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACjD,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,MAAM,IAAI,KAAK,CAAC,WAAW,OAAO,CAAC,IAAI,CAAC,SAAS,qBAAqB,CAAC,CAAC;QAC1E,CAAC;QACD,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACnC,OAAO,CAAC,aAAa,GAAG,UAAU,MAAM,EAAE,CAAC;QAC7C,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,IAAI,eAAe,CAAC,GAAG,MAAM,CAAC;QAC3D,CAAC;IACH,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE;QACxC,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,OAAO;QACP,IAAI,EACF,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS;KACxE,CAAC,CAAC;IAEH,wEAAwE;IACxE,yEAAyE;IACzE,qDAAqD;IACrD,MAAM,WAAW,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC;IAC/D,MAAM,IAAI,GAAG,WAAW,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IAEzE,MAAM,eAAe,GAA2B,EAAE,CAAC;IACnD,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;QACtC,eAAe,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;IAC/B,CAAC,CAAC,CAAC;IAEH,OAAO;QACL,MAAM,EAAE,QAAQ,CAAC,MAAM;QACvB,EAAE,EAAE,QAAQ,CAAC,EAAE;QACf,OAAO,EAAE,eAAe;QACxB,IAAI;KACL,CAAC;AACJ,CAAC,CAAC"}
|