@proseql/rest 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/error-mapping.d.ts +60 -0
- package/dist/error-mapping.d.ts.map +1 -0
- package/dist/error-mapping.js +183 -0
- package/dist/error-mapping.js.map +1 -0
- package/dist/handlers.d.ts +112 -0
- package/dist/handlers.d.ts.map +1 -0
- package/dist/handlers.js +402 -0
- package/dist/handlers.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +46 -0
- package/dist/index.js.map +1 -0
- package/dist/query-params.d.ts +132 -0
- package/dist/query-params.d.ts.map +1 -0
- package/dist/query-params.js +380 -0
- package/dist/query-params.js.map +1 -0
- package/dist/relationship-routes.d.ts +82 -0
- package/dist/relationship-routes.d.ts.map +1 -0
- package/dist/relationship-routes.js +273 -0
- package/dist/relationship-routes.js.map +1 -0
- package/package.json +49 -0
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* REST Query Parameter Parsing for proseql databases.
|
|
3
|
+
*
|
|
4
|
+
* Converts URL query parameters into proseql-compatible query configurations.
|
|
5
|
+
* Supports simple equality, operator syntax, sorting, pagination, and field selection.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// Constants
|
|
11
|
+
// ============================================================================
|
|
12
|
+
/**
|
|
13
|
+
* Reserved parameter names that are not field filters.
|
|
14
|
+
*/
|
|
15
|
+
const RESERVED_PARAMS = new Set(["sort", "limit", "offset", "select"]);
|
|
16
|
+
/**
|
|
17
|
+
* Valid comparison operators for filter syntax.
|
|
18
|
+
*/
|
|
19
|
+
const VALID_OPERATORS = new Set([
|
|
20
|
+
"$eq",
|
|
21
|
+
"$ne",
|
|
22
|
+
"$gt",
|
|
23
|
+
"$gte",
|
|
24
|
+
"$lt",
|
|
25
|
+
"$lte",
|
|
26
|
+
"$in",
|
|
27
|
+
"$nin",
|
|
28
|
+
"$startsWith",
|
|
29
|
+
"$endsWith",
|
|
30
|
+
"$contains",
|
|
31
|
+
"$search",
|
|
32
|
+
"$all",
|
|
33
|
+
"$size",
|
|
34
|
+
]);
|
|
35
|
+
// ============================================================================
|
|
36
|
+
// Main Parser
|
|
37
|
+
// ============================================================================
|
|
38
|
+
/**
|
|
39
|
+
* Parse URL query parameters into a proseql-compatible query configuration.
|
|
40
|
+
*
|
|
41
|
+
* Supports the following syntax:
|
|
42
|
+
*
|
|
43
|
+
* - Simple equality: `?genre=sci-fi` becomes `where: { genre: "sci-fi" }`
|
|
44
|
+
* - Operator syntax: `?year[$gte]=1970&year[$lt]=2000` becomes `where: { year: { $gte: 1970, $lt: 2000 } }`
|
|
45
|
+
* - Sorting: `?sort=year:desc` or `?sort=year:desc,title:asc` becomes `sort: { year: "desc", title: "asc" }`
|
|
46
|
+
* - Pagination: `?limit=10&offset=20` becomes `limit: 10, offset: 20`
|
|
47
|
+
* - Field selection: `?select=title,year` becomes `select: ["title", "year"]`
|
|
48
|
+
*
|
|
49
|
+
* Type coercion is applied:
|
|
50
|
+
* - Numeric strings become numbers when used with numeric operators ($gt, $gte, $lt, $lte, $size)
|
|
51
|
+
* - "true" and "false" become booleans
|
|
52
|
+
* - Comma-separated values in $in/$nin become arrays
|
|
53
|
+
*
|
|
54
|
+
* @param query - URL query parameters as key-value pairs
|
|
55
|
+
* @returns Parsed query configuration for proseql's query method
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```typescript
|
|
59
|
+
* // Simple equality
|
|
60
|
+
* parseQueryParams({ genre: "sci-fi" })
|
|
61
|
+
* // → { where: { genre: "sci-fi" } }
|
|
62
|
+
*
|
|
63
|
+
* // Operator syntax
|
|
64
|
+
* parseQueryParams({ "year[$gte]": "1970", "year[$lt]": "2000" })
|
|
65
|
+
* // → { where: { year: { $gte: 1970, $lt: 2000 } } }
|
|
66
|
+
*
|
|
67
|
+
* // Combined
|
|
68
|
+
* parseQueryParams({
|
|
69
|
+
* genre: "sci-fi",
|
|
70
|
+
* "year[$gte]": "1970",
|
|
71
|
+
* sort: "year:desc",
|
|
72
|
+
* limit: "10",
|
|
73
|
+
* select: "title,year"
|
|
74
|
+
* })
|
|
75
|
+
* // → {
|
|
76
|
+
* // where: { genre: "sci-fi", year: { $gte: 1970 } },
|
|
77
|
+
* // sort: { year: "desc" },
|
|
78
|
+
* // limit: 10,
|
|
79
|
+
* // select: ["title", "year"]
|
|
80
|
+
* // }
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
export const parseQueryParams = (query) => {
|
|
84
|
+
const where = {};
|
|
85
|
+
let sort;
|
|
86
|
+
let limit;
|
|
87
|
+
let offset;
|
|
88
|
+
let select;
|
|
89
|
+
for (const [key, value] of Object.entries(query)) {
|
|
90
|
+
const strValue = normalizeValue(value);
|
|
91
|
+
// Handle reserved parameters
|
|
92
|
+
if (key === "sort" && strValue) {
|
|
93
|
+
sort = parseSortParam(strValue);
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (key === "limit" && strValue) {
|
|
97
|
+
const parsed = Number.parseInt(strValue, 10);
|
|
98
|
+
if (!Number.isNaN(parsed) && parsed >= 0) {
|
|
99
|
+
limit = parsed;
|
|
100
|
+
}
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (key === "offset" && strValue) {
|
|
104
|
+
const parsed = Number.parseInt(strValue, 10);
|
|
105
|
+
if (!Number.isNaN(parsed) && parsed >= 0) {
|
|
106
|
+
offset = parsed;
|
|
107
|
+
}
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (key === "select" && strValue) {
|
|
111
|
+
select = parseSelectParam(strValue);
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
// Skip reserved params that were handled above
|
|
115
|
+
if (RESERVED_PARAMS.has(key)) {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
// Check for operator syntax: field[$op]=value
|
|
119
|
+
const operatorMatch = key.match(/^(.+)\[(\$[a-zA-Z]+)\]$/);
|
|
120
|
+
if (operatorMatch) {
|
|
121
|
+
const [, field, operator] = operatorMatch;
|
|
122
|
+
if (field && operator && VALID_OPERATORS.has(operator)) {
|
|
123
|
+
parseOperatorFilter(where, field, operator, strValue);
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// Simple equality filter
|
|
128
|
+
if (strValue !== undefined) {
|
|
129
|
+
where[key] = coerceValue(strValue);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
where,
|
|
134
|
+
...(sort !== undefined && { sort }),
|
|
135
|
+
...(limit !== undefined && { limit }),
|
|
136
|
+
...(offset !== undefined && { offset }),
|
|
137
|
+
...(select !== undefined && { select }),
|
|
138
|
+
};
|
|
139
|
+
};
|
|
140
|
+
// ============================================================================
|
|
141
|
+
// Helper Functions
|
|
142
|
+
// ============================================================================
|
|
143
|
+
/**
|
|
144
|
+
* Normalize a query parameter value to a string or undefined.
|
|
145
|
+
* Arrays are joined with commas (for $in syntax or multi-value params).
|
|
146
|
+
*/
|
|
147
|
+
const normalizeValue = (value) => {
|
|
148
|
+
if (typeof value === "string") {
|
|
149
|
+
return value || undefined;
|
|
150
|
+
}
|
|
151
|
+
// Value is ReadonlyArray<string>
|
|
152
|
+
return value.length > 0 ? value.join(",") : undefined;
|
|
153
|
+
};
|
|
154
|
+
/**
|
|
155
|
+
* Parse the sort parameter into a sort configuration.
|
|
156
|
+
*
|
|
157
|
+
* Supports single field (`year:desc`) or multiple fields (`year:desc,title:asc`).
|
|
158
|
+
* Defaults to ascending if direction is not specified.
|
|
159
|
+
*/
|
|
160
|
+
const parseSortParam = (value) => {
|
|
161
|
+
const sort = {};
|
|
162
|
+
const parts = value.split(",");
|
|
163
|
+
for (const part of parts) {
|
|
164
|
+
const trimmed = part.trim();
|
|
165
|
+
if (!trimmed)
|
|
166
|
+
continue;
|
|
167
|
+
const colonIndex = trimmed.lastIndexOf(":");
|
|
168
|
+
if (colonIndex === -1) {
|
|
169
|
+
// No direction specified, default to asc
|
|
170
|
+
sort[trimmed] = "asc";
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
const field = trimmed.slice(0, colonIndex);
|
|
174
|
+
const direction = trimmed.slice(colonIndex + 1).toLowerCase();
|
|
175
|
+
if (field && (direction === "asc" || direction === "desc")) {
|
|
176
|
+
sort[field] = direction;
|
|
177
|
+
}
|
|
178
|
+
else if (field) {
|
|
179
|
+
// Invalid direction, default to asc
|
|
180
|
+
sort[field] = "asc";
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return sort;
|
|
185
|
+
};
|
|
186
|
+
/**
|
|
187
|
+
* Parse the select parameter into an array of field names.
|
|
188
|
+
*/
|
|
189
|
+
const parseSelectParam = (value) => {
|
|
190
|
+
return value
|
|
191
|
+
.split(",")
|
|
192
|
+
.map((s) => s.trim())
|
|
193
|
+
.filter((s) => s.length > 0);
|
|
194
|
+
};
|
|
195
|
+
/**
|
|
196
|
+
* Parse an operator filter and add it to the where clause.
|
|
197
|
+
*
|
|
198
|
+
* Handles type coercion based on the operator:
|
|
199
|
+
* - $gt, $gte, $lt, $lte, $size: numeric coercion
|
|
200
|
+
* - $in, $nin: array coercion (comma-separated values)
|
|
201
|
+
* - $all: array coercion
|
|
202
|
+
*/
|
|
203
|
+
const parseOperatorFilter = (where, field, operator, value) => {
|
|
204
|
+
if (value === undefined)
|
|
205
|
+
return;
|
|
206
|
+
// Get or create the operator object for this field
|
|
207
|
+
const existing = where[field];
|
|
208
|
+
const operatorObj = typeof existing === "object" && existing !== null
|
|
209
|
+
? existing
|
|
210
|
+
: {};
|
|
211
|
+
// Coerce value based on operator
|
|
212
|
+
let coercedValue;
|
|
213
|
+
switch (operator) {
|
|
214
|
+
case "$gt":
|
|
215
|
+
case "$gte":
|
|
216
|
+
case "$lt":
|
|
217
|
+
case "$lte":
|
|
218
|
+
case "$size":
|
|
219
|
+
// Numeric operators - attempt numeric coercion
|
|
220
|
+
coercedValue = coerceNumeric(value);
|
|
221
|
+
break;
|
|
222
|
+
case "$in":
|
|
223
|
+
case "$nin":
|
|
224
|
+
case "$all":
|
|
225
|
+
// Array operators - split by comma and coerce each element
|
|
226
|
+
coercedValue = value.split(",").map((v) => coerceValue(v.trim()));
|
|
227
|
+
break;
|
|
228
|
+
default:
|
|
229
|
+
// String operators and others - coerce as regular value
|
|
230
|
+
coercedValue = coerceValue(value);
|
|
231
|
+
}
|
|
232
|
+
operatorObj[operator] = coercedValue;
|
|
233
|
+
where[field] = operatorObj;
|
|
234
|
+
};
|
|
235
|
+
/**
|
|
236
|
+
* Coerce a string value to its appropriate type.
|
|
237
|
+
*
|
|
238
|
+
* - Numeric strings become numbers
|
|
239
|
+
* - "true" and "false" become booleans
|
|
240
|
+
* - Everything else remains a string
|
|
241
|
+
*/
|
|
242
|
+
const coerceValue = (value) => {
|
|
243
|
+
// Boolean coercion
|
|
244
|
+
if (value === "true")
|
|
245
|
+
return true;
|
|
246
|
+
if (value === "false")
|
|
247
|
+
return false;
|
|
248
|
+
// Numeric coercion (only for values that look numeric)
|
|
249
|
+
const numValue = coerceNumeric(value);
|
|
250
|
+
if (typeof numValue === "number")
|
|
251
|
+
return numValue;
|
|
252
|
+
return value;
|
|
253
|
+
};
|
|
254
|
+
/**
|
|
255
|
+
* Attempt to coerce a string to a number.
|
|
256
|
+
* Returns the original string if it's not a valid number.
|
|
257
|
+
*/
|
|
258
|
+
const coerceNumeric = (value) => {
|
|
259
|
+
// Don't coerce empty strings or whitespace-only strings
|
|
260
|
+
if (!value.trim())
|
|
261
|
+
return value;
|
|
262
|
+
// Try integer first
|
|
263
|
+
const intValue = Number.parseInt(value, 10);
|
|
264
|
+
if (!Number.isNaN(intValue) && intValue.toString() === value) {
|
|
265
|
+
return intValue;
|
|
266
|
+
}
|
|
267
|
+
// Try float
|
|
268
|
+
const floatValue = Number.parseFloat(value);
|
|
269
|
+
if (!Number.isNaN(floatValue) && floatValue.toString() === value) {
|
|
270
|
+
return floatValue;
|
|
271
|
+
}
|
|
272
|
+
return value;
|
|
273
|
+
};
|
|
274
|
+
/**
|
|
275
|
+
* Reserved parameter names for aggregate queries.
|
|
276
|
+
*/
|
|
277
|
+
const AGGREGATE_PARAMS = new Set([
|
|
278
|
+
"count",
|
|
279
|
+
"sum",
|
|
280
|
+
"avg",
|
|
281
|
+
"min",
|
|
282
|
+
"max",
|
|
283
|
+
"groupBy",
|
|
284
|
+
]);
|
|
285
|
+
/**
|
|
286
|
+
* Parse URL query parameters into a proseql-compatible aggregate configuration.
|
|
287
|
+
*
|
|
288
|
+
* Supports the following syntax:
|
|
289
|
+
*
|
|
290
|
+
* - Count: `?count=true` becomes `{ count: true }`
|
|
291
|
+
* - Sum: `?sum=pages` or `?sum=pages,price` becomes `{ sum: "pages" }` or `{ sum: ["pages", "price"] }`
|
|
292
|
+
* - Avg: `?avg=rating` becomes `{ avg: "rating" }`
|
|
293
|
+
* - Min/Max: `?min=year&max=year` becomes `{ min: "year", max: "year" }`
|
|
294
|
+
* - GroupBy: `?groupBy=genre` or `?groupBy=genre,year` becomes `{ groupBy: "genre" }` or `{ groupBy: ["genre", "year"] }`
|
|
295
|
+
* - Filters: Same as query params - `?genre=sci-fi` or `?year[$gte]=1970`
|
|
296
|
+
*
|
|
297
|
+
* @param query - URL query parameters as key-value pairs
|
|
298
|
+
* @returns Parsed aggregate configuration for proseql's aggregate method
|
|
299
|
+
*
|
|
300
|
+
* @example
|
|
301
|
+
* ```typescript
|
|
302
|
+
* // Simple count
|
|
303
|
+
* parseAggregateParams({ count: "true" })
|
|
304
|
+
* // → { count: true }
|
|
305
|
+
*
|
|
306
|
+
* // Count with filter
|
|
307
|
+
* parseAggregateParams({ count: "true", genre: "sci-fi" })
|
|
308
|
+
* // → { count: true, where: { genre: "sci-fi" } }
|
|
309
|
+
*
|
|
310
|
+
* // Grouped aggregate
|
|
311
|
+
* parseAggregateParams({ count: "true", groupBy: "genre" })
|
|
312
|
+
* // → { count: true, groupBy: "genre" }
|
|
313
|
+
*
|
|
314
|
+
* // Multiple aggregations
|
|
315
|
+
* parseAggregateParams({ count: "true", sum: "pages", avg: "rating", groupBy: "genre" })
|
|
316
|
+
* // → { count: true, sum: "pages", avg: "rating", groupBy: "genre" }
|
|
317
|
+
* ```
|
|
318
|
+
*/
|
|
319
|
+
export const parseAggregateParams = (query) => {
|
|
320
|
+
const result = {};
|
|
321
|
+
const where = {};
|
|
322
|
+
for (const [key, value] of Object.entries(query)) {
|
|
323
|
+
const strValue = normalizeValue(value);
|
|
324
|
+
if (strValue === undefined)
|
|
325
|
+
continue;
|
|
326
|
+
// Handle aggregate-specific parameters
|
|
327
|
+
if (key === "count" && strValue.toLowerCase() === "true") {
|
|
328
|
+
result.count = true;
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
if (key === "sum" || key === "avg" || key === "min" || key === "max") {
|
|
332
|
+
const fields = parseFieldListParam(strValue);
|
|
333
|
+
result[key] = fields.length === 1 ? fields[0] : fields;
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
if (key === "groupBy") {
|
|
337
|
+
const fields = parseFieldListParam(strValue);
|
|
338
|
+
result.groupBy = fields.length === 1 ? fields[0] : fields;
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
// Skip aggregate params that were handled above
|
|
342
|
+
if (AGGREGATE_PARAMS.has(key)) {
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
// Check for operator syntax: field[$op]=value
|
|
346
|
+
const operatorMatch = key.match(/^(.+)\[(\$[a-zA-Z]+)\]$/);
|
|
347
|
+
if (operatorMatch) {
|
|
348
|
+
const [, field, operator] = operatorMatch;
|
|
349
|
+
if (field && operator && VALID_OPERATORS.has(operator)) {
|
|
350
|
+
parseOperatorFilter(where, field, operator, strValue);
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
// Simple equality filter
|
|
355
|
+
where[key] = coerceValue(strValue);
|
|
356
|
+
}
|
|
357
|
+
// Add where clause if any filters were specified
|
|
358
|
+
if (Object.keys(where).length > 0) {
|
|
359
|
+
result.where = where;
|
|
360
|
+
}
|
|
361
|
+
// If no aggregations were specified, default to count
|
|
362
|
+
if (result.count === undefined &&
|
|
363
|
+
result.sum === undefined &&
|
|
364
|
+
result.avg === undefined &&
|
|
365
|
+
result.min === undefined &&
|
|
366
|
+
result.max === undefined) {
|
|
367
|
+
result.count = true;
|
|
368
|
+
}
|
|
369
|
+
return result;
|
|
370
|
+
};
|
|
371
|
+
/**
|
|
372
|
+
* Parse a comma-separated field list parameter.
|
|
373
|
+
*/
|
|
374
|
+
const parseFieldListParam = (value) => {
|
|
375
|
+
return value
|
|
376
|
+
.split(",")
|
|
377
|
+
.map((s) => s.trim())
|
|
378
|
+
.filter((s) => s.length > 0);
|
|
379
|
+
};
|
|
380
|
+
//# sourceMappingURL=query-params.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query-params.js","sourceRoot":"","sources":["../src/query-params.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* REST Relationship Route Generation for proseql databases.
|
|
3
|
+
*
|
|
4
|
+
* Generates framework-agnostic HTTP handlers for relationship sub-routes
|
|
5
|
+
* derived from collection relationship definitions in the DatabaseConfig.
|
|
6
|
+
*
|
|
7
|
+
* For `ref` relationships (e.g., books.author), generates:
|
|
8
|
+
* GET /books/:id/author — returns the related author entity
|
|
9
|
+
*
|
|
10
|
+
* For `inverse` relationships (e.g., authors.books), generates:
|
|
11
|
+
* GET /authors/:id/books — returns related book entities
|
|
12
|
+
*
|
|
13
|
+
* @module
|
|
14
|
+
*/
|
|
15
|
+
import type { DatabaseConfig, GenerateDatabase, GenerateDatabaseWithPersistence } from "@proseql/core";
|
|
16
|
+
import type { RouteDescriptor } from "./handlers.js";
|
|
17
|
+
/**
|
|
18
|
+
* Relationship definition as found in collection config.
|
|
19
|
+
*/
|
|
20
|
+
interface RelationshipDef {
|
|
21
|
+
readonly type: "ref" | "inverse";
|
|
22
|
+
readonly target: string;
|
|
23
|
+
readonly foreignKey?: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Information about a relationship route to be generated.
|
|
27
|
+
*/
|
|
28
|
+
interface RelationshipRouteInfo {
|
|
29
|
+
/** The source collection name (e.g., "books") */
|
|
30
|
+
readonly sourceCollection: string;
|
|
31
|
+
/** The relationship name (e.g., "author") */
|
|
32
|
+
readonly relationshipName: string;
|
|
33
|
+
/** The relationship definition */
|
|
34
|
+
readonly relationship: RelationshipDef;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Extract all relationship definitions from a database configuration.
|
|
38
|
+
*
|
|
39
|
+
* Iterates over all collections in the config and extracts relationship
|
|
40
|
+
* metadata for each defined relationship.
|
|
41
|
+
*
|
|
42
|
+
* @param config - The database configuration
|
|
43
|
+
* @returns Array of relationship route info objects
|
|
44
|
+
*/
|
|
45
|
+
export declare const extractRelationships: <Config extends DatabaseConfig>(config: Config) => ReadonlyArray<RelationshipRouteInfo>;
|
|
46
|
+
/**
|
|
47
|
+
* Create REST handlers for all relationship routes in a database.
|
|
48
|
+
*
|
|
49
|
+
* Generates sub-routes for navigating relationships:
|
|
50
|
+
* - `ref` relationships: `GET /:collection/:id/:relationshipName`
|
|
51
|
+
* - `inverse` relationships: `GET /:collection/:id/:relationshipName`
|
|
52
|
+
*
|
|
53
|
+
* @param config - The database configuration defining collections and relationships
|
|
54
|
+
* @param db - An EffectDatabase or EffectDatabaseWithPersistence instance
|
|
55
|
+
* @returns Array of route descriptors for relationship routes
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```typescript
|
|
59
|
+
* const config = {
|
|
60
|
+
* books: {
|
|
61
|
+
* schema: BookSchema,
|
|
62
|
+
* relationships: {
|
|
63
|
+
* author: { type: "ref", target: "authors", foreignKey: "authorId" },
|
|
64
|
+
* },
|
|
65
|
+
* },
|
|
66
|
+
* authors: {
|
|
67
|
+
* schema: AuthorSchema,
|
|
68
|
+
* relationships: {
|
|
69
|
+
* books: { type: "inverse", target: "books", foreignKey: "authorId" },
|
|
70
|
+
* },
|
|
71
|
+
* },
|
|
72
|
+
* } as const
|
|
73
|
+
*
|
|
74
|
+
* const routes = createRelationshipRoutes(config, db)
|
|
75
|
+
* // Generates:
|
|
76
|
+
* // GET /books/:id/author — returns the author of a book
|
|
77
|
+
* // GET /authors/:id/books — returns all books by an author
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
export declare const createRelationshipRoutes: <Config extends DatabaseConfig>(config: Config, db: GenerateDatabase<Config> | GenerateDatabaseWithPersistence<Config>) => ReadonlyArray<RouteDescriptor>;
|
|
81
|
+
export {};
|
|
82
|
+
//# sourceMappingURL=relationship-routes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relationship-routes.d.ts","sourceRoot":"","sources":["../src/relationship-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EACX,cAAc,EACd,gBAAgB,EAChB,+BAA+B,EAC/B,MAAM,eAAe,CAAC;AAEvB,OAAO,KAAK,EAA6B,eAAe,EAAE,MAAM,eAAe,CAAC;AAMhF;;GAEG;AACH,UAAU,eAAe;IACxB,QAAQ,CAAC,IAAI,EAAE,KAAK,GAAG,SAAS,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;GAEG;AACH,UAAU,qBAAqB;IAC9B,iDAAiD;IACjD,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,6CAA6C;IAC7C,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,kCAAkC;IAClC,QAAQ,CAAC,YAAY,EAAE,eAAe,CAAC;CACvC;AAMD;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,GAAI,MAAM,SAAS,cAAc,EACjE,QAAQ,MAAM,KACZ,aAAa,CAAC,qBAAqB,CAmBrC,CAAC;AA4HF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,eAAO,MAAM,wBAAwB,GAAI,MAAM,SAAS,cAAc,EACrE,QAAQ,MAAM,EACd,IAAI,gBAAgB,CAAC,MAAM,CAAC,GAAG,+BAA+B,CAAC,MAAM,CAAC,KACpE,aAAa,CAAC,eAAe,CA4C/B,CAAC"}
|