codehooks-js 1.3.25 → 1.4.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,823 @@
1
+ /**
2
+ * Crudlify route documentation generator
3
+ * Generates OpenAPI paths and schemas for auto-generated CRUD routes
4
+ */
5
+
6
+ import { convertToJsonSchema } from './schema-converter.mjs';
7
+
8
+ /**
9
+ * Generate OpenAPI paths and components for crudlify routes
10
+ * @param {object} schemas - Map of collection name to schema
11
+ * @param {object} options - Crudlify options
12
+ * @param {string} [options.prefix] - Route prefix
13
+ * @returns {Promise<{paths: object, components: object}>}
14
+ */
15
+ export async function generateCrudlifyPaths(schemas, options = {}) {
16
+ const prefix = options.prefix || '';
17
+ const paths = {};
18
+ const components = { schemas: {} };
19
+
20
+ // If no schemas defined, generate generic collection docs
21
+ if (!schemas || Object.keys(schemas).length === 0) {
22
+ return generateGenericCrudPaths(prefix);
23
+ }
24
+
25
+ for (const [collection, schemaOrConfig] of Object.entries(schemas)) {
26
+ // Handle both formats:
27
+ // { users: zodSchema } - direct schema
28
+ // { users: { schema: zodSchema } } - wrapped schema
29
+ let schema = schemaOrConfig;
30
+ if (schemaOrConfig && typeof schemaOrConfig === 'object' && schemaOrConfig.schema) {
31
+ schema = schemaOrConfig.schema;
32
+ }
33
+
34
+ // Skip null schemas (allows any data)
35
+ const hasSchema = schema !== null && schema !== undefined;
36
+
37
+ // Check if user provided explicit openApiSchema in config
38
+ let explicitOpenApiSchema = null;
39
+ if (schemaOrConfig && typeof schemaOrConfig === 'object' && schemaOrConfig.openApiSchema) {
40
+ explicitOpenApiSchema = schemaOrConfig.openApiSchema;
41
+ }
42
+
43
+ // Convert schema to JSON Schema
44
+ let jsonSchema = {
45
+ type: 'object',
46
+ additionalProperties: true,
47
+ description: 'Accepts any valid JSON object (no schema validation)'
48
+ };
49
+
50
+ if (explicitOpenApiSchema) {
51
+ // User provided explicit OpenAPI schema - use it directly
52
+ jsonSchema = explicitOpenApiSchema;
53
+ } else if (hasSchema) {
54
+ try {
55
+ jsonSchema = await convertToJsonSchema(schema);
56
+ } catch (error) {
57
+ console.warn(`Failed to convert schema for ${collection}:`, error.message);
58
+ }
59
+ }
60
+
61
+ // Store in components
62
+ const schemaName = capitalizeFirst(collection);
63
+ components.schemas[schemaName] = jsonSchema;
64
+
65
+ // Collection routes: POST (create), GET (list)
66
+ const collectionPath = `${prefix}/${collection}`;
67
+ paths[collectionPath] = {
68
+ get: generateGetManySpec(collection, schemaName),
69
+ post: generateCreateSpec(collection, schemaName, hasSchema)
70
+ };
71
+
72
+ // Document routes: GET, PUT, PATCH, DELETE by ID
73
+ const documentPath = `${prefix}/${collection}/{ID}`;
74
+ paths[documentPath] = {
75
+ get: generateGetOneSpec(collection, schemaName),
76
+ put: generateReplaceSpec(collection, schemaName, hasSchema),
77
+ patch: generatePatchSpec(collection, schemaName),
78
+ delete: generateDeleteSpec(collection, schemaName)
79
+ };
80
+
81
+ // Batch operations: PATCH, DELETE by query
82
+ const byQueryPath = `${prefix}/${collection}/_byquery`;
83
+ paths[byQueryPath] = {
84
+ patch: generatePatchManySpec(collection, schemaName),
85
+ delete: generateDeleteManySpec(collection, schemaName)
86
+ };
87
+ }
88
+
89
+ return { paths, components };
90
+ }
91
+
92
+ /**
93
+ * Generate GET (list) endpoint spec
94
+ */
95
+ function generateGetManySpec(collection, schemaName) {
96
+ return {
97
+ summary: `List ${collection}`,
98
+ description: `Retrieve a list of ${collection} with optional filtering and pagination`,
99
+ tags: [capitalizeFirst(collection)],
100
+ operationId: `list${capitalizeFirst(collection)}`,
101
+ parameters: [
102
+ {
103
+ name: 'q',
104
+ in: 'query',
105
+ description: 'MongoDB-style query filter as JSON string, e.g. {"status":"active"}',
106
+ required: false,
107
+ schema: { type: 'string' }
108
+ },
109
+ {
110
+ name: 'h',
111
+ in: 'query',
112
+ description: 'Query hints as JSON: {"$limit":10,"$offset":0,"$fields":{"name":1},"$sort":{"created":-1}}',
113
+ required: false,
114
+ schema: { type: 'string' }
115
+ },
116
+ {
117
+ name: 'limit',
118
+ in: 'query',
119
+ description: 'Maximum number of items to return',
120
+ required: false,
121
+ schema: { type: 'integer', default: 100 }
122
+ },
123
+ {
124
+ name: 'offset',
125
+ in: 'query',
126
+ description: 'Number of items to skip',
127
+ required: false,
128
+ schema: { type: 'integer', default: 0 }
129
+ },
130
+ {
131
+ name: 'sort',
132
+ in: 'query',
133
+ description: 'Sort field (prefix with - for descending)',
134
+ required: false,
135
+ schema: { type: 'string' }
136
+ }
137
+ ],
138
+ responses: {
139
+ 200: {
140
+ description: `List of ${collection}`,
141
+ content: {
142
+ 'application/json': {
143
+ schema: {
144
+ type: 'array',
145
+ items: { $ref: `#/components/schemas/${schemaName}` }
146
+ }
147
+ }
148
+ }
149
+ },
150
+ 400: { description: 'Invalid query parameters' },
151
+ 404: { description: 'Collection schema not found' }
152
+ }
153
+ };
154
+ }
155
+
156
+ /**
157
+ * Generate POST (create) endpoint spec
158
+ */
159
+ function generateCreateSpec(collection, schemaName, hasSchema) {
160
+ return {
161
+ summary: `Create ${singularize(collection)}`,
162
+ description: `Create a new document in ${collection}${hasSchema ? ' (validated against schema)' : ''}`,
163
+ tags: [capitalizeFirst(collection)],
164
+ operationId: `create${capitalizeFirst(singularize(collection))}`,
165
+ requestBody: {
166
+ required: true,
167
+ description: `${capitalizeFirst(singularize(collection))} data`,
168
+ content: {
169
+ 'application/json': {
170
+ schema: { $ref: `#/components/schemas/${schemaName}` }
171
+ }
172
+ }
173
+ },
174
+ responses: {
175
+ 201: {
176
+ description: `${capitalizeFirst(singularize(collection))} created successfully`,
177
+ content: {
178
+ 'application/json': {
179
+ schema: {
180
+ allOf: [
181
+ { $ref: `#/components/schemas/${schemaName}` },
182
+ {
183
+ type: 'object',
184
+ properties: {
185
+ _id: { type: 'string', description: 'Unique document ID' }
186
+ }
187
+ }
188
+ ]
189
+ }
190
+ }
191
+ }
192
+ },
193
+ 400: { description: 'Validation error or invalid request' }
194
+ }
195
+ };
196
+ }
197
+
198
+ /**
199
+ * Generate GET (one) endpoint spec
200
+ */
201
+ function generateGetOneSpec(collection, schemaName) {
202
+ return {
203
+ summary: `Get ${singularize(collection)} by ID`,
204
+ description: `Retrieve a single ${singularize(collection)} by its unique identifier`,
205
+ tags: [capitalizeFirst(collection)],
206
+ operationId: `get${capitalizeFirst(singularize(collection))}`,
207
+ parameters: [
208
+ {
209
+ name: 'ID',
210
+ in: 'path',
211
+ required: true,
212
+ description: `Unique ${singularize(collection)} identifier`,
213
+ schema: { type: 'string' }
214
+ }
215
+ ],
216
+ responses: {
217
+ 200: {
218
+ description: `${capitalizeFirst(singularize(collection))} found`,
219
+ content: {
220
+ 'application/json': {
221
+ schema: {
222
+ allOf: [
223
+ { $ref: `#/components/schemas/${schemaName}` },
224
+ {
225
+ type: 'object',
226
+ properties: {
227
+ _id: { type: 'string', description: 'Unique document ID' }
228
+ }
229
+ }
230
+ ]
231
+ }
232
+ }
233
+ }
234
+ },
235
+ 404: { description: `${capitalizeFirst(singularize(collection))} not found` }
236
+ }
237
+ };
238
+ }
239
+
240
+ /**
241
+ * Generate PUT (replace) endpoint spec
242
+ */
243
+ function generateReplaceSpec(collection, schemaName, hasSchema) {
244
+ return {
245
+ summary: `Replace ${singularize(collection)}`,
246
+ description: `Replace an existing ${singularize(collection)} document entirely${hasSchema ? ' (validated against schema)' : ''}`,
247
+ tags: [capitalizeFirst(collection)],
248
+ operationId: `replace${capitalizeFirst(singularize(collection))}`,
249
+ parameters: [
250
+ {
251
+ name: 'ID',
252
+ in: 'path',
253
+ required: true,
254
+ description: `Unique ${singularize(collection)} identifier`,
255
+ schema: { type: 'string' }
256
+ }
257
+ ],
258
+ requestBody: {
259
+ required: true,
260
+ description: `Complete ${singularize(collection)} data`,
261
+ content: {
262
+ 'application/json': {
263
+ schema: { $ref: `#/components/schemas/${schemaName}` }
264
+ }
265
+ }
266
+ },
267
+ responses: {
268
+ 200: {
269
+ description: `${capitalizeFirst(singularize(collection))} replaced successfully`,
270
+ content: {
271
+ 'application/json': {
272
+ schema: {
273
+ allOf: [
274
+ { $ref: `#/components/schemas/${schemaName}` },
275
+ {
276
+ type: 'object',
277
+ properties: {
278
+ _id: { type: 'string', description: 'Unique document ID' }
279
+ }
280
+ }
281
+ ]
282
+ }
283
+ }
284
+ }
285
+ },
286
+ 400: { description: 'Validation error' },
287
+ 404: { description: `${capitalizeFirst(singularize(collection))} not found` }
288
+ }
289
+ };
290
+ }
291
+
292
+ /**
293
+ * Generate PATCH (update) endpoint spec
294
+ */
295
+ function generatePatchSpec(collection, schemaName) {
296
+ return {
297
+ summary: `Update ${singularize(collection)}`,
298
+ description: `Partially update an existing ${singularize(collection)} document`,
299
+ tags: [capitalizeFirst(collection)],
300
+ operationId: `update${capitalizeFirst(singularize(collection))}`,
301
+ parameters: [
302
+ {
303
+ name: 'ID',
304
+ in: 'path',
305
+ required: true,
306
+ description: `Unique ${singularize(collection)} identifier`,
307
+ schema: { type: 'string' }
308
+ }
309
+ ],
310
+ requestBody: {
311
+ required: true,
312
+ description: `Partial ${singularize(collection)} data to update`,
313
+ content: {
314
+ 'application/json': {
315
+ schema: {
316
+ type: 'object',
317
+ description: 'Fields to update'
318
+ }
319
+ }
320
+ }
321
+ },
322
+ responses: {
323
+ 200: {
324
+ description: `${capitalizeFirst(singularize(collection))} updated successfully`,
325
+ content: {
326
+ 'application/json': {
327
+ schema: {
328
+ allOf: [
329
+ { $ref: `#/components/schemas/${schemaName}` },
330
+ {
331
+ type: 'object',
332
+ properties: {
333
+ _id: { type: 'string', description: 'Unique document ID' }
334
+ }
335
+ }
336
+ ]
337
+ }
338
+ }
339
+ }
340
+ },
341
+ 400: { description: 'Validation error' },
342
+ 404: { description: `${capitalizeFirst(singularize(collection))} not found` }
343
+ }
344
+ };
345
+ }
346
+
347
+ /**
348
+ * Generate DELETE endpoint spec
349
+ */
350
+ function generateDeleteSpec(collection, schemaName) {
351
+ return {
352
+ summary: `Delete ${singularize(collection)}`,
353
+ description: `Delete a ${singularize(collection)} by its unique identifier`,
354
+ tags: [capitalizeFirst(collection)],
355
+ operationId: `delete${capitalizeFirst(singularize(collection))}`,
356
+ parameters: [
357
+ {
358
+ name: 'ID',
359
+ in: 'path',
360
+ required: true,
361
+ description: `Unique ${singularize(collection)} identifier`,
362
+ schema: { type: 'string' }
363
+ }
364
+ ],
365
+ responses: {
366
+ 200: {
367
+ description: `${capitalizeFirst(singularize(collection))} deleted successfully`,
368
+ content: {
369
+ 'application/json': {
370
+ schema: {
371
+ type: 'object',
372
+ properties: {
373
+ _id: { type: 'string', description: 'ID of deleted document' }
374
+ }
375
+ }
376
+ }
377
+ }
378
+ },
379
+ 404: { description: `${capitalizeFirst(singularize(collection))} not found` }
380
+ }
381
+ };
382
+ }
383
+
384
+ /**
385
+ * Generate PATCH (batch update) endpoint spec
386
+ */
387
+ function generatePatchManySpec(collection, schemaName) {
388
+ return {
389
+ summary: `Batch update ${collection}`,
390
+ description: `Update multiple ${collection} documents matching a query`,
391
+ tags: [capitalizeFirst(collection)],
392
+ operationId: `updateMany${capitalizeFirst(collection)}`,
393
+ parameters: [
394
+ {
395
+ name: 'q',
396
+ in: 'query',
397
+ description: 'MongoDB-style query filter as JSON string',
398
+ required: true,
399
+ schema: { type: 'string' }
400
+ }
401
+ ],
402
+ requestBody: {
403
+ required: true,
404
+ description: 'Fields to update on matching documents',
405
+ content: {
406
+ 'application/json': {
407
+ schema: {
408
+ type: 'object',
409
+ description: 'Update operations'
410
+ }
411
+ }
412
+ }
413
+ },
414
+ responses: {
415
+ 200: {
416
+ description: 'Documents updated successfully',
417
+ content: {
418
+ 'application/json': {
419
+ schema: {
420
+ type: 'object',
421
+ properties: {
422
+ modifiedCount: { type: 'integer', description: 'Number of documents modified' }
423
+ }
424
+ }
425
+ }
426
+ }
427
+ },
428
+ 400: { description: 'Invalid query or validation error' },
429
+ 404: { description: 'Collection schema not found' }
430
+ }
431
+ };
432
+ }
433
+
434
+ /**
435
+ * Generate DELETE (batch) endpoint spec
436
+ */
437
+ function generateDeleteManySpec(collection, schemaName) {
438
+ return {
439
+ summary: `Batch delete ${collection}`,
440
+ description: `Delete multiple ${collection} documents matching a query`,
441
+ tags: [capitalizeFirst(collection)],
442
+ operationId: `deleteMany${capitalizeFirst(collection)}`,
443
+ parameters: [
444
+ {
445
+ name: 'q',
446
+ in: 'query',
447
+ description: 'MongoDB-style query filter as JSON string',
448
+ required: true,
449
+ schema: { type: 'string' }
450
+ }
451
+ ],
452
+ responses: {
453
+ 200: {
454
+ description: 'Documents deleted successfully',
455
+ content: {
456
+ 'application/json': {
457
+ schema: {
458
+ type: 'object',
459
+ properties: {
460
+ deletedCount: { type: 'integer', description: 'Number of documents deleted' }
461
+ }
462
+ }
463
+ }
464
+ }
465
+ },
466
+ 400: { description: 'Invalid query' },
467
+ 404: { description: 'Collection schema not found' }
468
+ }
469
+ };
470
+ }
471
+
472
+ /**
473
+ * Capitalize first letter
474
+ * @param {string} str
475
+ * @returns {string}
476
+ */
477
+ function capitalizeFirst(str) {
478
+ if (!str) return '';
479
+ return str.charAt(0).toUpperCase() + str.slice(1);
480
+ }
481
+
482
+ /**
483
+ * Simple singularize (removes trailing 's')
484
+ * @param {string} str
485
+ * @returns {string}
486
+ */
487
+ function singularize(str) {
488
+ if (!str) return '';
489
+ if (str.endsWith('ies')) {
490
+ return str.slice(0, -3) + 'y';
491
+ }
492
+ if (str.endsWith('es') && (str.endsWith('sses') || str.endsWith('xes') || str.endsWith('ches') || str.endsWith('shes'))) {
493
+ return str.slice(0, -2);
494
+ }
495
+ if (str.endsWith('s') && !str.endsWith('ss')) {
496
+ return str.slice(0, -1);
497
+ }
498
+ return str;
499
+ }
500
+
501
+ /**
502
+ * Generate generic CRUD documentation when no schemas are defined
503
+ * Documents the /{collection} routes that accept any collection name
504
+ * @param {string} prefix - Route prefix
505
+ * @returns {{paths: object, components: object}}
506
+ */
507
+ function generateGenericCrudPaths(prefix) {
508
+ const paths = {};
509
+ const components = {
510
+ schemas: {
511
+ Document: {
512
+ type: 'object',
513
+ additionalProperties: true,
514
+ description: 'Any valid JSON object'
515
+ }
516
+ }
517
+ };
518
+
519
+ const collectionParam = {
520
+ name: 'collection',
521
+ in: 'path',
522
+ required: true,
523
+ description: 'Collection name (e.g., users, products, orders)',
524
+ schema: { type: 'string' }
525
+ };
526
+
527
+ const idParam = {
528
+ name: 'ID',
529
+ in: 'path',
530
+ required: true,
531
+ description: 'Unique document identifier',
532
+ schema: { type: 'string' }
533
+ };
534
+
535
+ // Collection routes: /{collection}
536
+ const collectionPath = `${prefix}/{collection}`;
537
+ paths[collectionPath] = {
538
+ get: {
539
+ summary: 'List documents',
540
+ description: 'Retrieve documents from any collection with optional filtering and pagination',
541
+ tags: ['Collections'],
542
+ operationId: 'listDocuments',
543
+ parameters: [
544
+ collectionParam,
545
+ {
546
+ name: 'q',
547
+ in: 'query',
548
+ description: 'MongoDB-style query filter as JSON string, e.g. {"status":"active"}',
549
+ required: false,
550
+ schema: { type: 'string' }
551
+ },
552
+ {
553
+ name: 'h',
554
+ in: 'query',
555
+ description: 'Query hints as JSON: {"$limit":10,"$offset":0,"$fields":{"name":1},"$sort":{"created":-1}}',
556
+ required: false,
557
+ schema: { type: 'string' }
558
+ },
559
+ {
560
+ name: 'limit',
561
+ in: 'query',
562
+ description: 'Maximum number of items to return',
563
+ required: false,
564
+ schema: { type: 'integer', default: 100 }
565
+ },
566
+ {
567
+ name: 'offset',
568
+ in: 'query',
569
+ description: 'Number of items to skip',
570
+ required: false,
571
+ schema: { type: 'integer', default: 0 }
572
+ }
573
+ ],
574
+ responses: {
575
+ 200: {
576
+ description: 'List of documents',
577
+ content: {
578
+ 'application/json': {
579
+ schema: {
580
+ type: 'array',
581
+ items: { $ref: '#/components/schemas/Document' }
582
+ }
583
+ }
584
+ }
585
+ },
586
+ 400: { description: 'Invalid query parameters' }
587
+ }
588
+ },
589
+ post: {
590
+ summary: 'Create document',
591
+ description: 'Create a new document in any collection. No schema validation is applied.',
592
+ tags: ['Collections'],
593
+ operationId: 'createDocument',
594
+ parameters: [collectionParam],
595
+ requestBody: {
596
+ required: true,
597
+ description: 'Document data (any valid JSON)',
598
+ content: {
599
+ 'application/json': {
600
+ schema: { $ref: '#/components/schemas/Document' }
601
+ }
602
+ }
603
+ },
604
+ responses: {
605
+ 201: {
606
+ description: 'Document created successfully',
607
+ content: {
608
+ 'application/json': {
609
+ schema: {
610
+ allOf: [
611
+ { $ref: '#/components/schemas/Document' },
612
+ {
613
+ type: 'object',
614
+ properties: {
615
+ _id: { type: 'string', description: 'Unique document ID' }
616
+ }
617
+ }
618
+ ]
619
+ }
620
+ }
621
+ }
622
+ },
623
+ 400: { description: 'Invalid request body' }
624
+ }
625
+ }
626
+ };
627
+
628
+ // Document routes: /{collection}/{ID}
629
+ const documentPath = `${prefix}/{collection}/{ID}`;
630
+ paths[documentPath] = {
631
+ get: {
632
+ summary: 'Get document by ID',
633
+ description: 'Retrieve a single document by its unique identifier',
634
+ tags: ['Collections'],
635
+ operationId: 'getDocument',
636
+ parameters: [collectionParam, idParam],
637
+ responses: {
638
+ 200: {
639
+ description: 'Document found',
640
+ content: {
641
+ 'application/json': {
642
+ schema: {
643
+ allOf: [
644
+ { $ref: '#/components/schemas/Document' },
645
+ {
646
+ type: 'object',
647
+ properties: {
648
+ _id: { type: 'string', description: 'Unique document ID' }
649
+ }
650
+ }
651
+ ]
652
+ }
653
+ }
654
+ }
655
+ },
656
+ 404: { description: 'Document not found' }
657
+ }
658
+ },
659
+ put: {
660
+ summary: 'Replace document',
661
+ description: 'Replace an existing document entirely',
662
+ tags: ['Collections'],
663
+ operationId: 'replaceDocument',
664
+ parameters: [collectionParam, idParam],
665
+ requestBody: {
666
+ required: true,
667
+ description: 'Complete document data',
668
+ content: {
669
+ 'application/json': {
670
+ schema: { $ref: '#/components/schemas/Document' }
671
+ }
672
+ }
673
+ },
674
+ responses: {
675
+ 200: {
676
+ description: 'Document replaced successfully',
677
+ content: {
678
+ 'application/json': {
679
+ schema: { $ref: '#/components/schemas/Document' }
680
+ }
681
+ }
682
+ },
683
+ 404: { description: 'Document not found' }
684
+ }
685
+ },
686
+ patch: {
687
+ summary: 'Update document',
688
+ description: 'Partially update an existing document',
689
+ tags: ['Collections'],
690
+ operationId: 'updateDocument',
691
+ parameters: [collectionParam, idParam],
692
+ requestBody: {
693
+ required: true,
694
+ description: 'Fields to update',
695
+ content: {
696
+ 'application/json': {
697
+ schema: {
698
+ type: 'object',
699
+ description: 'Partial document data'
700
+ }
701
+ }
702
+ }
703
+ },
704
+ responses: {
705
+ 200: {
706
+ description: 'Document updated successfully',
707
+ content: {
708
+ 'application/json': {
709
+ schema: { $ref: '#/components/schemas/Document' }
710
+ }
711
+ }
712
+ },
713
+ 404: { description: 'Document not found' }
714
+ }
715
+ },
716
+ delete: {
717
+ summary: 'Delete document',
718
+ description: 'Delete a document by its unique identifier',
719
+ tags: ['Collections'],
720
+ operationId: 'deleteDocument',
721
+ parameters: [collectionParam, idParam],
722
+ responses: {
723
+ 200: {
724
+ description: 'Document deleted successfully',
725
+ content: {
726
+ 'application/json': {
727
+ schema: {
728
+ type: 'object',
729
+ properties: {
730
+ _id: { type: 'string', description: 'ID of deleted document' }
731
+ }
732
+ }
733
+ }
734
+ }
735
+ },
736
+ 404: { description: 'Document not found' }
737
+ }
738
+ }
739
+ };
740
+
741
+ // Batch operations: /{collection}/_byquery
742
+ const byQueryPath = `${prefix}/{collection}/_byquery`;
743
+ paths[byQueryPath] = {
744
+ patch: {
745
+ summary: 'Batch update documents',
746
+ description: 'Update multiple documents matching a query',
747
+ tags: ['Collections'],
748
+ operationId: 'updateDocuments',
749
+ parameters: [
750
+ collectionParam,
751
+ {
752
+ name: 'q',
753
+ in: 'query',
754
+ description: 'MongoDB-style query filter as JSON string',
755
+ required: true,
756
+ schema: { type: 'string' }
757
+ }
758
+ ],
759
+ requestBody: {
760
+ required: true,
761
+ description: 'Fields to update on matching documents',
762
+ content: {
763
+ 'application/json': {
764
+ schema: {
765
+ type: 'object',
766
+ description: 'Update operations'
767
+ }
768
+ }
769
+ }
770
+ },
771
+ responses: {
772
+ 200: {
773
+ description: 'Documents updated successfully',
774
+ content: {
775
+ 'application/json': {
776
+ schema: {
777
+ type: 'object',
778
+ properties: {
779
+ modifiedCount: { type: 'integer', description: 'Number of documents modified' }
780
+ }
781
+ }
782
+ }
783
+ }
784
+ },
785
+ 400: { description: 'Invalid query' }
786
+ }
787
+ },
788
+ delete: {
789
+ summary: 'Batch delete documents',
790
+ description: 'Delete multiple documents matching a query',
791
+ tags: ['Collections'],
792
+ operationId: 'deleteDocuments',
793
+ parameters: [
794
+ collectionParam,
795
+ {
796
+ name: 'q',
797
+ in: 'query',
798
+ description: 'MongoDB-style query filter as JSON string',
799
+ required: true,
800
+ schema: { type: 'string' }
801
+ }
802
+ ],
803
+ responses: {
804
+ 200: {
805
+ description: 'Documents deleted successfully',
806
+ content: {
807
+ 'application/json': {
808
+ schema: {
809
+ type: 'object',
810
+ properties: {
811
+ deletedCount: { type: 'integer', description: 'Number of documents deleted' }
812
+ }
813
+ }
814
+ }
815
+ }
816
+ },
817
+ 400: { description: 'Invalid query' }
818
+ }
819
+ }
820
+ };
821
+
822
+ return { paths, components };
823
+ }