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.cjs
CHANGED
|
@@ -43,14 +43,347 @@ function ApiResource(instance) {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
//#endregion
|
|
46
|
-
//#region src/
|
|
46
|
+
//#region src/utilities/state.ts
|
|
47
|
+
let globalPreferredCase;
|
|
48
|
+
let globalResponseStructure;
|
|
49
|
+
let globalPaginatedExtras = ["meta", "links"];
|
|
50
|
+
let globalPaginatedLinks = {
|
|
51
|
+
first: "first",
|
|
52
|
+
last: "last",
|
|
53
|
+
prev: "prev",
|
|
54
|
+
next: "next"
|
|
55
|
+
};
|
|
56
|
+
let globalBaseUrl = "https://localhost";
|
|
57
|
+
let globalPageName = "page";
|
|
58
|
+
let globalPaginatedMeta = {
|
|
59
|
+
to: "to",
|
|
60
|
+
from: "from",
|
|
61
|
+
links: "links",
|
|
62
|
+
path: "path",
|
|
63
|
+
total: "total",
|
|
64
|
+
per_page: "per_page",
|
|
65
|
+
last_page: "last_page",
|
|
66
|
+
current_page: "current_page"
|
|
67
|
+
};
|
|
68
|
+
let globalCursorMeta = {
|
|
69
|
+
previous: "previous",
|
|
70
|
+
next: "next"
|
|
71
|
+
};
|
|
72
|
+
const setGlobalCase = (style) => {
|
|
73
|
+
globalPreferredCase = style;
|
|
74
|
+
};
|
|
75
|
+
const getGlobalCase = () => {
|
|
76
|
+
return globalPreferredCase;
|
|
77
|
+
};
|
|
78
|
+
const setGlobalResponseStructure = (config) => {
|
|
79
|
+
globalResponseStructure = config;
|
|
80
|
+
};
|
|
81
|
+
const getGlobalResponseStructure = () => {
|
|
82
|
+
return globalResponseStructure;
|
|
83
|
+
};
|
|
84
|
+
const setGlobalResponseRootKey = (rootKey) => {
|
|
85
|
+
globalResponseStructure = {
|
|
86
|
+
...globalResponseStructure || {},
|
|
87
|
+
rootKey
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
const setGlobalResponseWrap = (wrap) => {
|
|
91
|
+
globalResponseStructure = {
|
|
92
|
+
...globalResponseStructure || {},
|
|
93
|
+
wrap
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
const getGlobalResponseWrap = () => {
|
|
97
|
+
return globalResponseStructure?.wrap;
|
|
98
|
+
};
|
|
99
|
+
const getGlobalResponseRootKey = () => {
|
|
100
|
+
return globalResponseStructure?.rootKey;
|
|
101
|
+
};
|
|
102
|
+
const setGlobalResponseFactory = (factory) => {
|
|
103
|
+
globalResponseStructure = {
|
|
104
|
+
...globalResponseStructure || {},
|
|
105
|
+
factory
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
const getGlobalResponseFactory = () => {
|
|
109
|
+
return globalResponseStructure?.factory;
|
|
110
|
+
};
|
|
111
|
+
const setGlobalPaginatedExtras = (extras) => {
|
|
112
|
+
globalPaginatedExtras = extras;
|
|
113
|
+
};
|
|
114
|
+
const getGlobalPaginatedExtras = () => {
|
|
115
|
+
return globalPaginatedExtras;
|
|
116
|
+
};
|
|
117
|
+
const setGlobalPaginatedLinks = (links) => {
|
|
118
|
+
globalPaginatedLinks = {
|
|
119
|
+
...globalPaginatedLinks,
|
|
120
|
+
...links
|
|
121
|
+
};
|
|
122
|
+
};
|
|
123
|
+
const getGlobalPaginatedLinks = () => {
|
|
124
|
+
return globalPaginatedLinks;
|
|
125
|
+
};
|
|
126
|
+
const setGlobalBaseUrl = (baseUrl) => {
|
|
127
|
+
globalBaseUrl = baseUrl;
|
|
128
|
+
};
|
|
129
|
+
const getGlobalBaseUrl = () => {
|
|
130
|
+
return globalBaseUrl;
|
|
131
|
+
};
|
|
132
|
+
const setGlobalPageName = (pageName) => {
|
|
133
|
+
globalPageName = pageName;
|
|
134
|
+
};
|
|
135
|
+
const getGlobalPageName = () => {
|
|
136
|
+
return globalPageName;
|
|
137
|
+
};
|
|
138
|
+
const setGlobalPaginatedMeta = (meta) => {
|
|
139
|
+
globalPaginatedMeta = {
|
|
140
|
+
...globalPaginatedMeta,
|
|
141
|
+
...meta
|
|
142
|
+
};
|
|
143
|
+
};
|
|
144
|
+
const getGlobalPaginatedMeta = () => {
|
|
145
|
+
return globalPaginatedMeta;
|
|
146
|
+
};
|
|
147
|
+
const setGlobalCursorMeta = (meta) => {
|
|
148
|
+
globalCursorMeta = {
|
|
149
|
+
...globalCursorMeta,
|
|
150
|
+
...meta
|
|
151
|
+
};
|
|
152
|
+
};
|
|
153
|
+
const getGlobalCursorMeta = () => {
|
|
154
|
+
return globalCursorMeta;
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
//#endregion
|
|
158
|
+
//#region src/utilities/pagination.ts
|
|
159
|
+
const getPaginationExtraKeys = () => {
|
|
160
|
+
const extras = getGlobalPaginatedExtras();
|
|
161
|
+
if (Array.isArray(extras)) return {
|
|
162
|
+
metaKey: extras.includes("meta") ? "meta" : void 0,
|
|
163
|
+
linksKey: extras.includes("links") ? "links" : void 0,
|
|
164
|
+
cursorKey: extras.includes("cursor") ? "cursor" : void 0
|
|
165
|
+
};
|
|
166
|
+
return {
|
|
167
|
+
metaKey: extras.meta,
|
|
168
|
+
linksKey: extras.links,
|
|
169
|
+
cursorKey: extras.cursor
|
|
170
|
+
};
|
|
171
|
+
};
|
|
172
|
+
const buildPageUrl = (page, pathName) => {
|
|
173
|
+
if (typeof page === "undefined") return;
|
|
174
|
+
const rawPath = pathName || "";
|
|
175
|
+
const base = getGlobalBaseUrl() || "";
|
|
176
|
+
const isAbsolutePath = /^https?:\/\//i.test(rawPath);
|
|
177
|
+
const normalizedBase = base.replace(/\/$/, "");
|
|
178
|
+
const normalizedPath = rawPath.replace(/^\//, "");
|
|
179
|
+
const root = isAbsolutePath ? rawPath : normalizedBase ? normalizedPath ? `${normalizedBase}/${normalizedPath}` : normalizedBase : "";
|
|
180
|
+
if (!root) return;
|
|
181
|
+
const url = new URL(root);
|
|
182
|
+
url.searchParams.set(getGlobalPageName() || "page", String(page));
|
|
183
|
+
return url.toString();
|
|
184
|
+
};
|
|
185
|
+
const buildPaginationExtras = (resource) => {
|
|
186
|
+
const { metaKey, linksKey, cursorKey } = getPaginationExtraKeys();
|
|
187
|
+
const extra = {};
|
|
188
|
+
const pagination = resource?.pagination;
|
|
189
|
+
const cursor = resource?.cursor;
|
|
190
|
+
const metaBlock = {};
|
|
191
|
+
const linksBlock = {};
|
|
192
|
+
if (pagination) {
|
|
193
|
+
const metaSource = {
|
|
194
|
+
to: pagination.to,
|
|
195
|
+
from: pagination.from,
|
|
196
|
+
links: pagination.links,
|
|
197
|
+
path: pagination.path,
|
|
198
|
+
total: pagination.total,
|
|
199
|
+
per_page: pagination.perPage,
|
|
200
|
+
last_page: pagination.lastPage,
|
|
201
|
+
current_page: pagination.currentPage
|
|
202
|
+
};
|
|
203
|
+
for (const [sourceKey, outputKey] of Object.entries(getGlobalPaginatedMeta())) {
|
|
204
|
+
if (!outputKey) continue;
|
|
205
|
+
const value = metaSource[sourceKey];
|
|
206
|
+
if (typeof value !== "undefined") metaBlock[outputKey] = value;
|
|
207
|
+
}
|
|
208
|
+
const linksSource = {
|
|
209
|
+
first: buildPageUrl(pagination.firstPage, pagination.path),
|
|
210
|
+
last: buildPageUrl(pagination.lastPage, pagination.path),
|
|
211
|
+
prev: buildPageUrl(pagination.prevPage, pagination.path),
|
|
212
|
+
next: buildPageUrl(pagination.nextPage, pagination.path)
|
|
213
|
+
};
|
|
214
|
+
for (const [sourceKey, outputKey] of Object.entries(getGlobalPaginatedLinks())) {
|
|
215
|
+
if (!outputKey) continue;
|
|
216
|
+
const value = linksSource[sourceKey];
|
|
217
|
+
if (typeof value !== "undefined") linksBlock[outputKey] = value;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (cursor) {
|
|
221
|
+
const cursorBlock = {};
|
|
222
|
+
const cursorSource = {
|
|
223
|
+
previous: cursor.previous,
|
|
224
|
+
next: cursor.next
|
|
225
|
+
};
|
|
226
|
+
for (const [sourceKey, outputKey] of Object.entries(getGlobalCursorMeta())) {
|
|
227
|
+
if (!outputKey) continue;
|
|
228
|
+
const value = cursorSource[sourceKey];
|
|
229
|
+
if (typeof value !== "undefined") cursorBlock[outputKey] = value;
|
|
230
|
+
}
|
|
231
|
+
if (cursorKey && Object.keys(cursorBlock).length > 0) extra[cursorKey] = cursorBlock;
|
|
232
|
+
else if (Object.keys(cursorBlock).length > 0) metaBlock.cursor = cursorBlock;
|
|
233
|
+
}
|
|
234
|
+
if (metaKey && Object.keys(metaBlock).length > 0) extra[metaKey] = metaBlock;
|
|
235
|
+
if (linksKey && Object.keys(linksBlock).length > 0) extra[linksKey] = linksBlock;
|
|
236
|
+
return extra;
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
//#endregion
|
|
240
|
+
//#region src/utilities/objects.ts
|
|
241
|
+
const isPlainObject = (value) => {
|
|
242
|
+
if (typeof value !== "object" || value === null) return false;
|
|
243
|
+
if (Array.isArray(value) || value instanceof Date || value instanceof RegExp) return false;
|
|
244
|
+
const proto = Object.getPrototypeOf(value);
|
|
245
|
+
return proto === Object.prototype || proto === null;
|
|
246
|
+
};
|
|
247
|
+
const appendRootProperties = (body, extra, rootKey = "data") => {
|
|
248
|
+
if (!extra || Object.keys(extra).length === 0) return body;
|
|
249
|
+
if (Array.isArray(body)) return {
|
|
250
|
+
[rootKey]: body,
|
|
251
|
+
...extra
|
|
252
|
+
};
|
|
253
|
+
if (isPlainObject(body)) return {
|
|
254
|
+
...body,
|
|
255
|
+
...extra
|
|
256
|
+
};
|
|
257
|
+
return {
|
|
258
|
+
[rootKey]: body,
|
|
259
|
+
...extra
|
|
260
|
+
};
|
|
261
|
+
};
|
|
262
|
+
const mergeMetadata = (base, incoming) => {
|
|
263
|
+
if (!incoming) return base;
|
|
264
|
+
if (!base) return incoming;
|
|
265
|
+
const merged = { ...base };
|
|
266
|
+
for (const [key, value] of Object.entries(incoming)) {
|
|
267
|
+
const existing = merged[key];
|
|
268
|
+
if (isPlainObject(existing) && isPlainObject(value)) merged[key] = mergeMetadata(existing, value);
|
|
269
|
+
else merged[key] = value;
|
|
270
|
+
}
|
|
271
|
+
return merged;
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
//#endregion
|
|
275
|
+
//#region src/utilities/response.ts
|
|
276
|
+
const buildResponseEnvelope = ({ payload, meta, metaKey = "meta", wrap = true, rootKey = "data", factory, context }) => {
|
|
277
|
+
if (factory) return factory(payload, {
|
|
278
|
+
...context,
|
|
279
|
+
rootKey,
|
|
280
|
+
meta
|
|
281
|
+
});
|
|
282
|
+
if (!wrap) {
|
|
283
|
+
if (typeof meta === "undefined") return payload;
|
|
284
|
+
if (isPlainObject(payload)) return {
|
|
285
|
+
...payload,
|
|
286
|
+
[metaKey]: meta
|
|
287
|
+
};
|
|
288
|
+
return {
|
|
289
|
+
[rootKey]: payload,
|
|
290
|
+
[metaKey]: meta
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
const body = { [rootKey]: payload };
|
|
294
|
+
if (typeof meta !== "undefined") body[metaKey] = meta;
|
|
295
|
+
return body;
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
//#endregion
|
|
299
|
+
//#region src/utilities/metadata.ts
|
|
300
|
+
const resolveWithHookMetadata = (instance, baseWithMethod) => {
|
|
301
|
+
const candidate = instance?.with;
|
|
302
|
+
if (typeof candidate !== "function" || candidate === baseWithMethod) return;
|
|
303
|
+
if (candidate.length > 0) return;
|
|
304
|
+
const result = candidate.call(instance);
|
|
305
|
+
return isPlainObject(result) ? result : void 0;
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
//#endregion
|
|
309
|
+
//#region src/utilities/conditional.ts
|
|
310
|
+
const CONDITIONAL_ATTRIBUTE_MISSING = Symbol("resora.conditional.missing");
|
|
311
|
+
const resolveWhen = (condition, value) => {
|
|
312
|
+
if (!condition) return CONDITIONAL_ATTRIBUTE_MISSING;
|
|
313
|
+
return typeof value === "function" ? value() : value;
|
|
314
|
+
};
|
|
315
|
+
const resolveWhenNotNull = (value) => {
|
|
316
|
+
return value === null || typeof value === "undefined" ? CONDITIONAL_ATTRIBUTE_MISSING : value;
|
|
317
|
+
};
|
|
318
|
+
const resolveMergeWhen = (condition, value) => {
|
|
319
|
+
if (!condition) return {};
|
|
320
|
+
const resolved = typeof value === "function" ? value() : value;
|
|
321
|
+
return isPlainObject(resolved) ? resolved : {};
|
|
322
|
+
};
|
|
323
|
+
const sanitizeConditionalAttributes = (value) => {
|
|
324
|
+
if (value === CONDITIONAL_ATTRIBUTE_MISSING) return CONDITIONAL_ATTRIBUTE_MISSING;
|
|
325
|
+
if (Array.isArray(value)) return value.map((item) => sanitizeConditionalAttributes(item)).filter((item) => item !== CONDITIONAL_ATTRIBUTE_MISSING);
|
|
326
|
+
if (isPlainObject(value)) {
|
|
327
|
+
const result = {};
|
|
328
|
+
for (const [key, nestedValue] of Object.entries(value)) {
|
|
329
|
+
const sanitizedValue = sanitizeConditionalAttributes(nestedValue);
|
|
330
|
+
if (sanitizedValue === CONDITIONAL_ATTRIBUTE_MISSING) continue;
|
|
331
|
+
result[key] = sanitizedValue;
|
|
332
|
+
}
|
|
333
|
+
return result;
|
|
334
|
+
}
|
|
335
|
+
return value;
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
//#endregion
|
|
339
|
+
//#region src/utilities/case.ts
|
|
340
|
+
const splitWords = (str) => {
|
|
341
|
+
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);
|
|
342
|
+
};
|
|
343
|
+
const toCamelCase = (str) => {
|
|
344
|
+
return splitWords(str).map((w, i) => i === 0 ? w : w.charAt(0).toUpperCase() + w.slice(1)).join("");
|
|
345
|
+
};
|
|
346
|
+
const toSnakeCase = (str) => {
|
|
347
|
+
return splitWords(str).join("_");
|
|
348
|
+
};
|
|
349
|
+
const toPascalCase = (str) => {
|
|
350
|
+
return splitWords(str).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
|
|
351
|
+
};
|
|
352
|
+
const toKebabCase = (str) => {
|
|
353
|
+
return splitWords(str).join("-");
|
|
354
|
+
};
|
|
355
|
+
const getCaseTransformer = (style) => {
|
|
356
|
+
if (typeof style === "function") return style;
|
|
357
|
+
switch (style) {
|
|
358
|
+
case "camel": return toCamelCase;
|
|
359
|
+
case "snake": return toSnakeCase;
|
|
360
|
+
case "pascal": return toPascalCase;
|
|
361
|
+
case "kebab": return toKebabCase;
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
const transformKeys = (obj, transformer) => {
|
|
365
|
+
if (obj === null || obj === void 0) return obj;
|
|
366
|
+
if (Array.isArray(obj)) return obj.map((item) => transformKeys(item, transformer));
|
|
367
|
+
if (obj instanceof Date || obj instanceof RegExp) return obj;
|
|
368
|
+
if (typeof obj === "object") return Object.fromEntries(Object.entries(obj).map(([key, value]) => [transformer(key), transformKeys(value, transformer)]));
|
|
369
|
+
return obj;
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
//#endregion
|
|
373
|
+
//#region src/utilities/config.ts
|
|
47
374
|
let stubsDir = path.default.resolve(process.cwd(), "node_modules/resora/stubs");
|
|
48
375
|
if (!(0, fs.existsSync)(stubsDir)) stubsDir = path.default.resolve(process.cwd(), "stubs");
|
|
49
376
|
const getDefaultConfig = () => {
|
|
50
377
|
return {
|
|
51
378
|
stubsDir,
|
|
52
379
|
preferredCase: "camel",
|
|
380
|
+
responseStructure: {
|
|
381
|
+
wrap: true,
|
|
382
|
+
rootKey: "data"
|
|
383
|
+
},
|
|
53
384
|
paginatedExtras: ["meta", "links"],
|
|
385
|
+
baseUrl: "https://localhost",
|
|
386
|
+
pageName: "page",
|
|
54
387
|
paginatedLinks: {
|
|
55
388
|
first: "first",
|
|
56
389
|
last: "last",
|
|
@@ -67,6 +400,10 @@ const getDefaultConfig = () => {
|
|
|
67
400
|
last_page: "last_page",
|
|
68
401
|
current_page: "current_page"
|
|
69
402
|
},
|
|
403
|
+
cursorMeta: {
|
|
404
|
+
previous: "previous",
|
|
405
|
+
next: "next"
|
|
406
|
+
},
|
|
70
407
|
resourcesDir: "src/resources",
|
|
71
408
|
stubs: {
|
|
72
409
|
config: "resora.config.stub",
|
|
@@ -75,12 +412,6 @@ const getDefaultConfig = () => {
|
|
|
75
412
|
}
|
|
76
413
|
};
|
|
77
414
|
};
|
|
78
|
-
/**
|
|
79
|
-
* Define the configuration for the package
|
|
80
|
-
*
|
|
81
|
-
* @param userConfig The user configuration to override the default configuration
|
|
82
|
-
* @returns The merged configuration object
|
|
83
|
-
*/
|
|
84
415
|
const defineConfig = (userConfig = {}) => {
|
|
85
416
|
const defaultConfig = getDefaultConfig();
|
|
86
417
|
return Object.assign(defaultConfig, userConfig, { stubs: Object.assign(defaultConfig.stubs, userConfig.stubs || {}) });
|
|
@@ -111,6 +442,13 @@ var CliApp = class {
|
|
|
111
442
|
return this;
|
|
112
443
|
}
|
|
113
444
|
/**
|
|
445
|
+
* Get the current configuration object
|
|
446
|
+
* @returns
|
|
447
|
+
*/
|
|
448
|
+
getConfig() {
|
|
449
|
+
return this.config;
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
114
452
|
* Initialize Resora by creating a default config file in the current directory
|
|
115
453
|
*
|
|
116
454
|
* @returns
|
|
@@ -192,6 +530,20 @@ var CliApp = class {
|
|
|
192
530
|
}
|
|
193
531
|
};
|
|
194
532
|
|
|
533
|
+
//#endregion
|
|
534
|
+
//#region src/cli/commands/InitCommand.ts
|
|
535
|
+
var InitCommand = class extends _h3ravel_musket.Command {
|
|
536
|
+
signature = `init
|
|
537
|
+
{--force : Force overwrite if config file already exists (existing file will be backed up) }
|
|
538
|
+
`;
|
|
539
|
+
description = "Initialize Resora";
|
|
540
|
+
async handle() {
|
|
541
|
+
this.app.command = this;
|
|
542
|
+
this.app.init();
|
|
543
|
+
this.success("Resora initialized");
|
|
544
|
+
}
|
|
545
|
+
};
|
|
546
|
+
|
|
195
547
|
//#endregion
|
|
196
548
|
//#region src/cli/commands/MakeResource.ts
|
|
197
549
|
var MakeResource = class extends _h3ravel_musket.Command {
|
|
@@ -369,10 +721,21 @@ var ServerResponse = class {
|
|
|
369
721
|
/**
|
|
370
722
|
* GenericResource class to handle API resource transformation and response building
|
|
371
723
|
*/
|
|
372
|
-
var GenericResource = class {
|
|
724
|
+
var GenericResource = class GenericResource {
|
|
373
725
|
body = { data: {} };
|
|
374
726
|
resource;
|
|
375
727
|
collects;
|
|
728
|
+
additionalMeta;
|
|
729
|
+
withResponseContext;
|
|
730
|
+
/**
|
|
731
|
+
* Preferred case style for this resource's output keys.
|
|
732
|
+
* Set on a subclass to override the global default.
|
|
733
|
+
*/
|
|
734
|
+
static preferredCase;
|
|
735
|
+
/**
|
|
736
|
+
* Response structure override for this generic resource class.
|
|
737
|
+
*/
|
|
738
|
+
static responseStructure;
|
|
376
739
|
called = {};
|
|
377
740
|
constructor(rsc, res) {
|
|
378
741
|
this.res = res;
|
|
@@ -402,6 +765,52 @@ var GenericResource = class {
|
|
|
402
765
|
return this.resource;
|
|
403
766
|
}
|
|
404
767
|
/**
|
|
768
|
+
* Get the current serialized output body.
|
|
769
|
+
*/
|
|
770
|
+
getBody() {
|
|
771
|
+
this.json();
|
|
772
|
+
return this.body;
|
|
773
|
+
}
|
|
774
|
+
/**
|
|
775
|
+
* Replace the current serialized output body.
|
|
776
|
+
*/
|
|
777
|
+
setBody(body) {
|
|
778
|
+
this.body = body;
|
|
779
|
+
return this;
|
|
780
|
+
}
|
|
781
|
+
/**
|
|
782
|
+
* Conditionally include a value in serialized output.
|
|
783
|
+
*/
|
|
784
|
+
when(condition, value) {
|
|
785
|
+
return resolveWhen(condition, value);
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* Include a value only when it is not null/undefined.
|
|
789
|
+
*/
|
|
790
|
+
whenNotNull(value) {
|
|
791
|
+
return resolveWhenNotNull(value);
|
|
792
|
+
}
|
|
793
|
+
/**
|
|
794
|
+
* Conditionally merge object attributes into serialized output.
|
|
795
|
+
*/
|
|
796
|
+
mergeWhen(condition, value) {
|
|
797
|
+
return resolveMergeWhen(condition, value);
|
|
798
|
+
}
|
|
799
|
+
resolveResponseStructure() {
|
|
800
|
+
const local = this.constructor.responseStructure;
|
|
801
|
+
const collectsLocal = this.collects?.responseStructure;
|
|
802
|
+
const global = getGlobalResponseStructure();
|
|
803
|
+
return {
|
|
804
|
+
wrap: local?.wrap ?? collectsLocal?.wrap ?? global?.wrap ?? true,
|
|
805
|
+
rootKey: local?.rootKey ?? collectsLocal?.rootKey ?? global?.rootKey ?? "data",
|
|
806
|
+
factory: local?.factory ?? collectsLocal?.factory ?? global?.factory
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
getPayloadKey() {
|
|
810
|
+
const { wrap, rootKey, factory } = this.resolveResponseStructure();
|
|
811
|
+
return factory || !wrap ? void 0 : rootKey;
|
|
812
|
+
}
|
|
813
|
+
/**
|
|
405
814
|
* Convert resource to JSON response format
|
|
406
815
|
*
|
|
407
816
|
* @returns
|
|
@@ -416,13 +825,65 @@ var GenericResource = class {
|
|
|
416
825
|
this.resource = data;
|
|
417
826
|
}
|
|
418
827
|
if (typeof data.data !== "undefined") data = data.data;
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
828
|
+
data = sanitizeConditionalAttributes(data);
|
|
829
|
+
const paginationExtras = buildPaginationExtras(this.resource);
|
|
830
|
+
const { metaKey } = getPaginationExtraKeys();
|
|
831
|
+
const configuredMeta = metaKey ? paginationExtras[metaKey] : void 0;
|
|
832
|
+
if (metaKey) delete paginationExtras[metaKey];
|
|
833
|
+
const caseStyle = this.constructor.preferredCase ?? getGlobalCase();
|
|
834
|
+
if (caseStyle) {
|
|
835
|
+
const transformer = getCaseTransformer(caseStyle);
|
|
836
|
+
data = transformKeys(data, transformer);
|
|
837
|
+
}
|
|
838
|
+
const customMeta = mergeMetadata(resolveWithHookMetadata(this, GenericResource.prototype.with), this.additionalMeta);
|
|
839
|
+
const { wrap, rootKey, factory } = this.resolveResponseStructure();
|
|
840
|
+
this.body = buildResponseEnvelope({
|
|
841
|
+
payload: data,
|
|
842
|
+
meta: configuredMeta,
|
|
843
|
+
metaKey,
|
|
844
|
+
wrap,
|
|
845
|
+
rootKey,
|
|
846
|
+
factory,
|
|
847
|
+
context: {
|
|
848
|
+
type: "generic",
|
|
849
|
+
resource: this.resource
|
|
850
|
+
}
|
|
851
|
+
});
|
|
852
|
+
this.body = appendRootProperties(this.body, {
|
|
853
|
+
...paginationExtras,
|
|
854
|
+
...customMeta || {}
|
|
855
|
+
}, rootKey);
|
|
856
|
+
}
|
|
857
|
+
return this;
|
|
858
|
+
}
|
|
859
|
+
/**
|
|
860
|
+
* Append structured metadata to the response body.
|
|
861
|
+
*
|
|
862
|
+
* @param meta Metadata object or metadata factory
|
|
863
|
+
* @returns
|
|
864
|
+
*/
|
|
865
|
+
with(meta) {
|
|
866
|
+
this.called.with = true;
|
|
867
|
+
if (typeof meta === "undefined") return this.additionalMeta || {};
|
|
868
|
+
const resolvedMeta = typeof meta === "function" ? meta(this.resource) : meta;
|
|
869
|
+
this.additionalMeta = mergeMetadata(this.additionalMeta, resolvedMeta);
|
|
870
|
+
if (this.called.json) {
|
|
871
|
+
const { rootKey } = this.resolveResponseStructure();
|
|
872
|
+
this.body = appendRootProperties(this.body, resolvedMeta, rootKey);
|
|
422
873
|
}
|
|
423
874
|
return this;
|
|
424
875
|
}
|
|
425
876
|
/**
|
|
877
|
+
* Typed fluent metadata helper.
|
|
878
|
+
*
|
|
879
|
+
* @param meta Metadata object or metadata factory
|
|
880
|
+
* @returns
|
|
881
|
+
*/
|
|
882
|
+
withMeta(meta) {
|
|
883
|
+
this.with(meta);
|
|
884
|
+
return this;
|
|
885
|
+
}
|
|
886
|
+
/**
|
|
426
887
|
* Convert resource to array format (for collections)
|
|
427
888
|
*
|
|
428
889
|
* @returns
|
|
@@ -443,8 +904,14 @@ var GenericResource = class {
|
|
|
443
904
|
additional(extra) {
|
|
444
905
|
this.called.additional = true;
|
|
445
906
|
this.json();
|
|
907
|
+
const extraData = extra.data;
|
|
446
908
|
delete extra.data;
|
|
447
909
|
delete extra.pagination;
|
|
910
|
+
const payloadKey = this.getPayloadKey();
|
|
911
|
+
if (extraData && payloadKey && typeof this.body[payloadKey] !== "undefined") this.body[payloadKey] = Array.isArray(this.body[payloadKey]) ? [...this.body[payloadKey], ...extraData] : {
|
|
912
|
+
...this.body[payloadKey],
|
|
913
|
+
...extraData
|
|
914
|
+
};
|
|
448
915
|
this.body = {
|
|
449
916
|
...this.body,
|
|
450
917
|
...extra
|
|
@@ -453,7 +920,24 @@ var GenericResource = class {
|
|
|
453
920
|
}
|
|
454
921
|
response(res) {
|
|
455
922
|
this.called.toResponse = true;
|
|
456
|
-
|
|
923
|
+
this.json();
|
|
924
|
+
const rawResponse = res ?? this.res;
|
|
925
|
+
const response = new ServerResponse(rawResponse, this.body);
|
|
926
|
+
this.withResponseContext = {
|
|
927
|
+
response,
|
|
928
|
+
raw: rawResponse
|
|
929
|
+
};
|
|
930
|
+
this.called.withResponse = true;
|
|
931
|
+
this.withResponse(response, rawResponse);
|
|
932
|
+
return response;
|
|
933
|
+
}
|
|
934
|
+
/**
|
|
935
|
+
* Customize the outgoing transport response right before dispatch.
|
|
936
|
+
*
|
|
937
|
+
* Override in custom classes to mutate headers/status/body.
|
|
938
|
+
*/
|
|
939
|
+
withResponse(_response, _rawResponse) {
|
|
940
|
+
return this;
|
|
457
941
|
}
|
|
458
942
|
/**
|
|
459
943
|
* Promise-like then method to allow chaining with async/await or .then() syntax
|
|
@@ -465,6 +949,18 @@ var GenericResource = class {
|
|
|
465
949
|
then(onfulfilled, onrejected) {
|
|
466
950
|
this.called.then = true;
|
|
467
951
|
this.json();
|
|
952
|
+
if (this.res) {
|
|
953
|
+
const response = new ServerResponse(this.res, this.body);
|
|
954
|
+
this.withResponseContext = {
|
|
955
|
+
response,
|
|
956
|
+
raw: this.res
|
|
957
|
+
};
|
|
958
|
+
this.called.withResponse = true;
|
|
959
|
+
this.withResponse(response, this.res);
|
|
960
|
+
} else {
|
|
961
|
+
this.called.withResponse = true;
|
|
962
|
+
this.withResponse();
|
|
963
|
+
}
|
|
468
964
|
const resolved = Promise.resolve(this.body).then(onfulfilled, onrejected);
|
|
469
965
|
if (this.res) this.res.send(this.body);
|
|
470
966
|
return resolved;
|
|
@@ -476,10 +972,21 @@ var GenericResource = class {
|
|
|
476
972
|
/**
|
|
477
973
|
* ResourceCollection class to handle API resource transformation and response building for collections
|
|
478
974
|
*/
|
|
479
|
-
var ResourceCollection = class {
|
|
975
|
+
var ResourceCollection = class ResourceCollection {
|
|
480
976
|
body = { data: [] };
|
|
481
977
|
resource;
|
|
482
978
|
collects;
|
|
979
|
+
additionalMeta;
|
|
980
|
+
withResponseContext;
|
|
981
|
+
/**
|
|
982
|
+
* Preferred case style for this collection's output keys.
|
|
983
|
+
* Set on a subclass to override the global default.
|
|
984
|
+
*/
|
|
985
|
+
static preferredCase;
|
|
986
|
+
/**
|
|
987
|
+
* Response structure override for this collection class.
|
|
988
|
+
*/
|
|
989
|
+
static responseStructure;
|
|
483
990
|
called = {};
|
|
484
991
|
constructor(rsc, res) {
|
|
485
992
|
this.res = res;
|
|
@@ -492,6 +999,52 @@ var ResourceCollection = class {
|
|
|
492
999
|
return this.toArray();
|
|
493
1000
|
}
|
|
494
1001
|
/**
|
|
1002
|
+
* Get the current serialized output body.
|
|
1003
|
+
*/
|
|
1004
|
+
getBody() {
|
|
1005
|
+
this.json();
|
|
1006
|
+
return this.body;
|
|
1007
|
+
}
|
|
1008
|
+
/**
|
|
1009
|
+
* Replace the current serialized output body.
|
|
1010
|
+
*/
|
|
1011
|
+
setBody(body) {
|
|
1012
|
+
this.body = body;
|
|
1013
|
+
return this;
|
|
1014
|
+
}
|
|
1015
|
+
/**
|
|
1016
|
+
* Conditionally include a value in serialized output.
|
|
1017
|
+
*/
|
|
1018
|
+
when(condition, value) {
|
|
1019
|
+
return resolveWhen(condition, value);
|
|
1020
|
+
}
|
|
1021
|
+
/**
|
|
1022
|
+
* Include a value only when it is not null/undefined.
|
|
1023
|
+
*/
|
|
1024
|
+
whenNotNull(value) {
|
|
1025
|
+
return resolveWhenNotNull(value);
|
|
1026
|
+
}
|
|
1027
|
+
/**
|
|
1028
|
+
* Conditionally merge object attributes into serialized output.
|
|
1029
|
+
*/
|
|
1030
|
+
mergeWhen(condition, value) {
|
|
1031
|
+
return resolveMergeWhen(condition, value);
|
|
1032
|
+
}
|
|
1033
|
+
resolveResponseStructure() {
|
|
1034
|
+
const local = this.constructor.responseStructure;
|
|
1035
|
+
const collectsLocal = this.collects?.responseStructure;
|
|
1036
|
+
const global = getGlobalResponseStructure();
|
|
1037
|
+
return {
|
|
1038
|
+
wrap: local?.wrap ?? collectsLocal?.wrap ?? global?.wrap ?? true,
|
|
1039
|
+
rootKey: local?.rootKey ?? collectsLocal?.rootKey ?? global?.rootKey ?? "data",
|
|
1040
|
+
factory: local?.factory ?? collectsLocal?.factory ?? global?.factory
|
|
1041
|
+
};
|
|
1042
|
+
}
|
|
1043
|
+
getPayloadKey() {
|
|
1044
|
+
const { wrap, rootKey, factory } = this.resolveResponseStructure();
|
|
1045
|
+
return factory || !wrap ? void 0 : rootKey;
|
|
1046
|
+
}
|
|
1047
|
+
/**
|
|
495
1048
|
* Convert resource to JSON response format
|
|
496
1049
|
*
|
|
497
1050
|
* @returns
|
|
@@ -501,19 +1054,65 @@ var ResourceCollection = class {
|
|
|
501
1054
|
this.called.json = true;
|
|
502
1055
|
let data = this.data();
|
|
503
1056
|
if (this.collects) data = data.map((item) => new this.collects(item).data());
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
1057
|
+
data = sanitizeConditionalAttributes(data);
|
|
1058
|
+
const paginationExtras = !Array.isArray(this.resource) ? buildPaginationExtras(this.resource) : {};
|
|
1059
|
+
const { metaKey } = getPaginationExtraKeys();
|
|
1060
|
+
const configuredMeta = metaKey ? paginationExtras[metaKey] : void 0;
|
|
1061
|
+
if (metaKey) delete paginationExtras[metaKey];
|
|
1062
|
+
const caseStyle = this.constructor.preferredCase ?? this.collects?.preferredCase ?? getGlobalCase();
|
|
1063
|
+
if (caseStyle) {
|
|
1064
|
+
const transformer = getCaseTransformer(caseStyle);
|
|
1065
|
+
data = transformKeys(data, transformer);
|
|
512
1066
|
}
|
|
1067
|
+
const customMeta = mergeMetadata(resolveWithHookMetadata(this, ResourceCollection.prototype.with), this.additionalMeta);
|
|
1068
|
+
const { wrap, rootKey, factory } = this.resolveResponseStructure();
|
|
1069
|
+
this.body = buildResponseEnvelope({
|
|
1070
|
+
payload: data,
|
|
1071
|
+
meta: configuredMeta,
|
|
1072
|
+
metaKey,
|
|
1073
|
+
wrap,
|
|
1074
|
+
rootKey,
|
|
1075
|
+
factory,
|
|
1076
|
+
context: {
|
|
1077
|
+
type: "collection",
|
|
1078
|
+
resource: this.resource
|
|
1079
|
+
}
|
|
1080
|
+
});
|
|
1081
|
+
this.body = appendRootProperties(this.body, {
|
|
1082
|
+
...paginationExtras,
|
|
1083
|
+
...customMeta || {}
|
|
1084
|
+
}, rootKey);
|
|
513
1085
|
}
|
|
514
1086
|
return this;
|
|
515
1087
|
}
|
|
516
1088
|
/**
|
|
1089
|
+
* Append structured metadata to the response body.
|
|
1090
|
+
*
|
|
1091
|
+
* @param meta Metadata object or metadata factory
|
|
1092
|
+
* @returns
|
|
1093
|
+
*/
|
|
1094
|
+
with(meta) {
|
|
1095
|
+
this.called.with = true;
|
|
1096
|
+
if (typeof meta === "undefined") return this.additionalMeta || {};
|
|
1097
|
+
const resolvedMeta = typeof meta === "function" ? meta(this.resource) : meta;
|
|
1098
|
+
this.additionalMeta = mergeMetadata(this.additionalMeta, resolvedMeta);
|
|
1099
|
+
if (this.called.json) {
|
|
1100
|
+
const { rootKey } = this.resolveResponseStructure();
|
|
1101
|
+
this.body = appendRootProperties(this.body, resolvedMeta, rootKey);
|
|
1102
|
+
}
|
|
1103
|
+
return this;
|
|
1104
|
+
}
|
|
1105
|
+
/**
|
|
1106
|
+
* Typed fluent metadata helper.
|
|
1107
|
+
*
|
|
1108
|
+
* @param meta Metadata object or metadata factory
|
|
1109
|
+
* @returns
|
|
1110
|
+
*/
|
|
1111
|
+
withMeta(meta) {
|
|
1112
|
+
this.with(meta);
|
|
1113
|
+
return this;
|
|
1114
|
+
}
|
|
1115
|
+
/**
|
|
517
1116
|
* Flatten resource to return original data
|
|
518
1117
|
*
|
|
519
1118
|
* @returns
|
|
@@ -534,7 +1133,8 @@ var ResourceCollection = class {
|
|
|
534
1133
|
this.json();
|
|
535
1134
|
delete extra.cursor;
|
|
536
1135
|
delete extra.pagination;
|
|
537
|
-
|
|
1136
|
+
const payloadKey = this.getPayloadKey();
|
|
1137
|
+
if (extra.data && payloadKey && Array.isArray(this.body[payloadKey])) this.body[payloadKey] = [...this.body[payloadKey], ...extra.data];
|
|
538
1138
|
this.body = {
|
|
539
1139
|
...this.body,
|
|
540
1140
|
...extra
|
|
@@ -543,7 +1143,24 @@ var ResourceCollection = class {
|
|
|
543
1143
|
}
|
|
544
1144
|
response(res) {
|
|
545
1145
|
this.called.toResponse = true;
|
|
546
|
-
|
|
1146
|
+
this.json();
|
|
1147
|
+
const rawResponse = res ?? this.res;
|
|
1148
|
+
const response = new ServerResponse(rawResponse, this.body);
|
|
1149
|
+
this.withResponseContext = {
|
|
1150
|
+
response,
|
|
1151
|
+
raw: rawResponse
|
|
1152
|
+
};
|
|
1153
|
+
this.called.withResponse = true;
|
|
1154
|
+
this.withResponse(response, rawResponse);
|
|
1155
|
+
return response;
|
|
1156
|
+
}
|
|
1157
|
+
/**
|
|
1158
|
+
* Customize the outgoing transport response right before dispatch.
|
|
1159
|
+
*
|
|
1160
|
+
* Override in custom classes to mutate headers/status/body.
|
|
1161
|
+
*/
|
|
1162
|
+
withResponse(_response, _rawResponse) {
|
|
1163
|
+
return this;
|
|
547
1164
|
}
|
|
548
1165
|
setCollects(collects) {
|
|
549
1166
|
this.collects = collects;
|
|
@@ -559,6 +1176,18 @@ var ResourceCollection = class {
|
|
|
559
1176
|
then(onfulfilled, onrejected) {
|
|
560
1177
|
this.called.then = true;
|
|
561
1178
|
this.json();
|
|
1179
|
+
if (this.res) {
|
|
1180
|
+
const response = new ServerResponse(this.res, this.body);
|
|
1181
|
+
this.withResponseContext = {
|
|
1182
|
+
response,
|
|
1183
|
+
raw: this.res
|
|
1184
|
+
};
|
|
1185
|
+
this.called.withResponse = true;
|
|
1186
|
+
this.withResponse(response, this.res);
|
|
1187
|
+
} else {
|
|
1188
|
+
this.called.withResponse = true;
|
|
1189
|
+
this.withResponse();
|
|
1190
|
+
}
|
|
562
1191
|
const resolved = Promise.resolve(this.body).then(onfulfilled, onrejected);
|
|
563
1192
|
if (this.res) this.res.send(this.body);
|
|
564
1193
|
return resolved;
|
|
@@ -588,9 +1217,20 @@ var ResourceCollection = class {
|
|
|
588
1217
|
/**
|
|
589
1218
|
* Resource class to handle API resource transformation and response building
|
|
590
1219
|
*/
|
|
591
|
-
var Resource = class {
|
|
1220
|
+
var Resource = class Resource {
|
|
592
1221
|
body = { data: {} };
|
|
593
1222
|
resource;
|
|
1223
|
+
additionalMeta;
|
|
1224
|
+
withResponseContext;
|
|
1225
|
+
/**
|
|
1226
|
+
* Preferred case style for this resource's output keys.
|
|
1227
|
+
* Set on a subclass to override the global default.
|
|
1228
|
+
*/
|
|
1229
|
+
static preferredCase;
|
|
1230
|
+
/**
|
|
1231
|
+
* Response structure override for this resource class.
|
|
1232
|
+
*/
|
|
1233
|
+
static responseStructure;
|
|
594
1234
|
called = {};
|
|
595
1235
|
constructor(rsc, res) {
|
|
596
1236
|
this.res = res;
|
|
@@ -629,6 +1269,51 @@ var Resource = class {
|
|
|
629
1269
|
return this.toArray();
|
|
630
1270
|
}
|
|
631
1271
|
/**
|
|
1272
|
+
* Get the current serialized output body.
|
|
1273
|
+
*/
|
|
1274
|
+
getBody() {
|
|
1275
|
+
this.json();
|
|
1276
|
+
return this.body;
|
|
1277
|
+
}
|
|
1278
|
+
/**
|
|
1279
|
+
* Replace the current serialized output body.
|
|
1280
|
+
*/
|
|
1281
|
+
setBody(body) {
|
|
1282
|
+
this.body = body;
|
|
1283
|
+
return this;
|
|
1284
|
+
}
|
|
1285
|
+
/**
|
|
1286
|
+
* Conditionally include a value in serialized output.
|
|
1287
|
+
*/
|
|
1288
|
+
when(condition, value) {
|
|
1289
|
+
return resolveWhen(condition, value);
|
|
1290
|
+
}
|
|
1291
|
+
/**
|
|
1292
|
+
* Include a value only when it is not null/undefined.
|
|
1293
|
+
*/
|
|
1294
|
+
whenNotNull(value) {
|
|
1295
|
+
return resolveWhenNotNull(value);
|
|
1296
|
+
}
|
|
1297
|
+
/**
|
|
1298
|
+
* Conditionally merge object attributes into serialized output.
|
|
1299
|
+
*/
|
|
1300
|
+
mergeWhen(condition, value) {
|
|
1301
|
+
return resolveMergeWhen(condition, value);
|
|
1302
|
+
}
|
|
1303
|
+
resolveResponseStructure() {
|
|
1304
|
+
const local = this.constructor.responseStructure;
|
|
1305
|
+
const global = getGlobalResponseStructure();
|
|
1306
|
+
return {
|
|
1307
|
+
wrap: local?.wrap ?? global?.wrap ?? true,
|
|
1308
|
+
rootKey: local?.rootKey ?? global?.rootKey ?? "data",
|
|
1309
|
+
factory: local?.factory ?? global?.factory
|
|
1310
|
+
};
|
|
1311
|
+
}
|
|
1312
|
+
getPayloadKey() {
|
|
1313
|
+
const { wrap, rootKey, factory } = this.resolveResponseStructure();
|
|
1314
|
+
return factory || !wrap ? void 0 : rootKey;
|
|
1315
|
+
}
|
|
1316
|
+
/**
|
|
632
1317
|
* Convert resource to JSON response format
|
|
633
1318
|
*
|
|
634
1319
|
* @returns
|
|
@@ -639,11 +1324,56 @@ var Resource = class {
|
|
|
639
1324
|
const resource = this.data();
|
|
640
1325
|
let data = Array.isArray(resource) ? [...resource] : { ...resource };
|
|
641
1326
|
if (typeof data.data !== "undefined") data = data.data;
|
|
642
|
-
|
|
1327
|
+
data = sanitizeConditionalAttributes(data);
|
|
1328
|
+
const caseStyle = this.constructor.preferredCase ?? getGlobalCase();
|
|
1329
|
+
if (caseStyle) {
|
|
1330
|
+
const transformer = getCaseTransformer(caseStyle);
|
|
1331
|
+
data = transformKeys(data, transformer);
|
|
1332
|
+
}
|
|
1333
|
+
const customMeta = mergeMetadata(resolveWithHookMetadata(this, Resource.prototype.with), this.additionalMeta);
|
|
1334
|
+
const { wrap, rootKey, factory } = this.resolveResponseStructure();
|
|
1335
|
+
this.body = buildResponseEnvelope({
|
|
1336
|
+
payload: data,
|
|
1337
|
+
wrap,
|
|
1338
|
+
rootKey,
|
|
1339
|
+
factory,
|
|
1340
|
+
context: {
|
|
1341
|
+
type: "resource",
|
|
1342
|
+
resource: this.resource
|
|
1343
|
+
}
|
|
1344
|
+
});
|
|
1345
|
+
this.body = appendRootProperties(this.body, customMeta, rootKey);
|
|
643
1346
|
}
|
|
644
1347
|
return this;
|
|
645
1348
|
}
|
|
646
1349
|
/**
|
|
1350
|
+
* Append structured metadata to the response body.
|
|
1351
|
+
*
|
|
1352
|
+
* @param meta Metadata object or metadata factory
|
|
1353
|
+
* @returns
|
|
1354
|
+
*/
|
|
1355
|
+
with(meta) {
|
|
1356
|
+
this.called.with = true;
|
|
1357
|
+
if (typeof meta === "undefined") return this.additionalMeta || {};
|
|
1358
|
+
const resolvedMeta = typeof meta === "function" ? meta(this.resource) : meta;
|
|
1359
|
+
this.additionalMeta = mergeMetadata(this.additionalMeta, resolvedMeta);
|
|
1360
|
+
if (this.called.json) {
|
|
1361
|
+
const { rootKey } = this.resolveResponseStructure();
|
|
1362
|
+
this.body = appendRootProperties(this.body, resolvedMeta, rootKey);
|
|
1363
|
+
}
|
|
1364
|
+
return this;
|
|
1365
|
+
}
|
|
1366
|
+
/**
|
|
1367
|
+
* Typed fluent metadata helper.
|
|
1368
|
+
*
|
|
1369
|
+
* @param meta Metadata object or metadata factory
|
|
1370
|
+
* @returns
|
|
1371
|
+
*/
|
|
1372
|
+
withMeta(meta) {
|
|
1373
|
+
this.with(meta);
|
|
1374
|
+
return this;
|
|
1375
|
+
}
|
|
1376
|
+
/**
|
|
647
1377
|
* Flatten resource to array format (for collections) or return original data for single resources
|
|
648
1378
|
*
|
|
649
1379
|
* @returns
|
|
@@ -664,8 +1394,9 @@ var Resource = class {
|
|
|
664
1394
|
additional(extra) {
|
|
665
1395
|
this.called.additional = true;
|
|
666
1396
|
this.json();
|
|
667
|
-
|
|
668
|
-
|
|
1397
|
+
const payloadKey = this.getPayloadKey();
|
|
1398
|
+
if (extra.data && payloadKey && typeof this.body[payloadKey] !== "undefined") this.body[payloadKey] = Array.isArray(this.body[payloadKey]) ? [...this.body[payloadKey], ...extra.data] : {
|
|
1399
|
+
...this.body[payloadKey],
|
|
669
1400
|
...extra.data
|
|
670
1401
|
};
|
|
671
1402
|
this.body = {
|
|
@@ -676,7 +1407,24 @@ var Resource = class {
|
|
|
676
1407
|
}
|
|
677
1408
|
response(res) {
|
|
678
1409
|
this.called.toResponse = true;
|
|
679
|
-
|
|
1410
|
+
this.json();
|
|
1411
|
+
const rawResponse = res ?? this.res;
|
|
1412
|
+
const response = new ServerResponse(rawResponse, this.body);
|
|
1413
|
+
this.withResponseContext = {
|
|
1414
|
+
response,
|
|
1415
|
+
raw: rawResponse
|
|
1416
|
+
};
|
|
1417
|
+
this.called.withResponse = true;
|
|
1418
|
+
this.withResponse(response, rawResponse);
|
|
1419
|
+
return response;
|
|
1420
|
+
}
|
|
1421
|
+
/**
|
|
1422
|
+
* Customize the outgoing transport response right before dispatch.
|
|
1423
|
+
*
|
|
1424
|
+
* Override in custom classes to mutate headers/status/body.
|
|
1425
|
+
*/
|
|
1426
|
+
withResponse(_response, _rawResponse) {
|
|
1427
|
+
return this;
|
|
680
1428
|
}
|
|
681
1429
|
/**
|
|
682
1430
|
* Promise-like then method to allow chaining with async/await or .then() syntax
|
|
@@ -688,6 +1436,18 @@ var Resource = class {
|
|
|
688
1436
|
then(onfulfilled, onrejected) {
|
|
689
1437
|
this.called.then = true;
|
|
690
1438
|
this.json();
|
|
1439
|
+
if (this.res) {
|
|
1440
|
+
const response = new ServerResponse(this.res, this.body);
|
|
1441
|
+
this.withResponseContext = {
|
|
1442
|
+
response,
|
|
1443
|
+
raw: this.res
|
|
1444
|
+
};
|
|
1445
|
+
this.called.withResponse = true;
|
|
1446
|
+
this.withResponse(response, this.res);
|
|
1447
|
+
} else {
|
|
1448
|
+
this.called.withResponse = true;
|
|
1449
|
+
this.withResponse();
|
|
1450
|
+
}
|
|
691
1451
|
const resolved = Promise.resolve(this.body).then(onfulfilled, onrejected);
|
|
692
1452
|
if (this.res) this.res.send(this.body);
|
|
693
1453
|
return resolved;
|
|
@@ -714,11 +1474,53 @@ var Resource = class {
|
|
|
714
1474
|
|
|
715
1475
|
//#endregion
|
|
716
1476
|
exports.ApiResource = ApiResource;
|
|
1477
|
+
exports.CONDITIONAL_ATTRIBUTE_MISSING = CONDITIONAL_ATTRIBUTE_MISSING;
|
|
717
1478
|
exports.CliApp = CliApp;
|
|
718
1479
|
exports.GenericResource = GenericResource;
|
|
1480
|
+
exports.InitCommand = InitCommand;
|
|
719
1481
|
exports.MakeResource = MakeResource;
|
|
720
1482
|
exports.Resource = Resource;
|
|
721
1483
|
exports.ResourceCollection = ResourceCollection;
|
|
722
1484
|
exports.ServerResponse = ServerResponse;
|
|
1485
|
+
exports.appendRootProperties = appendRootProperties;
|
|
1486
|
+
exports.buildPaginationExtras = buildPaginationExtras;
|
|
1487
|
+
exports.buildResponseEnvelope = buildResponseEnvelope;
|
|
723
1488
|
exports.defineConfig = defineConfig;
|
|
724
|
-
exports.
|
|
1489
|
+
exports.getCaseTransformer = getCaseTransformer;
|
|
1490
|
+
exports.getDefaultConfig = getDefaultConfig;
|
|
1491
|
+
exports.getGlobalBaseUrl = getGlobalBaseUrl;
|
|
1492
|
+
exports.getGlobalCase = getGlobalCase;
|
|
1493
|
+
exports.getGlobalCursorMeta = getGlobalCursorMeta;
|
|
1494
|
+
exports.getGlobalPageName = getGlobalPageName;
|
|
1495
|
+
exports.getGlobalPaginatedExtras = getGlobalPaginatedExtras;
|
|
1496
|
+
exports.getGlobalPaginatedLinks = getGlobalPaginatedLinks;
|
|
1497
|
+
exports.getGlobalPaginatedMeta = getGlobalPaginatedMeta;
|
|
1498
|
+
exports.getGlobalResponseFactory = getGlobalResponseFactory;
|
|
1499
|
+
exports.getGlobalResponseRootKey = getGlobalResponseRootKey;
|
|
1500
|
+
exports.getGlobalResponseStructure = getGlobalResponseStructure;
|
|
1501
|
+
exports.getGlobalResponseWrap = getGlobalResponseWrap;
|
|
1502
|
+
exports.getPaginationExtraKeys = getPaginationExtraKeys;
|
|
1503
|
+
exports.isPlainObject = isPlainObject;
|
|
1504
|
+
exports.mergeMetadata = mergeMetadata;
|
|
1505
|
+
exports.resolveMergeWhen = resolveMergeWhen;
|
|
1506
|
+
exports.resolveWhen = resolveWhen;
|
|
1507
|
+
exports.resolveWhenNotNull = resolveWhenNotNull;
|
|
1508
|
+
exports.resolveWithHookMetadata = resolveWithHookMetadata;
|
|
1509
|
+
exports.sanitizeConditionalAttributes = sanitizeConditionalAttributes;
|
|
1510
|
+
exports.setGlobalBaseUrl = setGlobalBaseUrl;
|
|
1511
|
+
exports.setGlobalCase = setGlobalCase;
|
|
1512
|
+
exports.setGlobalCursorMeta = setGlobalCursorMeta;
|
|
1513
|
+
exports.setGlobalPageName = setGlobalPageName;
|
|
1514
|
+
exports.setGlobalPaginatedExtras = setGlobalPaginatedExtras;
|
|
1515
|
+
exports.setGlobalPaginatedLinks = setGlobalPaginatedLinks;
|
|
1516
|
+
exports.setGlobalPaginatedMeta = setGlobalPaginatedMeta;
|
|
1517
|
+
exports.setGlobalResponseFactory = setGlobalResponseFactory;
|
|
1518
|
+
exports.setGlobalResponseRootKey = setGlobalResponseRootKey;
|
|
1519
|
+
exports.setGlobalResponseStructure = setGlobalResponseStructure;
|
|
1520
|
+
exports.setGlobalResponseWrap = setGlobalResponseWrap;
|
|
1521
|
+
exports.splitWords = splitWords;
|
|
1522
|
+
exports.toCamelCase = toCamelCase;
|
|
1523
|
+
exports.toKebabCase = toKebabCase;
|
|
1524
|
+
exports.toPascalCase = toPascalCase;
|
|
1525
|
+
exports.toSnakeCase = toSnakeCase;
|
|
1526
|
+
exports.transformKeys = transformKeys;
|