@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.
@@ -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":"AAAA;;;;;;;GAOG;AAmCH,+EAA+E;AAC/E,YAAY;AACZ,+EAA+E;AAE/E;;GAEG;AACH,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC;AAEvE;;GAEG;AACH,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC;IAC/B,KAAK;IACL,KAAK;IACL,KAAK;IACL,MAAM;IACN,KAAK;IACL,MAAM;IACN,KAAK;IACL,MAAM;IACN,aAAa;IACb,WAAW;IACX,WAAW;IACX,SAAS;IACT,MAAM;IACN,OAAO;CACP,CAAC,CAAC;AAEH,+EAA+E;AAC/E,cAAc;AACd,+EAA+E;AAE/E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,KAAkB,EAAqB,EAAE;IACzE,MAAM,KAAK,GAA4B,EAAE,CAAC;IAC1C,IAAI,IAAgD,CAAC;IACrD,IAAI,KAAyB,CAAC;IAC9B,IAAI,MAA0B,CAAC;IAC/B,IAAI,MAAyC,CAAC;IAE9C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAClD,MAAM,QAAQ,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;QAEvC,6BAA6B;QAC7B,IAAI,GAAG,KAAK,MAAM,IAAI,QAAQ,EAAE,CAAC;YAChC,IAAI,GAAG,cAAc,CAAC,QAAQ,CAAC,CAAC;YAChC,SAAS;QACV,CAAC;QAED,IAAI,GAAG,KAAK,OAAO,IAAI,QAAQ,EAAE,CAAC;YACjC,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;YAC7C,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,MAAM,IAAI,CAAC,EAAE,CAAC;gBAC1C,KAAK,GAAG,MAAM,CAAC;YAChB,CAAC;YACD,SAAS;QACV,CAAC;QAED,IAAI,GAAG,KAAK,QAAQ,IAAI,QAAQ,EAAE,CAAC;YAClC,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;YAC7C,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,MAAM,IAAI,CAAC,EAAE,CAAC;gBAC1C,MAAM,GAAG,MAAM,CAAC;YACjB,CAAC;YACD,SAAS;QACV,CAAC;QAED,IAAI,GAAG,KAAK,QAAQ,IAAI,QAAQ,EAAE,CAAC;YAClC,MAAM,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAC;YACpC,SAAS;QACV,CAAC;QAED,+CAA+C;QAC/C,IAAI,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YAC9B,SAAS;QACV,CAAC;QAED,8CAA8C;QAC9C,MAAM,aAAa,GAAG,GAAG,CAAC,KAAK,CAAC,yBAAyB,CAAC,CAAC;QAC3D,IAAI,aAAa,EAAE,CAAC;YACnB,MAAM,CAAC,EAAE,KAAK,EAAE,QAAQ,CAAC,GAAG,aAAa,CAAC;YAC1C,IAAI,KAAK,IAAI,QAAQ,IAAI,eAAe,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACxD,mBAAmB,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;gBACtD,SAAS;YACV,CAAC;QACF,CAAC;QAED,yBAAyB;QACzB,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC5B,KAAK,CAAC,GAAG,CAAC,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC;QACpC,CAAC;IACF,CAAC;IAED,OAAO;QACN,KAAK;QACL,GAAG,CAAC,IAAI,KAAK,SAAS,IAAI,EAAE,IAAI,EAAE,CAAC;QACnC,GAAG,CAAC,KAAK,KAAK,SAAS,IAAI,EAAE,KAAK,EAAE,CAAC;QACrC,GAAG,CAAC,MAAM,KAAK,SAAS,IAAI,EAAE,MAAM,EAAE,CAAC;QACvC,GAAG,CAAC,MAAM,KAAK,SAAS,IAAI,EAAE,MAAM,EAAE,CAAC;KACvC,CAAC;AACH,CAAC,CAAC;AAEF,+EAA+E;AAC/E,mBAAmB;AACnB,+EAA+E;AAE/E;;;GAGG;AACH,MAAM,cAAc,GAAG,CACtB,KAAqC,EAChB,EAAE;IACvB,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC/B,OAAO,KAAK,IAAI,SAAS,CAAC;IAC3B,CAAC;IACD,iCAAiC;IACjC,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AACvD,CAAC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,cAAc,GAAG,CAAC,KAAa,EAAkC,EAAE;IACxE,MAAM,IAAI,GAAmC,EAAE,CAAC;IAChD,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAE/B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QAC1B,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,CAAC,OAAO;YAAE,SAAS;QAEvB,MAAM,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;QAC5C,IAAI,UAAU,KAAK,CAAC,CAAC,EAAE,CAAC;YACvB,yCAAyC;YACzC,IAAI,CAAC,OAAO,CAAC,GAAG,KAAK,CAAC;QACvB,CAAC;aAAM,CAAC;YACP,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;YAC3C,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;YAE9D,IAAI,KAAK,IAAI,CAAC,SAAS,KAAK,KAAK,IAAI,SAAS,KAAK,MAAM,CAAC,EAAE,CAAC;gBAC5D,IAAI,CAAC,KAAK,CAAC,GAAG,SAAS,CAAC;YACzB,CAAC;iBAAM,IAAI,KAAK,EAAE,CAAC;gBAClB,oCAAoC;gBACpC,IAAI,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC;YACrB,CAAC;QACF,CAAC;IACF,CAAC;IAED,OAAO,IAAI,CAAC;AACb,CAAC,CAAC;AAEF;;GAEG;AACH,MAAM,gBAAgB,GAAG,CAAC,KAAa,EAAyB,EAAE;IACjE,OAAO,KAAK;SACV,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AAC/B,CAAC,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,mBAAmB,GAAG,CAC3B,KAA8B,EAC9B,KAAa,EACb,QAAgB,EAChB,KAAyB,EAClB,EAAE;IACT,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO;IAEhC,mDAAmD;IACnD,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC;IAC9B,MAAM,WAAW,GAChB,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,KAAK,IAAI;QAChD,CAAC,CAAE,QAAoC;QACvC,CAAC,CAAC,EAAE,CAAC;IAEP,iCAAiC;IACjC,IAAI,YAAqB,CAAC;IAE1B,QAAQ,QAAQ,EAAE,CAAC;QAClB,KAAK,KAAK,CAAC;QACX,KAAK,MAAM,CAAC;QACZ,KAAK,KAAK,CAAC;QACX,KAAK,MAAM,CAAC;QACZ,KAAK,OAAO;YACX,+CAA+C;YAC/C,YAAY,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;YACpC,MAAM;QAEP,KAAK,KAAK,CAAC;QACX,KAAK,MAAM,CAAC;QACZ,KAAK,MAAM;YACV,2DAA2D;YAC3D,YAAY,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;YAClE,MAAM;QAEP;YACC,wDAAwD;YACxD,YAAY,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;IACpC,CAAC;IAED,WAAW,CAAC,QAAQ,CAAC,GAAG,YAAY,CAAC;IACrC,KAAK,CAAC,KAAK,CAAC,GAAG,WAAW,CAAC;AAC5B,CAAC,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,WAAW,GAAG,CAAC,KAAa,EAA6B,EAAE;IAChE,mBAAmB;IACnB,IAAI,KAAK,KAAK,MAAM;QAAE,OAAO,IAAI,CAAC;IAClC,IAAI,KAAK,KAAK,OAAO;QAAE,OAAO,KAAK,CAAC;IAEpC,uDAAuD;IACvD,MAAM,QAAQ,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IACtC,IAAI,OAAO,QAAQ,KAAK,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAElD,OAAO,KAAK,CAAC;AACd,CAAC,CAAC;AAEF;;;GAGG;AACH,MAAM,aAAa,GAAG,CAAC,KAAa,EAAmB,EAAE;IACxD,wDAAwD;IACxD,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE;QAAE,OAAO,KAAK,CAAC;IAEhC,oBAAoB;IACpB,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IAC5C,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAC,QAAQ,EAAE,KAAK,KAAK,EAAE,CAAC;QAC9D,OAAO,QAAQ,CAAC;IACjB,CAAC;IAED,YAAY;IACZ,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;IAC5C,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,UAAU,CAAC,QAAQ,EAAE,KAAK,KAAK,EAAE,CAAC;QAClE,OAAO,UAAU,CAAC;IACnB,CAAC;IAED,OAAO,KAAK,CAAC;AACd,CAAC,CAAC;AAiCF;;GAEG;AACH,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC;IAChC,OAAO;IACP,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK;IACL,SAAS;CACT,CAAC,CAAC;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,KAAkB,EAAmB,EAAE;IAC3E,MAAM,MAAM,GAA4B,EAAE,CAAC;IAC3C,MAAM,KAAK,GAA4B,EAAE,CAAC;IAE1C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAClD,MAAM,QAAQ,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;QACvC,IAAI,QAAQ,KAAK,SAAS;YAAE,SAAS;QAErC,uCAAuC;QACvC,IAAI,GAAG,KAAK,OAAO,IAAI,QAAQ,CAAC,WAAW,EAAE,KAAK,MAAM,EAAE,CAAC;YAC1D,MAAM,CAAC,KAAK,GAAG,IAAI,CAAC;YACpB,SAAS;QACV,CAAC;QAED,IAAI,GAAG,KAAK,KAAK,IAAI,GAAG,KAAK,KAAK,IAAI,GAAG,KAAK,KAAK,IAAI,GAAG,KAAK,KAAK,EAAE,CAAC;YACtE,MAAM,MAAM,GAAG,mBAAmB,CAAC,QAAQ,CAAC,CAAC;YAC7C,MAAM,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;YACvD,SAAS;QACV,CAAC;QAED,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;YACvB,MAAM,MAAM,GAAG,mBAAmB,CAAC,QAAQ,CAAC,CAAC;YAC7C,MAAM,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;YAC1D,SAAS;QACV,CAAC;QAED,gDAAgD;QAChD,IAAI,gBAAgB,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YAC/B,SAAS;QACV,CAAC;QAED,8CAA8C;QAC9C,MAAM,aAAa,GAAG,GAAG,CAAC,KAAK,CAAC,yBAAyB,CAAC,CAAC;QAC3D,IAAI,aAAa,EAAE,CAAC;YACnB,MAAM,CAAC,EAAE,KAAK,EAAE,QAAQ,CAAC,GAAG,aAAa,CAAC;YAC1C,IAAI,KAAK,IAAI,QAAQ,IAAI,eAAe,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACxD,mBAAmB,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;gBACtD,SAAS;YACV,CAAC;QACF,CAAC;QAED,yBAAyB;QACzB,KAAK,CAAC,GAAG,CAAC,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC;IACpC,CAAC;IAED,iDAAiD;IACjD,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACnC,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC;IACtB,CAAC;IAED,sDAAsD;IACtD,IACC,MAAM,CAAC,KAAK,KAAK,SAAS;QAC1B,MAAM,CAAC,GAAG,KAAK,SAAS;QACxB,MAAM,CAAC,GAAG,KAAK,SAAS;QACxB,MAAM,CAAC,GAAG,KAAK,SAAS;QACxB,MAAM,CAAC,GAAG,KAAK,SAAS,EACvB,CAAC;QACF,MAAM,CAAC,KAAK,GAAG,IAAI,CAAC;IACrB,CAAC;IAED,OAAO,MAAyB,CAAC;AAClC,CAAC,CAAC;AAEF;;GAEG;AACH,MAAM,mBAAmB,GAAG,CAAC,KAAa,EAAyB,EAAE;IACpE,OAAO,KAAK;SACV,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AAC/B,CAAC,CAAC"}
@@ -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"}