resora 1.1.0 → 1.2.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 +56 -9
- package/dist/index.cjs +17 -5
- package/dist/index.mjs +17 -5
- package/package.json +3 -4
package/README.md
CHANGED
|
@@ -33,6 +33,7 @@ Resora introduces a dedicated **response transformation layer** that removes the
|
|
|
33
33
|
- Explicit data-to-response transformation
|
|
34
34
|
- Automatic JSON response dispatch
|
|
35
35
|
- First-class collection support
|
|
36
|
+
- Nested resource and collection composition
|
|
36
37
|
- Built-in pagination metadata handling
|
|
37
38
|
- Built-in cursor metadata handling
|
|
38
39
|
- Conditional attribute helpers (`when`, `whenNotNull`, `mergeWhen`)
|
|
@@ -134,7 +135,57 @@ Response:
|
|
|
134
135
|
}
|
|
135
136
|
```
|
|
136
137
|
|
|
137
|
-
|
|
138
|
+
### Nested Resources and Collections
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
import { Resource, ResourceCollection } from 'resora';
|
|
142
|
+
|
|
143
|
+
class FamilyMemberResource extends Resource {
|
|
144
|
+
data() {
|
|
145
|
+
return {
|
|
146
|
+
id: this.id,
|
|
147
|
+
fullName: `${this.firstName} ${this.lastName}`,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
class FamilyMemberCollection extends ResourceCollection {
|
|
153
|
+
collects = FamilyMemberResource;
|
|
154
|
+
|
|
155
|
+
data() {
|
|
156
|
+
return this.toObject();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
class FamilyOverviewResource extends Resource {
|
|
161
|
+
data() {
|
|
162
|
+
return {
|
|
163
|
+
id: this.id,
|
|
164
|
+
familyName: this.familyName,
|
|
165
|
+
members: new FamilyMemberCollection(this.members ?? []),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Response:
|
|
172
|
+
|
|
173
|
+
```json
|
|
174
|
+
{
|
|
175
|
+
"data": {
|
|
176
|
+
"id": 1,
|
|
177
|
+
"familyName": "Smiths",
|
|
178
|
+
"members": [
|
|
179
|
+
{
|
|
180
|
+
"id": 1,
|
|
181
|
+
"fullName": "Jane Doe"
|
|
182
|
+
}
|
|
183
|
+
]
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
You can also call `.toObject()` explicitly when you want the transformed collection array immediately inside a parent resource.
|
|
138
189
|
|
|
139
190
|
## Architectural Positioning
|
|
140
191
|
|
|
@@ -160,8 +211,6 @@ This separation ensures:
|
|
|
160
211
|
- Strong typing as a first-class feature
|
|
161
212
|
- Framework independence
|
|
162
213
|
|
|
163
|
-
---
|
|
164
|
-
|
|
165
214
|
## Framework Compatibility
|
|
166
215
|
|
|
167
216
|
Resora is not tied to a specific HTTP framework.
|
|
@@ -191,6 +240,10 @@ Plugins can:
|
|
|
191
240
|
- inject framework integrations without core changes
|
|
192
241
|
- register reusable transformation utilities
|
|
193
242
|
|
|
243
|
+
Available plugins:
|
|
244
|
+
|
|
245
|
+
- [`@resora/plugin-clear-router`](https://arkstack-hq.github.io/resora/plugins/clear-router.md)
|
|
246
|
+
|
|
194
247
|
## Conditional Rendering Example
|
|
195
248
|
|
|
196
249
|
```ts
|
|
@@ -208,8 +261,6 @@ class UserResource extends Resource {
|
|
|
208
261
|
|
|
209
262
|
Falsy/null attributes are omitted from the final serialized payload.
|
|
210
263
|
|
|
211
|
-
---
|
|
212
|
-
|
|
213
264
|
## When to Use Resora
|
|
214
265
|
|
|
215
266
|
Resora is a good fit if you:
|
|
@@ -221,8 +272,6 @@ Resora is a good fit if you:
|
|
|
221
272
|
|
|
222
273
|
It is intentionally not opinionated about routing, validation, or persistence.
|
|
223
274
|
|
|
224
|
-
---
|
|
225
|
-
|
|
226
275
|
## Documentation
|
|
227
276
|
|
|
228
277
|
- Getting Started: https://arkstack-hq.github.io/resora/guide/getting-started
|
|
@@ -230,8 +279,6 @@ It is intentionally not opinionated about routing, validation, or persistence.
|
|
|
230
279
|
- Conditional Rendering: https://arkstack-hq.github.io/resora/guide/conditional-attributes
|
|
231
280
|
- Pagination & Cursor Recipes: https://arkstack-hq.github.io/resora/guide/pagination-cursor-recipes
|
|
232
281
|
|
|
233
|
-
---
|
|
234
|
-
|
|
235
282
|
## License
|
|
236
283
|
|
|
237
284
|
MIT
|
package/dist/index.cjs
CHANGED
|
@@ -358,6 +358,8 @@ const extractUrlFromRequest = (req) => {
|
|
|
358
358
|
const extractResponseFromCtx = (ctx) => {
|
|
359
359
|
if (!ctx || typeof ctx !== "object") return;
|
|
360
360
|
const obj = ctx;
|
|
361
|
+
if (typeof obj.header === "function" && typeof obj.status === "function") return ctx;
|
|
362
|
+
if (typeof obj.set === "function" && "status" in obj && "body" in obj) return ctx;
|
|
361
363
|
if (obj.res && typeof obj.res === "object") return obj.res;
|
|
362
364
|
return ctx;
|
|
363
365
|
};
|
|
@@ -1352,8 +1354,10 @@ var ServerResponse = class {
|
|
|
1352
1354
|
*/
|
|
1353
1355
|
#addHeader(key, value) {
|
|
1354
1356
|
this.headers[key] = value;
|
|
1355
|
-
if ("headers" in this.response) this.response.headers.set(key, value);
|
|
1357
|
+
if ("headers" in this.response && this.response.headers && typeof this.response.headers.set === "function") this.response.headers.set(key, value);
|
|
1356
1358
|
else if ("setHeader" in this.response) this.response.setHeader(key, value);
|
|
1359
|
+
else if ("set" in this.response && typeof this.response.set === "function") this.response.set(key, value);
|
|
1360
|
+
else if ("header" in this.response && typeof this.response.header === "function") this.response.header(key, value);
|
|
1357
1361
|
}
|
|
1358
1362
|
/**
|
|
1359
1363
|
* Dispatch the current body and apply any deferred transport state.
|
|
@@ -1375,9 +1379,10 @@ var ServerResponse = class {
|
|
|
1375
1379
|
this.headers = { ...beforeSend.headers };
|
|
1376
1380
|
if ("send" in this.response && typeof this.response.send === "function") {
|
|
1377
1381
|
if ("statusCode" in this.response) this.response.statusCode = this._status;
|
|
1378
|
-
|
|
1379
|
-
if (
|
|
1380
|
-
|
|
1382
|
+
if ("status" in this.response && typeof this.response.status === "function") this.response.status(this._status);
|
|
1383
|
+
else if ("code" in this.response && typeof this.response.code === "function") this.response.code(this._status);
|
|
1384
|
+
this.response.__resoraStatus = this._status;
|
|
1385
|
+
this.response.send(this.body);
|
|
1381
1386
|
runPluginHook("afterSend", {
|
|
1382
1387
|
response: this,
|
|
1383
1388
|
rawResponse: this.response,
|
|
@@ -1387,7 +1392,14 @@ var ServerResponse = class {
|
|
|
1387
1392
|
});
|
|
1388
1393
|
return this.body;
|
|
1389
1394
|
}
|
|
1390
|
-
if ("status" in this.response && typeof this.response.status
|
|
1395
|
+
if ("status" in this.response && typeof this.response.status === "function") {
|
|
1396
|
+
this.response.status(this._status);
|
|
1397
|
+
this.response.__resoraStatus = this._status;
|
|
1398
|
+
} else if ("status" in this.response && typeof this.response.status !== "function") this.response.status = this._status;
|
|
1399
|
+
if ("body" in this.response) {
|
|
1400
|
+
this.response.body = this.body;
|
|
1401
|
+
this.response.__resoraStatus = this._status;
|
|
1402
|
+
}
|
|
1391
1403
|
runPluginHook("afterSend", {
|
|
1392
1404
|
response: this,
|
|
1393
1405
|
rawResponse: this.response,
|
package/dist/index.mjs
CHANGED
|
@@ -329,6 +329,8 @@ const extractUrlFromRequest = (req) => {
|
|
|
329
329
|
const extractResponseFromCtx = (ctx) => {
|
|
330
330
|
if (!ctx || typeof ctx !== "object") return;
|
|
331
331
|
const obj = ctx;
|
|
332
|
+
if (typeof obj.header === "function" && typeof obj.status === "function") return ctx;
|
|
333
|
+
if (typeof obj.set === "function" && "status" in obj && "body" in obj) return ctx;
|
|
332
334
|
if (obj.res && typeof obj.res === "object") return obj.res;
|
|
333
335
|
return ctx;
|
|
334
336
|
};
|
|
@@ -1323,8 +1325,10 @@ var ServerResponse = class {
|
|
|
1323
1325
|
*/
|
|
1324
1326
|
#addHeader(key, value) {
|
|
1325
1327
|
this.headers[key] = value;
|
|
1326
|
-
if ("headers" in this.response) this.response.headers.set(key, value);
|
|
1328
|
+
if ("headers" in this.response && this.response.headers && typeof this.response.headers.set === "function") this.response.headers.set(key, value);
|
|
1327
1329
|
else if ("setHeader" in this.response) this.response.setHeader(key, value);
|
|
1330
|
+
else if ("set" in this.response && typeof this.response.set === "function") this.response.set(key, value);
|
|
1331
|
+
else if ("header" in this.response && typeof this.response.header === "function") this.response.header(key, value);
|
|
1328
1332
|
}
|
|
1329
1333
|
/**
|
|
1330
1334
|
* Dispatch the current body and apply any deferred transport state.
|
|
@@ -1346,9 +1350,10 @@ var ServerResponse = class {
|
|
|
1346
1350
|
this.headers = { ...beforeSend.headers };
|
|
1347
1351
|
if ("send" in this.response && typeof this.response.send === "function") {
|
|
1348
1352
|
if ("statusCode" in this.response) this.response.statusCode = this._status;
|
|
1349
|
-
|
|
1350
|
-
if (
|
|
1351
|
-
|
|
1353
|
+
if ("status" in this.response && typeof this.response.status === "function") this.response.status(this._status);
|
|
1354
|
+
else if ("code" in this.response && typeof this.response.code === "function") this.response.code(this._status);
|
|
1355
|
+
this.response.__resoraStatus = this._status;
|
|
1356
|
+
this.response.send(this.body);
|
|
1352
1357
|
runPluginHook("afterSend", {
|
|
1353
1358
|
response: this,
|
|
1354
1359
|
rawResponse: this.response,
|
|
@@ -1358,7 +1363,14 @@ var ServerResponse = class {
|
|
|
1358
1363
|
});
|
|
1359
1364
|
return this.body;
|
|
1360
1365
|
}
|
|
1361
|
-
if ("status" in this.response && typeof this.response.status
|
|
1366
|
+
if ("status" in this.response && typeof this.response.status === "function") {
|
|
1367
|
+
this.response.status(this._status);
|
|
1368
|
+
this.response.__resoraStatus = this._status;
|
|
1369
|
+
} else if ("status" in this.response && typeof this.response.status !== "function") this.response.status = this._status;
|
|
1370
|
+
if ("body" in this.response) {
|
|
1371
|
+
this.response.body = this.body;
|
|
1372
|
+
this.response.__resoraStatus = this._status;
|
|
1373
|
+
}
|
|
1362
1374
|
runPluginHook("afterSend", {
|
|
1363
1375
|
response: this,
|
|
1364
1376
|
rawResponse: this.response,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "resora",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "A structured API response layer for Node.js and TypeScript with automatic JSON responses, collection support, and pagination handling.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"api",
|
|
@@ -50,14 +50,13 @@
|
|
|
50
50
|
"@eslint/markdown": "^7.5.1",
|
|
51
51
|
"@types/express": "^4.17.21",
|
|
52
52
|
"@types/node": "^20.10.6",
|
|
53
|
-
"@types/supertest": "^6.0.3",
|
|
54
53
|
"@vitest/coverage-v8": "4.0.18",
|
|
55
54
|
"arkormx": "^0.2.6",
|
|
56
55
|
"barrelize": "^1.7.3",
|
|
57
56
|
"eslint": "^10.0.0",
|
|
58
57
|
"express": "^5.1.0",
|
|
59
58
|
"h3": "2.0.1-rc.14",
|
|
60
|
-
"
|
|
59
|
+
"parasito": "^0.1.6",
|
|
61
60
|
"tsdown": "^0.20.3",
|
|
62
61
|
"tsx": "^4.21.0",
|
|
63
62
|
"typescript": "^5.3.3",
|
|
@@ -85,6 +84,6 @@
|
|
|
85
84
|
"docs:build": "vitepress build docs",
|
|
86
85
|
"docs:preview": "vitepress preview docs",
|
|
87
86
|
"version:packages": "pnpm -r --filter \"@resora/*\" version:patch && git add . && git commit -m \"chore: bump package versions\"",
|
|
88
|
-
"publish:packages": "pnpm version:packages && pnpm -r --filter \"@
|
|
87
|
+
"publish:packages": "pnpm version:packages && pnpm -r --filter \"@resora/*\" publish --access public"
|
|
89
88
|
}
|
|
90
89
|
}
|