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/dist/index.mjs
ADDED
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
//#region src/ApiResource.ts
|
|
2
|
+
/**
|
|
3
|
+
* ApiResource function to return the Resource instance
|
|
4
|
+
*
|
|
5
|
+
* @param instance Resource instance
|
|
6
|
+
* @returns Resource instance
|
|
7
|
+
*/
|
|
8
|
+
function ApiResource(instance) {
|
|
9
|
+
return instance;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
//#endregion
|
|
13
|
+
//#region src/ServerResponse.ts
|
|
14
|
+
var ServerResponse = class {
|
|
15
|
+
_status = 200;
|
|
16
|
+
headers = {};
|
|
17
|
+
constructor(response, body) {
|
|
18
|
+
this.response = response;
|
|
19
|
+
this.body = body;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Set the HTTP status code for the response
|
|
23
|
+
*
|
|
24
|
+
* @param status
|
|
25
|
+
* @returns The current ServerResponse instance
|
|
26
|
+
*/
|
|
27
|
+
setStatusCode(status) {
|
|
28
|
+
this._status = status;
|
|
29
|
+
if ("status" in this.response && typeof this.response.status === "function") this.response.status(status);
|
|
30
|
+
else if ("status" in this.response) this.response.status = status;
|
|
31
|
+
return this;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Get the current HTTP status code for the response
|
|
35
|
+
*
|
|
36
|
+
* @returns
|
|
37
|
+
*/
|
|
38
|
+
status() {
|
|
39
|
+
return this._status;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Get the current HTTP status text for the response
|
|
43
|
+
*
|
|
44
|
+
* @returns
|
|
45
|
+
*/
|
|
46
|
+
statusText() {
|
|
47
|
+
if ("statusMessage" in this.response) return this.response.statusMessage;
|
|
48
|
+
else if ("statusText" in this.response) return this.response.statusText;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Set a cookie in the response header
|
|
52
|
+
*
|
|
53
|
+
* @param name The name of the cookie
|
|
54
|
+
* @param value The value of the cookie
|
|
55
|
+
* @param options Optional cookie attributes (e.g., path, domain, maxAge)
|
|
56
|
+
* @returns The current ServerResponse instance
|
|
57
|
+
*/
|
|
58
|
+
setCookie(name, value, options) {
|
|
59
|
+
this.#addHeader("Set-Cookie", `${name}=${value}; ${Object.entries(options || {}).map(([key, val]) => `${key}=${val}`).join("; ")}`);
|
|
60
|
+
return this;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Convert the resource to a JSON response body
|
|
64
|
+
*
|
|
65
|
+
* @param headers Optional headers to add to the response
|
|
66
|
+
* @returns The current ServerResponse instance
|
|
67
|
+
*/
|
|
68
|
+
setHeaders(headers) {
|
|
69
|
+
for (const [key, value] of Object.entries(headers)) this.#addHeader(key, value);
|
|
70
|
+
return this;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Add a single header to the response
|
|
74
|
+
*
|
|
75
|
+
* @param key The name of the header
|
|
76
|
+
* @param value The value of the header
|
|
77
|
+
* @returns The current ServerResponse instance
|
|
78
|
+
*/
|
|
79
|
+
header(key, value) {
|
|
80
|
+
this.#addHeader(key, value);
|
|
81
|
+
return this;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Add a single header to the response
|
|
85
|
+
*
|
|
86
|
+
* @param key The name of the header
|
|
87
|
+
* @param value The value of the header
|
|
88
|
+
*/
|
|
89
|
+
#addHeader(key, value) {
|
|
90
|
+
this.headers[key] = value;
|
|
91
|
+
if ("headers" in this.response) this.response.headers.set(key, value);
|
|
92
|
+
else if ("setHeader" in this.response) this.response.setHeader(key, value);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Promise-like then method to allow chaining with async/await or .then() syntax
|
|
96
|
+
*
|
|
97
|
+
* @param onfulfilled Callback to handle the fulfilled state of the promise, receiving the response body
|
|
98
|
+
* @param onrejected Callback to handle the rejected state of the promise, receiving the error reason
|
|
99
|
+
* @returns A promise that resolves to the result of the onfulfilled or onrejected callback
|
|
100
|
+
*/
|
|
101
|
+
then(onfulfilled, onrejected) {
|
|
102
|
+
const resolved = Promise.resolve(this.body).then(onfulfilled, onrejected);
|
|
103
|
+
if ("send" in this.response) this.response.send(this.body);
|
|
104
|
+
return resolved;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Promise-like catch method to handle rejected state of the promise
|
|
108
|
+
*
|
|
109
|
+
* @param onrejected
|
|
110
|
+
* @returns
|
|
111
|
+
*/
|
|
112
|
+
catch(onrejected) {
|
|
113
|
+
return this.then(void 0, onrejected);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Promise-like finally method to handle cleanup after promise is settled
|
|
117
|
+
*
|
|
118
|
+
* @param onfinally
|
|
119
|
+
* @returns
|
|
120
|
+
*/
|
|
121
|
+
finally(onfinally) {
|
|
122
|
+
return this.then(onfinally, onfinally);
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
//#endregion
|
|
127
|
+
//#region src/ResourceCollection.ts
|
|
128
|
+
/**
|
|
129
|
+
* ResourceCollection class to handle API resource transformation and response building for collections
|
|
130
|
+
*/
|
|
131
|
+
var ResourceCollection = class {
|
|
132
|
+
body = { data: [] };
|
|
133
|
+
resource;
|
|
134
|
+
collects;
|
|
135
|
+
called = {};
|
|
136
|
+
constructor(rsc, res) {
|
|
137
|
+
this.res = res;
|
|
138
|
+
this.resource = rsc;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Get the original resource data
|
|
142
|
+
*/
|
|
143
|
+
data() {
|
|
144
|
+
return this.toArray();
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Convert resource to JSON response format
|
|
148
|
+
*
|
|
149
|
+
* @returns
|
|
150
|
+
*/
|
|
151
|
+
json() {
|
|
152
|
+
if (!this.called.json) {
|
|
153
|
+
this.called.json = true;
|
|
154
|
+
let data = this.data();
|
|
155
|
+
if (this.collects) data = data.map((item) => new this.collects(item).data());
|
|
156
|
+
this.body = { data };
|
|
157
|
+
if (!Array.isArray(this.resource)) {
|
|
158
|
+
if (this.resource.pagination && this.resource.cursor) this.body.meta = {
|
|
159
|
+
pagination: this.resource.pagination,
|
|
160
|
+
cursor: this.resource.cursor
|
|
161
|
+
};
|
|
162
|
+
else if (this.resource.pagination) this.body.meta = { pagination: this.resource.pagination };
|
|
163
|
+
else if (this.resource.cursor) this.body.meta = { cursor: this.resource.cursor };
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return this;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Flatten resource to return original data
|
|
170
|
+
*
|
|
171
|
+
* @returns
|
|
172
|
+
*/
|
|
173
|
+
toArray() {
|
|
174
|
+
this.called.toArray = true;
|
|
175
|
+
this.json();
|
|
176
|
+
return Array.isArray(this.resource) ? [...this.resource] : [...this.resource.data];
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Add additional properties to the response body
|
|
180
|
+
*
|
|
181
|
+
* @param extra Additional properties to merge into the response body
|
|
182
|
+
* @returns
|
|
183
|
+
*/
|
|
184
|
+
additional(extra) {
|
|
185
|
+
this.called.additional = true;
|
|
186
|
+
this.json();
|
|
187
|
+
delete extra.cursor;
|
|
188
|
+
delete extra.pagination;
|
|
189
|
+
if (extra.data && Array.isArray(this.body.data)) this.body.data = [...this.body.data, ...extra.data];
|
|
190
|
+
this.body = {
|
|
191
|
+
...this.body,
|
|
192
|
+
...extra
|
|
193
|
+
};
|
|
194
|
+
return this;
|
|
195
|
+
}
|
|
196
|
+
response(res) {
|
|
197
|
+
this.called.toResponse = true;
|
|
198
|
+
return new ServerResponse(res ?? this.res, this.body);
|
|
199
|
+
}
|
|
200
|
+
setCollects(collects) {
|
|
201
|
+
this.collects = collects;
|
|
202
|
+
return this;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Promise-like then method to allow chaining with async/await or .then() syntax
|
|
206
|
+
*
|
|
207
|
+
* @param onfulfilled Callback to handle the fulfilled state of the promise, receiving the response body
|
|
208
|
+
* @param onrejected Callback to handle the rejected state of the promise, receiving the error reason
|
|
209
|
+
* @returns A promise that resolves to the result of the onfulfilled or onrejected callback
|
|
210
|
+
*/
|
|
211
|
+
then(onfulfilled, onrejected) {
|
|
212
|
+
this.called.then = true;
|
|
213
|
+
this.json();
|
|
214
|
+
const resolved = Promise.resolve(this.body).then(onfulfilled, onrejected);
|
|
215
|
+
if (this.res) this.res.send(this.body);
|
|
216
|
+
return resolved;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Promise-like catch method to handle rejected state of the promise
|
|
220
|
+
*
|
|
221
|
+
* @param onrejected
|
|
222
|
+
* @returns
|
|
223
|
+
*/
|
|
224
|
+
catch(onrejected) {
|
|
225
|
+
return this.then(void 0, onrejected);
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Promise-like finally method to handle cleanup after promise is settled
|
|
229
|
+
*
|
|
230
|
+
* @param onfinally
|
|
231
|
+
* @returns
|
|
232
|
+
*/
|
|
233
|
+
finally(onfinally) {
|
|
234
|
+
return this.then(onfinally, onfinally);
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
//#endregion
|
|
239
|
+
//#region src/Resource.ts
|
|
240
|
+
/**
|
|
241
|
+
* Resource class to handle API resource transformation and response building
|
|
242
|
+
*/
|
|
243
|
+
var Resource = class {
|
|
244
|
+
body = { data: {} };
|
|
245
|
+
resource;
|
|
246
|
+
called = {};
|
|
247
|
+
constructor(rsc, res) {
|
|
248
|
+
this.res = res;
|
|
249
|
+
this.resource = rsc;
|
|
250
|
+
/**
|
|
251
|
+
* Copy properties from rsc to this instance for easy
|
|
252
|
+
* access, but only if data is not an array
|
|
253
|
+
*/
|
|
254
|
+
if (!Array.isArray(this.resource.data ?? this.resource)) {
|
|
255
|
+
for (const key of Object.keys(this.resource.data ?? this.resource)) if (!(key in this)) Object.defineProperty(this, key, {
|
|
256
|
+
enumerable: true,
|
|
257
|
+
configurable: true,
|
|
258
|
+
get: () => {
|
|
259
|
+
return this.resource.data?.[key] ?? this.resource[key];
|
|
260
|
+
},
|
|
261
|
+
set: (value) => {
|
|
262
|
+
if (this.resource.data && this.resource.data[key]) this.resource.data[key] = value;
|
|
263
|
+
else this.resource[key] = value;
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Create a ResourceCollection from an array of resource data or a Collectible instance
|
|
270
|
+
*
|
|
271
|
+
* @param data
|
|
272
|
+
* @returns
|
|
273
|
+
*/
|
|
274
|
+
static collection(data) {
|
|
275
|
+
return new ResourceCollection(data).setCollects(this);
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Get the original resource data
|
|
279
|
+
*/
|
|
280
|
+
data() {
|
|
281
|
+
return this.toArray();
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Convert resource to JSON response format
|
|
285
|
+
*
|
|
286
|
+
* @returns
|
|
287
|
+
*/
|
|
288
|
+
json() {
|
|
289
|
+
if (!this.called.json) {
|
|
290
|
+
this.called.json = true;
|
|
291
|
+
const resource = this.data();
|
|
292
|
+
let data = Array.isArray(resource) ? [...resource] : { ...resource };
|
|
293
|
+
if (typeof data.data !== "undefined") data = data.data;
|
|
294
|
+
this.body = { data };
|
|
295
|
+
}
|
|
296
|
+
return this;
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Flatten resource to array format (for collections) or return original data for single resources
|
|
300
|
+
*
|
|
301
|
+
* @returns
|
|
302
|
+
*/
|
|
303
|
+
toArray() {
|
|
304
|
+
this.called.toArray = true;
|
|
305
|
+
this.json();
|
|
306
|
+
let data = Array.isArray(this.resource) ? [...this.resource] : { ...this.resource };
|
|
307
|
+
if (!Array.isArray(data) && typeof data.data !== "undefined") data = data.data;
|
|
308
|
+
return data;
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Add additional properties to the response body
|
|
312
|
+
*
|
|
313
|
+
* @param extra Additional properties to merge into the response body
|
|
314
|
+
* @returns
|
|
315
|
+
*/
|
|
316
|
+
additional(extra) {
|
|
317
|
+
this.called.additional = true;
|
|
318
|
+
this.json();
|
|
319
|
+
if (extra.data) this.body.data = Array.isArray(this.body.data) ? [...this.body.data, ...extra.data] : {
|
|
320
|
+
...this.body.data,
|
|
321
|
+
...extra.data
|
|
322
|
+
};
|
|
323
|
+
this.body = {
|
|
324
|
+
...this.body,
|
|
325
|
+
...extra
|
|
326
|
+
};
|
|
327
|
+
return this;
|
|
328
|
+
}
|
|
329
|
+
response(res) {
|
|
330
|
+
this.called.toResponse = true;
|
|
331
|
+
return new ServerResponse(res ?? this.res, this.body);
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Promise-like then method to allow chaining with async/await or .then() syntax
|
|
335
|
+
*
|
|
336
|
+
* @param onfulfilled Callback to handle the fulfilled state of the promise, receiving the response body
|
|
337
|
+
* @param onrejected Callback to handle the rejected state of the promise, receiving the error reason
|
|
338
|
+
* @returns A promise that resolves to the result of the onfulfilled or onrejected callback
|
|
339
|
+
*/
|
|
340
|
+
then(onfulfilled, onrejected) {
|
|
341
|
+
this.called.then = true;
|
|
342
|
+
this.json();
|
|
343
|
+
const resolved = Promise.resolve(this.body).then(onfulfilled, onrejected);
|
|
344
|
+
if (this.res) this.res.send(this.body);
|
|
345
|
+
return resolved;
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Promise-like catch method to handle rejected state of the promise
|
|
349
|
+
*
|
|
350
|
+
* @param onrejected
|
|
351
|
+
* @returns
|
|
352
|
+
*/
|
|
353
|
+
catch(onrejected) {
|
|
354
|
+
return this.then(void 0, onrejected);
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Promise-like finally method to handle cleanup after promise is settled
|
|
358
|
+
*
|
|
359
|
+
* @param onfinally
|
|
360
|
+
* @returns
|
|
361
|
+
*/
|
|
362
|
+
finally(onfinally) {
|
|
363
|
+
return this.then(onfinally, onfinally);
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
//#endregion
|
|
368
|
+
export { ApiResource, Resource };
|
package/package.json
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "resora",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A structured API response layer for Node.js and TypeScript with automatic JSON responses, collection support, and pagination handling.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"api",
|
|
7
|
+
"resource",
|
|
8
|
+
"transformer",
|
|
9
|
+
"laravel",
|
|
10
|
+
"typescript",
|
|
11
|
+
"express",
|
|
12
|
+
"h3",
|
|
13
|
+
"pagination",
|
|
14
|
+
"json",
|
|
15
|
+
"response"
|
|
16
|
+
],
|
|
17
|
+
"homepage": "https://github.com/toneflix/resora",
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/toneflix/resora/issues"
|
|
20
|
+
},
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/toneflix/resora.git"
|
|
24
|
+
},
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"author": "3m1n1nce <3m1n1nce@toneflix.net>",
|
|
27
|
+
"type": "module",
|
|
28
|
+
"main": "./dist/index.cjs",
|
|
29
|
+
"module": "./dist/index.mjs",
|
|
30
|
+
"types": "./dist/index.d.cts",
|
|
31
|
+
"exports": {
|
|
32
|
+
".": {
|
|
33
|
+
"import": "./dist/index.mjs",
|
|
34
|
+
"require": "./dist/index.cjs"
|
|
35
|
+
},
|
|
36
|
+
"./package.json": "./package.json"
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"dist",
|
|
40
|
+
"README.md",
|
|
41
|
+
"LICENSE"
|
|
42
|
+
],
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@eslint/js": "^10.0.1",
|
|
45
|
+
"@eslint/markdown": "^7.5.1",
|
|
46
|
+
"@types/express": "^4.17.21",
|
|
47
|
+
"@types/node": "^20.10.6",
|
|
48
|
+
"@types/supertest": "^6.0.3",
|
|
49
|
+
"@vitest/coverage-v8": "4.0.18",
|
|
50
|
+
"barrelize": "^1.7.3",
|
|
51
|
+
"eslint": "^10.0.0",
|
|
52
|
+
"express": "^5.1.0",
|
|
53
|
+
"h3": "2.0.1-rc.14",
|
|
54
|
+
"supertest": "^7.1.1",
|
|
55
|
+
"tsdown": "^0.20.3",
|
|
56
|
+
"tsx": "^4.21.0",
|
|
57
|
+
"typescript": "^5.3.3",
|
|
58
|
+
"typescript-eslint": "^8.56.0",
|
|
59
|
+
"vite-tsconfig-paths": "^6.1.1",
|
|
60
|
+
"vitepress": "2.0.0-alpha.16",
|
|
61
|
+
"vitest": "^4.0.18",
|
|
62
|
+
"vue": "^3.5.28"
|
|
63
|
+
},
|
|
64
|
+
"engines": {
|
|
65
|
+
"node": ">=20.0.0"
|
|
66
|
+
},
|
|
67
|
+
"scripts": {
|
|
68
|
+
"lint": "eslint",
|
|
69
|
+
"test": "pnpm vitest",
|
|
70
|
+
"test:coverage": "pnpm vitest --coverage --watch=false",
|
|
71
|
+
"build": "pnpm tsdown",
|
|
72
|
+
"barrel": "barrelize",
|
|
73
|
+
"docs:dev": "vitepress dev docs",
|
|
74
|
+
"docs:build": "vitepress build docs",
|
|
75
|
+
"docs:preview": "vitepress preview docs"
|
|
76
|
+
}
|
|
77
|
+
}
|