exdrf-gen-openapi2rtk 0.1.17__py3-none-any.whl
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.
- exdrf_gen_openapi2rtk/__init__.py +77 -0
- exdrf_gen_openapi2rtk/__version__.py +24 -0
- exdrf_gen_openapi2rtk/creator.py +314 -0
- exdrf_gen_openapi2rtk/endpoint_keys.py +29 -0
- exdrf_gen_openapi2rtk/openapi2rtk_templates/openapi2rtk/api.ts.j2 +112 -0
- exdrf_gen_openapi2rtk/openapi2rtk_templates/openapi2rtk/base-api.fr_one.ts.j2 +61 -0
- exdrf_gen_openapi2rtk/openapi2rtk_templates/openapi2rtk/base-api.minimal.ts.j2 +40 -0
- exdrf_gen_openapi2rtk/openapi2rtk_templates/openapi2rtk/cacheUtils.ts.j2 +58 -0
- exdrf_gen_openapi2rtk/openapi2rtk_templates/openapi2rtk/index.ts.j2 +17 -0
- exdrf_gen_openapi2rtk/openapi2rtk_templates/openapi2rtk/list-query-contract.ts.j2 +37 -0
- exdrf_gen_openapi2rtk/openapi_cache.py +151 -0
- exdrf_gen_openapi2rtk/py.typed +0 -0
- exdrf_gen_openapi2rtk/spec_routes.py +415 -0
- exdrf_gen_openapi2rtk-0.1.17.dist-info/METADATA +80 -0
- exdrf_gen_openapi2rtk-0.1.17.dist-info/RECORD +20 -0
- exdrf_gen_openapi2rtk-0.1.17.dist-info/WHEEL +5 -0
- exdrf_gen_openapi2rtk-0.1.17.dist-info/entry_points.txt +2 -0
- exdrf_gen_openapi2rtk-0.1.17.dist-info/top_level.txt +3 -0
- tests/openapi2rtk_cli_test.py +50 -0
- tests/spec_routes_opid_test.py +27 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""exdrf-gen plugin: RTK Query TypeScript from OpenAPI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
from click import Context
|
|
9
|
+
|
|
10
|
+
from exdrf_gen.cli_base import cli
|
|
11
|
+
from exdrf_gen.plugin_support import install_plugin
|
|
12
|
+
from exdrf_gen_openapi2rtk.creator import run_openapi2rtk
|
|
13
|
+
|
|
14
|
+
install_plugin(
|
|
15
|
+
template_paths=[
|
|
16
|
+
os.path.join(os.path.dirname(__file__), "openapi2rtk_templates"),
|
|
17
|
+
],
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@cli.command(name="openapi2rtk")
|
|
22
|
+
@click.argument(
|
|
23
|
+
"routes_out_dir",
|
|
24
|
+
type=click.Path(file_okay=False, dir_okay=True),
|
|
25
|
+
envvar="EXDRF_OPENAPI2RTK_ROUTES_DIR",
|
|
26
|
+
)
|
|
27
|
+
@click.option(
|
|
28
|
+
"--openapi-file",
|
|
29
|
+
type=click.Path(exists=True, dir_okay=False),
|
|
30
|
+
default=None,
|
|
31
|
+
help="Path to a local openapi.json (or use --openapi-url).",
|
|
32
|
+
)
|
|
33
|
+
@click.option(
|
|
34
|
+
"--openapi-url",
|
|
35
|
+
default=None,
|
|
36
|
+
help="HTTP(S) URL to fetch OpenAPI JSON (requires --cache-file).",
|
|
37
|
+
)
|
|
38
|
+
@click.option(
|
|
39
|
+
"--cache-file",
|
|
40
|
+
type=click.Path(dir_okay=False),
|
|
41
|
+
default=None,
|
|
42
|
+
help="Cache path for --openapi-url downloads and ETag metadata.",
|
|
43
|
+
)
|
|
44
|
+
@click.option(
|
|
45
|
+
"--types-import",
|
|
46
|
+
default="@resi/models",
|
|
47
|
+
show_default=True,
|
|
48
|
+
help="Module path passed to emitted ``from '…'`` type imports.",
|
|
49
|
+
)
|
|
50
|
+
@click.option(
|
|
51
|
+
"--base-api-profile",
|
|
52
|
+
type=click.Choice(["minimal", "fr_one"]),
|
|
53
|
+
default="minimal",
|
|
54
|
+
show_default=True,
|
|
55
|
+
help=("``fr_one`` matches fr-one auth/baseUrl wiring; ``minimal`` is generic."),
|
|
56
|
+
)
|
|
57
|
+
@click.pass_context
|
|
58
|
+
def openapi2rtk(
|
|
59
|
+
context: Context,
|
|
60
|
+
routes_out_dir: str,
|
|
61
|
+
openapi_file: str | None,
|
|
62
|
+
openapi_url: str | None,
|
|
63
|
+
cache_file: str | None,
|
|
64
|
+
types_import: str,
|
|
65
|
+
base_api_profile: str,
|
|
66
|
+
) -> None:
|
|
67
|
+
"""Generate RTK Query route modules from an OpenAPI document."""
|
|
68
|
+
|
|
69
|
+
run_openapi2rtk(
|
|
70
|
+
context,
|
|
71
|
+
routes_out_dir,
|
|
72
|
+
openapi_file,
|
|
73
|
+
openapi_url,
|
|
74
|
+
cache_file,
|
|
75
|
+
types_import,
|
|
76
|
+
base_api_profile,
|
|
77
|
+
)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Package version from PEP 621 or installed metadata."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from exdrf.pep621_version import distribution_version, version_tuple_from_string
|
|
8
|
+
|
|
9
|
+
_PYPROJECT = Path(__file__).resolve().parents[1] / "pyproject.toml"
|
|
10
|
+
_DIST_NAME = "exdrf-gen-openapi2rtk"
|
|
11
|
+
|
|
12
|
+
__version__ = version = distribution_version(_DIST_NAME, _PYPROJECT)
|
|
13
|
+
__version_tuple__ = version_tuple = version_tuple_from_string(__version__)
|
|
14
|
+
|
|
15
|
+
__commit_id__ = commit_id = None
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"__version__",
|
|
19
|
+
"__version_tuple__",
|
|
20
|
+
"version",
|
|
21
|
+
"version_tuple",
|
|
22
|
+
"__commit_id__",
|
|
23
|
+
"commit_id",
|
|
24
|
+
]
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
"""Emit RTK Query TypeScript from a parsed OpenAPI document."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import re
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import TYPE_CHECKING, Any, Mapping
|
|
10
|
+
|
|
11
|
+
import click
|
|
12
|
+
from attrs import define, field
|
|
13
|
+
|
|
14
|
+
from exdrf_gen.fs_support import Base, Dir, File
|
|
15
|
+
from exdrf_gen_openapi2rtk.endpoint_keys import assert_unique_rtk_endpoint_keys
|
|
16
|
+
from exdrf_gen_openapi2rtk.openapi_cache import (
|
|
17
|
+
fetch_openapi_url_cached,
|
|
18
|
+
load_openapi_from_file,
|
|
19
|
+
)
|
|
20
|
+
from exdrf_gen_openapi2rtk.spec_routes import (
|
|
21
|
+
GenRoute,
|
|
22
|
+
routes_by_primary_tag,
|
|
23
|
+
tag_file_stem,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
if TYPE_CHECKING:
|
|
27
|
+
from jinja2 import Environment
|
|
28
|
+
|
|
29
|
+
logger = logging.getLogger(__name__)
|
|
30
|
+
|
|
31
|
+
_IN_STATE = {"tenant": "Tenant"}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _restrict_templates_to_package(env: "Environment", suffix: str) -> None:
|
|
35
|
+
"""Limit Jinja search paths to this package's template directory."""
|
|
36
|
+
|
|
37
|
+
loader = getattr(env, "loader", None)
|
|
38
|
+
if loader is None:
|
|
39
|
+
return
|
|
40
|
+
paths = list(getattr(loader, "paths", []))
|
|
41
|
+
filtered = [p for p in paths if str(p).endswith(suffix)]
|
|
42
|
+
setattr(loader, "paths", filtered)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _category_camel(tag: str) -> str:
|
|
46
|
+
"""Derive ``c_camel`` from a primary OpenAPI tag string."""
|
|
47
|
+
|
|
48
|
+
if not tag:
|
|
49
|
+
return tag
|
|
50
|
+
return tag[0].lower() + tag[1:]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _used_schema_names(routes: list[GenRoute]) -> list[str]:
|
|
54
|
+
"""Collect named schema tokens imported from ``types_import``."""
|
|
55
|
+
|
|
56
|
+
names: set[str] = set()
|
|
57
|
+
for r in routes:
|
|
58
|
+
if r.body_m:
|
|
59
|
+
names.add(r.body_m.name)
|
|
60
|
+
if re.fullmatch(r"[A-Za-z][A-Za-z0-9_]*", r.response_ts):
|
|
61
|
+
names.add(r.response_ts)
|
|
62
|
+
return sorted(names)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _build_api_bundle(
|
|
66
|
+
category: str,
|
|
67
|
+
routes: list[GenRoute],
|
|
68
|
+
*,
|
|
69
|
+
types_import: str,
|
|
70
|
+
m_name: str,
|
|
71
|
+
) -> dict[str, Any]:
|
|
72
|
+
"""Assemble the Jinja context for one per-tag ``*.ts`` module."""
|
|
73
|
+
|
|
74
|
+
assert_unique_rtk_endpoint_keys(category, routes)
|
|
75
|
+
|
|
76
|
+
c_snake = tag_file_stem(category)
|
|
77
|
+
c_camel = _category_camel(category)
|
|
78
|
+
uses_list_query_contract = any(
|
|
79
|
+
r.body_schema_name == "ListQueryRequest" for r in routes
|
|
80
|
+
)
|
|
81
|
+
used_res_names = _used_schema_names(routes)
|
|
82
|
+
|
|
83
|
+
global_sourced = {
|
|
84
|
+
**{
|
|
85
|
+
arg: f"(api.getState() as LocalState).runtime.{arg}.id" for arg in _IN_STATE
|
|
86
|
+
},
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
def has_local_state(rs: list[GenRoute]) -> bool:
|
|
90
|
+
"""True when any route body references ``_IN_STATE`` keys."""
|
|
91
|
+
|
|
92
|
+
for r in rs:
|
|
93
|
+
if r.body_m:
|
|
94
|
+
for fld in r.body_m.fields:
|
|
95
|
+
if fld.name in _IN_STATE:
|
|
96
|
+
return True
|
|
97
|
+
return False
|
|
98
|
+
|
|
99
|
+
def get_local_state(fld: Any) -> str:
|
|
100
|
+
"""Map a body field to its global-state TypeScript type label."""
|
|
101
|
+
|
|
102
|
+
return _IN_STATE[fld.name]
|
|
103
|
+
|
|
104
|
+
def get_arg(arg: str) -> str:
|
|
105
|
+
"""Build the JavaScript expression for one request argument."""
|
|
106
|
+
|
|
107
|
+
if arg in global_sourced:
|
|
108
|
+
return global_sourced[arg]
|
|
109
|
+
return "args." + arg
|
|
110
|
+
|
|
111
|
+
arg_pattern = re.compile(r"\{([^}]+)\}")
|
|
112
|
+
|
|
113
|
+
def get_route(route: GenRoute) -> str:
|
|
114
|
+
"""Interpolate path and query template literals for RTK ``url``."""
|
|
115
|
+
|
|
116
|
+
result = arg_pattern.sub(
|
|
117
|
+
lambda m: "${" + get_arg(m.group(1)) + "}",
|
|
118
|
+
route.path,
|
|
119
|
+
)
|
|
120
|
+
if route.query_args:
|
|
121
|
+
result += "?" + "&".join(
|
|
122
|
+
q[0] + "=${" + get_arg(q[0]) + "}" for q in route.query_args
|
|
123
|
+
)
|
|
124
|
+
return result
|
|
125
|
+
|
|
126
|
+
def get_arg_type(route: GenRoute) -> str:
|
|
127
|
+
"""Return the precomputed RTK args type for ``route``."""
|
|
128
|
+
|
|
129
|
+
return route.arg_type_ts
|
|
130
|
+
|
|
131
|
+
def get_response_type(route: GenRoute) -> str:
|
|
132
|
+
"""Return the precomputed RTK response type for ``route``."""
|
|
133
|
+
|
|
134
|
+
return route.response_ts
|
|
135
|
+
|
|
136
|
+
def get_body_arg_names(route: GenRoute) -> list[str]:
|
|
137
|
+
"""Return JSON body property names for ``route``."""
|
|
138
|
+
|
|
139
|
+
if route.body_m:
|
|
140
|
+
return [f.name for f in route.body_m.fields]
|
|
141
|
+
return [n for n, _ in route.body_args]
|
|
142
|
+
|
|
143
|
+
kind_str = ["mutation" if r.is_mutation else "query" for r in routes]
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
"tag_snake": c_snake,
|
|
147
|
+
"category": category,
|
|
148
|
+
"routes": routes,
|
|
149
|
+
"c_snake": c_snake,
|
|
150
|
+
"c_camel": c_camel,
|
|
151
|
+
"r_count": len(routes),
|
|
152
|
+
"r_snake": [r.name_snake_case for r in routes],
|
|
153
|
+
"r_camel": [r.name_camel for r in routes],
|
|
154
|
+
"r_pascal": [r.name_pascal for r in routes],
|
|
155
|
+
"uses_list_query_contract": uses_list_query_contract,
|
|
156
|
+
"used_res_names": used_res_names,
|
|
157
|
+
"used_res": {n: None for n in used_res_names},
|
|
158
|
+
"kind_str": kind_str,
|
|
159
|
+
"global_sourced": global_sourced,
|
|
160
|
+
"get_arg": get_arg,
|
|
161
|
+
"get_route": get_route,
|
|
162
|
+
"get_arg_type": get_arg_type,
|
|
163
|
+
"get_response_type": get_response_type,
|
|
164
|
+
"get_local_state": get_local_state,
|
|
165
|
+
"has_local_state": has_local_state,
|
|
166
|
+
"get_body_arg_names": get_body_arg_names,
|
|
167
|
+
"in_state": _IN_STATE,
|
|
168
|
+
"types_import": types_import,
|
|
169
|
+
"m_name": m_name,
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
@define
|
|
174
|
+
class _RoutesPackageDir(Base):
|
|
175
|
+
"""Emit ``index.ts`` plus one ``{tag_snake}.ts`` file per OpenAPI tag."""
|
|
176
|
+
|
|
177
|
+
name: str = field()
|
|
178
|
+
bundles: list[dict[str, Any]] = field(factory=list)
|
|
179
|
+
extra: dict[str, Any] = field(factory=dict)
|
|
180
|
+
|
|
181
|
+
def generate(self, out_path: str, **kwargs: Any) -> None:
|
|
182
|
+
"""Create the routes directory and all per-tag RTK modules."""
|
|
183
|
+
|
|
184
|
+
mapping = {**self.extra, **kwargs}
|
|
185
|
+
c_path = self.create_directory(out_path, self.name, **mapping)
|
|
186
|
+
env = mapping["env"]
|
|
187
|
+
child_kw = {k: v for k, v in mapping.items() if k != "env"}
|
|
188
|
+
File("index.ts", "openapi2rtk/index.ts.j2").generate(
|
|
189
|
+
c_path,
|
|
190
|
+
env=env,
|
|
191
|
+
**child_kw,
|
|
192
|
+
)
|
|
193
|
+
for b in self.bundles:
|
|
194
|
+
File("{tag_snake}.ts", "openapi2rtk/api.ts.j2").generate(
|
|
195
|
+
c_path,
|
|
196
|
+
env=env,
|
|
197
|
+
**{**child_kw, **b},
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def generate_openapi2rtk(
|
|
202
|
+
spec: Mapping[str, Any],
|
|
203
|
+
routes_out_dir: str,
|
|
204
|
+
env: "Environment",
|
|
205
|
+
*,
|
|
206
|
+
types_import: str,
|
|
207
|
+
base_api_profile: str,
|
|
208
|
+
m_name: str,
|
|
209
|
+
) -> None:
|
|
210
|
+
"""Write RTK route modules and shared helpers next to ``routes_out_dir``."""
|
|
211
|
+
|
|
212
|
+
by_category = routes_by_primary_tag(spec)
|
|
213
|
+
if not by_category:
|
|
214
|
+
logger.warning(
|
|
215
|
+
"OpenAPI document produced no tagged operations; no TS files.",
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
bundles: list[dict[str, Any]] = []
|
|
219
|
+
for cat in sorted(by_category):
|
|
220
|
+
routes = by_category[cat]
|
|
221
|
+
if not routes:
|
|
222
|
+
continue
|
|
223
|
+
bundles.append(
|
|
224
|
+
_build_api_bundle(
|
|
225
|
+
cat,
|
|
226
|
+
routes,
|
|
227
|
+
types_import=types_import,
|
|
228
|
+
m_name=m_name,
|
|
229
|
+
)
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
c_keys = sorted(by_category.keys())
|
|
233
|
+
c_camel = [_category_camel(c) for c in c_keys]
|
|
234
|
+
c_snake = [tag_file_stem(c) for c in c_keys]
|
|
235
|
+
glb_vars: dict[str, Any] = {
|
|
236
|
+
"c_count": len(c_keys),
|
|
237
|
+
"c_keys": c_keys,
|
|
238
|
+
"c_snake": c_snake,
|
|
239
|
+
"c_camel": c_camel,
|
|
240
|
+
"categories": by_category,
|
|
241
|
+
"m_name": m_name,
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
routes_path = Path(routes_out_dir)
|
|
245
|
+
parent = str(routes_path.parent)
|
|
246
|
+
routes_folder_name = routes_path.name
|
|
247
|
+
|
|
248
|
+
base_tpl = (
|
|
249
|
+
"openapi2rtk/base-api.fr_one.ts.j2"
|
|
250
|
+
if base_api_profile == "fr_one"
|
|
251
|
+
else "openapi2rtk/base-api.minimal.ts.j2"
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
root = Dir(
|
|
255
|
+
name="",
|
|
256
|
+
comp=[
|
|
257
|
+
File(
|
|
258
|
+
"list-query-contract.ts",
|
|
259
|
+
"openapi2rtk/list-query-contract.ts.j2",
|
|
260
|
+
),
|
|
261
|
+
File("base-api.ts", base_tpl),
|
|
262
|
+
File("cacheUtils.ts", "openapi2rtk/cacheUtils.ts.j2"),
|
|
263
|
+
_RoutesPackageDir(
|
|
264
|
+
name=routes_folder_name,
|
|
265
|
+
bundles=bundles,
|
|
266
|
+
),
|
|
267
|
+
],
|
|
268
|
+
)
|
|
269
|
+
root.generate(
|
|
270
|
+
parent,
|
|
271
|
+
env=env,
|
|
272
|
+
**glb_vars,
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def run_openapi2rtk(
|
|
277
|
+
context: click.Context,
|
|
278
|
+
routes_out_dir: str,
|
|
279
|
+
openapi_file: str | None,
|
|
280
|
+
openapi_url: str | None,
|
|
281
|
+
cache_file: str | None,
|
|
282
|
+
types_import: str,
|
|
283
|
+
base_api_profile: str,
|
|
284
|
+
) -> None:
|
|
285
|
+
"""CLI entry: load spec, then emit RTK TypeScript."""
|
|
286
|
+
|
|
287
|
+
env = context.obj["jinja_env"]
|
|
288
|
+
_restrict_templates_to_package(env, "openapi2rtk_templates")
|
|
289
|
+
|
|
290
|
+
if openapi_file:
|
|
291
|
+
spec = load_openapi_from_file(openapi_file)
|
|
292
|
+
elif openapi_url:
|
|
293
|
+
if not cache_file:
|
|
294
|
+
click.echo(
|
|
295
|
+
"--cache-file is required when using --openapi-url.",
|
|
296
|
+
err=True,
|
|
297
|
+
)
|
|
298
|
+
sys.exit(1)
|
|
299
|
+
spec = fetch_openapi_url_cached(openapi_url, Path(cache_file))
|
|
300
|
+
else:
|
|
301
|
+
click.echo(
|
|
302
|
+
"Provide --openapi-file or --openapi-url (with --cache-file).",
|
|
303
|
+
err=True,
|
|
304
|
+
)
|
|
305
|
+
sys.exit(1)
|
|
306
|
+
|
|
307
|
+
generate_openapi2rtk(
|
|
308
|
+
spec,
|
|
309
|
+
routes_out_dir,
|
|
310
|
+
env,
|
|
311
|
+
types_import=types_import,
|
|
312
|
+
base_api_profile=base_api_profile,
|
|
313
|
+
m_name="exdrf_gen_openapi2rtk.creator",
|
|
314
|
+
)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""RTK ``injectEndpoints`` key uniqueness for OpenAPI-derived routes."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def assert_unique_rtk_endpoint_keys(category: str, routes: list[Any]) -> None:
|
|
9
|
+
"""Fail fast when two routes would share the same RTK endpoint object key.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
category: OpenAPI primary tag / grouping label for the emitted file.
|
|
13
|
+
routes: Route objects with ``name_camel`` (camelCase operation key).
|
|
14
|
+
|
|
15
|
+
Raises:
|
|
16
|
+
ValueError: When two routes share the same ``name_camel``.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
seen: dict[str, str] = {}
|
|
20
|
+
for route in routes:
|
|
21
|
+
key = route.name_camel
|
|
22
|
+
if key in seen:
|
|
23
|
+
raise ValueError(
|
|
24
|
+
"Duplicate RTK endpoint key %r in category %r: paths %r vs %r. "
|
|
25
|
+
"Adjust ``operationId`` values or OpenAPI tags so keys are "
|
|
26
|
+
"unique within each emitted module."
|
|
27
|
+
% (key, category, seen[key], getattr(route, "path", "?"))
|
|
28
|
+
)
|
|
29
|
+
seen[key] = getattr(route, "path", "?")
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// This file was automatically generated using exdrf-gen-openapi2rtk.
|
|
2
|
+
// Source: {{ m_name }}
|
|
3
|
+
// Don't change it manually.
|
|
4
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
5
|
+
import { BaseQueryApi } from '@reduxjs/toolkit/query';
|
|
6
|
+
import { apiBase } from '../base-api';
|
|
7
|
+
import {
|
|
8
|
+
providesNestedList,
|
|
9
|
+
cacheByIdArgProperty,
|
|
10
|
+
invalidateByIdArgPropertyAndList,
|
|
11
|
+
} from '../cacheUtils';
|
|
12
|
+
{%- if uses_list_query_contract %}
|
|
13
|
+
import type { ListQueryRequest } from '../list-query-contract';
|
|
14
|
+
{%- endif %}
|
|
15
|
+
{%- for res_name in used_res_names %}
|
|
16
|
+
import { {{ res_name }} } from '{{ types_import }}';
|
|
17
|
+
{%- endfor %}
|
|
18
|
+
|
|
19
|
+
{%- if has_local_state(routes) -%}
|
|
20
|
+
{% set name_seen = {} %}
|
|
21
|
+
|
|
22
|
+
export interface LocalState {
|
|
23
|
+
runtime: {
|
|
24
|
+
{%- for i in range(r_count) -%}
|
|
25
|
+
{%- for field in routes[i].body_m.fields -%}
|
|
26
|
+
{%- if field.name in in_state and field.name not in name_seen -%}
|
|
27
|
+
{%- set _ = name_seen.update({field.name: true}) %}
|
|
28
|
+
{{ field.name }}: {{ get_local_state(field) }};
|
|
29
|
+
{% endif -%}
|
|
30
|
+
{%- endfor -%}
|
|
31
|
+
{%- endfor %}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
{% endif %}
|
|
35
|
+
|
|
36
|
+
apiBase.enhanceEndpoints({
|
|
37
|
+
addTagTypes: ['{{ category.capitalize() }}']
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
export const {{ c_camel }}Api = apiBase.injectEndpoints({
|
|
42
|
+
endpoints: (build) => ({
|
|
43
|
+
{%- for i in range(r_count) %}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
{% for line in routes[i].doc_lines %} * {{ line }}
|
|
47
|
+
{% endfor %} */
|
|
48
|
+
{{ r_camel[i] }}: build.{{ kind_str[i] }}<{{ get_response_type(routes[i]) }}, {{
|
|
49
|
+
get_arg_type(routes[i])
|
|
50
|
+
}}>({
|
|
51
|
+
queryFn: async (
|
|
52
|
+
args,
|
|
53
|
+
api: BaseQueryApi,
|
|
54
|
+
extraOptions: any, // eslint-disable-line
|
|
55
|
+
usingBase
|
|
56
|
+
) => {
|
|
57
|
+
return usingBase({
|
|
58
|
+
url: `{{ get_route(routes[i]) }}`,
|
|
59
|
+
method: '{{ routes[i].method.name }}',
|
|
60
|
+
{%- if routes[i].body_m %}
|
|
61
|
+
body: {
|
|
62
|
+
{%- for arg_name in get_body_arg_names(routes[i]) %}
|
|
63
|
+
{{ arg_name }}: {{ get_arg(arg_name) }},
|
|
64
|
+
{%- endfor %}
|
|
65
|
+
},
|
|
66
|
+
{%- elif routes[i].body_args %}
|
|
67
|
+
body: {
|
|
68
|
+
{%- for arg_name in get_body_arg_names(routes[i]) %}
|
|
69
|
+
{{ arg_name }}: {{ get_arg(arg_name) }},
|
|
70
|
+
{%- endfor %}
|
|
71
|
+
},
|
|
72
|
+
{%- endif %}
|
|
73
|
+
});
|
|
74
|
+
},
|
|
75
|
+
{% if r_camel[i].startswith('create') -%}
|
|
76
|
+
invalidatesTags: [
|
|
77
|
+
{ type: '{{ category.capitalize() }}', id: 'LIST' },
|
|
78
|
+
{ type: '{{ category.capitalize() }}', id: 'Q-LIST' }
|
|
79
|
+
] as any,
|
|
80
|
+
{%- elif r_camel[i].startswith('remove') -%}
|
|
81
|
+
invalidatesTags: [
|
|
82
|
+
{ type: '{{ category.capitalize() }}', id: 'LIST' },
|
|
83
|
+
{ type: '{{ category.capitalize() }}', id: 'Q-LIST' }
|
|
84
|
+
] as any,
|
|
85
|
+
{%- elif r_camel[i].startswith('listQuick') -%}
|
|
86
|
+
providesTags: [{ type: '{{ category.capitalize() }}', id: 'Q-LIST' }] as any,
|
|
87
|
+
{%- elif routes[i].name.startswith('query_') -%}
|
|
88
|
+
providesTags: providesNestedList('{{ category.capitalize() }}') as any,
|
|
89
|
+
{%- elif r_camel[i].startswith('list') -%}
|
|
90
|
+
providesTags: providesNestedList('{{ category.capitalize() }}') as any,
|
|
91
|
+
{%- elif r_camel[i].startswith('read') -%}
|
|
92
|
+
providesTags: cacheByIdArgProperty('{{ category.capitalize() }}') as any,
|
|
93
|
+
{%- elif r_camel[i].startswith('get') -%}
|
|
94
|
+
providesTags: cacheByIdArgProperty('{{ category.capitalize() }}') as any,
|
|
95
|
+
{%- elif r_camel[i].startswith('update') -%}
|
|
96
|
+
invalidatesTags: invalidateByIdArgPropertyAndList('{{ category.capitalize() }}') as any,
|
|
97
|
+
{%- endif %}
|
|
98
|
+
}),
|
|
99
|
+
{%- endfor %}
|
|
100
|
+
}),
|
|
101
|
+
overrideExisting: false,
|
|
102
|
+
});{{'\n'}}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
export const {
|
|
106
|
+
{%- for i in range(r_count) %}
|
|
107
|
+
use{{ r_pascal[i] }}{{ kind_str[i] | capitalize }},
|
|
108
|
+
{%- if kind_str[i] == 'query' %}
|
|
109
|
+
useLazy{{ r_pascal[i] }}{{ kind_str[i] | capitalize }},
|
|
110
|
+
{%- endif -%}
|
|
111
|
+
{%- endfor %}
|
|
112
|
+
} = {{ c_camel }}Api;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// This file was automatically generated using exdrf-gen-openapi2rtk.
|
|
2
|
+
// Source: {{ m_name }}
|
|
3
|
+
// Don't change it manually.
|
|
4
|
+
import { refreshHttpAccessTokenSingleFlight } from '@fr-one/shared-comms';
|
|
5
|
+
import { getRtkAccessToken } from '@fr-one/shared-sec';
|
|
6
|
+
import { getDefaultPublicEnv } from '@fr-one/core-config';
|
|
7
|
+
import type {
|
|
8
|
+
BaseQueryFn,
|
|
9
|
+
FetchArgs,
|
|
10
|
+
FetchBaseQueryError,
|
|
11
|
+
} from '@reduxjs/toolkit/query';
|
|
12
|
+
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
|
|
13
|
+
|
|
14
|
+
const rawBaseQuery = fetchBaseQuery({
|
|
15
|
+
baseUrl: getDefaultPublicEnv().apiBaseUrl,
|
|
16
|
+
credentials: 'include',
|
|
17
|
+
prepareHeaders: (headers) => {
|
|
18
|
+
const token = getRtkAccessToken();
|
|
19
|
+
if (token) {
|
|
20
|
+
headers.set('Authorization', `Bearer ${token}`);
|
|
21
|
+
}
|
|
22
|
+
return headers;
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Retries once after a cookie-backed refresh when RTK sees HTTP 401.
|
|
28
|
+
*/
|
|
29
|
+
const baseQueryWithReauth: BaseQueryFn<
|
|
30
|
+
string | FetchArgs,
|
|
31
|
+
unknown,
|
|
32
|
+
FetchBaseQueryError
|
|
33
|
+
> = async (args, api, extraOptions) => {
|
|
34
|
+
const result = await rawBaseQuery(args, api, extraOptions);
|
|
35
|
+
if (
|
|
36
|
+
result.error &&
|
|
37
|
+
typeof result.error === 'object' &&
|
|
38
|
+
'status' in result.error &&
|
|
39
|
+
result.error.status === 401
|
|
40
|
+
) {
|
|
41
|
+
const newTok = await refreshHttpAccessTokenSingleFlight();
|
|
42
|
+
if (newTok) {
|
|
43
|
+
return rawBaseQuery(args, api, extraOptions);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return result;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Shared RTK Query API instance used by generated route clients.
|
|
51
|
+
*
|
|
52
|
+
* It points to the runtime API base URL and keeps endpoint registration open
|
|
53
|
+
* for route-category files generated under `generated/routes`.
|
|
54
|
+
*/
|
|
55
|
+
export const apiBase = createApi({
|
|
56
|
+
reducerPath: 'generatedApi',
|
|
57
|
+
refetchOnFocus: true,
|
|
58
|
+
refetchOnReconnect: true,
|
|
59
|
+
baseQuery: baseQueryWithReauth,
|
|
60
|
+
endpoints: () => ({}),
|
|
61
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// This file was automatically generated using exdrf-gen-openapi2rtk.
|
|
2
|
+
// Source: {{ m_name }}
|
|
3
|
+
// Don't change it manually.
|
|
4
|
+
//
|
|
5
|
+
// Minimal RTK base for hosts that wire auth/baseUrl outside this package.
|
|
6
|
+
import type {
|
|
7
|
+
BaseQueryFn,
|
|
8
|
+
FetchArgs,
|
|
9
|
+
FetchBaseQueryError,
|
|
10
|
+
} from '@reduxjs/toolkit/query';
|
|
11
|
+
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
|
|
12
|
+
|
|
13
|
+
declare const __OPENAPI2RTK_API_BASE_URL__: string | undefined;
|
|
14
|
+
|
|
15
|
+
const baseUrl =
|
|
16
|
+
(typeof __OPENAPI2RTK_API_BASE_URL__ === 'string' &&
|
|
17
|
+
__OPENAPI2RTK_API_BASE_URL__) ||
|
|
18
|
+
'';
|
|
19
|
+
|
|
20
|
+
const rawBaseQuery = fetchBaseQuery({
|
|
21
|
+
baseUrl,
|
|
22
|
+
credentials: 'include',
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const baseQuery: BaseQueryFn<
|
|
26
|
+
string | FetchArgs,
|
|
27
|
+
unknown,
|
|
28
|
+
FetchBaseQueryError
|
|
29
|
+
> = async (args, api, extraOptions) => rawBaseQuery(args, api, extraOptions);
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Shared RTK Query API instance used by generated route clients.
|
|
33
|
+
*/
|
|
34
|
+
export const apiBase = createApi({
|
|
35
|
+
reducerPath: 'generatedApi',
|
|
36
|
+
refetchOnFocus: true,
|
|
37
|
+
refetchOnReconnect: true,
|
|
38
|
+
baseQuery,
|
|
39
|
+
endpoints: () => ({}),
|
|
40
|
+
});
|