@edium/halifax 1.0.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.
Files changed (135) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/LICENSE +21 -0
  3. package/README.md +148 -0
  4. package/README_AUTH.md +172 -0
  5. package/README_AUTOCRUD.md +253 -0
  6. package/README_CACHE.md +164 -0
  7. package/README_HTTP_ADAPTERS.md +309 -0
  8. package/README_MULTITENANCY.md +162 -0
  9. package/README_QUERYBUILDER.md +219 -0
  10. package/README_REPO_ADAPTERS.md +266 -0
  11. package/dist/adapters/http/ExpressAdapter.d.ts +40 -0
  12. package/dist/adapters/http/ExpressAdapter.d.ts.map +1 -0
  13. package/dist/adapters/http/ExpressAdapter.js +109 -0
  14. package/dist/adapters/http/ExpressAdapter.js.map +1 -0
  15. package/dist/adapters/orm/prisma/PrismaAdapter.d.ts +143 -0
  16. package/dist/adapters/orm/prisma/PrismaAdapter.d.ts.map +1 -0
  17. package/dist/adapters/orm/prisma/PrismaAdapter.js +277 -0
  18. package/dist/adapters/orm/prisma/PrismaAdapter.js.map +1 -0
  19. package/dist/adapters/orm/prisma/createPrismaResources.d.ts +15 -0
  20. package/dist/adapters/orm/prisma/createPrismaResources.d.ts.map +1 -0
  21. package/dist/adapters/orm/prisma/createPrismaResources.js +51 -0
  22. package/dist/adapters/orm/prisma/createPrismaResources.js.map +1 -0
  23. package/dist/adapters/orm/prisma/helpers.d.ts +27 -0
  24. package/dist/adapters/orm/prisma/helpers.d.ts.map +1 -0
  25. package/dist/adapters/orm/prisma/helpers.js +45 -0
  26. package/dist/adapters/orm/prisma/helpers.js.map +1 -0
  27. package/dist/adapters/orm/prisma/index.d.ts +4 -0
  28. package/dist/adapters/orm/prisma/index.d.ts.map +1 -0
  29. package/dist/adapters/orm/prisma/index.js +3 -0
  30. package/dist/adapters/orm/prisma/index.js.map +1 -0
  31. package/dist/adapters/orm/prisma/types.d.ts +49 -0
  32. package/dist/adapters/orm/prisma/types.d.ts.map +1 -0
  33. package/dist/adapters/orm/prisma/types.js +2 -0
  34. package/dist/adapters/orm/prisma/types.js.map +1 -0
  35. package/dist/auth/AuthStrategy.d.ts +198 -0
  36. package/dist/auth/AuthStrategy.d.ts.map +1 -0
  37. package/dist/auth/AuthStrategy.js +227 -0
  38. package/dist/auth/AuthStrategy.js.map +1 -0
  39. package/dist/classes/QueryBuilder.d.ts +33 -0
  40. package/dist/classes/QueryBuilder.d.ts.map +1 -0
  41. package/dist/classes/QueryBuilder.js +262 -0
  42. package/dist/classes/QueryBuilder.js.map +1 -0
  43. package/dist/core/crudRouter.d.ts +36 -0
  44. package/dist/core/crudRouter.d.ts.map +1 -0
  45. package/dist/core/crudRouter.js +391 -0
  46. package/dist/core/crudRouter.js.map +1 -0
  47. package/dist/core/queryString.d.ts +13 -0
  48. package/dist/core/queryString.d.ts.map +1 -0
  49. package/dist/core/queryString.js +89 -0
  50. package/dist/core/queryString.js.map +1 -0
  51. package/dist/core/types.d.ts +293 -0
  52. package/dist/core/types.d.ts.map +1 -0
  53. package/dist/core/types.js +13 -0
  54. package/dist/core/types.js.map +1 -0
  55. package/dist/core/validation.d.ts +75 -0
  56. package/dist/core/validation.d.ts.map +1 -0
  57. package/dist/core/validation.js +206 -0
  58. package/dist/core/validation.js.map +1 -0
  59. package/dist/enums/SqlComparison.d.ts +18 -0
  60. package/dist/enums/SqlComparison.d.ts.map +1 -0
  61. package/dist/enums/SqlComparison.js +19 -0
  62. package/dist/enums/SqlComparison.js.map +1 -0
  63. package/dist/enums/SqlOperator.d.ts +6 -0
  64. package/dist/enums/SqlOperator.d.ts.map +1 -0
  65. package/dist/enums/SqlOperator.js +7 -0
  66. package/dist/enums/SqlOperator.js.map +1 -0
  67. package/dist/enums/SqlOrder.d.ts +6 -0
  68. package/dist/enums/SqlOrder.d.ts.map +1 -0
  69. package/dist/enums/SqlOrder.js +7 -0
  70. package/dist/enums/SqlOrder.js.map +1 -0
  71. package/dist/errors/AuthenticationError.d.ts +10 -0
  72. package/dist/errors/AuthenticationError.d.ts.map +1 -0
  73. package/dist/errors/AuthenticationError.js +13 -0
  74. package/dist/errors/AuthenticationError.js.map +1 -0
  75. package/dist/errors/AuthorizationError.d.ts +10 -0
  76. package/dist/errors/AuthorizationError.d.ts.map +1 -0
  77. package/dist/errors/AuthorizationError.js +13 -0
  78. package/dist/errors/AuthorizationError.js.map +1 -0
  79. package/dist/errors/BadRequestError.d.ts +10 -0
  80. package/dist/errors/BadRequestError.d.ts.map +1 -0
  81. package/dist/errors/BadRequestError.js +13 -0
  82. package/dist/errors/BadRequestError.js.map +1 -0
  83. package/dist/errors/HttpError.d.ts +12 -0
  84. package/dist/errors/HttpError.d.ts.map +1 -0
  85. package/dist/errors/HttpError.js +17 -0
  86. package/dist/errors/HttpError.js.map +1 -0
  87. package/dist/errors/MethodNotAllowedError.d.ts +10 -0
  88. package/dist/errors/MethodNotAllowedError.d.ts.map +1 -0
  89. package/dist/errors/MethodNotAllowedError.js +13 -0
  90. package/dist/errors/MethodNotAllowedError.js.map +1 -0
  91. package/dist/errors/NotAcceptableError.d.ts +10 -0
  92. package/dist/errors/NotAcceptableError.d.ts.map +1 -0
  93. package/dist/errors/NotAcceptableError.js +13 -0
  94. package/dist/errors/NotAcceptableError.js.map +1 -0
  95. package/dist/errors/NotFoundError.d.ts +10 -0
  96. package/dist/errors/NotFoundError.d.ts.map +1 -0
  97. package/dist/errors/NotFoundError.js +13 -0
  98. package/dist/errors/NotFoundError.js.map +1 -0
  99. package/dist/errors/NotImplementedError.d.ts +10 -0
  100. package/dist/errors/NotImplementedError.d.ts.map +1 -0
  101. package/dist/errors/NotImplementedError.js +13 -0
  102. package/dist/errors/NotImplementedError.js.map +1 -0
  103. package/dist/errors/ServerError.d.ts +10 -0
  104. package/dist/errors/ServerError.d.ts.map +1 -0
  105. package/dist/errors/ServerError.js +13 -0
  106. package/dist/errors/ServerError.js.map +1 -0
  107. package/dist/errors/UnprocessableEntityError.d.ts +10 -0
  108. package/dist/errors/UnprocessableEntityError.d.ts.map +1 -0
  109. package/dist/errors/UnprocessableEntityError.js +13 -0
  110. package/dist/errors/UnprocessableEntityError.js.map +1 -0
  111. package/dist/errors/UnsupportedMediaTypeError.d.ts +10 -0
  112. package/dist/errors/UnsupportedMediaTypeError.d.ts.map +1 -0
  113. package/dist/errors/UnsupportedMediaTypeError.js +13 -0
  114. package/dist/errors/UnsupportedMediaTypeError.js.map +1 -0
  115. package/dist/index.d.ts +27 -0
  116. package/dist/index.d.ts.map +1 -0
  117. package/dist/index.js +27 -0
  118. package/dist/index.js.map +1 -0
  119. package/dist/interfaces/IParamQuery.d.ts +8 -0
  120. package/dist/interfaces/IParamQuery.d.ts.map +1 -0
  121. package/dist/interfaces/IParamQuery.js +2 -0
  122. package/dist/interfaces/IParamQuery.js.map +1 -0
  123. package/dist/interfaces/IQueryFilter.d.ts +18 -0
  124. package/dist/interfaces/IQueryFilter.d.ts.map +1 -0
  125. package/dist/interfaces/IQueryFilter.js +2 -0
  126. package/dist/interfaces/IQueryFilter.js.map +1 -0
  127. package/dist/interfaces/IQueryOptions.d.ts +20 -0
  128. package/dist/interfaces/IQueryOptions.d.ts.map +1 -0
  129. package/dist/interfaces/IQueryOptions.js +2 -0
  130. package/dist/interfaces/IQueryOptions.js.map +1 -0
  131. package/dist/interfaces/ISort.d.ts +9 -0
  132. package/dist/interfaces/ISort.d.ts.map +1 -0
  133. package/dist/interfaces/ISort.js +2 -0
  134. package/dist/interfaces/ISort.js.map +1 -0
  135. package/package.json +169 -0
