resora 0.1.11 → 0.2.1
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 +44 -7
- package/bin/index.mjs +2 -2
- package/dist/index.cjs +831 -29
- package/dist/index.d.cts +346 -158
- package/dist/index.d.mts +346 -158
- package/dist/index.mjs +789 -29
- package/package.json +1 -1
- package/stubs/resora.config.stub +32 -0
package/dist/index.mjs
CHANGED
|
@@ -14,14 +14,347 @@ function ApiResource(instance) {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
//#endregion
|
|
17
|
-
//#region src/
|
|
17
|
+
//#region src/utilities/state.ts
|
|
18
|
+
let globalPreferredCase;
|
|
19
|
+
let globalResponseStructure;
|
|
20
|
+
let globalPaginatedExtras = ["meta", "links"];
|
|
21
|
+
let globalPaginatedLinks = {
|
|
22
|
+
first: "first",
|
|
23
|
+
last: "last",
|
|
24
|
+
prev: "prev",
|
|
25
|
+
next: "next"
|
|
26
|
+
};
|
|
27
|
+
let globalBaseUrl = "https://localhost";
|
|
28
|
+
let globalPageName = "page";
|
|
29
|
+
let globalPaginatedMeta = {
|
|
30
|
+
to: "to",
|
|
31
|
+
from: "from",
|
|
32
|
+
links: "links",
|
|
33
|
+
path: "path",
|
|
34
|
+
total: "total",
|
|
35
|
+
per_page: "per_page",
|
|
36
|
+
last_page: "last_page",
|
|
37
|
+
current_page: "current_page"
|
|
38
|
+
};
|
|
39
|
+
let globalCursorMeta = {
|
|
40
|
+
previous: "previous",
|
|
41
|
+
next: "next"
|
|
42
|
+
};
|
|
43
|
+
const setGlobalCase = (style) => {
|
|
44
|
+
globalPreferredCase = style;
|
|
45
|
+
};
|
|
46
|
+
const getGlobalCase = () => {
|
|
47
|
+
return globalPreferredCase;
|
|
48
|
+
};
|
|
49
|
+
const setGlobalResponseStructure = (config) => {
|
|
50
|
+
globalResponseStructure = config;
|
|
51
|
+
};
|
|
52
|
+
const getGlobalResponseStructure = () => {
|
|
53
|
+
return globalResponseStructure;
|
|
54
|
+
};
|
|
55
|
+
const setGlobalResponseRootKey = (rootKey) => {
|
|
56
|
+
globalResponseStructure = {
|
|
57
|
+
...globalResponseStructure || {},
|
|
58
|
+
rootKey
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
const setGlobalResponseWrap = (wrap) => {
|
|
62
|
+
globalResponseStructure = {
|
|
63
|
+
...globalResponseStructure || {},
|
|
64
|
+
wrap
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
const getGlobalResponseWrap = () => {
|
|
68
|
+
return globalResponseStructure?.wrap;
|
|
69
|
+
};
|
|
70
|
+
const getGlobalResponseRootKey = () => {
|
|
71
|
+
return globalResponseStructure?.rootKey;
|
|
72
|
+
};
|
|
73
|
+
const setGlobalResponseFactory = (factory) => {
|
|
74
|
+
globalResponseStructure = {
|
|
75
|
+
...globalResponseStructure || {},
|
|
76
|
+
factory
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
const getGlobalResponseFactory = () => {
|
|
80
|
+
return globalResponseStructure?.factory;
|
|
81
|
+
};
|
|
82
|
+
const setGlobalPaginatedExtras = (extras) => {
|
|
83
|
+
globalPaginatedExtras = extras;
|
|
84
|
+
};
|
|
85
|
+
const getGlobalPaginatedExtras = () => {
|
|
86
|
+
return globalPaginatedExtras;
|
|
87
|
+
};
|
|
88
|
+
const setGlobalPaginatedLinks = (links) => {
|
|
89
|
+
globalPaginatedLinks = {
|
|
90
|
+
...globalPaginatedLinks,
|
|
91
|
+
...links
|
|
92
|
+
};
|
|
93
|
+
};
|
|
94
|
+
const getGlobalPaginatedLinks = () => {
|
|
95
|
+
return globalPaginatedLinks;
|
|
96
|
+
};
|
|
97
|
+
const setGlobalBaseUrl = (baseUrl) => {
|
|
98
|
+
globalBaseUrl = baseUrl;
|
|
99
|
+
};
|
|
100
|
+
const getGlobalBaseUrl = () => {
|
|
101
|
+
return globalBaseUrl;
|
|
102
|
+
};
|
|
103
|
+
const setGlobalPageName = (pageName) => {
|
|
104
|
+
globalPageName = pageName;
|
|
105
|
+
};
|
|
106
|
+
const getGlobalPageName = () => {
|
|
107
|
+
return globalPageName;
|
|
108
|
+
};
|
|
109
|
+
const setGlobalPaginatedMeta = (meta) => {
|
|
110
|
+
globalPaginatedMeta = {
|
|
111
|
+
...globalPaginatedMeta,
|
|
112
|
+
...meta
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
const getGlobalPaginatedMeta = () => {
|
|
116
|
+
return globalPaginatedMeta;
|
|
117
|
+
};
|
|
118
|
+
const setGlobalCursorMeta = (meta) => {
|
|
119
|
+
globalCursorMeta = {
|
|
120
|
+
...globalCursorMeta,
|
|
121
|
+
...meta
|
|
122
|
+
};
|
|
123
|
+
};
|
|
124
|
+
const getGlobalCursorMeta = () => {
|
|
125
|
+
return globalCursorMeta;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
//#endregion
|
|
129
|
+
//#region src/utilities/pagination.ts
|
|
130
|
+
const getPaginationExtraKeys = () => {
|
|
131
|
+
const extras = getGlobalPaginatedExtras();
|
|
132
|
+
if (Array.isArray(extras)) return {
|
|
133
|
+
metaKey: extras.includes("meta") ? "meta" : void 0,
|
|
134
|
+
linksKey: extras.includes("links") ? "links" : void 0,
|
|
135
|
+
cursorKey: extras.includes("cursor") ? "cursor" : void 0
|
|
136
|
+
};
|
|
137
|
+
return {
|
|
138
|
+
metaKey: extras.meta,
|
|
139
|
+
linksKey: extras.links,
|
|
140
|
+
cursorKey: extras.cursor
|
|
141
|
+
};
|
|
142
|
+
};
|
|
143
|
+
const buildPageUrl = (page, pathName) => {
|
|
144
|
+
if (typeof page === "undefined") return;
|
|
145
|
+
const rawPath = pathName || "";
|
|
146
|
+
const base = getGlobalBaseUrl() || "";
|
|
147
|
+
const isAbsolutePath = /^https?:\/\//i.test(rawPath);
|
|
148
|
+
const normalizedBase = base.replace(/\/$/, "");
|
|
149
|
+
const normalizedPath = rawPath.replace(/^\//, "");
|
|
150
|
+
const root = isAbsolutePath ? rawPath : normalizedBase ? normalizedPath ? `${normalizedBase}/${normalizedPath}` : normalizedBase : "";
|
|
151
|
+
if (!root) return;
|
|
152
|
+
const url = new URL(root);
|
|
153
|
+
url.searchParams.set(getGlobalPageName() || "page", String(page));
|
|
154
|
+
return url.toString();
|
|
155
|
+
};
|
|
156
|
+
const buildPaginationExtras = (resource) => {
|
|
157
|
+
const { metaKey, linksKey, cursorKey } = getPaginationExtraKeys();
|
|
158
|
+
const extra = {};
|
|
159
|
+
const pagination = resource?.pagination;
|
|
160
|
+
const cursor = resource?.cursor;
|
|
161
|
+
const metaBlock = {};
|
|
162
|
+
const linksBlock = {};
|
|
163
|
+
if (pagination) {
|
|
164
|
+
const metaSource = {
|
|
165
|
+
to: pagination.to,
|
|
166
|
+
from: pagination.from,
|
|
167
|
+
links: pagination.links,
|
|
168
|
+
path: pagination.path,
|
|
169
|
+
total: pagination.total,
|
|
170
|
+
per_page: pagination.perPage,
|
|
171
|
+
last_page: pagination.lastPage,
|
|
172
|
+
current_page: pagination.currentPage
|
|
173
|
+
};
|
|
174
|
+
for (const [sourceKey, outputKey] of Object.entries(getGlobalPaginatedMeta())) {
|
|
175
|
+
if (!outputKey) continue;
|
|
176
|
+
const value = metaSource[sourceKey];
|
|
177
|
+
if (typeof value !== "undefined") metaBlock[outputKey] = value;
|
|
178
|
+
}
|
|
179
|
+
const linksSource = {
|
|
180
|
+
first: buildPageUrl(pagination.firstPage, pagination.path),
|
|
181
|
+
last: buildPageUrl(pagination.lastPage, pagination.path),
|
|
182
|
+
prev: buildPageUrl(pagination.prevPage, pagination.path),
|
|
183
|
+
next: buildPageUrl(pagination.nextPage, pagination.path)
|
|
184
|
+
};
|
|
185
|
+
for (const [sourceKey, outputKey] of Object.entries(getGlobalPaginatedLinks())) {
|
|
186
|
+
if (!outputKey) continue;
|
|
187
|
+
const value = linksSource[sourceKey];
|
|
188
|
+
if (typeof value !== "undefined") linksBlock[outputKey] = value;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if (cursor) {
|
|
192
|
+
const cursorBlock = {};
|
|
193
|
+
const cursorSource = {
|
|
194
|
+
previous: cursor.previous,
|
|
195
|
+
next: cursor.next
|
|
196
|
+
};
|
|
197
|
+
for (const [sourceKey, outputKey] of Object.entries(getGlobalCursorMeta())) {
|
|
198
|
+
if (!outputKey) continue;
|
|
199
|
+
const value = cursorSource[sourceKey];
|
|
200
|
+
if (typeof value !== "undefined") cursorBlock[outputKey] = value;
|
|
201
|
+
}
|
|
202
|
+
if (cursorKey && Object.keys(cursorBlock).length > 0) extra[cursorKey] = cursorBlock;
|
|
203
|
+
else if (Object.keys(cursorBlock).length > 0) metaBlock.cursor = cursorBlock;
|
|
204
|
+
}
|
|
205
|
+
if (metaKey && Object.keys(metaBlock).length > 0) extra[metaKey] = metaBlock;
|
|
206
|
+
if (linksKey && Object.keys(linksBlock).length > 0) extra[linksKey] = linksBlock;
|
|
207
|
+
return extra;
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
//#endregion
|
|
211
|
+
//#region src/utilities/objects.ts
|
|
212
|
+
const isPlainObject = (value) => {
|
|
213
|
+
if (typeof value !== "object" || value === null) return false;
|
|
214
|
+
if (Array.isArray(value) || value instanceof Date || value instanceof RegExp) return false;
|
|
215
|
+
const proto = Object.getPrototypeOf(value);
|
|
216
|
+
return proto === Object.prototype || proto === null;
|
|
217
|
+
};
|
|
218
|
+
const appendRootProperties = (body, extra, rootKey = "data") => {
|
|
219
|
+
if (!extra || Object.keys(extra).length === 0) return body;
|
|
220
|
+
if (Array.isArray(body)) return {
|
|
221
|
+
[rootKey]: body,
|
|
222
|
+
...extra
|
|
223
|
+
};
|
|
224
|
+
if (isPlainObject(body)) return {
|
|
225
|
+
...body,
|
|
226
|
+
...extra
|
|
227
|
+
};
|
|
228
|
+
return {
|
|
229
|
+
[rootKey]: body,
|
|
230
|
+
...extra
|
|
231
|
+
};
|
|
232
|
+
};
|
|
233
|
+
const mergeMetadata = (base, incoming) => {
|
|
234
|
+
if (!incoming) return base;
|
|
235
|
+
if (!base) return incoming;
|
|
236
|
+
const merged = { ...base };
|
|
237
|
+
for (const [key, value] of Object.entries(incoming)) {
|
|
238
|
+
const existing = merged[key];
|
|
239
|
+
if (isPlainObject(existing) && isPlainObject(value)) merged[key] = mergeMetadata(existing, value);
|
|
240
|
+
else merged[key] = value;
|
|
241
|
+
}
|
|
242
|
+
return merged;
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
//#endregion
|
|
246
|
+
//#region src/utilities/response.ts
|
|
247
|
+
const buildResponseEnvelope = ({ payload, meta, metaKey = "meta", wrap = true, rootKey = "data", factory, context }) => {
|
|
248
|
+
if (factory) return factory(payload, {
|
|
249
|
+
...context,
|
|
250
|
+
rootKey,
|
|
251
|
+
meta
|
|
252
|
+
});
|
|
253
|
+
if (!wrap) {
|
|
254
|
+
if (typeof meta === "undefined") return payload;
|
|
255
|
+
if (isPlainObject(payload)) return {
|
|
256
|
+
...payload,
|
|
257
|
+
[metaKey]: meta
|
|
258
|
+
};
|
|
259
|
+
return {
|
|
260
|
+
[rootKey]: payload,
|
|
261
|
+
[metaKey]: meta
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
const body = { [rootKey]: payload };
|
|
265
|
+
if (typeof meta !== "undefined") body[metaKey] = meta;
|
|
266
|
+
return body;
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
//#endregion
|
|
270
|
+
//#region src/utilities/metadata.ts
|
|
271
|
+
const resolveWithHookMetadata = (instance, baseWithMethod) => {
|
|
272
|
+
const candidate = instance?.with;
|
|
273
|
+
if (typeof candidate !== "function" || candidate === baseWithMethod) return;
|
|
274
|
+
if (candidate.length > 0) return;
|
|
275
|
+
const result = candidate.call(instance);
|
|
276
|
+
return isPlainObject(result) ? result : void 0;
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
//#endregion
|
|
280
|
+
//#region src/utilities/conditional.ts
|
|
281
|
+
const CONDITIONAL_ATTRIBUTE_MISSING = Symbol("resora.conditional.missing");
|
|
282
|
+
const resolveWhen = (condition, value) => {
|
|
283
|
+
if (!condition) return CONDITIONAL_ATTRIBUTE_MISSING;
|
|
284
|
+
return typeof value === "function" ? value() : value;
|
|
285
|
+
};
|
|
286
|
+
const resolveWhenNotNull = (value) => {
|
|
287
|
+
return value === null || typeof value === "undefined" ? CONDITIONAL_ATTRIBUTE_MISSING : value;
|
|
288
|
+
};
|
|
289
|
+
const resolveMergeWhen = (condition, value) => {
|
|
290
|
+
if (!condition) return {};
|
|
291
|
+
const resolved = typeof value === "function" ? value() : value;
|
|
292
|
+
return isPlainObject(resolved) ? resolved : {};
|
|
293
|
+
};
|
|
294
|
+
const sanitizeConditionalAttributes = (value) => {
|
|
295
|
+
if (value === CONDITIONAL_ATTRIBUTE_MISSING) return CONDITIONAL_ATTRIBUTE_MISSING;
|
|
296
|
+
if (Array.isArray(value)) return value.map((item) => sanitizeConditionalAttributes(item)).filter((item) => item !== CONDITIONAL_ATTRIBUTE_MISSING);
|
|
297
|
+
if (isPlainObject(value)) {
|
|
298
|
+
const result = {};
|
|
299
|
+
for (const [key, nestedValue] of Object.entries(value)) {
|
|
300
|
+
const sanitizedValue = sanitizeConditionalAttributes(nestedValue);
|
|
301
|
+
if (sanitizedValue === CONDITIONAL_ATTRIBUTE_MISSING) continue;
|
|
302
|
+
result[key] = sanitizedValue;
|
|
303
|
+
}
|
|
304
|
+
return result;
|
|
305
|
+
}
|
|
306
|
+
return value;
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
//#endregion
|
|
310
|
+
//#region src/utilities/case.ts
|
|
311
|
+
const splitWords = (str) => {
|
|
312
|
+
return str.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/[-_\s]+/g, " ").trim().toLowerCase().split(" ").filter(Boolean);
|
|
313
|
+
};
|
|
314
|
+
const toCamelCase = (str) => {
|
|
315
|
+
return splitWords(str).map((w, i) => i === 0 ? w : w.charAt(0).toUpperCase() + w.slice(1)).join("");
|
|
316
|
+
};
|
|
317
|
+
const toSnakeCase = (str) => {
|
|
318
|
+
return splitWords(str).join("_");
|
|
319
|
+
};
|
|
320
|
+
const toPascalCase = (str) => {
|
|
321
|
+
return splitWords(str).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
|
|
322
|
+
};
|
|
323
|
+
const toKebabCase = (str) => {
|
|
324
|
+
return splitWords(str).join("-");
|
|
325
|
+
};
|
|
326
|
+
const getCaseTransformer = (style) => {
|
|
327
|
+
if (typeof style === "function") return style;
|
|
328
|
+
switch (style) {
|
|
329
|
+
case "camel": return toCamelCase;
|
|
330
|
+
case "snake": return toSnakeCase;
|
|
331
|
+
case "pascal": return toPascalCase;
|
|
332
|
+
case "kebab": return toKebabCase;
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
const transformKeys = (obj, transformer) => {
|
|
336
|
+
if (obj === null || obj === void 0) return obj;
|
|
337
|
+
if (Array.isArray(obj)) return obj.map((item) => transformKeys(item, transformer));
|
|
338
|
+
if (obj instanceof Date || obj instanceof RegExp) return obj;
|
|
339
|
+
if (typeof obj === "object") return Object.fromEntries(Object.entries(obj).map(([key, value]) => [transformer(key), transformKeys(value, transformer)]));
|
|
340
|
+
return obj;
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
//#endregion
|
|
344
|
+
//#region src/utilities/config.ts
|
|
18
345
|
let stubsDir = path.resolve(process.cwd(), "node_modules/resora/stubs");
|
|
19
346
|
if (!existsSync(stubsDir)) stubsDir = path.resolve(process.cwd(), "stubs");
|
|
20
347
|
const getDefaultConfig = () => {
|
|
21
348
|
return {
|
|
22
349
|
stubsDir,
|
|
23
350
|
preferredCase: "camel",
|
|
351
|
+
responseStructure: {
|
|
352
|
+
wrap: true,
|
|
353
|
+
rootKey: "data"
|
|
354
|
+
},
|
|
24
355
|
paginatedExtras: ["meta", "links"],
|
|
356
|
+
baseUrl: "https://localhost",
|
|
357
|
+
pageName: "page",
|
|
25
358
|
paginatedLinks: {
|
|
26
359
|
first: "first",
|
|
27
360
|
last: "last",
|
|
@@ -38,6 +371,10 @@ const getDefaultConfig = () => {
|
|
|
38
371
|
last_page: "last_page",
|
|
39
372
|
current_page: "current_page"
|
|
40
373
|
},
|
|
374
|
+
cursorMeta: {
|
|
375
|
+
previous: "previous",
|
|
376
|
+
next: "next"
|
|
377
|
+
},
|
|
41
378
|
resourcesDir: "src/resources",
|
|
42
379
|
stubs: {
|
|
43
380
|
config: "resora.config.stub",
|
|
@@ -46,12 +383,6 @@ const getDefaultConfig = () => {
|
|
|
46
383
|
}
|
|
47
384
|
};
|
|
48
385
|
};
|
|
49
|
-
/**
|
|
50
|
-
* Define the configuration for the package
|
|
51
|
-
*
|
|
52
|
-
* @param userConfig The user configuration to override the default configuration
|
|
53
|
-
* @returns The merged configuration object
|
|
54
|
-
*/
|
|
55
386
|
const defineConfig = (userConfig = {}) => {
|
|
56
387
|
const defaultConfig = getDefaultConfig();
|
|
57
388
|
return Object.assign(defaultConfig, userConfig, { stubs: Object.assign(defaultConfig.stubs, userConfig.stubs || {}) });
|
|
@@ -82,6 +413,13 @@ var CliApp = class {
|
|
|
82
413
|
return this;
|
|
83
414
|
}
|
|
84
415
|
/**
|
|
416
|
+
* Get the current configuration object
|
|
417
|
+
* @returns
|
|
418
|
+
*/
|
|
419
|
+
getConfig() {
|
|
420
|
+
return this.config;
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
85
423
|
* Initialize Resora by creating a default config file in the current directory
|
|
86
424
|
*
|
|
87
425
|
* @returns
|
|
@@ -163,6 +501,20 @@ var CliApp = class {
|
|
|
163
501
|
}
|
|
164
502
|
};
|
|
165
503
|
|
|
504
|
+
//#endregion
|
|
505
|
+
//#region src/cli/commands/InitCommand.ts
|
|
506
|
+
var InitCommand = class extends Command {
|
|
507
|
+
signature = `init
|
|
508
|
+
{--force : Force overwrite if config file already exists (existing file will be backed up) }
|
|
509
|
+
`;
|
|
510
|
+
description = "Initialize Resora";
|
|
511
|
+
async handle() {
|
|
512
|
+
this.app.command = this;
|
|
513
|
+
this.app.init();
|
|
514
|
+
this.success("Resora initialized");
|
|
515
|
+
}
|
|
516
|
+
};
|
|
517
|
+
|
|
166
518
|
//#endregion
|
|
167
519
|
//#region src/cli/commands/MakeResource.ts
|
|
168
520
|
var MakeResource = class extends Command {
|
|
@@ -340,10 +692,21 @@ var ServerResponse = class {
|
|
|
340
692
|
/**
|
|
341
693
|
* GenericResource class to handle API resource transformation and response building
|
|
342
694
|
*/
|
|
343
|
-
var GenericResource = class {
|
|
695
|
+
var GenericResource = class GenericResource {
|
|
344
696
|
body = { data: {} };
|
|
345
697
|
resource;
|
|
346
698
|
collects;
|
|
699
|
+
additionalMeta;
|
|
700
|
+
withResponseContext;
|
|
701
|
+
/**
|
|
702
|
+
* Preferred case style for this resource's output keys.
|
|
703
|
+
* Set on a subclass to override the global default.
|
|
704
|
+
*/
|
|
705
|
+
static preferredCase;
|
|
706
|
+
/**
|
|
707
|
+
* Response structure override for this generic resource class.
|
|
708
|
+
*/
|
|
709
|
+
static responseStructure;
|
|
347
710
|
called = {};
|
|
348
711
|
constructor(rsc, res) {
|
|
349
712
|
this.res = res;
|
|
@@ -373,6 +736,52 @@ var GenericResource = class {
|
|
|
373
736
|
return this.resource;
|
|
374
737
|
}
|
|
375
738
|
/**
|
|
739
|
+
* Get the current serialized output body.
|
|
740
|
+
*/
|
|
741
|
+
getBody() {
|
|
742
|
+
this.json();
|
|
743
|
+
return this.body;
|
|
744
|
+
}
|
|
745
|
+
/**
|
|
746
|
+
* Replace the current serialized output body.
|
|
747
|
+
*/
|
|
748
|
+
setBody(body) {
|
|
749
|
+
this.body = body;
|
|
750
|
+
return this;
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* Conditionally include a value in serialized output.
|
|
754
|
+
*/
|
|
755
|
+
when(condition, value) {
|
|
756
|
+
return resolveWhen(condition, value);
|
|
757
|
+
}
|
|
758
|
+
/**
|
|
759
|
+
* Include a value only when it is not null/undefined.
|
|
760
|
+
*/
|
|
761
|
+
whenNotNull(value) {
|
|
762
|
+
return resolveWhenNotNull(value);
|
|
763
|
+
}
|
|
764
|
+
/**
|
|
765
|
+
* Conditionally merge object attributes into serialized output.
|
|
766
|
+
*/
|
|
767
|
+
mergeWhen(condition, value) {
|
|
768
|
+
return resolveMergeWhen(condition, value);
|
|
769
|
+
}
|
|
770
|
+
resolveResponseStructure() {
|
|
771
|
+
const local = this.constructor.responseStructure;
|
|
772
|
+
const collectsLocal = this.collects?.responseStructure;
|
|
773
|
+
const global = getGlobalResponseStructure();
|
|
774
|
+
return {
|
|
775
|
+
wrap: local?.wrap ?? collectsLocal?.wrap ?? global?.wrap ?? true,
|
|
776
|
+
rootKey: local?.rootKey ?? collectsLocal?.rootKey ?? global?.rootKey ?? "data",
|
|
777
|
+
factory: local?.factory ?? collectsLocal?.factory ?? global?.factory
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
getPayloadKey() {
|
|
781
|
+
const { wrap, rootKey, factory } = this.resolveResponseStructure();
|
|
782
|
+
return factory || !wrap ? void 0 : rootKey;
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
376
785
|
* Convert resource to JSON response format
|
|
377
786
|
*
|
|
378
787
|
* @returns
|
|
@@ -387,13 +796,65 @@ var GenericResource = class {
|
|
|
387
796
|
this.resource = data;
|
|
388
797
|
}
|
|
389
798
|
if (typeof data.data !== "undefined") data = data.data;
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
799
|
+
data = sanitizeConditionalAttributes(data);
|
|
800
|
+
const paginationExtras = buildPaginationExtras(this.resource);
|
|
801
|
+
const { metaKey } = getPaginationExtraKeys();
|
|
802
|
+
const configuredMeta = metaKey ? paginationExtras[metaKey] : void 0;
|
|
803
|
+
if (metaKey) delete paginationExtras[metaKey];
|
|
804
|
+
const caseStyle = this.constructor.preferredCase ?? getGlobalCase();
|
|
805
|
+
if (caseStyle) {
|
|
806
|
+
const transformer = getCaseTransformer(caseStyle);
|
|
807
|
+
data = transformKeys(data, transformer);
|
|
808
|
+
}
|
|
809
|
+
const customMeta = mergeMetadata(resolveWithHookMetadata(this, GenericResource.prototype.with), this.additionalMeta);
|
|
810
|
+
const { wrap, rootKey, factory } = this.resolveResponseStructure();
|
|
811
|
+
this.body = buildResponseEnvelope({
|
|
812
|
+
payload: data,
|
|
813
|
+
meta: configuredMeta,
|
|
814
|
+
metaKey,
|
|
815
|
+
wrap,
|
|
816
|
+
rootKey,
|
|
817
|
+
factory,
|
|
818
|
+
context: {
|
|
819
|
+
type: "generic",
|
|
820
|
+
resource: this.resource
|
|
821
|
+
}
|
|
822
|
+
});
|
|
823
|
+
this.body = appendRootProperties(this.body, {
|
|
824
|
+
...paginationExtras,
|
|
825
|
+
...customMeta || {}
|
|
826
|
+
}, rootKey);
|
|
827
|
+
}
|
|
828
|
+
return this;
|
|
829
|
+
}
|
|
830
|
+
/**
|
|
831
|
+
* Append structured metadata to the response body.
|
|
832
|
+
*
|
|
833
|
+
* @param meta Metadata object or metadata factory
|
|
834
|
+
* @returns
|
|
835
|
+
*/
|
|
836
|
+
with(meta) {
|
|
837
|
+
this.called.with = true;
|
|
838
|
+
if (typeof meta === "undefined") return this.additionalMeta || {};
|
|
839
|
+
const resolvedMeta = typeof meta === "function" ? meta(this.resource) : meta;
|
|
840
|
+
this.additionalMeta = mergeMetadata(this.additionalMeta, resolvedMeta);
|
|
841
|
+
if (this.called.json) {
|
|
842
|
+
const { rootKey } = this.resolveResponseStructure();
|
|
843
|
+
this.body = appendRootProperties(this.body, resolvedMeta, rootKey);
|
|
393
844
|
}
|
|
394
845
|
return this;
|
|
395
846
|
}
|
|
396
847
|
/**
|
|
848
|
+
* Typed fluent metadata helper.
|
|
849
|
+
*
|
|
850
|
+
* @param meta Metadata object or metadata factory
|
|
851
|
+
* @returns
|
|
852
|
+
*/
|
|
853
|
+
withMeta(meta) {
|
|
854
|
+
this.with(meta);
|
|
855
|
+
return this;
|
|
856
|
+
}
|
|
857
|
+
/**
|
|
397
858
|
* Convert resource to array format (for collections)
|
|
398
859
|
*
|
|
399
860
|
* @returns
|
|
@@ -414,8 +875,14 @@ var GenericResource = class {
|
|
|
414
875
|
additional(extra) {
|
|
415
876
|
this.called.additional = true;
|
|
416
877
|
this.json();
|
|
878
|
+
const extraData = extra.data;
|
|
417
879
|
delete extra.data;
|
|
418
880
|
delete extra.pagination;
|
|
881
|
+
const payloadKey = this.getPayloadKey();
|
|
882
|
+
if (extraData && payloadKey && typeof this.body[payloadKey] !== "undefined") this.body[payloadKey] = Array.isArray(this.body[payloadKey]) ? [...this.body[payloadKey], ...extraData] : {
|
|
883
|
+
...this.body[payloadKey],
|
|
884
|
+
...extraData
|
|
885
|
+
};
|
|
419
886
|
this.body = {
|
|
420
887
|
...this.body,
|
|
421
888
|
...extra
|
|
@@ -424,7 +891,24 @@ var GenericResource = class {
|
|
|
424
891
|
}
|
|
425
892
|
response(res) {
|
|
426
893
|
this.called.toResponse = true;
|
|
427
|
-
|
|
894
|
+
this.json();
|
|
895
|
+
const rawResponse = res ?? this.res;
|
|
896
|
+
const response = new ServerResponse(rawResponse, this.body);
|
|
897
|
+
this.withResponseContext = {
|
|
898
|
+
response,
|
|
899
|
+
raw: rawResponse
|
|
900
|
+
};
|
|
901
|
+
this.called.withResponse = true;
|
|
902
|
+
this.withResponse(response, rawResponse);
|
|
903
|
+
return response;
|
|
904
|
+
}
|
|
905
|
+
/**
|
|
906
|
+
* Customize the outgoing transport response right before dispatch.
|
|
907
|
+
*
|
|
908
|
+
* Override in custom classes to mutate headers/status/body.
|
|
909
|
+
*/
|
|
910
|
+
withResponse(_response, _rawResponse) {
|
|
911
|
+
return this;
|
|
428
912
|
}
|
|
429
913
|
/**
|
|
430
914
|
* Promise-like then method to allow chaining with async/await or .then() syntax
|
|
@@ -436,6 +920,18 @@ var GenericResource = class {
|
|
|
436
920
|
then(onfulfilled, onrejected) {
|
|
437
921
|
this.called.then = true;
|
|
438
922
|
this.json();
|
|
923
|
+
if (this.res) {
|
|
924
|
+
const response = new ServerResponse(this.res, this.body);
|
|
925
|
+
this.withResponseContext = {
|
|
926
|
+
response,
|
|
927
|
+
raw: this.res
|
|
928
|
+
};
|
|
929
|
+
this.called.withResponse = true;
|
|
930
|
+
this.withResponse(response, this.res);
|
|
931
|
+
} else {
|
|
932
|
+
this.called.withResponse = true;
|
|
933
|
+
this.withResponse();
|
|
934
|
+
}
|
|
439
935
|
const resolved = Promise.resolve(this.body).then(onfulfilled, onrejected);
|
|
440
936
|
if (this.res) this.res.send(this.body);
|
|
441
937
|
return resolved;
|
|
@@ -447,10 +943,21 @@ var GenericResource = class {
|
|
|
447
943
|
/**
|
|
448
944
|
* ResourceCollection class to handle API resource transformation and response building for collections
|
|
449
945
|
*/
|
|
450
|
-
var ResourceCollection = class {
|
|
946
|
+
var ResourceCollection = class ResourceCollection {
|
|
451
947
|
body = { data: [] };
|
|
452
948
|
resource;
|
|
453
949
|
collects;
|
|
950
|
+
additionalMeta;
|
|
951
|
+
withResponseContext;
|
|
952
|
+
/**
|
|
953
|
+
* Preferred case style for this collection's output keys.
|
|
954
|
+
* Set on a subclass to override the global default.
|
|
955
|
+
*/
|
|
956
|
+
static preferredCase;
|
|
957
|
+
/**
|
|
958
|
+
* Response structure override for this collection class.
|
|
959
|
+
*/
|
|
960
|
+
static responseStructure;
|
|
454
961
|
called = {};
|
|
455
962
|
constructor(rsc, res) {
|
|
456
963
|
this.res = res;
|
|
@@ -463,6 +970,52 @@ var ResourceCollection = class {
|
|
|
463
970
|
return this.toArray();
|
|
464
971
|
}
|
|
465
972
|
/**
|
|
973
|
+
* Get the current serialized output body.
|
|
974
|
+
*/
|
|
975
|
+
getBody() {
|
|
976
|
+
this.json();
|
|
977
|
+
return this.body;
|
|
978
|
+
}
|
|
979
|
+
/**
|
|
980
|
+
* Replace the current serialized output body.
|
|
981
|
+
*/
|
|
982
|
+
setBody(body) {
|
|
983
|
+
this.body = body;
|
|
984
|
+
return this;
|
|
985
|
+
}
|
|
986
|
+
/**
|
|
987
|
+
* Conditionally include a value in serialized output.
|
|
988
|
+
*/
|
|
989
|
+
when(condition, value) {
|
|
990
|
+
return resolveWhen(condition, value);
|
|
991
|
+
}
|
|
992
|
+
/**
|
|
993
|
+
* Include a value only when it is not null/undefined.
|
|
994
|
+
*/
|
|
995
|
+
whenNotNull(value) {
|
|
996
|
+
return resolveWhenNotNull(value);
|
|
997
|
+
}
|
|
998
|
+
/**
|
|
999
|
+
* Conditionally merge object attributes into serialized output.
|
|
1000
|
+
*/
|
|
1001
|
+
mergeWhen(condition, value) {
|
|
1002
|
+
return resolveMergeWhen(condition, value);
|
|
1003
|
+
}
|
|
1004
|
+
resolveResponseStructure() {
|
|
1005
|
+
const local = this.constructor.responseStructure;
|
|
1006
|
+
const collectsLocal = this.collects?.responseStructure;
|
|
1007
|
+
const global = getGlobalResponseStructure();
|
|
1008
|
+
return {
|
|
1009
|
+
wrap: local?.wrap ?? collectsLocal?.wrap ?? global?.wrap ?? true,
|
|
1010
|
+
rootKey: local?.rootKey ?? collectsLocal?.rootKey ?? global?.rootKey ?? "data",
|
|
1011
|
+
factory: local?.factory ?? collectsLocal?.factory ?? global?.factory
|
|
1012
|
+
};
|
|
1013
|
+
}
|
|
1014
|
+
getPayloadKey() {
|
|
1015
|
+
const { wrap, rootKey, factory } = this.resolveResponseStructure();
|
|
1016
|
+
return factory || !wrap ? void 0 : rootKey;
|
|
1017
|
+
}
|
|
1018
|
+
/**
|
|
466
1019
|
* Convert resource to JSON response format
|
|
467
1020
|
*
|
|
468
1021
|
* @returns
|
|
@@ -472,19 +1025,65 @@ var ResourceCollection = class {
|
|
|
472
1025
|
this.called.json = true;
|
|
473
1026
|
let data = this.data();
|
|
474
1027
|
if (this.collects) data = data.map((item) => new this.collects(item).data());
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
1028
|
+
data = sanitizeConditionalAttributes(data);
|
|
1029
|
+
const paginationExtras = !Array.isArray(this.resource) ? buildPaginationExtras(this.resource) : {};
|
|
1030
|
+
const { metaKey } = getPaginationExtraKeys();
|
|
1031
|
+
const configuredMeta = metaKey ? paginationExtras[metaKey] : void 0;
|
|
1032
|
+
if (metaKey) delete paginationExtras[metaKey];
|
|
1033
|
+
const caseStyle = this.constructor.preferredCase ?? this.collects?.preferredCase ?? getGlobalCase();
|
|
1034
|
+
if (caseStyle) {
|
|
1035
|
+
const transformer = getCaseTransformer(caseStyle);
|
|
1036
|
+
data = transformKeys(data, transformer);
|
|
483
1037
|
}
|
|
1038
|
+
const customMeta = mergeMetadata(resolveWithHookMetadata(this, ResourceCollection.prototype.with), this.additionalMeta);
|
|
1039
|
+
const { wrap, rootKey, factory } = this.resolveResponseStructure();
|
|
1040
|
+
this.body = buildResponseEnvelope({
|
|
1041
|
+
payload: data,
|
|
1042
|
+
meta: configuredMeta,
|
|
1043
|
+
metaKey,
|
|
1044
|
+
wrap,
|
|
1045
|
+
rootKey,
|
|
1046
|
+
factory,
|
|
1047
|
+
context: {
|
|
1048
|
+
type: "collection",
|
|
1049
|
+
resource: this.resource
|
|
1050
|
+
}
|
|
1051
|
+
});
|
|
1052
|
+
this.body = appendRootProperties(this.body, {
|
|
1053
|
+
...paginationExtras,
|
|
1054
|
+
...customMeta || {}
|
|
1055
|
+
}, rootKey);
|
|
484
1056
|
}
|
|
485
1057
|
return this;
|
|
486
1058
|
}
|
|
487
1059
|
/**
|
|
1060
|
+
* Append structured metadata to the response body.
|
|
1061
|
+
*
|
|
1062
|
+
* @param meta Metadata object or metadata factory
|
|
1063
|
+
* @returns
|
|
1064
|
+
*/
|
|
1065
|
+
with(meta) {
|
|
1066
|
+
this.called.with = true;
|
|
1067
|
+
if (typeof meta === "undefined") return this.additionalMeta || {};
|
|
1068
|
+
const resolvedMeta = typeof meta === "function" ? meta(this.resource) : meta;
|
|
1069
|
+
this.additionalMeta = mergeMetadata(this.additionalMeta, resolvedMeta);
|
|
1070
|
+
if (this.called.json) {
|
|
1071
|
+
const { rootKey } = this.resolveResponseStructure();
|
|
1072
|
+
this.body = appendRootProperties(this.body, resolvedMeta, rootKey);
|
|
1073
|
+
}
|
|
1074
|
+
return this;
|
|
1075
|
+
}
|
|
1076
|
+
/**
|
|
1077
|
+
* Typed fluent metadata helper.
|
|
1078
|
+
*
|
|
1079
|
+
* @param meta Metadata object or metadata factory
|
|
1080
|
+
* @returns
|
|
1081
|
+
*/
|
|
1082
|
+
withMeta(meta) {
|
|
1083
|
+
this.with(meta);
|
|
1084
|
+
return this;
|
|
1085
|
+
}
|
|
1086
|
+
/**
|
|
488
1087
|
* Flatten resource to return original data
|
|
489
1088
|
*
|
|
490
1089
|
* @returns
|
|
@@ -505,7 +1104,8 @@ var ResourceCollection = class {
|
|
|
505
1104
|
this.json();
|
|
506
1105
|
delete extra.cursor;
|
|
507
1106
|
delete extra.pagination;
|
|
508
|
-
|
|
1107
|
+
const payloadKey = this.getPayloadKey();
|
|
1108
|
+
if (extra.data && payloadKey && Array.isArray(this.body[payloadKey])) this.body[payloadKey] = [...this.body[payloadKey], ...extra.data];
|
|
509
1109
|
this.body = {
|
|
510
1110
|
...this.body,
|
|
511
1111
|
...extra
|
|
@@ -514,7 +1114,24 @@ var ResourceCollection = class {
|
|
|
514
1114
|
}
|
|
515
1115
|
response(res) {
|
|
516
1116
|
this.called.toResponse = true;
|
|
517
|
-
|
|
1117
|
+
this.json();
|
|
1118
|
+
const rawResponse = res ?? this.res;
|
|
1119
|
+
const response = new ServerResponse(rawResponse, this.body);
|
|
1120
|
+
this.withResponseContext = {
|
|
1121
|
+
response,
|
|
1122
|
+
raw: rawResponse
|
|
1123
|
+
};
|
|
1124
|
+
this.called.withResponse = true;
|
|
1125
|
+
this.withResponse(response, rawResponse);
|
|
1126
|
+
return response;
|
|
1127
|
+
}
|
|
1128
|
+
/**
|
|
1129
|
+
* Customize the outgoing transport response right before dispatch.
|
|
1130
|
+
*
|
|
1131
|
+
* Override in custom classes to mutate headers/status/body.
|
|
1132
|
+
*/
|
|
1133
|
+
withResponse(_response, _rawResponse) {
|
|
1134
|
+
return this;
|
|
518
1135
|
}
|
|
519
1136
|
setCollects(collects) {
|
|
520
1137
|
this.collects = collects;
|
|
@@ -530,6 +1147,18 @@ var ResourceCollection = class {
|
|
|
530
1147
|
then(onfulfilled, onrejected) {
|
|
531
1148
|
this.called.then = true;
|
|
532
1149
|
this.json();
|
|
1150
|
+
if (this.res) {
|
|
1151
|
+
const response = new ServerResponse(this.res, this.body);
|
|
1152
|
+
this.withResponseContext = {
|
|
1153
|
+
response,
|
|
1154
|
+
raw: this.res
|
|
1155
|
+
};
|
|
1156
|
+
this.called.withResponse = true;
|
|
1157
|
+
this.withResponse(response, this.res);
|
|
1158
|
+
} else {
|
|
1159
|
+
this.called.withResponse = true;
|
|
1160
|
+
this.withResponse();
|
|
1161
|
+
}
|
|
533
1162
|
const resolved = Promise.resolve(this.body).then(onfulfilled, onrejected);
|
|
534
1163
|
if (this.res) this.res.send(this.body);
|
|
535
1164
|
return resolved;
|
|
@@ -559,9 +1188,20 @@ var ResourceCollection = class {
|
|
|
559
1188
|
/**
|
|
560
1189
|
* Resource class to handle API resource transformation and response building
|
|
561
1190
|
*/
|
|
562
|
-
var Resource = class {
|
|
1191
|
+
var Resource = class Resource {
|
|
563
1192
|
body = { data: {} };
|
|
564
1193
|
resource;
|
|
1194
|
+
additionalMeta;
|
|
1195
|
+
withResponseContext;
|
|
1196
|
+
/**
|
|
1197
|
+
* Preferred case style for this resource's output keys.
|
|
1198
|
+
* Set on a subclass to override the global default.
|
|
1199
|
+
*/
|
|
1200
|
+
static preferredCase;
|
|
1201
|
+
/**
|
|
1202
|
+
* Response structure override for this resource class.
|
|
1203
|
+
*/
|
|
1204
|
+
static responseStructure;
|
|
565
1205
|
called = {};
|
|
566
1206
|
constructor(rsc, res) {
|
|
567
1207
|
this.res = res;
|
|
@@ -600,6 +1240,51 @@ var Resource = class {
|
|
|
600
1240
|
return this.toArray();
|
|
601
1241
|
}
|
|
602
1242
|
/**
|
|
1243
|
+
* Get the current serialized output body.
|
|
1244
|
+
*/
|
|
1245
|
+
getBody() {
|
|
1246
|
+
this.json();
|
|
1247
|
+
return this.body;
|
|
1248
|
+
}
|
|
1249
|
+
/**
|
|
1250
|
+
* Replace the current serialized output body.
|
|
1251
|
+
*/
|
|
1252
|
+
setBody(body) {
|
|
1253
|
+
this.body = body;
|
|
1254
|
+
return this;
|
|
1255
|
+
}
|
|
1256
|
+
/**
|
|
1257
|
+
* Conditionally include a value in serialized output.
|
|
1258
|
+
*/
|
|
1259
|
+
when(condition, value) {
|
|
1260
|
+
return resolveWhen(condition, value);
|
|
1261
|
+
}
|
|
1262
|
+
/**
|
|
1263
|
+
* Include a value only when it is not null/undefined.
|
|
1264
|
+
*/
|
|
1265
|
+
whenNotNull(value) {
|
|
1266
|
+
return resolveWhenNotNull(value);
|
|
1267
|
+
}
|
|
1268
|
+
/**
|
|
1269
|
+
* Conditionally merge object attributes into serialized output.
|
|
1270
|
+
*/
|
|
1271
|
+
mergeWhen(condition, value) {
|
|
1272
|
+
return resolveMergeWhen(condition, value);
|
|
1273
|
+
}
|
|
1274
|
+
resolveResponseStructure() {
|
|
1275
|
+
const local = this.constructor.responseStructure;
|
|
1276
|
+
const global = getGlobalResponseStructure();
|
|
1277
|
+
return {
|
|
1278
|
+
wrap: local?.wrap ?? global?.wrap ?? true,
|
|
1279
|
+
rootKey: local?.rootKey ?? global?.rootKey ?? "data",
|
|
1280
|
+
factory: local?.factory ?? global?.factory
|
|
1281
|
+
};
|
|
1282
|
+
}
|
|
1283
|
+
getPayloadKey() {
|
|
1284
|
+
const { wrap, rootKey, factory } = this.resolveResponseStructure();
|
|
1285
|
+
return factory || !wrap ? void 0 : rootKey;
|
|
1286
|
+
}
|
|
1287
|
+
/**
|
|
603
1288
|
* Convert resource to JSON response format
|
|
604
1289
|
*
|
|
605
1290
|
* @returns
|
|
@@ -610,11 +1295,56 @@ var Resource = class {
|
|
|
610
1295
|
const resource = this.data();
|
|
611
1296
|
let data = Array.isArray(resource) ? [...resource] : { ...resource };
|
|
612
1297
|
if (typeof data.data !== "undefined") data = data.data;
|
|
613
|
-
|
|
1298
|
+
data = sanitizeConditionalAttributes(data);
|
|
1299
|
+
const caseStyle = this.constructor.preferredCase ?? getGlobalCase();
|
|
1300
|
+
if (caseStyle) {
|
|
1301
|
+
const transformer = getCaseTransformer(caseStyle);
|
|
1302
|
+
data = transformKeys(data, transformer);
|
|
1303
|
+
}
|
|
1304
|
+
const customMeta = mergeMetadata(resolveWithHookMetadata(this, Resource.prototype.with), this.additionalMeta);
|
|
1305
|
+
const { wrap, rootKey, factory } = this.resolveResponseStructure();
|
|
1306
|
+
this.body = buildResponseEnvelope({
|
|
1307
|
+
payload: data,
|
|
1308
|
+
wrap,
|
|
1309
|
+
rootKey,
|
|
1310
|
+
factory,
|
|
1311
|
+
context: {
|
|
1312
|
+
type: "resource",
|
|
1313
|
+
resource: this.resource
|
|
1314
|
+
}
|
|
1315
|
+
});
|
|
1316
|
+
this.body = appendRootProperties(this.body, customMeta, rootKey);
|
|
614
1317
|
}
|
|
615
1318
|
return this;
|
|
616
1319
|
}
|
|
617
1320
|
/**
|
|
1321
|
+
* Append structured metadata to the response body.
|
|
1322
|
+
*
|
|
1323
|
+
* @param meta Metadata object or metadata factory
|
|
1324
|
+
* @returns
|
|
1325
|
+
*/
|
|
1326
|
+
with(meta) {
|
|
1327
|
+
this.called.with = true;
|
|
1328
|
+
if (typeof meta === "undefined") return this.additionalMeta || {};
|
|
1329
|
+
const resolvedMeta = typeof meta === "function" ? meta(this.resource) : meta;
|
|
1330
|
+
this.additionalMeta = mergeMetadata(this.additionalMeta, resolvedMeta);
|
|
1331
|
+
if (this.called.json) {
|
|
1332
|
+
const { rootKey } = this.resolveResponseStructure();
|
|
1333
|
+
this.body = appendRootProperties(this.body, resolvedMeta, rootKey);
|
|
1334
|
+
}
|
|
1335
|
+
return this;
|
|
1336
|
+
}
|
|
1337
|
+
/**
|
|
1338
|
+
* Typed fluent metadata helper.
|
|
1339
|
+
*
|
|
1340
|
+
* @param meta Metadata object or metadata factory
|
|
1341
|
+
* @returns
|
|
1342
|
+
*/
|
|
1343
|
+
withMeta(meta) {
|
|
1344
|
+
this.with(meta);
|
|
1345
|
+
return this;
|
|
1346
|
+
}
|
|
1347
|
+
/**
|
|
618
1348
|
* Flatten resource to array format (for collections) or return original data for single resources
|
|
619
1349
|
*
|
|
620
1350
|
* @returns
|
|
@@ -635,8 +1365,9 @@ var Resource = class {
|
|
|
635
1365
|
additional(extra) {
|
|
636
1366
|
this.called.additional = true;
|
|
637
1367
|
this.json();
|
|
638
|
-
|
|
639
|
-
|
|
1368
|
+
const payloadKey = this.getPayloadKey();
|
|
1369
|
+
if (extra.data && payloadKey && typeof this.body[payloadKey] !== "undefined") this.body[payloadKey] = Array.isArray(this.body[payloadKey]) ? [...this.body[payloadKey], ...extra.data] : {
|
|
1370
|
+
...this.body[payloadKey],
|
|
640
1371
|
...extra.data
|
|
641
1372
|
};
|
|
642
1373
|
this.body = {
|
|
@@ -647,7 +1378,24 @@ var Resource = class {
|
|
|
647
1378
|
}
|
|
648
1379
|
response(res) {
|
|
649
1380
|
this.called.toResponse = true;
|
|
650
|
-
|
|
1381
|
+
this.json();
|
|
1382
|
+
const rawResponse = res ?? this.res;
|
|
1383
|
+
const response = new ServerResponse(rawResponse, this.body);
|
|
1384
|
+
this.withResponseContext = {
|
|
1385
|
+
response,
|
|
1386
|
+
raw: rawResponse
|
|
1387
|
+
};
|
|
1388
|
+
this.called.withResponse = true;
|
|
1389
|
+
this.withResponse(response, rawResponse);
|
|
1390
|
+
return response;
|
|
1391
|
+
}
|
|
1392
|
+
/**
|
|
1393
|
+
* Customize the outgoing transport response right before dispatch.
|
|
1394
|
+
*
|
|
1395
|
+
* Override in custom classes to mutate headers/status/body.
|
|
1396
|
+
*/
|
|
1397
|
+
withResponse(_response, _rawResponse) {
|
|
1398
|
+
return this;
|
|
651
1399
|
}
|
|
652
1400
|
/**
|
|
653
1401
|
* Promise-like then method to allow chaining with async/await or .then() syntax
|
|
@@ -659,6 +1407,18 @@ var Resource = class {
|
|
|
659
1407
|
then(onfulfilled, onrejected) {
|
|
660
1408
|
this.called.then = true;
|
|
661
1409
|
this.json();
|
|
1410
|
+
if (this.res) {
|
|
1411
|
+
const response = new ServerResponse(this.res, this.body);
|
|
1412
|
+
this.withResponseContext = {
|
|
1413
|
+
response,
|
|
1414
|
+
raw: this.res
|
|
1415
|
+
};
|
|
1416
|
+
this.called.withResponse = true;
|
|
1417
|
+
this.withResponse(response, this.res);
|
|
1418
|
+
} else {
|
|
1419
|
+
this.called.withResponse = true;
|
|
1420
|
+
this.withResponse();
|
|
1421
|
+
}
|
|
662
1422
|
const resolved = Promise.resolve(this.body).then(onfulfilled, onrejected);
|
|
663
1423
|
if (this.res) this.res.send(this.body);
|
|
664
1424
|
return resolved;
|
|
@@ -684,4 +1444,4 @@ var Resource = class {
|
|
|
684
1444
|
};
|
|
685
1445
|
|
|
686
1446
|
//#endregion
|
|
687
|
-
export { ApiResource, CliApp, GenericResource, MakeResource, Resource, ResourceCollection, ServerResponse, defineConfig, getDefaultConfig };
|
|
1447
|
+
export { ApiResource, CONDITIONAL_ATTRIBUTE_MISSING, CliApp, GenericResource, InitCommand, MakeResource, Resource, ResourceCollection, ServerResponse, appendRootProperties, buildPaginationExtras, buildResponseEnvelope, defineConfig, getCaseTransformer, getDefaultConfig, getGlobalBaseUrl, getGlobalCase, getGlobalCursorMeta, getGlobalPageName, getGlobalPaginatedExtras, getGlobalPaginatedLinks, getGlobalPaginatedMeta, getGlobalResponseFactory, getGlobalResponseRootKey, getGlobalResponseStructure, getGlobalResponseWrap, getPaginationExtraKeys, isPlainObject, mergeMetadata, resolveMergeWhen, resolveWhen, resolveWhenNotNull, resolveWithHookMetadata, sanitizeConditionalAttributes, setGlobalBaseUrl, setGlobalCase, setGlobalCursorMeta, setGlobalPageName, setGlobalPaginatedExtras, setGlobalPaginatedLinks, setGlobalPaginatedMeta, setGlobalResponseFactory, setGlobalResponseRootKey, setGlobalResponseStructure, setGlobalResponseWrap, splitWords, toCamelCase, toKebabCase, toPascalCase, toSnakeCase, transformKeys };
|