resora 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/README.md +176 -0
- package/dist/index.cjs +371 -0
- package/dist/index.d.cts +328 -0
- package/dist/index.d.mts +328 -0
- package/dist/index.mjs +368 -0
- package/package.json +77 -0
package/README.md
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# Resora
|
|
2
|
+
|
|
3
|
+
Resora is a structured API response layer for Node.js and TypeScript backends.
|
|
4
|
+
|
|
5
|
+
It provides a clean, explicit way to transform data into consistent JSON responses and automatically send them to the client. Resora supports single resources, collections, and pagination metadata while remaining framework-agnostic and strongly typed.
|
|
6
|
+
|
|
7
|
+
Resora is designed for teams that care about long-term maintainability, predictable API contracts, and clean separation of concerns.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## What Problem Does Resora Solve?
|
|
12
|
+
|
|
13
|
+
In most Node.js backends:
|
|
14
|
+
|
|
15
|
+
- Controllers shape JSON directly
|
|
16
|
+
- Response formats drift over time
|
|
17
|
+
- Pagination logic is duplicated
|
|
18
|
+
- Metadata handling is inconsistent
|
|
19
|
+
|
|
20
|
+
Resora introduces a dedicated **response transformation layer** that removes these concerns from controllers and centralizes response structure in one place.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Core Capabilities
|
|
25
|
+
|
|
26
|
+
- Explicit data-to-response transformation
|
|
27
|
+
- Automatic JSON response dispatch
|
|
28
|
+
- First-class collection support
|
|
29
|
+
- Built-in pagination metadata handling
|
|
30
|
+
- Predictable and consistent response contracts
|
|
31
|
+
- Strong TypeScript typing
|
|
32
|
+
- Transport-layer friendly (Express, H3, and others)
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Basic Example
|
|
37
|
+
|
|
38
|
+
### Single Resource
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import { Resource } from 'resora';
|
|
42
|
+
|
|
43
|
+
class UserResource extends Resource {
|
|
44
|
+
data() {
|
|
45
|
+
return this.toArray();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
return new UserResource(user).additional({
|
|
52
|
+
status: 'success',
|
|
53
|
+
message: 'User retrieved',
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Response:
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"data": {
|
|
62
|
+
"id": 1,
|
|
63
|
+
"name": "John"
|
|
64
|
+
},
|
|
65
|
+
"status": "success",
|
|
66
|
+
"message": "User retrieved"
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
### Collection with Pagination
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
import { ResourceCollection } from 'resora';
|
|
76
|
+
|
|
77
|
+
class UserCollection<R extends User[]> extends ResourceCollection<R> {
|
|
78
|
+
collects = UserResource;
|
|
79
|
+
|
|
80
|
+
data() {
|
|
81
|
+
return this.toArray();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
return new UserCollection({
|
|
88
|
+
data: users,
|
|
89
|
+
pagination: {
|
|
90
|
+
from: 1,
|
|
91
|
+
to: 10,
|
|
92
|
+
perPage: 10,
|
|
93
|
+
total: 100,
|
|
94
|
+
},
|
|
95
|
+
}).additional({
|
|
96
|
+
status: 'success',
|
|
97
|
+
message: 'Users retrieved',
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Response:
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
{
|
|
105
|
+
"data": [...],
|
|
106
|
+
"meta": {
|
|
107
|
+
"pagination": {
|
|
108
|
+
"from": 1,
|
|
109
|
+
"to": 10,
|
|
110
|
+
"perPage": 10,
|
|
111
|
+
"total": 100
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
"status": "success",
|
|
115
|
+
"message": "Users retrieved"
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Architectural Positioning
|
|
122
|
+
|
|
123
|
+
Resora sits **between your application logic and the HTTP layer**.
|
|
124
|
+
|
|
125
|
+
- Controllers handle request flow
|
|
126
|
+
- Services handle business logic
|
|
127
|
+
- Resora handles response structure
|
|
128
|
+
|
|
129
|
+
This separation ensures:
|
|
130
|
+
|
|
131
|
+
- Stable API contracts
|
|
132
|
+
- Minimal controller logic
|
|
133
|
+
- Clear ownership of response shape
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Design Principles
|
|
138
|
+
|
|
139
|
+
- Explicit over implicit behavior
|
|
140
|
+
- Separation of concerns
|
|
141
|
+
- Minimal abstraction cost
|
|
142
|
+
- Strong typing as a first-class feature
|
|
143
|
+
- Framework independence
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Framework Compatibility
|
|
148
|
+
|
|
149
|
+
Resora is not tied to a specific HTTP framework.
|
|
150
|
+
|
|
151
|
+
It works with:
|
|
152
|
+
|
|
153
|
+
- Express
|
|
154
|
+
- H3
|
|
155
|
+
- Any application or framework that supports Connect-style middleware
|
|
156
|
+
|
|
157
|
+
Adapters can be added without changing application logic.
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## When to Use Resora
|
|
162
|
+
|
|
163
|
+
Resora is a good fit if you:
|
|
164
|
+
|
|
165
|
+
- Build APIs with long-term maintenance in mind
|
|
166
|
+
- Care about response consistency across teams
|
|
167
|
+
- Want pagination and metadata handled once
|
|
168
|
+
- Prefer explicit structure over ad-hoc JSON responses
|
|
169
|
+
|
|
170
|
+
It is intentionally not opinionated about routing, validation, or persistence.
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## License
|
|
175
|
+
|
|
176
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
|
|
3
|
+
//#region src/ApiResource.ts
|
|
4
|
+
/**
|
|
5
|
+
* ApiResource function to return the Resource instance
|
|
6
|
+
*
|
|
7
|
+
* @param instance Resource instance
|
|
8
|
+
* @returns Resource instance
|
|
9
|
+
*/
|
|
10
|
+
function ApiResource(instance) {
|
|
11
|
+
return instance;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/ServerResponse.ts
|
|
16
|
+
var ServerResponse = class {
|
|
17
|
+
_status = 200;
|
|
18
|
+
headers = {};
|
|
19
|
+
constructor(response, body) {
|
|
20
|
+
this.response = response;
|
|
21
|
+
this.body = body;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Set the HTTP status code for the response
|
|
25
|
+
*
|
|
26
|
+
* @param status
|
|
27
|
+
* @returns The current ServerResponse instance
|
|
28
|
+
*/
|
|
29
|
+
setStatusCode(status) {
|
|
30
|
+
this._status = status;
|
|
31
|
+
if ("status" in this.response && typeof this.response.status === "function") this.response.status(status);
|
|
32
|
+
else if ("status" in this.response) this.response.status = status;
|
|
33
|
+
return this;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Get the current HTTP status code for the response
|
|
37
|
+
*
|
|
38
|
+
* @returns
|
|
39
|
+
*/
|
|
40
|
+
status() {
|
|
41
|
+
return this._status;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Get the current HTTP status text for the response
|
|
45
|
+
*
|
|
46
|
+
* @returns
|
|
47
|
+
*/
|
|
48
|
+
statusText() {
|
|
49
|
+
if ("statusMessage" in this.response) return this.response.statusMessage;
|
|
50
|
+
else if ("statusText" in this.response) return this.response.statusText;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Set a cookie in the response header
|
|
54
|
+
*
|
|
55
|
+
* @param name The name of the cookie
|
|
56
|
+
* @param value The value of the cookie
|
|
57
|
+
* @param options Optional cookie attributes (e.g., path, domain, maxAge)
|
|
58
|
+
* @returns The current ServerResponse instance
|
|
59
|
+
*/
|
|
60
|
+
setCookie(name, value, options) {
|
|
61
|
+
this.#addHeader("Set-Cookie", `${name}=${value}; ${Object.entries(options || {}).map(([key, val]) => `${key}=${val}`).join("; ")}`);
|
|
62
|
+
return this;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Convert the resource to a JSON response body
|
|
66
|
+
*
|
|
67
|
+
* @param headers Optional headers to add to the response
|
|
68
|
+
* @returns The current ServerResponse instance
|
|
69
|
+
*/
|
|
70
|
+
setHeaders(headers) {
|
|
71
|
+
for (const [key, value] of Object.entries(headers)) this.#addHeader(key, value);
|
|
72
|
+
return this;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Add a single header to the response
|
|
76
|
+
*
|
|
77
|
+
* @param key The name of the header
|
|
78
|
+
* @param value The value of the header
|
|
79
|
+
* @returns The current ServerResponse instance
|
|
80
|
+
*/
|
|
81
|
+
header(key, value) {
|
|
82
|
+
this.#addHeader(key, value);
|
|
83
|
+
return this;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Add a single header to the response
|
|
87
|
+
*
|
|
88
|
+
* @param key The name of the header
|
|
89
|
+
* @param value The value of the header
|
|
90
|
+
*/
|
|
91
|
+
#addHeader(key, value) {
|
|
92
|
+
this.headers[key] = value;
|
|
93
|
+
if ("headers" in this.response) this.response.headers.set(key, value);
|
|
94
|
+
else if ("setHeader" in this.response) this.response.setHeader(key, value);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Promise-like then method to allow chaining with async/await or .then() syntax
|
|
98
|
+
*
|
|
99
|
+
* @param onfulfilled Callback to handle the fulfilled state of the promise, receiving the response body
|
|
100
|
+
* @param onrejected Callback to handle the rejected state of the promise, receiving the error reason
|
|
101
|
+
* @returns A promise that resolves to the result of the onfulfilled or onrejected callback
|
|
102
|
+
*/
|
|
103
|
+
then(onfulfilled, onrejected) {
|
|
104
|
+
const resolved = Promise.resolve(this.body).then(onfulfilled, onrejected);
|
|
105
|
+
if ("send" in this.response) this.response.send(this.body);
|
|
106
|
+
return resolved;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Promise-like catch method to handle rejected state of the promise
|
|
110
|
+
*
|
|
111
|
+
* @param onrejected
|
|
112
|
+
* @returns
|
|
113
|
+
*/
|
|
114
|
+
catch(onrejected) {
|
|
115
|
+
return this.then(void 0, onrejected);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Promise-like finally method to handle cleanup after promise is settled
|
|
119
|
+
*
|
|
120
|
+
* @param onfinally
|
|
121
|
+
* @returns
|
|
122
|
+
*/
|
|
123
|
+
finally(onfinally) {
|
|
124
|
+
return this.then(onfinally, onfinally);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
//#endregion
|
|
129
|
+
//#region src/ResourceCollection.ts
|
|
130
|
+
/**
|
|
131
|
+
* ResourceCollection class to handle API resource transformation and response building for collections
|
|
132
|
+
*/
|
|
133
|
+
var ResourceCollection = class {
|
|
134
|
+
body = { data: [] };
|
|
135
|
+
resource;
|
|
136
|
+
collects;
|
|
137
|
+
called = {};
|
|
138
|
+
constructor(rsc, res) {
|
|
139
|
+
this.res = res;
|
|
140
|
+
this.resource = rsc;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Get the original resource data
|
|
144
|
+
*/
|
|
145
|
+
data() {
|
|
146
|
+
return this.toArray();
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Convert resource to JSON response format
|
|
150
|
+
*
|
|
151
|
+
* @returns
|
|
152
|
+
*/
|
|
153
|
+
json() {
|
|
154
|
+
if (!this.called.json) {
|
|
155
|
+
this.called.json = true;
|
|
156
|
+
let data = this.data();
|
|
157
|
+
if (this.collects) data = data.map((item) => new this.collects(item).data());
|
|
158
|
+
this.body = { data };
|
|
159
|
+
if (!Array.isArray(this.resource)) {
|
|
160
|
+
if (this.resource.pagination && this.resource.cursor) this.body.meta = {
|
|
161
|
+
pagination: this.resource.pagination,
|
|
162
|
+
cursor: this.resource.cursor
|
|
163
|
+
};
|
|
164
|
+
else if (this.resource.pagination) this.body.meta = { pagination: this.resource.pagination };
|
|
165
|
+
else if (this.resource.cursor) this.body.meta = { cursor: this.resource.cursor };
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return this;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Flatten resource to return original data
|
|
172
|
+
*
|
|
173
|
+
* @returns
|
|
174
|
+
*/
|
|
175
|
+
toArray() {
|
|
176
|
+
this.called.toArray = true;
|
|
177
|
+
this.json();
|
|
178
|
+
return Array.isArray(this.resource) ? [...this.resource] : [...this.resource.data];
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Add additional properties to the response body
|
|
182
|
+
*
|
|
183
|
+
* @param extra Additional properties to merge into the response body
|
|
184
|
+
* @returns
|
|
185
|
+
*/
|
|
186
|
+
additional(extra) {
|
|
187
|
+
this.called.additional = true;
|
|
188
|
+
this.json();
|
|
189
|
+
delete extra.cursor;
|
|
190
|
+
delete extra.pagination;
|
|
191
|
+
if (extra.data && Array.isArray(this.body.data)) this.body.data = [...this.body.data, ...extra.data];
|
|
192
|
+
this.body = {
|
|
193
|
+
...this.body,
|
|
194
|
+
...extra
|
|
195
|
+
};
|
|
196
|
+
return this;
|
|
197
|
+
}
|
|
198
|
+
response(res) {
|
|
199
|
+
this.called.toResponse = true;
|
|
200
|
+
return new ServerResponse(res ?? this.res, this.body);
|
|
201
|
+
}
|
|
202
|
+
setCollects(collects) {
|
|
203
|
+
this.collects = collects;
|
|
204
|
+
return this;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Promise-like then method to allow chaining with async/await or .then() syntax
|
|
208
|
+
*
|
|
209
|
+
* @param onfulfilled Callback to handle the fulfilled state of the promise, receiving the response body
|
|
210
|
+
* @param onrejected Callback to handle the rejected state of the promise, receiving the error reason
|
|
211
|
+
* @returns A promise that resolves to the result of the onfulfilled or onrejected callback
|
|
212
|
+
*/
|
|
213
|
+
then(onfulfilled, onrejected) {
|
|
214
|
+
this.called.then = true;
|
|
215
|
+
this.json();
|
|
216
|
+
const resolved = Promise.resolve(this.body).then(onfulfilled, onrejected);
|
|
217
|
+
if (this.res) this.res.send(this.body);
|
|
218
|
+
return resolved;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Promise-like catch method to handle rejected state of the promise
|
|
222
|
+
*
|
|
223
|
+
* @param onrejected
|
|
224
|
+
* @returns
|
|
225
|
+
*/
|
|
226
|
+
catch(onrejected) {
|
|
227
|
+
return this.then(void 0, onrejected);
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Promise-like finally method to handle cleanup after promise is settled
|
|
231
|
+
*
|
|
232
|
+
* @param onfinally
|
|
233
|
+
* @returns
|
|
234
|
+
*/
|
|
235
|
+
finally(onfinally) {
|
|
236
|
+
return this.then(onfinally, onfinally);
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
//#endregion
|
|
241
|
+
//#region src/Resource.ts
|
|
242
|
+
/**
|
|
243
|
+
* Resource class to handle API resource transformation and response building
|
|
244
|
+
*/
|
|
245
|
+
var Resource = class {
|
|
246
|
+
body = { data: {} };
|
|
247
|
+
resource;
|
|
248
|
+
called = {};
|
|
249
|
+
constructor(rsc, res) {
|
|
250
|
+
this.res = res;
|
|
251
|
+
this.resource = rsc;
|
|
252
|
+
/**
|
|
253
|
+
* Copy properties from rsc to this instance for easy
|
|
254
|
+
* access, but only if data is not an array
|
|
255
|
+
*/
|
|
256
|
+
if (!Array.isArray(this.resource.data ?? this.resource)) {
|
|
257
|
+
for (const key of Object.keys(this.resource.data ?? this.resource)) if (!(key in this)) Object.defineProperty(this, key, {
|
|
258
|
+
enumerable: true,
|
|
259
|
+
configurable: true,
|
|
260
|
+
get: () => {
|
|
261
|
+
return this.resource.data?.[key] ?? this.resource[key];
|
|
262
|
+
},
|
|
263
|
+
set: (value) => {
|
|
264
|
+
if (this.resource.data && this.resource.data[key]) this.resource.data[key] = value;
|
|
265
|
+
else this.resource[key] = value;
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Create a ResourceCollection from an array of resource data or a Collectible instance
|
|
272
|
+
*
|
|
273
|
+
* @param data
|
|
274
|
+
* @returns
|
|
275
|
+
*/
|
|
276
|
+
static collection(data) {
|
|
277
|
+
return new ResourceCollection(data).setCollects(this);
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Get the original resource data
|
|
281
|
+
*/
|
|
282
|
+
data() {
|
|
283
|
+
return this.toArray();
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Convert resource to JSON response format
|
|
287
|
+
*
|
|
288
|
+
* @returns
|
|
289
|
+
*/
|
|
290
|
+
json() {
|
|
291
|
+
if (!this.called.json) {
|
|
292
|
+
this.called.json = true;
|
|
293
|
+
const resource = this.data();
|
|
294
|
+
let data = Array.isArray(resource) ? [...resource] : { ...resource };
|
|
295
|
+
if (typeof data.data !== "undefined") data = data.data;
|
|
296
|
+
this.body = { data };
|
|
297
|
+
}
|
|
298
|
+
return this;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Flatten resource to array format (for collections) or return original data for single resources
|
|
302
|
+
*
|
|
303
|
+
* @returns
|
|
304
|
+
*/
|
|
305
|
+
toArray() {
|
|
306
|
+
this.called.toArray = true;
|
|
307
|
+
this.json();
|
|
308
|
+
let data = Array.isArray(this.resource) ? [...this.resource] : { ...this.resource };
|
|
309
|
+
if (!Array.isArray(data) && typeof data.data !== "undefined") data = data.data;
|
|
310
|
+
return data;
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Add additional properties to the response body
|
|
314
|
+
*
|
|
315
|
+
* @param extra Additional properties to merge into the response body
|
|
316
|
+
* @returns
|
|
317
|
+
*/
|
|
318
|
+
additional(extra) {
|
|
319
|
+
this.called.additional = true;
|
|
320
|
+
this.json();
|
|
321
|
+
if (extra.data) this.body.data = Array.isArray(this.body.data) ? [...this.body.data, ...extra.data] : {
|
|
322
|
+
...this.body.data,
|
|
323
|
+
...extra.data
|
|
324
|
+
};
|
|
325
|
+
this.body = {
|
|
326
|
+
...this.body,
|
|
327
|
+
...extra
|
|
328
|
+
};
|
|
329
|
+
return this;
|
|
330
|
+
}
|
|
331
|
+
response(res) {
|
|
332
|
+
this.called.toResponse = true;
|
|
333
|
+
return new ServerResponse(res ?? this.res, this.body);
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Promise-like then method to allow chaining with async/await or .then() syntax
|
|
337
|
+
*
|
|
338
|
+
* @param onfulfilled Callback to handle the fulfilled state of the promise, receiving the response body
|
|
339
|
+
* @param onrejected Callback to handle the rejected state of the promise, receiving the error reason
|
|
340
|
+
* @returns A promise that resolves to the result of the onfulfilled or onrejected callback
|
|
341
|
+
*/
|
|
342
|
+
then(onfulfilled, onrejected) {
|
|
343
|
+
this.called.then = true;
|
|
344
|
+
this.json();
|
|
345
|
+
const resolved = Promise.resolve(this.body).then(onfulfilled, onrejected);
|
|
346
|
+
if (this.res) this.res.send(this.body);
|
|
347
|
+
return resolved;
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Promise-like catch method to handle rejected state of the promise
|
|
351
|
+
*
|
|
352
|
+
* @param onrejected
|
|
353
|
+
* @returns
|
|
354
|
+
*/
|
|
355
|
+
catch(onrejected) {
|
|
356
|
+
return this.then(void 0, onrejected);
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Promise-like finally method to handle cleanup after promise is settled
|
|
360
|
+
*
|
|
361
|
+
* @param onfinally
|
|
362
|
+
* @returns
|
|
363
|
+
*/
|
|
364
|
+
finally(onfinally) {
|
|
365
|
+
return this.then(onfinally, onfinally);
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
//#endregion
|
|
370
|
+
exports.ApiResource = ApiResource;
|
|
371
|
+
exports.Resource = Resource;
|