@@ -0,0 +1,391 @@
1
+ import { AllowAllAuthStrategy } from '../auth/AuthStrategy.js';
2
+ import { QueryBuilder } from '../classes/QueryBuilder.js';
3
+ import { BadRequestError } from '../errors/BadRequestError.js';
4
+ import { HttpError } from '../errors/HttpError.js';
5
+ import { MethodNotAllowedError } from '../errors/MethodNotAllowedError.js';
6
+ import { NotAcceptableError } from '../errors/NotAcceptableError.js';
7
+ import { NotFoundError } from '../errors/NotFoundError.js';
8
+ import { NotImplementedError } from '../errors/NotImplementedError.js';
9
+ import { UnprocessableEntityError } from '../errors/UnprocessableEntityError.js';
10
+ import { UnsupportedMediaTypeError } from '../errors/UnsupportedMediaTypeError.js';
11
+ import { defaultCrudPermissions } from '../core/types.js';
12
+ import { parseListOptions } from '../core/queryString.js';
13
+ import { validateAdvancedQuery, validateId, isValidUuid } from '../core/validation.js';
14
+ import { ServerError } from '../errors/ServerError.js';
15
+ import { AuthorizationError } from '../errors/AuthorizationError.js';
16
+ /**
17
+ * Parses and validates a raw `:id` route parameter.
18
+ * @param raw - The raw string value from `req.params.id`.
19
+ * @returns A parsed integer for numeric IDs, or the original string for UUIDs.
20
+ * @throws {@link BadRequestError} when the value is not a valid integer or UUID.
21
+ */
22
+ function parseId(raw) {
23
+ validateId(raw);
24
+ if (typeof raw === 'string' && isValidUuid(raw))
25
+ return raw;
26
+ return typeof raw === 'string' ? parseInt(raw, 10) : raw;
27
+ }
28
+ /**
29
+ * Strips non-writable fields from a request body and rejects unknown fields with a 422.
30
+ * Only fields explicitly marked `writable: true` are allowed through; fields with
31
+ * `writable: false` or `writable` unset are silently dropped.
32
+ * @param resource - The resource definition that defines writable fields.
33
+ * @param data - The raw request body key-value map.
34
+ * @returns A new object containing only explicitly writable fields.
35
+ * @throws {@link UnprocessableEntityError} when the body contains keys not defined on the resource.
36
+ */
37
+ function filterWritableFields(resource, data) {
38
+ const knownFields = new Set(resource.fields.map((f) => f.name));
39
+ const unknownFields = Object.keys(data).filter((key) => !knownFields.has(key));
40
+ if (unknownFields.length) {
41
+ throw new UnprocessableEntityError(`Unknown field(s): ${unknownFields.join(', ')}.`);
42
+ }
43
+ return Object.fromEntries(Object.entries(data).filter(([key]) => {
44
+ const field = resource.fields.find((f) => f.name === key);
45
+ return field?.writable === true;
46
+ }));
47
+ }
48
+ /** Maps HTTP status codes to machine-readable error code strings. */
49
+ const statusCodeMap = {
50
+ 400: 'BAD_REQUEST',
51
+ 401: 'UNAUTHORIZED',
52
+ 403: 'FORBIDDEN',
53
+ 404: 'NOT_FOUND',
54
+ 405: 'METHOD_NOT_ALLOWED',
55
+ 406: 'NOT_ACCEPTABLE',
56
+ 415: 'UNSUPPORTED_MEDIA_TYPE',
57
+ 422: 'UNPROCESSABLE_ENTITY',
58
+ 501: 'NOT_IMPLEMENTED'
59
+ };
60
+ /**
61
+ * Converts any thrown value to a structured `{ status, code, message, details }` object.
62
+ * {@link HttpError} subclasses preserve their status; all other errors become 500.
63
+ * @param error - The caught value to normalise (may be any type).
64
+ * @returns A plain object with `status`, `code`, `message`, and optional `details`.
65
+ */
66
+ export function normalizeError(error) {
67
+ if (error instanceof HttpError) {
68
+ return {
69
+ status: error.status,
70
+ code: statusCodeMap[error.status] ?? 'INTERNAL_ERROR',
71
+ message: error.message,
72
+ details: error.details
73
+ };
74
+ }
75
+ if (error instanceof Error) {
76
+ return { status: 500, code: 'INTERNAL_ERROR', message: 'Internal server error' };
77
+ }
78
+ return { status: 500, code: 'INTERNAL_ERROR', message: 'Internal server error' };
79
+ }
80
+ /**
81
+ * Validates that all identifier-shaped values in a preview query are safe SQL identifiers.
82
+ * Field names and the table name are interpolated directly into SQL strings (not parameterized),
83
+ * so they must match `[a-zA-Z_][a-zA-Z0-9_.]*` to prevent unexpected SQL fragments.
84
+ * @param query - The query AST to inspect.
85
+ * @throws {@link BadRequestError} when any identifier contains disallowed characters.
86
+ */
87
+ function assertSafePreviewIdentifiers(query) {
88
+ const safe = /^[a-zA-Z_][a-zA-Z0-9_.]*$/;
89
+ const check = (value, label) => {
90
+ if (!safe.test(value))
91
+ throw new BadRequestError(`Invalid ${label}: '${value}'.`);
92
+ };
93
+ if (query.tableName)
94
+ check(query.tableName, 'table name');
95
+ for (const f of query.fields ?? [])
96
+ check(f, 'field name');
97
+ for (const s of query.orderBy ?? [])
98
+ check(s.field, 'sort field');
99
+ const checkFilters = (filters) => {
100
+ for (const f of filters) {
101
+ if (f.field)
102
+ check(f.field, 'filter field');
103
+ if (f.children?.length)
104
+ checkFilters(f.children);
105
+ }
106
+ };
107
+ checkFilters(query.where ?? []);
108
+ }
109
+ /**
110
+ * Serialises a caught error and writes it as a JSON `{ errors: [...] }` response.
111
+ * @param error - The caught value to serialise.
112
+ * @param res - The response object to write to.
113
+ */
114
+ async function sendError(error, res) {
115
+ const { status, code, message, details } = normalizeError(error);
116
+ const item = { code, message };
117
+ if (details !== undefined)
118
+ item['details'] = details;
119
+ await res.status(status).json({ errors: [item] });
120
+ }
121
+ /**
122
+ * Runs the auth strategy for `action` and throws {@link AuthorizationError} when not allowed.
123
+ * @param req - The incoming HTTP request.
124
+ * @param resource - The resource being accessed (used to look up required permissions).
125
+ * @param action - The CRUD action being performed.
126
+ * @param authStrategy - The active auth strategy.
127
+ */
128
+ async function authorizeRequest(req, resource, action, authStrategy) {
129
+ const auth = await authStrategy.authenticate(req);
130
+ const requiredPermissions = resource.requiredPermissions?.[action] ?? [];
131
+ if (authStrategy.authorize) {
132
+ const allowed = await authStrategy.authorize({
133
+ auth,
134
+ action,
135
+ resource,
136
+ requiredPermissions,
137
+ req
138
+ });
139
+ if (!allowed)
140
+ throw new AuthorizationError();
141
+ return;
142
+ }
143
+ if (requiredPermissions.length) {
144
+ const permissions = new Set(auth.permissions ?? []);
145
+ const roles = new Set(auth.roles ?? []);
146
+ const allowed = requiredPermissions.every((permission) => permissions.has(permission) || roles.has(permission));
147
+ if (!allowed)
148
+ throw new AuthorizationError();
149
+ }
150
+ }
151
+ /**
152
+ * Reads a single header value by name (case-insensitive).
153
+ * @param req - The incoming HTTP request.
154
+ * @param name - Header name to look up (case-insensitive).
155
+ * @returns The header value as a string, or `undefined` when absent.
156
+ */
157
+ function getHeaderValue(req, name) {
158
+ const raw = req.headers[name.toLowerCase()] ?? req.headers[name];
159
+ const value = Array.isArray(raw) ? raw[0] : raw;
160
+ return typeof value === 'string' ? value : undefined;
161
+ }
162
+ /**
163
+ * Throws {@link UnsupportedMediaTypeError} when a body-carrying request uses a non-JSON Content-Type.
164
+ * @param req - The incoming HTTP request to check.
165
+ */
166
+ function checkContentType(req) {
167
+ if (!['POST', 'PATCH', 'PUT', 'DELETE'].includes(req.method.toUpperCase()))
168
+ return;
169
+ const contentType = getHeaderValue(req, 'content-type') ?? '';
170
+ if (contentType && !contentType.includes('application/json')) {
171
+ throw new UnsupportedMediaTypeError();
172
+ }
173
+ }
174
+ /**
175
+ * Throws {@link NotAcceptableError} when the client's Accept header excludes `application/json`.
176
+ * @param req - The incoming HTTP request to check.
177
+ */
178
+ function checkAcceptHeader(req) {
179
+ const accept = getHeaderValue(req, 'accept') ?? '';
180
+ if (accept &&
181
+ !accept.includes('*/*') &&
182
+ !accept.includes('application/*') &&
183
+ !accept.includes('application/json')) {
184
+ throw new NotAcceptableError();
185
+ }
186
+ }
187
+ /**
188
+ * Wraps a route handler with Content-Type / Accept checks, error serialisation,
189
+ * and `X-Correlation-ID` echo-back.
190
+ * @param handler - The inner async route handler to wrap.
191
+ * @returns A new handler with pre/post-processing applied.
192
+ */
193
+ function wrap(handler) {
194
+ return async (req, res) => {
195
+ const correlationId = getHeaderValue(req, 'x-correlation-id');
196
+ if (correlationId)
197
+ res.setHeader?.('X-Correlation-ID', correlationId);
198
+ try {
199
+ checkContentType(req);
200
+ checkAcceptHeader(req);
201
+ await handler(req, res);
202
+ }
203
+ catch (error) {
204
+ await sendError(error, res);
205
+ }
206
+ };
207
+ }
208
+ /**
209
+ * Registers all CRUD routes for every resource on the given HTTP server.
210
+ *
211
+ * Routes are controlled by `resource.permissions` merged with {@link defaultCrudPermissions}.
212
+ * A global query-builder preview endpoint is also registered at `previewQueryBuilderPath`.
213
+ *
214
+ * @param server - The HTTP server adapter to register routes on (e.g. {@link ExpressHttpServer}).
215
+ * @param resources - Resource definitions to wire up as CRUD endpoints.
216
+ * @param options - Auth strategy, query-builder path overrides, and preview path overrides.
217
+ */
218
+ export function registerCrudApi(server, resources, options = {}) {
219
+ const authStrategy = options.authStrategy ?? new AllowAllAuthStrategy();
220
+ const queryBuilderPath = options.queryBuilderPath ?? 'query-builder';
221
+ const previewPath = options.previewQueryBuilderPath ?? '/query-builder/preview';
222
+ resources.forEach((resource) => {
223
+ const repository = resource.repository;
224
+ if (!repository)
225
+ throw new ServerError(`Resource '${resource.name}' does not define a repository.`);
226
+ const permissions = { ...defaultCrudPermissions, ...resource.permissions };
227
+ const basePath = `/${resource.routePrefix}`;
228
+ if (permissions.allowCreate) {
229
+ server.registerRoute('POST', basePath, wrap(async (req, res) => {
230
+ await authorizeRequest(req, resource, 'create', authStrategy);
231
+ const idempotencyKey = getHeaderValue(req, 'idempotency-key');
232
+ const createOptions = idempotencyKey ? { idempotencyKey } : undefined;
233
+ const items = (Array.isArray(req.body) ? req.body : [req.body]).map((item) => filterWritableFields(resource, item));
234
+ if (items.length === 1) {
235
+ const result = await repository.createOne(items[0], createOptions);
236
+ await res.status(201).json(result);
237
+ return;
238
+ }
239
+ const results = await repository.createMany(items, createOptions);
240
+ await res.status(201).json(results);
241
+ }));
242
+ }
243
+ if (permissions.allowReadMany) {
244
+ server.registerRoute('GET', basePath, wrap(async (req, res) => {
245
+ await authorizeRequest(req, resource, 'readMany', authStrategy);
246
+ const listOptions = parseListOptions(req.query, resource);
247
+ const results = await repository.getMany(listOptions);
248
+ await res.status(200).json(results);
249
+ }));
250
+ }
251
+ if (permissions.allowReadManyWithQueryBuilder) {
252
+ server.registerRoute('POST', `${basePath}/${queryBuilderPath}`, wrap(async (req, res) => {
253
+ await authorizeRequest(req, resource, 'readManyWithQueryBuilder', authStrategy);
254
+ if (!repository.executeQueryBuilder)
255
+ throw new NotImplementedError('This resource does not support the query builder.');
256
+ const body = (req.body ?? {});
257
+ const query = {
258
+ ...body,
259
+ tableName: resource.tableName
260
+ };
261
+ validateAdvancedQuery(resource, query);
262
+ const results = await repository.executeQueryBuilder(query);
263
+ await res.status(200).json(results);
264
+ }));
265
+ }
266
+ if (permissions.allowReadOne) {
267
+ server.registerRoute('GET', `${basePath}/:id`, wrap(async (req, res) => {
268
+ await authorizeRequest(req, resource, 'readOne', authStrategy);
269
+ const id = parseId(req.params['id']);
270
+ const listOptions = parseListOptions(req.query, resource);
271
+ const result = await repository.getOne(id, {
272
+ fields: listOptions.fields,
273
+ include: listOptions.include
274
+ });
275
+ if (!result)
276
+ throw new NotFoundError();
277
+ await res.status(200).json(result);
278
+ }));
279
+ }
280
+ if (permissions.allowUpdateOne) {
281
+ server.registerRoute('PATCH', `${basePath}/:id`, wrap(async (req, res) => {
282
+ await authorizeRequest(req, resource, 'updateOne', authStrategy);
283
+ const id = parseId(req.params['id']);
284
+ const body = filterWritableFields(resource, req.body);
285
+ const result = await repository.updateOne(id, body);
286
+ if (!result)
287
+ throw new NotFoundError();
288
+ await res.status(200).json(result);
289
+ }));
290
+ }
291
+ if (permissions.allowUpdateMany) {
292
+ server.registerRoute('PATCH', basePath, wrap(async (req, res) => {
293
+ await authorizeRequest(req, resource, 'updateMany', authStrategy);
294
+ if (!repository.updateMany)
295
+ throw new NotImplementedError('This resource does not support updateMany.');
296
+ const { update, ...queryBody } = (req.body ?? {});
297
+ const filteredUpdate = filterWritableFields(resource, (update ?? {}));
298
+ if (!Object.keys(filteredUpdate).length)
299
+ throw new UnprocessableEntityError('updateMany requires at least one writable field in the update payload.');
300
+ const query = {
301
+ ...queryBody,
302
+ tableName: resource.tableName
303
+ };
304
+ validateAdvancedQuery(resource, query);
305
+ if (!query.where?.length)
306
+ throw new UnprocessableEntityError('updateMany requires at least one WHERE filter to prevent unintended bulk updates.');
307
+ const result = await repository.updateMany(query, filteredUpdate);
308
+ await res.status(200).json(result);
309
+ }));
310
+ }
311
+ if (permissions.allowUpsertOne) {
312
+ server.registerRoute('PUT', `${basePath}/:id`, wrap(async (req, res) => {
313
+ await authorizeRequest(req, resource, 'upsertOne', authStrategy);
314
+ if (!repository.upsertOne)
315
+ throw new NotImplementedError('This resource does not support upsert.');
316
+ const id = parseId(req.params['id']);
317
+ const body = filterWritableFields(resource, req.body);
318
+ const result = await repository.upsertOne(id, body);
319
+ await res.status(200).json(result);
320
+ }));
321
+ }
322
+ if (permissions.allowDeleteOne) {
323
+ server.registerRoute('DELETE', `${basePath}/:id`, wrap(async (req, res) => {
324
+ await authorizeRequest(req, resource, 'deleteOne', authStrategy);
325
+ const id = parseId(req.params['id']);
326
+ const deleted = await repository.deleteOne(id);
327
+ if (!deleted)
328
+ throw new NotFoundError();
329
+ await res.status(200).json({ deleted: true });
330
+ }));
331
+ }
332
+ if (permissions.allowDeleteMany) {
333
+ server.registerRoute('DELETE', basePath, wrap(async (req, res) => {
334
+ await authorizeRequest(req, resource, 'deleteMany', authStrategy);
335
+ if (!repository.deleteMany)
336
+ throw new NotImplementedError('This resource does not support deleteMany.');
337
+ const body = (req.body ?? {});
338
+ const query = {
339
+ ...body,
340
+ tableName: resource.tableName
341
+ };
342
+ validateAdvancedQuery(resource, query);
343
+ if (!query.where?.length)
344
+ throw new UnprocessableEntityError('deleteMany requires at least one WHERE filter to prevent unintended bulk deletes.');
345
+ const result = await repository.deleteMany(query);
346
+ await res.status(200).json(result);
347
+ }));
348
+ }
349
+ // 405 fallbacks — only registered when at least one method exists for the path
350
+ const baseMethods = [
351
+ ...(permissions.allowReadMany ? ['GET'] : []),
352
+ ...(permissions.allowCreate ? ['POST'] : []),
353
+ ...(permissions.allowUpdateMany ? ['PATCH'] : []),
354
+ ...(permissions.allowDeleteMany ? ['DELETE'] : [])
355
+ ];
356
+ if (baseMethods.length) {
357
+ server.registerRoute('*', basePath, async (req, res) => {
358
+ res.setHeader?.('Allow', baseMethods.join(', '));
359
+ await sendError(new MethodNotAllowedError(), res);
360
+ });
361
+ }
362
+ const idMethods = [
363
+ ...(permissions.allowReadOne ? ['GET'] : []),
364
+ ...(permissions.allowUpdateOne ? ['PATCH'] : []),
365
+ ...(permissions.allowUpsertOne ? ['PUT'] : []),
366
+ ...(permissions.allowDeleteOne ? ['DELETE'] : [])
367
+ ];
368
+ if (idMethods.length) {
369
+ server.registerRoute('*', `${basePath}/:id`, async (req, res) => {
370
+ res.setHeader?.('Allow', idMethods.join(', '));
371
+ await sendError(new MethodNotAllowedError(), res);
372
+ });
373
+ }
374
+ if (permissions.allowReadManyWithQueryBuilder) {
375
+ server.registerRoute('*', `${basePath}/${queryBuilderPath}`, async (req, res) => {
376
+ res.setHeader?.('Allow', 'POST');
377
+ await sendError(new MethodNotAllowedError(), res);
378
+ });
379
+ }
380
+ });
381
+ server.registerRoute('POST', previewPath, wrap(async (req, res) => {
382
+ await authStrategy.authenticate(req);
383
+ const query = req.body;
384
+ assertSafePreviewIdentifiers(query);
385
+ await res.status(200).json({
386
+ count: QueryBuilder.buildCountQuery(query),
387
+ select: QueryBuilder.buildSelectQuery(query)
388
+ });
389
+ }));
390
+ }
391
+ //# sourceMappingURL=crudRouter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crudRouter.js","sourceRoot":"","sources":["../../src/core/crudRouter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAqB,MAAM,wBAAwB,CAAA;AAChF,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAA;AAC7D,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AACjD,OAAO,EAAE,qBAAqB,EAAE,MAAM,mCAAmC,CAAA;AACzE,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAA;AACnE,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAA;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAA;AACrE,OAAO,EAAE,wBAAwB,EAAE,MAAM,sCAAsC,CAAA;AAC/E,OAAO,EAAE,yBAAyB,EAAE,MAAM,uCAAuC,CAAA;AAGjF,OAAO,EAAE,sBAAsB,EAA4C,MAAM,iBAAiB,CAAA;AAElG,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AACxD,OAAO,EAAE,qBAAqB,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AACrF,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AACrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAA;AAEnE;;;;;GAKG;AACH,SAAS,OAAO,CAAC,GAAuB;IACtC,UAAU,CAAC,GAAG,CAAC,CAAA;IACf,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,WAAW,CAAC,GAAG,CAAC;QAAE,OAAO,GAAG,CAAA;IAC3D,OAAO,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAA;AAC1D,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,oBAAoB,CAC3B,QAA4B,EAC5B,IAA6B;IAE7B,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAA;IAC/D,MAAM,aAAa,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAA;IAC9E,IAAI,aAAa,CAAC,MAAM,EAAE,CAAC;QACzB,MAAM,IAAI,wBAAwB,CAAC,qBAAqB,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACtF,CAAC;IAED,OAAO,MAAM,CAAC,WAAW,CACvB,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE;QACpC,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,GAAG,CAAC,CAAA;QACzD,OAAO,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAA;IACjC,CAAC,CAAC,CACH,CAAA;AACH,CAAC;AAYD,qEAAqE;AACrE,MAAM,aAAa,GAA2B;IAC5C,GAAG,EAAE,aAAa;IAClB,GAAG,EAAE,cAAc;IACnB,GAAG,EAAE,WAAW;IAChB,GAAG,EAAE,WAAW;IAChB,GAAG,EAAE,oBAAoB;IACzB,GAAG,EAAE,gBAAgB;IACrB,GAAG,EAAE,wBAAwB;IAC7B,GAAG,EAAE,sBAAsB;IAC3B,GAAG,EAAE,iBAAiB;CACvB,CAAA;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,KAAc;IAM3C,IAAI,KAAK,YAAY,SAAS,EAAE,CAAC;QAC/B,OAAO;YACL,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,IAAI,EAAE,aAAa,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,gBAAgB;YACrD,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,OAAO,EAAE,KAAK,CAAC,OAAO;SACvB,CAAA;IACH,CAAC;IACD,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;QAC3B,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,uBAAuB,EAAE,CAAA;IAClF,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,uBAAuB,EAAE,CAAA;AAClF,CAAC;AAED;;;;;;GAMG;AACH,SAAS,4BAA4B,CAAC,KAAoB;IACxD,MAAM,IAAI,GAAG,2BAA2B,CAAA;IACxC,MAAM,KAAK,GAAG,CAAC,KAAa,EAAE,KAAa,EAAE,EAAE;QAC7C,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC;YAAE,MAAM,IAAI,eAAe,CAAC,WAAW,KAAK,MAAM,KAAK,IAAI,CAAC,CAAA;IACnF,CAAC,CAAA;IACD,IAAI,KAAK,CAAC,SAAS;QAAE,KAAK,CAAC,KAAK,CAAC,SAAS,EAAE,YAAY,CAAC,CAAA;IACzD,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,MAAM,IAAI,EAAE;QAAE,KAAK,CAAC,CAAC,EAAE,YAAY,CAAC,CAAA;IAC1D,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,OAAO,IAAI,EAAE;QAAE,KAAK,CAAC,CAAC,CAAC,KAAK,EAAE,YAAY,CAAC,CAAA;IACjE,MAAM,YAAY,GAAG,CAAC,OAAuB,EAAE,EAAE;QAC/C,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;YACxB,IAAI,CAAC,CAAC,KAAK;gBAAE,KAAK,CAAC,CAAC,CAAC,KAAK,EAAE,cAAc,CAAC,CAAA;YAC3C,IAAI,CAAC,CAAC,QAAQ,EAAE,MAAM;gBAAE,YAAY,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAA;QAClD,CAAC;IACH,CAAC,CAAA;IACD,YAAY,CAAC,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC,CAAA;AACjC,CAAC;AAED;;;;GAIG;AACH,KAAK,UAAU,SAAS,CAAC,KAAc,EAAE,GAAiB;IACxD,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,cAAc,CAAC,KAAK,CAAC,CAAA;IAChE,MAAM,IAAI,GAA4B,EAAE,IAAI,EAAE,OAAO,EAAE,CAAA;IACvD,IAAI,OAAO,KAAK,SAAS;QAAE,IAAI,CAAC,SAAS,CAAC,GAAG,OAAO,CAAA;IACpD,MAAM,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;AACnD,CAAC;AAED;;;;;;GAMG;AACH,KAAK,UAAU,gBAAgB,CAC7B,GAAgB,EAChB,QAA4B,EAC5B,MAAkB,EAClB,YAA0B;IAE1B,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,YAAY,CAAC,GAAG,CAAC,CAAA;IACjD,MAAM,mBAAmB,GAAG,QAAQ,CAAC,mBAAmB,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,CAAA;IAExE,IAAI,YAAY,CAAC,SAAS,EAAE,CAAC;QAC3B,MAAM,OAAO,GAAG,MAAM,YAAY,CAAC,SAAS,CAAC;YAC3C,IAAI;YACJ,MAAM;YACN,QAAQ;YACR,mBAAmB;YACnB,GAAG;SACJ,CAAC,CAAA;QACF,IAAI,CAAC,OAAO;YAAE,MAAM,IAAI,kBAAkB,EAAE,CAAA;QAC5C,OAAM;IACR,CAAC;IAED,IAAI,mBAAmB,CAAC,MAAM,EAAE,CAAC;QAC/B,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,WAAW,IAAI,EAAE,CAAC,CAAA;QACnD,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,CAAA;QACvC,MAAM,OAAO,GAAG,mBAAmB,CAAC,KAAK,CACvC,CAAC,UAAU,EAAE,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,CACrE,CAAA;QACD,IAAI,CAAC,OAAO;YAAE,MAAM,IAAI,kBAAkB,EAAE,CAAA;IAC9C,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,SAAS,cAAc,CAAC,GAAgB,EAAE,IAAY;IACpD,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;IAChE,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAA;IAC/C,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAA;AACtD,CAAC;AAED;;;GAGG;AACH,SAAS,gBAAgB,CAAC,GAAgB;IACxC,IAAI,CAAC,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;QAAE,OAAM;IAClF,MAAM,WAAW,GAAG,cAAc,CAAC,GAAG,EAAE,cAAc,CAAC,IAAI,EAAE,CAAA;IAC7D,IAAI,WAAW,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,CAAC;QAC7D,MAAM,IAAI,yBAAyB,EAAE,CAAA;IACvC,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,SAAS,iBAAiB,CAAC,GAAgB;IACzC,MAAM,MAAM,GAAG,cAAc,CAAC,GAAG,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAA;IAClD,IACE,MAAM;QACN,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;QACvB,CAAC,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAC;QACjC,CAAC,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAC,EACpC,CAAC;QACD,MAAM,IAAI,kBAAkB,EAAE,CAAA;IAChC,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,SAAS,IAAI,CAAC,OAA+D;IAC3E,OAAO,KAAK,EAAE,GAAgB,EAAE,GAAiB,EAAiB,EAAE;QAClE,MAAM,aAAa,GAAG,cAAc,CAAC,GAAG,EAAE,kBAAkB,CAAC,CAAA;QAC7D,IAAI,aAAa;YAAE,GAAG,CAAC,SAAS,EAAE,CAAC,kBAAkB,EAAE,aAAa,CAAC,CAAA;QACrE,IAAI,CAAC;YACH,gBAAgB,CAAC,GAAG,CAAC,CAAA;YACrB,iBAAiB,CAAC,GAAG,CAAC,CAAA;YACtB,MAAM,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;QACzB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,SAAS,CAAC,KAAK,EAAE,GAAG,CAAC,CAAA;QAC7B,CAAC;IACH,CAAC,CAAA;AACH,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,eAAe,CAC7B,MAAkB,EAClB,SAA+B,EAC/B,UAA0B,EAAE;IAE5B,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,IAAI,oBAAoB,EAAE,CAAA;IACvE,MAAM,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,IAAI,eAAe,CAAA;IACpE,MAAM,WAAW,GAAG,OAAO,CAAC,uBAAuB,IAAI,wBAAwB,CAAA;IAE/E,SAAS,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,EAAE;QAC7B,MAAM,UAAU,GAAG,QAAQ,CAAC,UAAU,CAAA;QACtC,IAAI,CAAC,UAAU;YACb,MAAM,IAAI,WAAW,CAAC,aAAa,QAAQ,CAAC,IAAI,iCAAiC,CAAC,CAAA;QAEpF,MAAM,WAAW,GAAG,EAAE,GAAG,sBAAsB,EAAE,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAA;QAC1E,MAAM,QAAQ,GAAG,IAAI,QAAQ,CAAC,WAAW,EAAE,CAAA;QAE3C,IAAI,WAAW,CAAC,WAAW,EAAE,CAAC;YAC5B,MAAM,CAAC,aAAa,CAClB,MAAM,EACN,QAAQ,EACR,IAAI,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;gBACtB,MAAM,gBAAgB,CAAC,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,YAAY,CAAC,CAAA;gBAC7D,MAAM,cAAc,GAAG,cAAc,CAAC,GAAG,EAAE,iBAAiB,CAAC,CAAA;gBAC7D,MAAM,aAAa,GAAG,cAAc,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC,SAAS,CAAA;gBACrE,MAAM,KAAK,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CACjE,CAAC,IAA6B,EAAE,EAAE,CAAC,oBAAoB,CAAC,QAAQ,EAAE,IAAI,CAAC,CACxE,CAAA;gBACD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;oBACvB,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAU,EAAE,aAAa,CAAC,CAAA;oBAC3E,MAAM,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;oBAClC,OAAM;gBACR,CAAC;gBACD,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,UAAU,CAAC,KAAgB,EAAE,aAAa,CAAC,CAAA;gBAC5E,MAAM,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;YACrC,CAAC,CAAC,CACH,CAAA;QACH,CAAC;QAED,IAAI,WAAW,CAAC,aAAa,EAAE,CAAC;YAC9B,MAAM,CAAC,aAAa,CAClB,KAAK,EACL,QAAQ,EACR,IAAI,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;gBACtB,MAAM,gBAAgB,CAAC,GAAG,EAAE,QAAQ,EAAE,UAAU,EAAE,YAAY,CAAC,CAAA;gBAC/D,MAAM,WAAW,GAAG,gBAAgB,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAA;gBACzD,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,OAAO,CAAC,WAAW,CAAC,CAAA;gBACrD,MAAM,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;YACrC,CAAC,CAAC,CACH,CAAA;QACH,CAAC;QAED,IAAI,WAAW,CAAC,6BAA6B,EAAE,CAAC;YAC9C,MAAM,CAAC,aAAa,CAClB,MAAM,EACN,GAAG,QAAQ,IAAI,gBAAgB,EAAE,EACjC,IAAI,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;gBACtB,MAAM,gBAAgB,CAAC,GAAG,EAAE,QAAQ,EAAE,0BAA0B,EAAE,YAAY,CAAC,CAAA;gBAC/E,IAAI,CAAC,UAAU,CAAC,mBAAmB;oBACjC,MAAM,IAAI,mBAAmB,CAAC,mDAAmD,CAAC,CAAA;gBACpF,MAAM,IAAI,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAA4B,CAAA;gBACxD,MAAM,KAAK,GAAG;oBACZ,GAAG,IAAI;oBACP,SAAS,EAAE,QAAQ,CAAC,SAAS;iBACb,CAAA;gBAClB,qBAAqB,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAA;gBACtC,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAA;gBAC3D,MAAM,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;YACrC,CAAC,CAAC,CACH,CAAA;QACH,CAAC;QAED,IAAI,WAAW,CAAC,YAAY,EAAE,CAAC;YAC7B,MAAM,CAAC,aAAa,CAClB,KAAK,EACL,GAAG,QAAQ,MAAM,EACjB,IAAI,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;gBACtB,MAAM,gBAAgB,CAAC,GAAG,EAAE,QAAQ,EAAE,SAAS,EAAE,YAAY,CAAC,CAAA;gBAC9D,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAA;gBACpC,MAAM,WAAW,GAAG,gBAAgB,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAA;gBACzD,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,MAAM,CAAC,EAAE,EAAE;oBACzC,MAAM,EAAE,WAAW,CAAC,MAAM;oBAC1B,OAAO,EAAE,WAAW,CAAC,OAAO;iBAC7B,CAAC,CAAA;gBACF,IAAI,CAAC,MAAM;oBAAE,MAAM,IAAI,aAAa,EAAE,CAAA;gBACtC,MAAM,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;YACpC,CAAC,CAAC,CACH,CAAA;QACH,CAAC;QAED,IAAI,WAAW,CAAC,cAAc,EAAE,CAAC;YAC/B,MAAM,CAAC,aAAa,CAClB,OAAO,EACP,GAAG,QAAQ,MAAM,EACjB,IAAI,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;gBACtB,MAAM,gBAAgB,CAAC,GAAG,EAAE,QAAQ,EAAE,WAAW,EAAE,YAAY,CAAC,CAAA;gBAChE,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAA;gBACpC,MAAM,IAAI,GAAG,oBAAoB,CAAC,QAAQ,EAAE,GAAG,CAAC,IAA+B,CAAC,CAAA;gBAChF,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,SAAS,CAAC,EAAE,EAAE,IAAa,CAAC,CAAA;gBAC5D,IAAI,CAAC,MAAM;oBAAE,MAAM,IAAI,aAAa,EAAE,CAAA;gBACtC,MAAM,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;YACpC,CAAC,CAAC,CACH,CAAA;QACH,CAAC;QAED,IAAI,WAAW,CAAC,eAAe,EAAE,CAAC;YAChC,MAAM,CAAC,aAAa,CAClB,OAAO,EACP,QAAQ,EACR,IAAI,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;gBACtB,MAAM,gBAAgB,CAAC,GAAG,EAAE,QAAQ,EAAE,YAAY,EAAE,YAAY,CAAC,CAAA;gBACjE,IAAI,CAAC,UAAU,CAAC,UAAU;oBACxB,MAAM,IAAI,mBAAmB,CAAC,4CAA4C,CAAC,CAAA;gBAC7E,MAAM,EAAE,MAAM,EAAE,GAAG,SAAS,EAAE,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAA4B,CAAA;gBAC5E,MAAM,cAAc,GAAG,oBAAoB,CACzC,QAAQ,EACR,CAAC,MAAM,IAAI,EAAE,CAA4B,CAC1C,CAAA;gBACD,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,MAAM;oBACrC,MAAM,IAAI,wBAAwB,CAChC,wEAAwE,CACzE,CAAA;gBACH,MAAM,KAAK,GAAG;oBACZ,GAAG,SAAS;oBACZ,SAAS,EAAE,QAAQ,CAAC,SAAS;iBACb,CAAA;gBAClB,qBAAqB,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAA;gBACtC,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,MAAM;oBACtB,MAAM,IAAI,wBAAwB,CAChC,mFAAmF,CACpF,CAAA;gBACH,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,UAAU,CAAC,KAAK,EAAE,cAAuB,CAAC,CAAA;gBAC1E,MAAM,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;YACpC,CAAC,CAAC,CACH,CAAA;QACH,CAAC;QAED,IAAI,WAAW,CAAC,cAAc,EAAE,CAAC;YAC/B,MAAM,CAAC,aAAa,CAClB,KAAK,EACL,GAAG,QAAQ,MAAM,EACjB,IAAI,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;gBACtB,MAAM,gBAAgB,CAAC,GAAG,EAAE,QAAQ,EAAE,WAAW,EAAE,YAAY,CAAC,CAAA;gBAChE,IAAI,CAAC,UAAU,CAAC,SAAS;oBACvB,MAAM,IAAI,mBAAmB,CAAC,wCAAwC,CAAC,CAAA;gBACzE,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAA;gBACpC,MAAM,IAAI,GAAG,oBAAoB,CAAC,QAAQ,EAAE,GAAG,CAAC,IAA+B,CAAC,CAAA;gBAChF,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,SAAS,CAAC,EAAE,EAAE,IAAa,CAAC,CAAA;gBAC5D,MAAM,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;YACpC,CAAC,CAAC,CACH,CAAA;QACH,CAAC;QAED,IAAI,WAAW,CAAC,cAAc,EAAE,CAAC;YAC/B,MAAM,CAAC,aAAa,CAClB,QAAQ,EACR,GAAG,QAAQ,MAAM,EACjB,IAAI,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;gBACtB,MAAM,gBAAgB,CAAC,GAAG,EAAE,QAAQ,EAAE,WAAW,EAAE,YAAY,CAAC,CAAA;gBAChE,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAA;gBACpC,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC,CAAA;gBAC9C,IAAI,CAAC,OAAO;oBAAE,MAAM,IAAI,aAAa,EAAE,CAAA;gBACvC,MAAM,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAA;YAC/C,CAAC,CAAC,CACH,CAAA;QACH,CAAC;QAED,IAAI,WAAW,CAAC,eAAe,EAAE,CAAC;YAChC,MAAM,CAAC,aAAa,CAClB,QAAQ,EACR,QAAQ,EACR,IAAI,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;gBACtB,MAAM,gBAAgB,CAAC,GAAG,EAAE,QAAQ,EAAE,YAAY,EAAE,YAAY,CAAC,CAAA;gBACjE,IAAI,CAAC,UAAU,CAAC,UAAU;oBACxB,MAAM,IAAI,mBAAmB,CAAC,4CAA4C,CAAC,CAAA;gBAC7E,MAAM,IAAI,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAA4B,CAAA;gBACxD,MAAM,KAAK,GAAG;oBACZ,GAAG,IAAI;oBACP,SAAS,EAAE,QAAQ,CAAC,SAAS;iBACb,CAAA;gBAClB,qBAAqB,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAA;gBACtC,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,MAAM;oBACtB,MAAM,IAAI,wBAAwB,CAChC,mFAAmF,CACpF,CAAA;gBACH,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,UAAU,CAAC,KAAK,CAAC,CAAA;gBACjD,MAAM,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;YACpC,CAAC,CAAC,CACH,CAAA;QACH,CAAC;QAED,+EAA+E;QAC/E,MAAM,WAAW,GAAa;YAC5B,GAAG,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7C,GAAG,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAC5C,GAAG,CAAC,WAAW,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YACjD,GAAG,CAAC,WAAW,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;SACnD,CAAA;QACD,IAAI,WAAW,CAAC,MAAM,EAAE,CAAC;YACvB,MAAM,CAAC,aAAa,CAAC,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;gBACrD,GAAG,CAAC,SAAS,EAAE,CAAC,OAAO,EAAE,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAA;gBAChD,MAAM,SAAS,CAAC,IAAI,qBAAqB,EAAE,EAAE,GAAG,CAAC,CAAA;YACnD,CAAC,CAAC,CAAA;QACJ,CAAC;QAED,MAAM,SAAS,GAAa;YAC1B,GAAG,CAAC,WAAW,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAC5C,GAAG,CAAC,WAAW,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAChD,GAAG,CAAC,WAAW,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAC9C,GAAG,CAAC,WAAW,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;SAClD,CAAA;QACD,IAAI,SAAS,CAAC,MAAM,EAAE,CAAC;YACrB,MAAM,CAAC,aAAa,CAAC,GAAG,EAAE,GAAG,QAAQ,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;gBAC9D,GAAG,CAAC,SAAS,EAAE,CAAC,OAAO,EAAE,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAA;gBAC9C,MAAM,SAAS,CAAC,IAAI,qBAAqB,EAAE,EAAE,GAAG,CAAC,CAAA;YACnD,CAAC,CAAC,CAAA;QACJ,CAAC;QAED,IAAI,WAAW,CAAC,6BAA6B,EAAE,CAAC;YAC9C,MAAM,CAAC,aAAa,CAAC,GAAG,EAAE,GAAG,QAAQ,IAAI,gBAAgB,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;gBAC9E,GAAG,CAAC,SAAS,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;gBAChC,MAAM,SAAS,CAAC,IAAI,qBAAqB,EAAE,EAAE,GAAG,CAAC,CAAA;YACnD,CAAC,CAAC,CAAA;QACJ,CAAC;IACH,CAAC,CAAC,CAAA;IAEF,MAAM,CAAC,aAAa,CAClB,MAAM,EACN,WAAW,EACX,IAAI,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;QACtB,MAAM,YAAY,CAAC,YAAY,CAAC,GAAG,CAAC,CAAA;QACpC,MAAM,KAAK,GAAG,GAAG,CAAC,IAAqB,CAAA;QACvC,4BAA4B,CAAC,KAAK,CAAC,CAAA;QACnC,MAAM,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;YACzB,KAAK,EAAE,YAAY,CAAC,eAAe,CAAC,KAAK,CAAC;YAC1C,MAAM,EAAE,YAAY,CAAC,gBAAgB,CAAC,KAAK,CAAC;SAC7C,CAAC,CAAA;IACJ,CAAC,CAAC,CACH,CAAA;AACH,CAAC"}
@@ -0,0 +1,13 @@
1
+ import type { ListOptions, ResourceDefinition } from '../core/types.js';
2
+ /**
3
+ * Parses and validates the raw query-string from a GET request into typed {@link ListOptions}.
4
+ *
5
+ * Validates field names, sort fields, includes, and filter keys against the resource schema.
6
+ * Applies `defaultLimit` and `maxLimit` from the resource definition when set.
7
+ *
8
+ * @param query - The raw query-string object from the HTTP request.
9
+ * @param resource - The resource definition used for field and relation validation.
10
+ * @returns Typed list options ready to pass to `repository.getMany()`.
11
+ */
12
+ export declare function parseListOptions(query: Record<string, unknown>, resource: ResourceDefinition): ListOptions;
13
+ //# sourceMappingURL=queryString.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queryString.d.ts","sourceRoot":"","sources":["../../src/core/queryString.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AAwCtE;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,QAAQ,EAAE,kBAAkB,GAC3B,WAAW,CAkDb"}
@@ -0,0 +1,89 @@
1
+ import { SqlOrder } from '../enums/SqlOrder.js';
2
+ import { UnprocessableEntityError } from '../errors/UnprocessableEntityError.js';
3
+ import { isValidInt32, validateFields, validateIncludes, validateQueryString, validateSelectableFields, validateSortableFields } from '../core/validation.js';
4
+ /**
5
+ * Parses and validates a single integer query-string value.
6
+ * @param value - Raw query-string value (string, number, or array).
7
+ * @param property - Parameter name used in the error message (e.g. `'limit'`).
8
+ * @param min - Inclusive lower bound (default: `1`).
9
+ * @returns The parsed integer, or `undefined` when `value` is absent.
10
+ */
11
+ function parseInteger(value, property, min = 1) {
12
+ if (value === undefined)
13
+ return undefined;
14
+ const parsed = Number(Array.isArray(value) ? value[0] : value);
15
+ if (isValidInt32(parsed, min))
16
+ return parsed;
17
+ throw new UnprocessableEntityError(`${property} must be a valid integer greater than or equal to ${min}.`);
18
+ }
19
+ /**
20
+ * Splits a comma-separated string (or array) into a trimmed, non-empty string array.
21
+ * @param value - Raw query-string value to parse.
22
+ * @returns Array of trimmed strings, or `undefined` when the input is blank or absent.
23
+ */
24
+ function parseCsv(value) {
25
+ const raw = Array.isArray(value) ? value.join(',') : value;
26
+ if (typeof raw !== 'string' || !raw.trim())
27
+ return undefined;
28
+ return raw
29
+ .split(',')
30
+ .map((item) => item.trim())
31
+ .filter(Boolean);
32
+ }
33
+ /**
34
+ * Parses and validates the raw query-string from a GET request into typed {@link ListOptions}.
35
+ *
36
+ * Validates field names, sort fields, includes, and filter keys against the resource schema.
37
+ * Applies `defaultLimit` and `maxLimit` from the resource definition when set.
38
+ *
39
+ * @param query - The raw query-string object from the HTTP request.
40
+ * @param resource - The resource definition used for field and relation validation.
41
+ * @returns Typed list options ready to pass to `repository.getMany()`.
42
+ */
43
+ export function parseListOptions(query, resource) {
44
+ validateQueryString(resource, query);
45
+ const fields = parseCsv(query.fields);
46
+ const include = parseCsv(query.include);
47
+ let limit = parseInteger(query.limit, 'limit');
48
+ const offset = parseInteger(query.offset, 'offset', 0);
49
+ const order = parseCsv(query.order);
50
+ if (!limit && resource.defaultLimit)
51
+ limit = resource.defaultLimit;
52
+ if (limit && resource.maxLimit && limit > resource.maxLimit)
53
+ limit = resource.maxLimit;
54
+ if (fields) {
55
+ validateFields(resource, fields);
56
+ validateSelectableFields(resource, fields);
57
+ }
58
+ if (include)
59
+ validateIncludes(resource, include);
60
+ if (fields && include) {
61
+ throw new UnprocessableEntityError('Cannot use both ?fields= and ?include= in the same request.');
62
+ }
63
+ const orderFields = order?.map((item) => (item.startsWith('-') ? item.substring(1) : item));
64
+ if (orderFields?.length) {
65
+ validateFields(resource, orderFields);
66
+ validateSortableFields(resource, orderFields);
67
+ }
68
+ const where = {};
69
+ const fieldNames = new Set(resource.fields.map((field) => field.name));
70
+ Object.entries(query).forEach(([key, value]) => {
71
+ if (!fieldNames.has(key))
72
+ return;
73
+ const normalized = Array.isArray(value) ? value : String(value).split(',');
74
+ where[key] = normalized.length === 1 ? normalized[0] : { in: normalized };
75
+ });
76
+ return {
77
+ fields,
78
+ include,
79
+ limit,
80
+ offset,
81
+ where,
82
+ orderBy: order?.map((item) => {
83
+ if (item.startsWith('-'))
84
+ return { field: item.substring(1), direction: SqlOrder.DESC.toLowerCase() };
85
+ return { field: item, direction: SqlOrder.ASC.toLowerCase() };
86
+ })
87
+ };
88
+ }
89
+ //# sourceMappingURL=queryString.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queryString.js","sourceRoot":"","sources":["../../src/core/queryString.ts"],"names":[],"mappings":"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"}