@dynamicforms/fastapi-viewsets 0.1.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/LICENSE +21 -0
- package/README.md +65 -0
- package/dist/fastapi-viewsets.js +221 -0
- package/dist/fastapi-viewsets.js.map +1 -0
- package/dist/fastapi-viewsets.umd.cjs +3 -0
- package/dist/fastapi-viewsets.umd.cjs.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/mixins.d.ts +73 -0
- package/dist/mixins.d.ts.map +1 -0
- package/dist/rest-proxy.d.ts +71 -0
- package/dist/rest-proxy.d.ts.map +1 -0
- package/package.json +68 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Jure Erznožnik
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# DynamicForms FastAPI Viewsets
|
|
2
|
+
|
|
3
|
+
Django REST Framework-style viewsets for [FastAPI](https://fastapi.tiangolo.com/), with optional
|
|
4
|
+
Celery-backed async execution and a matching Vue/TypeScript client counterpart.
|
|
5
|
+
|
|
6
|
+
- **Python mixins for FastAPI** — compose CRUD and bulk endpoints from small, focused mixin classes.
|
|
7
|
+
- **`route_viewset` decorator** — register a viewset on a FastAPI router with a single decorator call.
|
|
8
|
+
Handles type resolution, lifecycle management and OpenAPI schema automatically.
|
|
9
|
+
- **`CollectionViewSet`** — zero-boilerplate in-memory viewset backed by any Python list, set or dict.
|
|
10
|
+
Great for prototyping and testing.
|
|
11
|
+
- **`CeleryViewSet`** — delegate all CRUD operations to Celery tasks, for long-running or background
|
|
12
|
+
processing scenarios (requires the `celery` extra).
|
|
13
|
+
- **Bulk operations** — first-class support for bulk create, update, partial update and destroy.
|
|
14
|
+
- **Vue / TypeScript counterpart** — mirror mixin classes and a `route_rest` factory give you a fully
|
|
15
|
+
typed HTTP client that matches your backend viewset exactly (published separately as
|
|
16
|
+
[`@dynamicforms/fastapi-viewsets`](https://www.npmjs.com/package/@dynamicforms/fastapi-viewsets) on npm).
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install dynamicforms-fastapi-viewsets
|
|
22
|
+
|
|
23
|
+
# with Celery-backed viewset support
|
|
24
|
+
pip install "dynamicforms-fastapi-viewsets[celery]"
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Requires Python 3.10+, FastAPI and Pydantic v2.
|
|
28
|
+
|
|
29
|
+
## Quick example
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
from fastapi import APIRouter, FastAPI
|
|
33
|
+
from pydantic import BaseModel
|
|
34
|
+
|
|
35
|
+
from fastapi_viewsets.collection_viewset import CollectionViewSet
|
|
36
|
+
from fastapi_viewsets.decorators.route_viewset import route_viewset
|
|
37
|
+
from fastapi_viewsets.mixins import BulkViewSetMixin
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class Item(BaseModel):
|
|
41
|
+
id: int
|
|
42
|
+
name: str
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
database: dict[int, Item] = {1: Item(id=1, name="First element")}
|
|
46
|
+
|
|
47
|
+
app = FastAPI()
|
|
48
|
+
router = APIRouter()
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@route_viewset(router, base_path="/items", pk_field_name="id")
|
|
52
|
+
class ItemViewSet(CollectionViewSet[int, Item], BulkViewSetMixin[int, Item]):
|
|
53
|
+
def __init__(self):
|
|
54
|
+
super().__init__(container=database, pk_field="id")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
app.include_router(router)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
See the [full documentation](https://docs.velis.si/dynamicforms/fastapi-viewsets/) for guides on
|
|
61
|
+
the mixin system, `route_viewset`, `CollectionViewSet`, `CeleryViewSet`, and the Vue client.
|
|
62
|
+
|
|
63
|
+
## License
|
|
64
|
+
|
|
65
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import e from "axios";
|
|
2
|
+
//#region \0@oxc-project+runtime@0.133.0/helpers/esm/typeof.js
|
|
3
|
+
function t(e) {
|
|
4
|
+
"@babel/helpers - typeof";
|
|
5
|
+
return t = typeof Symbol == "function" && typeof Symbol.iterator == "symbol" ? function(e) {
|
|
6
|
+
return typeof e;
|
|
7
|
+
} : function(e) {
|
|
8
|
+
return e && typeof Symbol == "function" && e.constructor === Symbol && e !== Symbol.prototype ? "symbol" : typeof e;
|
|
9
|
+
}, t(e);
|
|
10
|
+
}
|
|
11
|
+
//#endregion
|
|
12
|
+
//#region \0@oxc-project+runtime@0.133.0/helpers/esm/toPrimitive.js
|
|
13
|
+
function n(e, n) {
|
|
14
|
+
if (t(e) != "object" || !e) return e;
|
|
15
|
+
var r = e[Symbol.toPrimitive];
|
|
16
|
+
if (r !== void 0) {
|
|
17
|
+
var i = r.call(e, n || "default");
|
|
18
|
+
if (t(i) != "object") return i;
|
|
19
|
+
throw TypeError("@@toPrimitive must return a primitive value.");
|
|
20
|
+
}
|
|
21
|
+
return (n === "string" ? String : Number)(e);
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region \0@oxc-project+runtime@0.133.0/helpers/esm/toPropertyKey.js
|
|
25
|
+
function r(e) {
|
|
26
|
+
var r = n(e, "string");
|
|
27
|
+
return t(r) == "symbol" ? r : r + "";
|
|
28
|
+
}
|
|
29
|
+
//#endregion
|
|
30
|
+
//#region \0@oxc-project+runtime@0.133.0/helpers/esm/defineProperty.js
|
|
31
|
+
function i(e, t, n) {
|
|
32
|
+
return (t = r(t)) in e ? Object.defineProperty(e, t, {
|
|
33
|
+
value: n,
|
|
34
|
+
enumerable: !0,
|
|
35
|
+
configurable: !0,
|
|
36
|
+
writable: !0
|
|
37
|
+
}) : e[t] = n, e;
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
|
40
|
+
//#region \0@oxc-project+runtime@0.133.0/helpers/esm/asyncToGenerator.js
|
|
41
|
+
function a(e, t, n, r, i, a, o) {
|
|
42
|
+
try {
|
|
43
|
+
var s = e[a](o), c = s.value;
|
|
44
|
+
} catch (e) {
|
|
45
|
+
n(e);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
s.done ? t(c) : Promise.resolve(c).then(r, i);
|
|
49
|
+
}
|
|
50
|
+
function o(e) {
|
|
51
|
+
return function() {
|
|
52
|
+
var t = this, n = arguments;
|
|
53
|
+
return new Promise(function(r, i) {
|
|
54
|
+
var o = e.apply(t, n);
|
|
55
|
+
function s(e) {
|
|
56
|
+
a(o, r, i, s, c, "next", e);
|
|
57
|
+
}
|
|
58
|
+
function c(e) {
|
|
59
|
+
a(o, r, i, s, c, "throw", e);
|
|
60
|
+
}
|
|
61
|
+
s(void 0);
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
//#endregion
|
|
66
|
+
//#region vue/rest-proxy.ts
|
|
67
|
+
var s = new Set([
|
|
68
|
+
"get",
|
|
69
|
+
"post",
|
|
70
|
+
"put",
|
|
71
|
+
"patch",
|
|
72
|
+
"delete",
|
|
73
|
+
"head",
|
|
74
|
+
"options",
|
|
75
|
+
"trace"
|
|
76
|
+
]), c = {
|
|
77
|
+
base: {
|
|
78
|
+
GET: "list",
|
|
79
|
+
POST: "create"
|
|
80
|
+
},
|
|
81
|
+
pk: {
|
|
82
|
+
GET: "retrieve",
|
|
83
|
+
PUT: "update",
|
|
84
|
+
PATCH: "partialUpdate",
|
|
85
|
+
DELETE: "destroy"
|
|
86
|
+
},
|
|
87
|
+
bulk: {
|
|
88
|
+
POST: "bulkCreate",
|
|
89
|
+
PUT: "bulkUpdate",
|
|
90
|
+
PATCH: "bulkPartialUpdate",
|
|
91
|
+
DELETE: "bulkDestroy"
|
|
92
|
+
},
|
|
93
|
+
lookup: { GET: "lookup" }
|
|
94
|
+
}, l = [
|
|
95
|
+
"list",
|
|
96
|
+
"create",
|
|
97
|
+
"retrieve",
|
|
98
|
+
"update",
|
|
99
|
+
"partialUpdate",
|
|
100
|
+
"destroy",
|
|
101
|
+
"bulkCreate",
|
|
102
|
+
"bulkUpdate",
|
|
103
|
+
"bulkPartialUpdate",
|
|
104
|
+
"bulkDestroy",
|
|
105
|
+
"lookup"
|
|
106
|
+
], u = class {
|
|
107
|
+
constructor(t) {
|
|
108
|
+
var n;
|
|
109
|
+
i(this, "http", void 0), i(this, "basePath", void 0), i(this, "pkFieldName", void 0), this.basePath = t.basePath.replace(/\/$/, ""), this.pkFieldName = t.pkFieldName, this.http = (n = t.axiosInstance) == null ? e : n, this.validateAgainstSchema();
|
|
110
|
+
}
|
|
111
|
+
validateAgainstSchema() {
|
|
112
|
+
var e = this;
|
|
113
|
+
return o(function* () {
|
|
114
|
+
try {
|
|
115
|
+
var t, n;
|
|
116
|
+
let i = (t = (n = (yield e.http.get(`${e.basePath}/schema`)).data) == null ? void 0 : n.paths) == null ? {} : t, a = /* @__PURE__ */ new Set(), o = [];
|
|
117
|
+
for (let [t, n] of Object.entries(i)) {
|
|
118
|
+
var r;
|
|
119
|
+
let i = t.slice(e.basePath.length).replace(/^\//, ""), l;
|
|
120
|
+
if (i === "") l = "base";
|
|
121
|
+
else if (i === "bulk") l = "bulk";
|
|
122
|
+
else if (i === "lookup") l = "lookup";
|
|
123
|
+
else if (i === "schema") continue;
|
|
124
|
+
else if (i.startsWith("{")) l = "pk";
|
|
125
|
+
else {
|
|
126
|
+
for (let e of Object.keys(n)) s.has(e.toLowerCase()) && o.push(`${e.toUpperCase()} ${t}`);
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
let u = (r = c[l]) == null ? {} : r;
|
|
130
|
+
for (let e of Object.keys(n)) {
|
|
131
|
+
if (!s.has(e.toLowerCase())) continue;
|
|
132
|
+
let t = u[e.toUpperCase()];
|
|
133
|
+
t && a.add(t);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
let u = [];
|
|
137
|
+
for (let t of l) typeof e[t] == "function" && !a.has(t) && u.push(`FE declares '${t}()' but BE has no matching endpoint`);
|
|
138
|
+
for (let t of a) typeof e[t] != "function" && u.push(`BE exposes '${t}' endpoint but FE does not implement it`);
|
|
139
|
+
for (let e of o) u.push(`BE has non-standard endpoint '${e}' with no FE method`);
|
|
140
|
+
u.length > 0 && console.warn(`[ViewSet ${e.basePath}] FE/BE definition mismatch:\n` + u.map((e) => ` • ${e}`).join("\n"));
|
|
141
|
+
} catch (e) {}
|
|
142
|
+
})();
|
|
143
|
+
}
|
|
144
|
+
create(e) {
|
|
145
|
+
var t = this;
|
|
146
|
+
return o(function* () {
|
|
147
|
+
return (yield t.http.post(t.basePath, e)).data;
|
|
148
|
+
})();
|
|
149
|
+
}
|
|
150
|
+
bulkCreate(e) {
|
|
151
|
+
var t = this;
|
|
152
|
+
return o(function* () {
|
|
153
|
+
return (yield t.http.post(`${t.basePath}/bulk`, e)).data;
|
|
154
|
+
})();
|
|
155
|
+
}
|
|
156
|
+
list() {
|
|
157
|
+
var e = this;
|
|
158
|
+
return o(function* () {
|
|
159
|
+
return (yield e.http.get(e.basePath)).data;
|
|
160
|
+
})();
|
|
161
|
+
}
|
|
162
|
+
retrieve(e) {
|
|
163
|
+
var t = this;
|
|
164
|
+
return o(function* () {
|
|
165
|
+
return (yield t.http.get(`${t.basePath}/${e}`)).data;
|
|
166
|
+
})();
|
|
167
|
+
}
|
|
168
|
+
update(e, t) {
|
|
169
|
+
var n = this;
|
|
170
|
+
return o(function* () {
|
|
171
|
+
return (yield n.http.put(`${n.basePath}/${e}`, t)).data;
|
|
172
|
+
})();
|
|
173
|
+
}
|
|
174
|
+
partialUpdate(e, t) {
|
|
175
|
+
var n = this;
|
|
176
|
+
return o(function* () {
|
|
177
|
+
return (yield n.http.patch(`${n.basePath}/${e}`, t)).data;
|
|
178
|
+
})();
|
|
179
|
+
}
|
|
180
|
+
bulkUpdate(e) {
|
|
181
|
+
var t = this;
|
|
182
|
+
return o(function* () {
|
|
183
|
+
return (yield t.http.put(`${t.basePath}/bulk`, e)).data;
|
|
184
|
+
})();
|
|
185
|
+
}
|
|
186
|
+
bulkPartialUpdate(e) {
|
|
187
|
+
var t = this;
|
|
188
|
+
return o(function* () {
|
|
189
|
+
return (yield t.http.patch(`${t.basePath}/bulk`, e)).data;
|
|
190
|
+
})();
|
|
191
|
+
}
|
|
192
|
+
destroy(e) {
|
|
193
|
+
var t = this;
|
|
194
|
+
return o(function* () {
|
|
195
|
+
return (yield t.http.delete(`${t.basePath}/${e}`)).data;
|
|
196
|
+
})();
|
|
197
|
+
}
|
|
198
|
+
bulkDestroy(e) {
|
|
199
|
+
var t = this;
|
|
200
|
+
return o(function* () {
|
|
201
|
+
return (yield t.http.delete(`${t.basePath}/bulk`, { data: e })).data;
|
|
202
|
+
})();
|
|
203
|
+
}
|
|
204
|
+
lookup() {
|
|
205
|
+
var e = this;
|
|
206
|
+
return o(function* () {
|
|
207
|
+
return (yield e.http.get(`${e.basePath}/lookup`)).data;
|
|
208
|
+
})();
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
function d(e, t, n, r) {
|
|
212
|
+
return new u(typeof t == "string" ? {
|
|
213
|
+
basePath: t,
|
|
214
|
+
pkFieldName: n,
|
|
215
|
+
axiosInstance: r
|
|
216
|
+
} : t);
|
|
217
|
+
}
|
|
218
|
+
//#endregion
|
|
219
|
+
export { u as RestProxyImpl, d as route_rest };
|
|
220
|
+
|
|
221
|
+
//# sourceMappingURL=fastapi-viewsets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fastapi-viewsets.js","names":[],"sources":["../vue/rest-proxy.ts"],"sourcesContent":["/**\n * REST proxy for ViewSets — FE counterpart of the BE route_viewset decorator.\n *\n * Usage:\n * const restItems = route_rest<BulkViewSetMixin<number, Item> & LookupMixin>(\n * ItemViewSet, '/items', 'id',\n * );\n * const items = await restItems.list();\n * const item = await restItems.retrieve(1);\n */\n\nimport axios, { type AxiosInstance } from 'axios';\n\nimport type { BulkViewSetMixin, DestroyReturnData, KeyType, LookupItem, LookupMixin } from './mixins';\n\n// ---------------------------------------------------------------------------\n// Schema validation constants\n// ---------------------------------------------------------------------------\n\nconst HTTP_METHODS = new Set(['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']);\n\n/**\n * Maps (path type, HTTP method) → FE method name for standard ViewSet endpoints.\n * Path types: 'base' = root, 'pk' = /{pk}, 'bulk' = /bulk, 'lookup' = /lookup.\n */\nconst ENDPOINT_TO_FE_METHOD: Readonly<Record<string, Readonly<Record<string, string>>>> = {\n base: { GET: 'list', POST: 'create' },\n pk: {\n GET: 'retrieve',\n PUT: 'update',\n PATCH: 'partialUpdate',\n DELETE: 'destroy',\n },\n bulk: {\n POST: 'bulkCreate',\n PUT: 'bulkUpdate',\n PATCH: 'bulkPartialUpdate',\n DELETE: 'bulkDestroy',\n },\n lookup: { GET: 'lookup' },\n};\n\n/** All standard FE method names, in a stable order for warning output. */\nconst STANDARD_FE_METHODS: readonly string[] = [\n 'list',\n 'create',\n 'retrieve',\n 'update',\n 'partialUpdate',\n 'destroy',\n 'bulkCreate',\n 'bulkUpdate',\n 'bulkPartialUpdate',\n 'bulkDestroy',\n 'lookup',\n];\n\n// ---------------------------------------------------------------------------\n// Helper types\n// ---------------------------------------------------------------------------\n\n/** ViewSet class constructor (for type-level introspection only). */\n\ntype ViewSetClass = abstract new (...args: any[]) => any;\n\n/**\n * The REST proxy type is simply the mixin interface `M` the caller declares.\n * Because TypeScript cannot inspect Python class hierarchies at runtime, the\n * caller provides the explicit type via the generic parameter `M` (see route_rest).\n */\nexport type RestProxy<M> = M;\n\nexport interface RestProxyOptions {\n /** Base path to the resource, e.g. '/items'. */\n basePath: string;\n /** Name of the PK field on the model, e.g. 'id'. */\n pkFieldName: string;\n /** Optional: existing axios instance. Defaults to the global axios. */\n axiosInstance?: AxiosInstance;\n}\n\n// ---------------------------------------------------------------------------\n// Proxy implementation\n// ---------------------------------------------------------------------------\n\nexport class RestProxyImpl<K extends KeyType, T, PK extends keyof T>\n implements BulkViewSetMixin<K, T, PK>, LookupMixin\n{\n protected readonly http: AxiosInstance;\n\n protected readonly basePath: string;\n\n protected readonly pkFieldName: string;\n\n constructor(options: RestProxyOptions) {\n this.basePath = options.basePath.replace(/\\/$/, '');\n this.pkFieldName = options.pkFieldName;\n this.http = options.axiosInstance ?? axios;\n void this.validateAgainstSchema();\n }\n\n /**\n * Fetches the BE schema and compares it against the FE method set.\n * Logs a console warning for any mismatch found.\n * Non-critical: errors during fetch or parsing are silently ignored.\n */\n private async validateAgainstSchema(): Promise<void> {\n try {\n const res = await this.http.get<{\n paths?: Record<string, Record<string, unknown>>;\n }>(`${this.basePath}/schema`);\n const paths = res.data?.paths ?? {};\n\n const beMethods = new Set<string>();\n const unknownBeEndpoints: string[] = [];\n\n for (const [path, pathItem] of Object.entries(paths)) {\n const suffix = path.slice(this.basePath.length).replace(/^\\//, '');\n\n let pathType: string;\n if (suffix === '') {\n pathType = 'base';\n } else if (suffix === 'bulk') {\n pathType = 'bulk';\n } else if (suffix === 'lookup') {\n pathType = 'lookup';\n } else if (suffix === 'schema') {\n continue;\n } else if (suffix.startsWith('{')) {\n pathType = 'pk';\n } else {\n for (const httpMethod of Object.keys(pathItem)) {\n if (HTTP_METHODS.has(httpMethod.toLowerCase())) {\n unknownBeEndpoints.push(`${httpMethod.toUpperCase()} ${path}`);\n }\n }\n continue;\n }\n\n const methodMap = ENDPOINT_TO_FE_METHOD[pathType] ?? {};\n for (const httpMethod of Object.keys(pathItem)) {\n if (!HTTP_METHODS.has(httpMethod.toLowerCase())) continue;\n const feMethod = methodMap[httpMethod.toUpperCase()];\n if (feMethod) beMethods.add(feMethod);\n }\n }\n\n const warnings: string[] = [];\n\n for (const method of STANDARD_FE_METHODS) {\n if (typeof (this as unknown as Record<string, unknown>)[method] === 'function' && !beMethods.has(method)) {\n warnings.push(`FE declares '${method}()' but BE has no matching endpoint`);\n }\n }\n\n for (const method of beMethods) {\n if (typeof (this as unknown as Record<string, unknown>)[method] !== 'function') {\n warnings.push(`BE exposes '${method}' endpoint but FE does not implement it`);\n }\n }\n\n for (const endpoint of unknownBeEndpoints) {\n warnings.push(`BE has non-standard endpoint '${endpoint}' with no FE method`);\n }\n\n if (warnings.length > 0) {\n console.warn(\n `[ViewSet ${this.basePath}] FE/BE definition mismatch:\\n` + warnings.map((w) => ` • ${w}`).join('\\n'),\n );\n }\n } catch {\n // Schema validation is non-critical; ignore fetch/parse errors silently\n }\n }\n\n async create(data: Omit<T, PK>): Promise<T> {\n const res = await this.http.post<T>(this.basePath, data);\n return res.data;\n }\n\n async bulkCreate(data: Omit<T, PK>[]): Promise<T[]> {\n const res = await this.http.post<T[]>(`${this.basePath}/bulk`, data);\n return res.data;\n }\n\n async list(): Promise<T[]> {\n const res = await this.http.get<T[]>(this.basePath);\n return res.data;\n }\n\n async retrieve(pk: K): Promise<T> {\n const res = await this.http.get<T>(`${this.basePath}/${pk}`);\n return res.data;\n }\n\n async update(pk: K, data: T): Promise<T> {\n const res = await this.http.put<T>(`${this.basePath}/${pk}`, data);\n return res.data;\n }\n\n async partialUpdate(pk: K, data: Partial<T>): Promise<T> {\n const res = await this.http.patch<T>(`${this.basePath}/${pk}`, data);\n return res.data;\n }\n\n async bulkUpdate(records: Record<K, T>): Promise<T[]> {\n const res = await this.http.put<T[]>(`${this.basePath}/bulk`, records);\n return res.data;\n }\n\n async bulkPartialUpdate(records: Record<K, Partial<T>>): Promise<T[]> {\n const res = await this.http.patch<T[]>(`${this.basePath}/bulk`, records);\n return res.data;\n }\n\n async destroy(pk: K): Promise<DestroyReturnData> {\n const res = await this.http.delete<DestroyReturnData>(`${this.basePath}/${pk}`);\n return res.data;\n }\n\n async bulkDestroy(pks: K[]): Promise<DestroyReturnData[]> {\n const res = await this.http.delete<DestroyReturnData[]>(`${this.basePath}/bulk`, { data: pks });\n return res.data;\n }\n\n async lookup(): Promise<LookupItem[]> {\n const res = await this.http.get<LookupItem[]>(`${this.basePath}/lookup`);\n return res.data;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Decorator / factory\n// ---------------------------------------------------------------------------\n\n/**\n * Registers a REST proxy for the given ViewSet class.\n *\n * The generic parameter `M` determines which mixin interfaces are available —\n * typically the ViewSet type (or a union of mixin interfaces).\n *\n * @example\n * ```ts\n * import type { BulkViewSetMixin, LookupMixin } from './mixins';\n *\n * interface Item { id: number; name: string }\n *\n * // with separate arguments (recommended)\n * const restItems = route_rest<BulkViewSetMixin<number, Item> & LookupMixin>(\n * ItemViewSet, '/items', 'id',\n * );\n *\n * // or with an options object\n * const restItems2 = route_rest<BulkViewSetMixin<number, Item> & LookupMixin>(\n * ItemViewSet, { basePath: '/items', pkFieldName: 'id' },\n * );\n *\n * const items = await restItems.list();\n * const item = await restItems.retrieve(1);\n * ```\n */\nfunction route_rest<M>(\n _viewSetClass: ViewSetClass,\n basePath: string,\n pkFieldName: string,\n axiosInstance?: AxiosInstance,\n): RestProxy<M>;\nfunction route_rest<M>(_viewSetClass: ViewSetClass, options: RestProxyOptions): RestProxy<M>;\nfunction route_rest<M>(\n _viewSetClass: ViewSetClass,\n basePathOrOptions: string | RestProxyOptions,\n pkFieldName?: string,\n axiosInstance?: AxiosInstance,\n): RestProxy<M> {\n const options: RestProxyOptions =\n typeof basePathOrOptions === 'string'\n ? {\n basePath: basePathOrOptions,\n pkFieldName: pkFieldName!,\n axiosInstance,\n }\n : basePathOrOptions;\n return new RestProxyImpl(options) as unknown as RestProxy<M>;\n}\n\nexport { route_rest };\n"],"mappings":"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"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
(function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require("axios")):typeof define==`function`&&define.amd?define([`exports`,`axios`],t):(e=typeof globalThis<`u`?globalThis:e||self,t((e[`fastapi-viewsets`]=e[`fastapi-viewsets`]||{},e[`fastapi-viewsets`][`[name]`]={}),e.axios))})(this,function(e,t){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var n=Object.create,r=Object.defineProperty,i=Object.getOwnPropertyDescriptor,a=Object.getOwnPropertyNames,o=Object.getPrototypeOf,s=Object.prototype.hasOwnProperty,c=(e,t,n,o)=>{if(t&&typeof t==`object`||typeof t==`function`)for(var c=a(t),l=0,u=c.length,d;l<u;l++)d=c[l],!s.call(e,d)&&d!==n&&r(e,d,{get:(e=>t[e]).bind(null,d),enumerable:!(o=i(t,d))||o.enumerable});return e};t=((e,t,i)=>(i=e==null?{}:n(o(e)),c(t||!e||!e.__esModule?r(i,`default`,{value:e,enumerable:!0}):i,e)))(t,1);function l(e){"@babel/helpers - typeof";return l=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},l(e)}function u(e,t){if(l(e)!=`object`||!e)return e;var n=e[Symbol.toPrimitive];if(n!==void 0){var r=n.call(e,t||`default`);if(l(r)!=`object`)return r;throw TypeError(`@@toPrimitive must return a primitive value.`)}return(t===`string`?String:Number)(e)}function d(e){var t=u(e,`string`);return l(t)==`symbol`?t:t+``}function f(e,t,n){return(t=d(t))in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function p(e,t,n,r,i,a,o){try{var s=e[a](o),c=s.value}catch(e){n(e);return}s.done?t(c):Promise.resolve(c).then(r,i)}function m(e){return function(){var t=this,n=arguments;return new Promise(function(r,i){var a=e.apply(t,n);function o(e){p(a,r,i,o,s,`next`,e)}function s(e){p(a,r,i,o,s,`throw`,e)}o(void 0)})}}var h=new Set([`get`,`post`,`put`,`patch`,`delete`,`head`,`options`,`trace`]),g={base:{GET:`list`,POST:`create`},pk:{GET:`retrieve`,PUT:`update`,PATCH:`partialUpdate`,DELETE:`destroy`},bulk:{POST:`bulkCreate`,PUT:`bulkUpdate`,PATCH:`bulkPartialUpdate`,DELETE:`bulkDestroy`},lookup:{GET:`lookup`}},_=[`list`,`create`,`retrieve`,`update`,`partialUpdate`,`destroy`,`bulkCreate`,`bulkUpdate`,`bulkPartialUpdate`,`bulkDestroy`,`lookup`],v=class{constructor(e){var n;f(this,`http`,void 0),f(this,`basePath`,void 0),f(this,`pkFieldName`,void 0),this.basePath=e.basePath.replace(/\/$/,``),this.pkFieldName=e.pkFieldName,this.http=(n=e.axiosInstance)==null?t.default:n,this.validateAgainstSchema()}validateAgainstSchema(){var e=this;return m(function*(){try{var t,n;let i=(t=(n=(yield e.http.get(`${e.basePath}/schema`)).data)==null?void 0:n.paths)==null?{}:t,a=new Set,o=[];for(let[t,n]of Object.entries(i)){var r;let i=t.slice(e.basePath.length).replace(/^\//,``),s;if(i===``)s=`base`;else if(i===`bulk`)s=`bulk`;else if(i===`lookup`)s=`lookup`;else if(i===`schema`)continue;else if(i.startsWith(`{`))s=`pk`;else{for(let e of Object.keys(n))h.has(e.toLowerCase())&&o.push(`${e.toUpperCase()} ${t}`);continue}let c=(r=g[s])==null?{}:r;for(let e of Object.keys(n)){if(!h.has(e.toLowerCase()))continue;let t=c[e.toUpperCase()];t&&a.add(t)}}let s=[];for(let t of _)typeof e[t]==`function`&&!a.has(t)&&s.push(`FE declares '${t}()' but BE has no matching endpoint`);for(let t of a)typeof e[t]!=`function`&&s.push(`BE exposes '${t}' endpoint but FE does not implement it`);for(let e of o)s.push(`BE has non-standard endpoint '${e}' with no FE method`);s.length>0&&console.warn(`[ViewSet ${e.basePath}] FE/BE definition mismatch:\n`+s.map(e=>` • ${e}`).join(`
|
|
2
|
+
`))}catch(e){}})()}create(e){var t=this;return m(function*(){return(yield t.http.post(t.basePath,e)).data})()}bulkCreate(e){var t=this;return m(function*(){return(yield t.http.post(`${t.basePath}/bulk`,e)).data})()}list(){var e=this;return m(function*(){return(yield e.http.get(e.basePath)).data})()}retrieve(e){var t=this;return m(function*(){return(yield t.http.get(`${t.basePath}/${e}`)).data})()}update(e,t){var n=this;return m(function*(){return(yield n.http.put(`${n.basePath}/${e}`,t)).data})()}partialUpdate(e,t){var n=this;return m(function*(){return(yield n.http.patch(`${n.basePath}/${e}`,t)).data})()}bulkUpdate(e){var t=this;return m(function*(){return(yield t.http.put(`${t.basePath}/bulk`,e)).data})()}bulkPartialUpdate(e){var t=this;return m(function*(){return(yield t.http.patch(`${t.basePath}/bulk`,e)).data})()}destroy(e){var t=this;return m(function*(){return(yield t.http.delete(`${t.basePath}/${e}`)).data})()}bulkDestroy(e){var t=this;return m(function*(){return(yield t.http.delete(`${t.basePath}/bulk`,{data:e})).data})()}lookup(){var e=this;return m(function*(){return(yield e.http.get(`${e.basePath}/lookup`)).data})()}};function y(e,t,n,r){return new v(typeof t==`string`?{basePath:t,pkFieldName:n,axiosInstance:r}:t)}e.RestProxyImpl=v,e.route_rest=y});
|
|
3
|
+
//# sourceMappingURL=fastapi-viewsets.umd.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fastapi-viewsets.umd.cjs","names":[],"sources":["../vue/rest-proxy.ts"],"sourcesContent":["/**\n * REST proxy for ViewSets — FE counterpart of the BE route_viewset decorator.\n *\n * Usage:\n * const restItems = route_rest<BulkViewSetMixin<number, Item> & LookupMixin>(\n * ItemViewSet, '/items', 'id',\n * );\n * const items = await restItems.list();\n * const item = await restItems.retrieve(1);\n */\n\nimport axios, { type AxiosInstance } from 'axios';\n\nimport type { BulkViewSetMixin, DestroyReturnData, KeyType, LookupItem, LookupMixin } from './mixins';\n\n// ---------------------------------------------------------------------------\n// Schema validation constants\n// ---------------------------------------------------------------------------\n\nconst HTTP_METHODS = new Set(['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']);\n\n/**\n * Maps (path type, HTTP method) → FE method name for standard ViewSet endpoints.\n * Path types: 'base' = root, 'pk' = /{pk}, 'bulk' = /bulk, 'lookup' = /lookup.\n */\nconst ENDPOINT_TO_FE_METHOD: Readonly<Record<string, Readonly<Record<string, string>>>> = {\n base: { GET: 'list', POST: 'create' },\n pk: {\n GET: 'retrieve',\n PUT: 'update',\n PATCH: 'partialUpdate',\n DELETE: 'destroy',\n },\n bulk: {\n POST: 'bulkCreate',\n PUT: 'bulkUpdate',\n PATCH: 'bulkPartialUpdate',\n DELETE: 'bulkDestroy',\n },\n lookup: { GET: 'lookup' },\n};\n\n/** All standard FE method names, in a stable order for warning output. */\nconst STANDARD_FE_METHODS: readonly string[] = [\n 'list',\n 'create',\n 'retrieve',\n 'update',\n 'partialUpdate',\n 'destroy',\n 'bulkCreate',\n 'bulkUpdate',\n 'bulkPartialUpdate',\n 'bulkDestroy',\n 'lookup',\n];\n\n// ---------------------------------------------------------------------------\n// Helper types\n// ---------------------------------------------------------------------------\n\n/** ViewSet class constructor (for type-level introspection only). */\n\ntype ViewSetClass = abstract new (...args: any[]) => any;\n\n/**\n * The REST proxy type is simply the mixin interface `M` the caller declares.\n * Because TypeScript cannot inspect Python class hierarchies at runtime, the\n * caller provides the explicit type via the generic parameter `M` (see route_rest).\n */\nexport type RestProxy<M> = M;\n\nexport interface RestProxyOptions {\n /** Base path to the resource, e.g. '/items'. */\n basePath: string;\n /** Name of the PK field on the model, e.g. 'id'. */\n pkFieldName: string;\n /** Optional: existing axios instance. Defaults to the global axios. */\n axiosInstance?: AxiosInstance;\n}\n\n// ---------------------------------------------------------------------------\n// Proxy implementation\n// ---------------------------------------------------------------------------\n\nexport class RestProxyImpl<K extends KeyType, T, PK extends keyof T>\n implements BulkViewSetMixin<K, T, PK>, LookupMixin\n{\n protected readonly http: AxiosInstance;\n\n protected readonly basePath: string;\n\n protected readonly pkFieldName: string;\n\n constructor(options: RestProxyOptions) {\n this.basePath = options.basePath.replace(/\\/$/, '');\n this.pkFieldName = options.pkFieldName;\n this.http = options.axiosInstance ?? axios;\n void this.validateAgainstSchema();\n }\n\n /**\n * Fetches the BE schema and compares it against the FE method set.\n * Logs a console warning for any mismatch found.\n * Non-critical: errors during fetch or parsing are silently ignored.\n */\n private async validateAgainstSchema(): Promise<void> {\n try {\n const res = await this.http.get<{\n paths?: Record<string, Record<string, unknown>>;\n }>(`${this.basePath}/schema`);\n const paths = res.data?.paths ?? {};\n\n const beMethods = new Set<string>();\n const unknownBeEndpoints: string[] = [];\n\n for (const [path, pathItem] of Object.entries(paths)) {\n const suffix = path.slice(this.basePath.length).replace(/^\\//, '');\n\n let pathType: string;\n if (suffix === '') {\n pathType = 'base';\n } else if (suffix === 'bulk') {\n pathType = 'bulk';\n } else if (suffix === 'lookup') {\n pathType = 'lookup';\n } else if (suffix === 'schema') {\n continue;\n } else if (suffix.startsWith('{')) {\n pathType = 'pk';\n } else {\n for (const httpMethod of Object.keys(pathItem)) {\n if (HTTP_METHODS.has(httpMethod.toLowerCase())) {\n unknownBeEndpoints.push(`${httpMethod.toUpperCase()} ${path}`);\n }\n }\n continue;\n }\n\n const methodMap = ENDPOINT_TO_FE_METHOD[pathType] ?? {};\n for (const httpMethod of Object.keys(pathItem)) {\n if (!HTTP_METHODS.has(httpMethod.toLowerCase())) continue;\n const feMethod = methodMap[httpMethod.toUpperCase()];\n if (feMethod) beMethods.add(feMethod);\n }\n }\n\n const warnings: string[] = [];\n\n for (const method of STANDARD_FE_METHODS) {\n if (typeof (this as unknown as Record<string, unknown>)[method] === 'function' && !beMethods.has(method)) {\n warnings.push(`FE declares '${method}()' but BE has no matching endpoint`);\n }\n }\n\n for (const method of beMethods) {\n if (typeof (this as unknown as Record<string, unknown>)[method] !== 'function') {\n warnings.push(`BE exposes '${method}' endpoint but FE does not implement it`);\n }\n }\n\n for (const endpoint of unknownBeEndpoints) {\n warnings.push(`BE has non-standard endpoint '${endpoint}' with no FE method`);\n }\n\n if (warnings.length > 0) {\n console.warn(\n `[ViewSet ${this.basePath}] FE/BE definition mismatch:\\n` + warnings.map((w) => ` • ${w}`).join('\\n'),\n );\n }\n } catch {\n // Schema validation is non-critical; ignore fetch/parse errors silently\n }\n }\n\n async create(data: Omit<T, PK>): Promise<T> {\n const res = await this.http.post<T>(this.basePath, data);\n return res.data;\n }\n\n async bulkCreate(data: Omit<T, PK>[]): Promise<T[]> {\n const res = await this.http.post<T[]>(`${this.basePath}/bulk`, data);\n return res.data;\n }\n\n async list(): Promise<T[]> {\n const res = await this.http.get<T[]>(this.basePath);\n return res.data;\n }\n\n async retrieve(pk: K): Promise<T> {\n const res = await this.http.get<T>(`${this.basePath}/${pk}`);\n return res.data;\n }\n\n async update(pk: K, data: T): Promise<T> {\n const res = await this.http.put<T>(`${this.basePath}/${pk}`, data);\n return res.data;\n }\n\n async partialUpdate(pk: K, data: Partial<T>): Promise<T> {\n const res = await this.http.patch<T>(`${this.basePath}/${pk}`, data);\n return res.data;\n }\n\n async bulkUpdate(records: Record<K, T>): Promise<T[]> {\n const res = await this.http.put<T[]>(`${this.basePath}/bulk`, records);\n return res.data;\n }\n\n async bulkPartialUpdate(records: Record<K, Partial<T>>): Promise<T[]> {\n const res = await this.http.patch<T[]>(`${this.basePath}/bulk`, records);\n return res.data;\n }\n\n async destroy(pk: K): Promise<DestroyReturnData> {\n const res = await this.http.delete<DestroyReturnData>(`${this.basePath}/${pk}`);\n return res.data;\n }\n\n async bulkDestroy(pks: K[]): Promise<DestroyReturnData[]> {\n const res = await this.http.delete<DestroyReturnData[]>(`${this.basePath}/bulk`, { data: pks });\n return res.data;\n }\n\n async lookup(): Promise<LookupItem[]> {\n const res = await this.http.get<LookupItem[]>(`${this.basePath}/lookup`);\n return res.data;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Decorator / factory\n// ---------------------------------------------------------------------------\n\n/**\n * Registers a REST proxy for the given ViewSet class.\n *\n * The generic parameter `M` determines which mixin interfaces are available —\n * typically the ViewSet type (or a union of mixin interfaces).\n *\n * @example\n * ```ts\n * import type { BulkViewSetMixin, LookupMixin } from './mixins';\n *\n * interface Item { id: number; name: string }\n *\n * // with separate arguments (recommended)\n * const restItems = route_rest<BulkViewSetMixin<number, Item> & LookupMixin>(\n * ItemViewSet, '/items', 'id',\n * );\n *\n * // or with an options object\n * const restItems2 = route_rest<BulkViewSetMixin<number, Item> & LookupMixin>(\n * ItemViewSet, { basePath: '/items', pkFieldName: 'id' },\n * );\n *\n * const items = await restItems.list();\n * const item = await restItems.retrieve(1);\n * ```\n */\nfunction route_rest<M>(\n _viewSetClass: ViewSetClass,\n basePath: string,\n pkFieldName: string,\n axiosInstance?: AxiosInstance,\n): RestProxy<M>;\nfunction route_rest<M>(_viewSetClass: ViewSetClass, options: RestProxyOptions): RestProxy<M>;\nfunction route_rest<M>(\n _viewSetClass: ViewSetClass,\n basePathOrOptions: string | RestProxyOptions,\n pkFieldName?: string,\n axiosInstance?: AxiosInstance,\n): RestProxy<M> {\n const options: RestProxyOptions =\n typeof basePathOrOptions === 'string'\n ? {\n basePath: basePathOrOptions,\n pkFieldName: pkFieldName!,\n axiosInstance,\n }\n : basePathOrOptions;\n return new RestProxyImpl(options) as unknown as RestProxy<M>;\n}\n\nexport { route_rest };\n"],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export type { BulkCreateMixin, BulkDestroyMixin, BulkOnlyCreateMixin, BulkOnlyDestroyMixin, BulkOnlyUpdateMixin, BulkUpdateMixin, BulkViewSetMixin, CreateMixin, DestroyMixin, ListMixin, LookupItem, LookupMixin, ReadOnlyViewSetMixin, RetrieveMixin, UpdateMixin, ViewSetMixin, } from './mixins';
|
|
2
|
+
export type { RestProxy, RestProxyOptions } from './rest-proxy';
|
|
3
|
+
export { route_rest, RestProxyImpl } from './rest-proxy';
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../vue/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,eAAe,EACf,gBAAgB,EAChB,mBAAmB,EACnB,oBAAoB,EACpB,mBAAmB,EACnB,eAAe,EACf,gBAAgB,EAChB,WAAW,EACX,YAAY,EACZ,SAAS,EACT,UAAU,EACV,WAAW,EACX,oBAAoB,EACpB,aAAa,EACb,WAAW,EACX,YAAY,GACb,MAAM,UAAU,CAAC;AAElB,YAAY,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChE,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC"}
|
package/dist/mixins.d.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FE counterpart of BE mixins.py — abstract mixin classes for ViewSet declarations.
|
|
3
|
+
*
|
|
4
|
+
* Each mixin class corresponds to its BE counterpart. ViewSet classes on the FE
|
|
5
|
+
* declare their capabilities by extending these mixins, mirroring the BE pattern:
|
|
6
|
+
*
|
|
7
|
+
* class ItemViewSet extends BulkViewSetMixin<number, Item> implements LookupMixin {}
|
|
8
|
+
*
|
|
9
|
+
* The actual HTTP implementation is provided by RestProxyImpl via route_rest().
|
|
10
|
+
*/
|
|
11
|
+
export interface LookupItem {
|
|
12
|
+
group: unknown;
|
|
13
|
+
pk: unknown;
|
|
14
|
+
title: string;
|
|
15
|
+
icon: string | null;
|
|
16
|
+
}
|
|
17
|
+
export type KeyType = string | number;
|
|
18
|
+
export type DestroyReturnData = Record<KeyType, any>;
|
|
19
|
+
export declare class CreateMixin<T, PK extends keyof T> {
|
|
20
|
+
create: (data: Omit<T, PK>) => Promise<T>;
|
|
21
|
+
}
|
|
22
|
+
export declare class BulkOnlyCreateMixin<T, PK extends keyof T> {
|
|
23
|
+
bulkCreate: (data: Omit<T, PK>[]) => Promise<T[]>;
|
|
24
|
+
}
|
|
25
|
+
export declare class BulkCreateMixin<T, PK extends keyof T> extends CreateMixin<T, PK> implements BulkOnlyCreateMixin<T, PK> {
|
|
26
|
+
bulkCreate: (data: Omit<T, PK>[]) => Promise<T[]>;
|
|
27
|
+
}
|
|
28
|
+
export declare class ListMixin<T> {
|
|
29
|
+
list: () => Promise<T[]>;
|
|
30
|
+
}
|
|
31
|
+
export declare class RetrieveMixin<K extends KeyType, T> {
|
|
32
|
+
retrieve: (pk: K) => Promise<T>;
|
|
33
|
+
}
|
|
34
|
+
export declare class UpdateMixin<K extends KeyType, T> {
|
|
35
|
+
update: (pk: K, data: T) => Promise<T>;
|
|
36
|
+
partialUpdate: (pk: K, data: Partial<T>) => Promise<T>;
|
|
37
|
+
}
|
|
38
|
+
export declare class BulkOnlyUpdateMixin<K extends KeyType, T> {
|
|
39
|
+
bulkUpdate: (records: Record<K, T>) => Promise<T[]>;
|
|
40
|
+
bulkPartialUpdate: (records: Record<K, Partial<T>>) => Promise<T[]>;
|
|
41
|
+
}
|
|
42
|
+
export declare class BulkUpdateMixin<K extends KeyType, T> extends UpdateMixin<K, T> implements BulkOnlyUpdateMixin<K, T> {
|
|
43
|
+
bulkUpdate: (records: Record<K, T>) => Promise<T[]>;
|
|
44
|
+
bulkPartialUpdate: (records: Record<K, Partial<T>>) => Promise<T[]>;
|
|
45
|
+
}
|
|
46
|
+
export declare class DestroyMixin<K extends KeyType> {
|
|
47
|
+
destroy: (pk: K) => Promise<DestroyReturnData>;
|
|
48
|
+
}
|
|
49
|
+
export declare class BulkOnlyDestroyMixin<K extends KeyType> {
|
|
50
|
+
bulkDestroy: (pks: K[]) => Promise<DestroyReturnData[]>;
|
|
51
|
+
}
|
|
52
|
+
export declare class BulkDestroyMixin<K extends KeyType> extends DestroyMixin<K> implements BulkOnlyDestroyMixin<K> {
|
|
53
|
+
bulkDestroy: (pks: K[]) => Promise<DestroyReturnData[]>;
|
|
54
|
+
}
|
|
55
|
+
export declare class LookupMixin {
|
|
56
|
+
lookup: () => Promise<LookupItem[]>;
|
|
57
|
+
}
|
|
58
|
+
export declare class ReadOnlyViewSetMixin<K extends KeyType, T> extends ListMixin<T> implements RetrieveMixin<K, T> {
|
|
59
|
+
retrieve: (pk: K) => Promise<T>;
|
|
60
|
+
}
|
|
61
|
+
export declare class ViewSetMixin<K extends KeyType, T, PK extends keyof T> extends ReadOnlyViewSetMixin<K, T> implements CreateMixin<T, PK>, UpdateMixin<K, T>, DestroyMixin<K> {
|
|
62
|
+
create: (data: Omit<T, PK>) => Promise<T>;
|
|
63
|
+
update: (pk: K, data: T) => Promise<T>;
|
|
64
|
+
partialUpdate: (pk: K, data: Partial<T>) => Promise<T>;
|
|
65
|
+
destroy: (pk: K) => Promise<Record<string, unknown>>;
|
|
66
|
+
}
|
|
67
|
+
export declare class BulkViewSetMixin<K extends KeyType, T, PK extends keyof T> extends ViewSetMixin<K, T, PK> implements BulkCreateMixin<T, PK>, BulkUpdateMixin<K, T>, BulkDestroyMixin<K> {
|
|
68
|
+
bulkCreate: (data: Omit<T, PK>[]) => Promise<T[]>;
|
|
69
|
+
bulkUpdate: (records: Record<K, T>) => Promise<T[]>;
|
|
70
|
+
bulkPartialUpdate: (records: Record<K, Partial<T>>) => Promise<T[]>;
|
|
71
|
+
bulkDestroy: (pks: K[]) => Promise<Record<string, unknown>[]>;
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=mixins.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mixins.d.ts","sourceRoot":"","sources":["../vue/mixins.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { AxiosInstance } from 'axios';
|
|
2
|
+
import { BulkViewSetMixin, DestroyReturnData, KeyType, LookupItem, LookupMixin } from './mixins';
|
|
3
|
+
/** ViewSet class constructor (for type-level introspection only). */
|
|
4
|
+
type ViewSetClass = abstract new (...args: any[]) => any;
|
|
5
|
+
/**
|
|
6
|
+
* The REST proxy type is simply the mixin interface `M` the caller declares.
|
|
7
|
+
* Because TypeScript cannot inspect Python class hierarchies at runtime, the
|
|
8
|
+
* caller provides the explicit type via the generic parameter `M` (see route_rest).
|
|
9
|
+
*/
|
|
10
|
+
export type RestProxy<M> = M;
|
|
11
|
+
export interface RestProxyOptions {
|
|
12
|
+
/** Base path to the resource, e.g. '/items'. */
|
|
13
|
+
basePath: string;
|
|
14
|
+
/** Name of the PK field on the model, e.g. 'id'. */
|
|
15
|
+
pkFieldName: string;
|
|
16
|
+
/** Optional: existing axios instance. Defaults to the global axios. */
|
|
17
|
+
axiosInstance?: AxiosInstance;
|
|
18
|
+
}
|
|
19
|
+
export declare class RestProxyImpl<K extends KeyType, T, PK extends keyof T> implements BulkViewSetMixin<K, T, PK>, LookupMixin {
|
|
20
|
+
protected readonly http: AxiosInstance;
|
|
21
|
+
protected readonly basePath: string;
|
|
22
|
+
protected readonly pkFieldName: string;
|
|
23
|
+
constructor(options: RestProxyOptions);
|
|
24
|
+
/**
|
|
25
|
+
* Fetches the BE schema and compares it against the FE method set.
|
|
26
|
+
* Logs a console warning for any mismatch found.
|
|
27
|
+
* Non-critical: errors during fetch or parsing are silently ignored.
|
|
28
|
+
*/
|
|
29
|
+
private validateAgainstSchema;
|
|
30
|
+
create(data: Omit<T, PK>): Promise<T>;
|
|
31
|
+
bulkCreate(data: Omit<T, PK>[]): Promise<T[]>;
|
|
32
|
+
list(): Promise<T[]>;
|
|
33
|
+
retrieve(pk: K): Promise<T>;
|
|
34
|
+
update(pk: K, data: T): Promise<T>;
|
|
35
|
+
partialUpdate(pk: K, data: Partial<T>): Promise<T>;
|
|
36
|
+
bulkUpdate(records: Record<K, T>): Promise<T[]>;
|
|
37
|
+
bulkPartialUpdate(records: Record<K, Partial<T>>): Promise<T[]>;
|
|
38
|
+
destroy(pk: K): Promise<DestroyReturnData>;
|
|
39
|
+
bulkDestroy(pks: K[]): Promise<DestroyReturnData[]>;
|
|
40
|
+
lookup(): Promise<LookupItem[]>;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Registers a REST proxy for the given ViewSet class.
|
|
44
|
+
*
|
|
45
|
+
* The generic parameter `M` determines which mixin interfaces are available —
|
|
46
|
+
* typically the ViewSet type (or a union of mixin interfaces).
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```ts
|
|
50
|
+
* import type { BulkViewSetMixin, LookupMixin } from './mixins';
|
|
51
|
+
*
|
|
52
|
+
* interface Item { id: number; name: string }
|
|
53
|
+
*
|
|
54
|
+
* // with separate arguments (recommended)
|
|
55
|
+
* const restItems = route_rest<BulkViewSetMixin<number, Item> & LookupMixin>(
|
|
56
|
+
* ItemViewSet, '/items', 'id',
|
|
57
|
+
* );
|
|
58
|
+
*
|
|
59
|
+
* // or with an options object
|
|
60
|
+
* const restItems2 = route_rest<BulkViewSetMixin<number, Item> & LookupMixin>(
|
|
61
|
+
* ItemViewSet, { basePath: '/items', pkFieldName: 'id' },
|
|
62
|
+
* );
|
|
63
|
+
*
|
|
64
|
+
* const items = await restItems.list();
|
|
65
|
+
* const item = await restItems.retrieve(1);
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
declare function route_rest<M>(_viewSetClass: ViewSetClass, basePath: string, pkFieldName: string, axiosInstance?: AxiosInstance): RestProxy<M>;
|
|
69
|
+
declare function route_rest<M>(_viewSetClass: ViewSetClass, options: RestProxyOptions): RestProxy<M>;
|
|
70
|
+
export { route_rest };
|
|
71
|
+
//# sourceMappingURL=rest-proxy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rest-proxy.d.ts","sourceRoot":"","sources":["../vue/rest-proxy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAc,EAAE,KAAK,aAAa,EAAE,MAAM,OAAO,CAAC;AAElD,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAgDtG,qEAAqE;AAErE,KAAK,YAAY,GAAG,QAAQ,MAAM,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,CAAC;AAEzD;;;;GAIG;AACH,MAAM,MAAM,SAAS,CAAC,CAAC,IAAI,CAAC,CAAC;AAE7B,MAAM,WAAW,gBAAgB;IAC/B,gDAAgD;IAChD,QAAQ,EAAE,MAAM,CAAC;IACjB,oDAAoD;IACpD,WAAW,EAAE,MAAM,CAAC;IACpB,uEAAuE;IACvE,aAAa,CAAC,EAAE,aAAa,CAAC;CAC/B;AAMD,qBAAa,aAAa,CAAC,CAAC,SAAS,OAAO,EAAE,CAAC,EAAE,EAAE,SAAS,MAAM,CAAC,CACjE,YAAW,gBAAgB,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,WAAW;IAElD,SAAS,CAAC,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAEvC,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAEpC,SAAS,CAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;gBAE3B,OAAO,EAAE,gBAAgB;IAOrC;;;;OAIG;YACW,qBAAqB;IAqE7B,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAKrC,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC;IAK7C,IAAI,IAAI,OAAO,CAAC,CAAC,EAAE,CAAC;IAKpB,QAAQ,CAAC,EAAE,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAK3B,MAAM,CAAC,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAKlC,aAAa,CAAC,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAKlD,UAAU,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC;IAK/C,iBAAiB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC;IAK/D,OAAO,CAAC,EAAE,EAAE,CAAC,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAK1C,WAAW,CAAC,GAAG,EAAE,CAAC,EAAE,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC;IAKnD,MAAM,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;CAItC;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,iBAAS,UAAU,CAAC,CAAC,EACnB,aAAa,EAAE,YAAY,EAC3B,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,aAAa,CAAC,EAAE,aAAa,GAC5B,SAAS,CAAC,CAAC,CAAC,CAAC;AAChB,iBAAS,UAAU,CAAC,CAAC,EAAE,aAAa,EAAE,YAAY,EAAE,OAAO,EAAE,gBAAgB,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;AAkB7F,OAAO,EAAE,UAAU,EAAE,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dynamicforms/fastapi-viewsets",
|
|
3
|
+
"private": false,
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "RESTful viewsets for Vue",
|
|
7
|
+
"author": "Jure Erznožnik",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist/*"
|
|
10
|
+
],
|
|
11
|
+
"main": "dist/fastapi-viewsets.umd.cjs",
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"require": "./dist/fastapi-viewsets.umd.cjs",
|
|
16
|
+
"import": "./dist/fastapi-viewsets.js"
|
|
17
|
+
},
|
|
18
|
+
"./styles.css": "./dist/fastapi-viewsets.css"
|
|
19
|
+
},
|
|
20
|
+
"workspaces": [
|
|
21
|
+
"docs",
|
|
22
|
+
"demo/frontend"
|
|
23
|
+
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "vite build",
|
|
26
|
+
"test": "vitest run --coverage",
|
|
27
|
+
"lint": "eslint vue --fix && vue-tsc --noEmit",
|
|
28
|
+
"docs:dev": "npm run docs:dev -w docs",
|
|
29
|
+
"docs:build": "npm run docs:build -w docs",
|
|
30
|
+
"docs:preview": "npm run docs:preview -w docs",
|
|
31
|
+
"demo:dev": "npm run dev -w demo-frontend"
|
|
32
|
+
},
|
|
33
|
+
"keywords": [
|
|
34
|
+
"vue",
|
|
35
|
+
"dynamicforms",
|
|
36
|
+
"velis",
|
|
37
|
+
"viewsets"
|
|
38
|
+
],
|
|
39
|
+
"license": "MIT",
|
|
40
|
+
"repository": {
|
|
41
|
+
"type": "git",
|
|
42
|
+
"url": "git@github.com:dynamicforms/fastapi-viewsets.git"
|
|
43
|
+
},
|
|
44
|
+
"issues": "https://github.com/dynamicforms/fastapi-viewsets/issues",
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"@dynamicforms/vue-forms": "^0.5.0",
|
|
47
|
+
"axios": "^1.13.6",
|
|
48
|
+
"lodash-es": "^4.17.12",
|
|
49
|
+
"vue": "^3.4"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@types/lodash-es": "^4.17.12",
|
|
53
|
+
"@types/node": "^24",
|
|
54
|
+
"@vitejs/plugin-vue": "^6",
|
|
55
|
+
"@vitest/coverage-v8": "^3",
|
|
56
|
+
"@vue/test-utils": "^2.2.4",
|
|
57
|
+
"@vue/tsconfig": "^0.7.0",
|
|
58
|
+
"eslint-config-velis": "^2.0.12",
|
|
59
|
+
"jsdom": "^26.0.0",
|
|
60
|
+
"rollup-plugin-visualizer": "^5.14.0",
|
|
61
|
+
"typescript": "^5",
|
|
62
|
+
"vite": "^8",
|
|
63
|
+
"vite-plugin-dts": "^5",
|
|
64
|
+
"vite-plugin-eslint": "^1.8.1",
|
|
65
|
+
"vitest": "^3",
|
|
66
|
+
"vue-tsc": "^2"
|
|
67
|
+
}
|
|
68
|
+
}
|