@schmock/openapi 1.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.
Files changed (46) hide show
  1. package/dist/crud-detector.d.ts +35 -0
  2. package/dist/crud-detector.d.ts.map +1 -0
  3. package/dist/crud-detector.js +153 -0
  4. package/dist/generators.d.ts +14 -0
  5. package/dist/generators.d.ts.map +1 -0
  6. package/dist/generators.js +158 -0
  7. package/dist/index.d.ts +3 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +221 -0
  10. package/dist/normalizer.d.ts +14 -0
  11. package/dist/normalizer.d.ts.map +1 -0
  12. package/dist/normalizer.js +194 -0
  13. package/dist/parser.d.ts +32 -0
  14. package/dist/parser.d.ts.map +1 -0
  15. package/dist/parser.js +282 -0
  16. package/dist/plugin.d.ts +32 -0
  17. package/dist/plugin.d.ts.map +1 -0
  18. package/dist/plugin.js +129 -0
  19. package/dist/seed.d.ts +15 -0
  20. package/dist/seed.d.ts.map +1 -0
  21. package/dist/seed.js +41 -0
  22. package/package.json +45 -0
  23. package/src/__fixtures__/faker-stress-test.openapi.yaml +1030 -0
  24. package/src/__fixtures__/openapi31.json +34 -0
  25. package/src/__fixtures__/petstore-openapi3.json +168 -0
  26. package/src/__fixtures__/petstore-swagger2.json +141 -0
  27. package/src/__fixtures__/scalar-galaxy.yaml +1314 -0
  28. package/src/__fixtures__/stripe-fixtures3.json +6542 -0
  29. package/src/__fixtures__/stripe-spec3.yaml +161621 -0
  30. package/src/__fixtures__/train-travel.yaml +1264 -0
  31. package/src/crud-detector.test.ts +150 -0
  32. package/src/crud-detector.ts +194 -0
  33. package/src/generators.test.ts +214 -0
  34. package/src/generators.ts +212 -0
  35. package/src/index.ts +4 -0
  36. package/src/normalizer.test.ts +253 -0
  37. package/src/normalizer.ts +233 -0
  38. package/src/parser.test.ts +181 -0
  39. package/src/parser.ts +389 -0
  40. package/src/plugin.test.ts +205 -0
  41. package/src/plugin.ts +185 -0
  42. package/src/seed.ts +62 -0
  43. package/src/steps/openapi-crud.steps.ts +132 -0
  44. package/src/steps/openapi-parsing.steps.ts +111 -0
  45. package/src/steps/openapi-seed.steps.ts +94 -0
  46. package/src/stress.test.ts +2814 -0
@@ -0,0 +1,1030 @@
1
+ openapi: 3.0.3
2
+ info:
3
+ title: Data Faker Stress Test API
4
+ description: |
5
+ A synthetic OpenAPI spec designed to exercise every tricky schema feature
6
+ that a data faker / mock generator must handle correctly. This is NOT a real
7
+ API — it exists purely as a conformance test for fake-data generators.
8
+
9
+ ## Coverage checklist
10
+ - oneOf / anyOf / allOf with discriminators
11
+ - Recursive / self-referencing schemas
12
+ - Regex patterns & string formats (email, uri, uuid, ipv4, ipv6, date-time, date, byte, binary)
13
+ - Numeric constraints (min, max, exclusiveMin/Max, multipleOf)
14
+ - String constraints (minLength, maxLength, pattern)
15
+ - Array constraints (minItems, maxItems, uniqueItems)
16
+ - Enums (string, integer)
17
+ - Nullable fields
18
+ - readOnly / writeOnly
19
+ - additionalProperties (boolean & schema-typed)
20
+ - Free-form objects
21
+ - Default values
22
+ - Deep $ref chains
23
+ - Maps / dictionaries
24
+ - Empty schema (any type)
25
+ - Nested oneOf inside allOf
26
+ - Const-like single-value enums
27
+ version: 1.0.0
28
+ license:
29
+ name: MIT
30
+
31
+ servers:
32
+ - url: https://faker-test.localhost/api/v1
33
+
34
+ tags:
35
+ - name: Nodes
36
+ description: Recursive tree structures
37
+ - name: Events
38
+ description: Polymorphic event log
39
+ - name: Profiles
40
+ description: Constrained user profiles
41
+ - name: Matrices
42
+ description: Numeric edge cases
43
+ - name: Blobs
44
+ description: Binary and encoded payloads
45
+
46
+ paths:
47
+ # ── Recursive tree ────────────────────────────────────────────────
48
+ /nodes:
49
+ post:
50
+ tags: [Nodes]
51
+ operationId: createNode
52
+ summary: Create a recursive tree node
53
+ requestBody:
54
+ required: true
55
+ content:
56
+ application/json:
57
+ schema:
58
+ $ref: '#/components/schemas/TreeNode'
59
+ responses:
60
+ '201':
61
+ description: Created
62
+ content:
63
+ application/json:
64
+ schema:
65
+ $ref: '#/components/schemas/TreeNode'
66
+ '422':
67
+ description: Validation error
68
+ content:
69
+ application/json:
70
+ schema:
71
+ $ref: '#/components/schemas/ValidationErrorBatch'
72
+
73
+ # ── Polymorphic events ────────────────────────────────────────────
74
+ /events:
75
+ get:
76
+ tags: [Events]
77
+ operationId: listEvents
78
+ summary: List polymorphic events (oneOf + discriminator)
79
+ parameters:
80
+ - $ref: '#/components/parameters/PageOffset'
81
+ - $ref: '#/components/parameters/PageLimit'
82
+ - name: severity
83
+ in: query
84
+ required: false
85
+ schema:
86
+ $ref: '#/components/schemas/SeverityEnum'
87
+ responses:
88
+ '200':
89
+ description: Paginated event list
90
+ headers:
91
+ X-Total-Count:
92
+ schema:
93
+ type: integer
94
+ minimum: 0
95
+ content:
96
+ application/json:
97
+ schema:
98
+ $ref: '#/components/schemas/EventPage'
99
+
100
+ post:
101
+ tags: [Events]
102
+ operationId: createEvent
103
+ summary: Create an event (discriminated union)
104
+ requestBody:
105
+ required: true
106
+ content:
107
+ application/json:
108
+ schema:
109
+ $ref: '#/components/schemas/EventEnvelope'
110
+ responses:
111
+ '201':
112
+ description: Created
113
+ content:
114
+ application/json:
115
+ schema:
116
+ $ref: '#/components/schemas/EventEnvelope'
117
+
118
+ # ── Constrained profile ──────────────────────────────────────────
119
+ /profiles:
120
+ post:
121
+ tags: [Profiles]
122
+ operationId: createProfile
123
+ summary: Create a heavily-constrained user profile
124
+ requestBody:
125
+ required: true
126
+ content:
127
+ application/json:
128
+ schema:
129
+ $ref: '#/components/schemas/UserProfile'
130
+ responses:
131
+ '201':
132
+ description: Created
133
+ content:
134
+ application/json:
135
+ schema:
136
+ $ref: '#/components/schemas/UserProfile'
137
+
138
+ /profiles/{profileId}:
139
+ get:
140
+ tags: [Profiles]
141
+ operationId: getProfile
142
+ parameters:
143
+ - name: profileId
144
+ in: path
145
+ required: true
146
+ schema:
147
+ type: string
148
+ format: uuid
149
+ responses:
150
+ '200':
151
+ description: OK
152
+ content:
153
+ application/json:
154
+ schema:
155
+ $ref: '#/components/schemas/UserProfile'
156
+
157
+ # ── Numeric matrices ─────────────────────────────────────────────
158
+ /matrices:
159
+ post:
160
+ tags: [Matrices]
161
+ operationId: submitMatrix
162
+ summary: Submit a constrained numeric matrix
163
+ requestBody:
164
+ required: true
165
+ content:
166
+ application/json:
167
+ schema:
168
+ $ref: '#/components/schemas/NumericMatrix'
169
+ responses:
170
+ '200':
171
+ description: Processed
172
+ content:
173
+ application/json:
174
+ schema:
175
+ $ref: '#/components/schemas/MatrixResult'
176
+
177
+ # ── Binary blobs ─────────────────────────────────────────────────
178
+ /blobs:
179
+ post:
180
+ tags: [Blobs]
181
+ operationId: uploadBlob
182
+ summary: Upload mixed binary and metadata
183
+ requestBody:
184
+ required: true
185
+ content:
186
+ multipart/form-data:
187
+ schema:
188
+ $ref: '#/components/schemas/BlobUpload'
189
+ application/octet-stream:
190
+ schema:
191
+ type: string
192
+ format: binary
193
+ responses:
194
+ '201':
195
+ description: Stored
196
+ content:
197
+ application/json:
198
+ schema:
199
+ $ref: '#/components/schemas/BlobMeta'
200
+
201
+ # ── Kitchen sink (anyOf body) ────────────────────────────────────
202
+ /actions:
203
+ post:
204
+ tags: [Events]
205
+ operationId: performAction
206
+ summary: Accepts multiple shape variants via anyOf
207
+ requestBody:
208
+ required: true
209
+ content:
210
+ application/json:
211
+ schema:
212
+ $ref: '#/components/schemas/ActionRequest'
213
+ responses:
214
+ '200':
215
+ description: Result depends on variant
216
+ content:
217
+ application/json:
218
+ schema:
219
+ $ref: '#/components/schemas/ActionResult'
220
+
221
+ # ════════════════════════════════════════════════════════════════════
222
+ # COMPONENTS
223
+ # ════════════════════════════════════════════════════════════════════
224
+ components:
225
+
226
+ # ── Parameters ───────────────────────────────────────────────────
227
+ parameters:
228
+ PageOffset:
229
+ name: offset
230
+ in: query
231
+ required: false
232
+ schema:
233
+ type: integer
234
+ minimum: 0
235
+ default: 0
236
+ PageLimit:
237
+ name: limit
238
+ in: query
239
+ required: false
240
+ schema:
241
+ type: integer
242
+ minimum: 1
243
+ maximum: 100
244
+ default: 20
245
+
246
+ # ── Schemas ──────────────────────────────────────────────────────
247
+ schemas:
248
+
249
+ # ·· Enums ····················································
250
+ SeverityEnum:
251
+ type: string
252
+ enum: [debug, info, warning, error, critical]
253
+
254
+ StatusCodeEnum:
255
+ type: integer
256
+ enum: [100, 200, 301, 400, 403, 404, 500, 502, 503]
257
+
258
+ # ·· Recursive tree ···········································
259
+ TreeNode:
260
+ type: object
261
+ required: [id, label]
262
+ properties:
263
+ id:
264
+ type: string
265
+ format: uuid
266
+ readOnly: true
267
+ label:
268
+ type: string
269
+ minLength: 1
270
+ maxLength: 120
271
+ weight:
272
+ type: number
273
+ format: double
274
+ minimum: 0.0
275
+ maximum: 1.0
276
+ default: 0.5
277
+ tags:
278
+ type: array
279
+ items:
280
+ type: string
281
+ pattern: '^[a-z][a-z0-9_-]{0,31}$'
282
+ minItems: 0
283
+ maxItems: 10
284
+ uniqueItems: true
285
+ metadata:
286
+ description: Free-form key→value bag
287
+ type: object
288
+ additionalProperties:
289
+ type: string
290
+ children:
291
+ type: array
292
+ description: Recursive — each child is also a TreeNode
293
+ items:
294
+ $ref: '#/components/schemas/TreeNode'
295
+ maxItems: 50
296
+
297
+ # ·· Polymorphic events (oneOf + discriminator) ················
298
+ EventEnvelope:
299
+ type: object
300
+ required: [eventType, timestamp, payload]
301
+ properties:
302
+ eventType:
303
+ type: string
304
+ timestamp:
305
+ type: string
306
+ format: date-time
307
+ correlationId:
308
+ type: string
309
+ format: uuid
310
+ nullable: true
311
+ payload:
312
+ $ref: '#/components/schemas/EventPayload'
313
+
314
+ EventPayload:
315
+ oneOf:
316
+ - $ref: '#/components/schemas/HttpRequestEvent'
317
+ - $ref: '#/components/schemas/MetricEvent'
318
+ - $ref: '#/components/schemas/DeploymentEvent'
319
+ - $ref: '#/components/schemas/AuditEvent'
320
+ discriminator:
321
+ propertyName: kind
322
+ mapping:
323
+ http_request: '#/components/schemas/HttpRequestEvent'
324
+ metric: '#/components/schemas/MetricEvent'
325
+ deployment: '#/components/schemas/DeploymentEvent'
326
+ audit: '#/components/schemas/AuditEvent'
327
+
328
+ HttpRequestEvent:
329
+ type: object
330
+ required: [kind, method, path, statusCode, durationMs]
331
+ properties:
332
+ kind:
333
+ type: string
334
+ enum: [http_request]
335
+ method:
336
+ type: string
337
+ enum: [GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS]
338
+ path:
339
+ type: string
340
+ pattern: '^/[a-zA-Z0-9/_-]*$'
341
+ maxLength: 2048
342
+ statusCode:
343
+ $ref: '#/components/schemas/StatusCodeEnum'
344
+ durationMs:
345
+ type: number
346
+ format: float
347
+ minimum: 0
348
+ exclusiveMaximum: true
349
+ maximum: 300000
350
+ headers:
351
+ type: object
352
+ additionalProperties:
353
+ type: string
354
+ description: Arbitrary header map
355
+ clientIp:
356
+ type: string
357
+ oneOf:
358
+ - format: ipv4
359
+ - format: ipv6
360
+
361
+ MetricEvent:
362
+ type: object
363
+ required: [kind, name, value, unit]
364
+ properties:
365
+ kind:
366
+ type: string
367
+ enum: [metric]
368
+ name:
369
+ type: string
370
+ pattern: '^[a-z][a-z0-9_.]{0,127}$'
371
+ value:
372
+ type: number
373
+ format: double
374
+ unit:
375
+ type: string
376
+ enum: [ms, bytes, percent, count, rpm]
377
+ dimensions:
378
+ type: object
379
+ additionalProperties:
380
+ type: string
381
+ maxProperties: 10
382
+ percentiles:
383
+ type: object
384
+ description: Map of percentile label → value
385
+ additionalProperties:
386
+ type: number
387
+ format: double
388
+ example:
389
+ p50: 12.5
390
+ p95: 87.3
391
+ p99: 142.0
392
+
393
+ DeploymentEvent:
394
+ type: object
395
+ required: [kind, environment, version, status]
396
+ properties:
397
+ kind:
398
+ type: string
399
+ enum: [deployment]
400
+ environment:
401
+ type: string
402
+ enum: [dev, staging, production]
403
+ version:
404
+ type: string
405
+ description: SemVer string
406
+ pattern: '^\d+\.\d+\.\d+(-[a-zA-Z0-9.]+)?(\+[a-zA-Z0-9.]+)?$'
407
+ status:
408
+ type: string
409
+ enum: [pending, in_progress, succeeded, failed, rolled_back]
410
+ changedServices:
411
+ type: array
412
+ items:
413
+ type: string
414
+ minLength: 1
415
+ minItems: 1
416
+ maxItems: 50
417
+ rollbackTarget:
418
+ type: string
419
+ pattern: '^\d+\.\d+\.\d+(-[a-zA-Z0-9.]+)?(\+[a-zA-Z0-9.]+)?$'
420
+ nullable: true
421
+
422
+ AuditEvent:
423
+ type: object
424
+ required: [kind, actor, action, resource]
425
+ properties:
426
+ kind:
427
+ type: string
428
+ enum: [audit]
429
+ actor:
430
+ $ref: '#/components/schemas/ActorRef'
431
+ action:
432
+ type: string
433
+ enum: [create, read, update, delete, grant, revoke, login, logout]
434
+ resource:
435
+ $ref: '#/components/schemas/ResourceRef'
436
+ diff:
437
+ description: Before/after snapshot (nullable, free-form)
438
+ nullable: true
439
+ type: object
440
+ properties:
441
+ before:
442
+ type: object
443
+ additionalProperties: true
444
+ after:
445
+ type: object
446
+ additionalProperties: true
447
+
448
+ ActorRef:
449
+ type: object
450
+ required: [id, type]
451
+ properties:
452
+ id:
453
+ type: string
454
+ format: uuid
455
+ type:
456
+ type: string
457
+ enum: [user, service_account, api_key]
458
+ displayName:
459
+ type: string
460
+ maxLength: 256
461
+
462
+ ResourceRef:
463
+ type: object
464
+ required: [urn]
465
+ properties:
466
+ urn:
467
+ type: string
468
+ description: 'Uniform resource name like urn:service:type:id'
469
+ pattern: '^urn:[a-z0-9-]+:[a-z0-9-]+:[a-zA-Z0-9_-]+$'
470
+ href:
471
+ type: string
472
+ format: uri
473
+ nullable: true
474
+
475
+ EventPage:
476
+ type: object
477
+ required: [items, total, offset, limit]
478
+ properties:
479
+ items:
480
+ type: array
481
+ items:
482
+ $ref: '#/components/schemas/EventEnvelope'
483
+ total:
484
+ type: integer
485
+ minimum: 0
486
+ offset:
487
+ type: integer
488
+ minimum: 0
489
+ limit:
490
+ type: integer
491
+ minimum: 1
492
+ maximum: 100
493
+
494
+ # ·· Constrained user profile ·································
495
+ UserProfile:
496
+ type: object
497
+ required: [username, email, role, settings]
498
+ properties:
499
+ id:
500
+ type: string
501
+ format: uuid
502
+ readOnly: true
503
+ username:
504
+ type: string
505
+ minLength: 3
506
+ maxLength: 32
507
+ pattern: '^[a-zA-Z][a-zA-Z0-9_-]{2,31}$'
508
+ email:
509
+ type: string
510
+ format: email
511
+ maxLength: 254
512
+ role:
513
+ type: string
514
+ enum: [viewer, editor, admin, superadmin]
515
+ avatarUrl:
516
+ type: string
517
+ format: uri
518
+ nullable: true
519
+ bio:
520
+ type: string
521
+ maxLength: 2000
522
+ default: ''
523
+ dateOfBirth:
524
+ type: string
525
+ format: date
526
+ nullable: true
527
+ settings:
528
+ $ref: '#/components/schemas/ProfileSettings'
529
+ addresses:
530
+ type: array
531
+ items:
532
+ $ref: '#/components/schemas/Address'
533
+ maxItems: 5
534
+ password:
535
+ type: string
536
+ writeOnly: true
537
+ minLength: 12
538
+ maxLength: 128
539
+ pattern: '^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*]).{12,128}$'
540
+ createdAt:
541
+ type: string
542
+ format: date-time
543
+ readOnly: true
544
+ updatedAt:
545
+ type: string
546
+ format: date-time
547
+ readOnly: true
548
+
549
+ ProfileSettings:
550
+ type: object
551
+ required: [locale, timezone, theme]
552
+ properties:
553
+ locale:
554
+ type: string
555
+ description: 'BCP-47 language tag'
556
+ pattern: '^[a-z]{2,3}(-[A-Z]{2})?$'
557
+ default: 'en-US'
558
+ timezone:
559
+ type: string
560
+ description: 'IANA timezone identifier'
561
+ pattern: '^[A-Z][a-zA-Z]+/[A-Z][a-zA-Z_]+$'
562
+ example: 'Europe/Zurich'
563
+ theme:
564
+ type: string
565
+ enum: [light, dark, system]
566
+ default: system
567
+ notifications:
568
+ $ref: '#/components/schemas/NotificationPrefs'
569
+
570
+ NotificationPrefs:
571
+ type: object
572
+ properties:
573
+ emailDigest:
574
+ type: string
575
+ enum: [none, daily, weekly]
576
+ default: weekly
577
+ pushEnabled:
578
+ type: boolean
579
+ default: true
580
+ quietHoursStart:
581
+ type: string
582
+ description: 'HH:MM in 24h format'
583
+ pattern: '^([01]\d|2[0-3]):([0-5]\d)$'
584
+ nullable: true
585
+ quietHoursEnd:
586
+ type: string
587
+ pattern: '^([01]\d|2[0-3]):([0-5]\d)$'
588
+ nullable: true
589
+ channels:
590
+ type: array
591
+ items:
592
+ type: string
593
+ enum: [email, sms, push, slack, webhook]
594
+ uniqueItems: true
595
+ minItems: 1
596
+ maxItems: 5
597
+
598
+ Address:
599
+ type: object
600
+ required: [line1, city, countryCode]
601
+ properties:
602
+ line1:
603
+ type: string
604
+ minLength: 1
605
+ maxLength: 200
606
+ line2:
607
+ type: string
608
+ maxLength: 200
609
+ nullable: true
610
+ city:
611
+ type: string
612
+ minLength: 1
613
+ maxLength: 100
614
+ stateOrProvince:
615
+ type: string
616
+ maxLength: 100
617
+ nullable: true
618
+ postalCode:
619
+ type: string
620
+ pattern: '^\d{4,10}$'
621
+ nullable: true
622
+ countryCode:
623
+ type: string
624
+ description: 'ISO 3166-1 alpha-2'
625
+ pattern: '^[A-Z]{2}$'
626
+ minLength: 2
627
+ maxLength: 2
628
+
629
+ # ·· Numeric matrix edge cases ································
630
+ NumericMatrix:
631
+ type: object
632
+ required: [label, rows]
633
+ properties:
634
+ label:
635
+ type: string
636
+ minLength: 1
637
+ rows:
638
+ type: array
639
+ items:
640
+ $ref: '#/components/schemas/MatrixRow'
641
+ minItems: 1
642
+ maxItems: 100
643
+ precision:
644
+ type: integer
645
+ minimum: 0
646
+ maximum: 15
647
+ default: 6
648
+ description: Decimal places for display
649
+ constraints:
650
+ $ref: '#/components/schemas/CellConstraints'
651
+
652
+ MatrixRow:
653
+ type: object
654
+ required: [cells]
655
+ properties:
656
+ rowLabel:
657
+ type: string
658
+ nullable: true
659
+ cells:
660
+ type: array
661
+ items:
662
+ $ref: '#/components/schemas/MatrixCell'
663
+ minItems: 1
664
+ maxItems: 500
665
+
666
+ MatrixCell:
667
+ oneOf:
668
+ - $ref: '#/components/schemas/NumericCell'
669
+ - $ref: '#/components/schemas/FormulaCell'
670
+ - $ref: '#/components/schemas/EmptyCell'
671
+
672
+ NumericCell:
673
+ type: object
674
+ required: [type, value]
675
+ properties:
676
+ type:
677
+ type: string
678
+ enum: [numeric]
679
+ value:
680
+ type: number
681
+ format: double
682
+ formatted:
683
+ type: string
684
+ readOnly: true
685
+
686
+ FormulaCell:
687
+ type: object
688
+ required: [type, expression]
689
+ properties:
690
+ type:
691
+ type: string
692
+ enum: [formula]
693
+ expression:
694
+ type: string
695
+ description: 'Infix expression referencing cell addresses like A1, B2'
696
+ pattern: '^[A-Z]+\d+([\s]*[+\-*/]\s*[A-Z]+\d+)*$'
697
+ cachedValue:
698
+ type: number
699
+ format: double
700
+ nullable: true
701
+ readOnly: true
702
+
703
+ EmptyCell:
704
+ type: object
705
+ required: [type]
706
+ properties:
707
+ type:
708
+ type: string
709
+ enum: [empty]
710
+
711
+ CellConstraints:
712
+ type: object
713
+ description: Optional per-matrix value constraints
714
+ properties:
715
+ minValue:
716
+ type: number
717
+ format: double
718
+ nullable: true
719
+ maxValue:
720
+ type: number
721
+ format: double
722
+ nullable: true
723
+ multipleOf:
724
+ type: number
725
+ format: double
726
+ exclusiveMinimum: true
727
+ minimum: 0
728
+ nullable: true
729
+ description: 'Every numeric cell must be a multiple of this value'
730
+ allowNegative:
731
+ type: boolean
732
+ default: true
733
+
734
+ MatrixResult:
735
+ type: object
736
+ required: [checksum, stats]
737
+ properties:
738
+ checksum:
739
+ type: string
740
+ pattern: '^[a-f0-9]{64}$'
741
+ description: SHA-256 of the serialized matrix
742
+ stats:
743
+ type: object
744
+ required: [cellCount, numericCount, formulaCount, emptyCount, sum, mean]
745
+ properties:
746
+ cellCount:
747
+ type: integer
748
+ minimum: 0
749
+ numericCount:
750
+ type: integer
751
+ minimum: 0
752
+ formulaCount:
753
+ type: integer
754
+ minimum: 0
755
+ emptyCount:
756
+ type: integer
757
+ minimum: 0
758
+ sum:
759
+ type: number
760
+ format: double
761
+ mean:
762
+ type: number
763
+ format: double
764
+ nullable: true
765
+ stddev:
766
+ type: number
767
+ format: double
768
+ nullable: true
769
+
770
+ # ·· Binary upload ············································
771
+ BlobUpload:
772
+ type: object
773
+ required: [file, checksum]
774
+ properties:
775
+ file:
776
+ type: string
777
+ format: binary
778
+ thumbnail:
779
+ type: string
780
+ format: byte
781
+ description: Base64-encoded thumbnail preview
782
+ nullable: true
783
+ checksum:
784
+ type: string
785
+ description: 'SHA-256 hex digest of the file'
786
+ pattern: '^[a-f0-9]{64}$'
787
+ tags:
788
+ type: array
789
+ items:
790
+ type: string
791
+ minLength: 1
792
+ maxLength: 50
793
+ uniqueItems: true
794
+ maxItems: 20
795
+
796
+ BlobMeta:
797
+ type: object
798
+ required: [id, sizeBytes, mimeType, createdAt]
799
+ properties:
800
+ id:
801
+ type: string
802
+ format: uuid
803
+ sizeBytes:
804
+ type: integer
805
+ format: int64
806
+ minimum: 0
807
+ mimeType:
808
+ type: string
809
+ pattern: '^[a-z]+/[a-z0-9.+-]+$'
810
+ createdAt:
811
+ type: string
812
+ format: date-time
813
+ expiresAt:
814
+ type: string
815
+ format: date-time
816
+ nullable: true
817
+ downloadUrl:
818
+ type: string
819
+ format: uri
820
+
821
+ # ·· anyOf kitchen sink ·······································
822
+ ActionRequest:
823
+ type: object
824
+ required: [action]
825
+ properties:
826
+ action:
827
+ anyOf:
828
+ - $ref: '#/components/schemas/SendEmailAction'
829
+ - $ref: '#/components/schemas/WebhookAction'
830
+ - $ref: '#/components/schemas/TransformAction'
831
+ idempotencyKey:
832
+ type: string
833
+ format: uuid
834
+ dryRun:
835
+ type: boolean
836
+ default: false
837
+ scheduledAt:
838
+ type: string
839
+ format: date-time
840
+ nullable: true
841
+
842
+ SendEmailAction:
843
+ type: object
844
+ required: [type, to, subject, body]
845
+ properties:
846
+ type:
847
+ type: string
848
+ enum: [send_email]
849
+ to:
850
+ type: array
851
+ items:
852
+ type: string
853
+ format: email
854
+ minItems: 1
855
+ maxItems: 50
856
+ cc:
857
+ type: array
858
+ items:
859
+ type: string
860
+ format: email
861
+ maxItems: 20
862
+ default: []
863
+ subject:
864
+ type: string
865
+ minLength: 1
866
+ maxLength: 998
867
+ body:
868
+ type: string
869
+ minLength: 1
870
+ maxLength: 100000
871
+
872
+ WebhookAction:
873
+ type: object
874
+ required: [type, url, method]
875
+ properties:
876
+ type:
877
+ type: string
878
+ enum: [webhook]
879
+ url:
880
+ type: string
881
+ format: uri
882
+ method:
883
+ type: string
884
+ enum: [GET, POST, PUT, PATCH]
885
+ headers:
886
+ type: object
887
+ additionalProperties:
888
+ type: string
889
+ payload:
890
+ description: Arbitrary JSON body
891
+ nullable: true
892
+ oneOf:
893
+ - type: object
894
+ additionalProperties: true
895
+ - type: array
896
+ items: {}
897
+ - type: string
898
+ retryPolicy:
899
+ $ref: '#/components/schemas/RetryPolicy'
900
+
901
+ TransformAction:
902
+ type: object
903
+ required: [type, input, pipeline]
904
+ properties:
905
+ type:
906
+ type: string
907
+ enum: [transform]
908
+ input:
909
+ description: Any valid JSON value
910
+ oneOf:
911
+ - type: string
912
+ - type: number
913
+ - type: integer
914
+ - type: boolean
915
+ - type: object
916
+ additionalProperties: true
917
+ - type: array
918
+ items: {}
919
+ pipeline:
920
+ type: array
921
+ description: Ordered list of transformation steps
922
+ items:
923
+ $ref: '#/components/schemas/TransformStep'
924
+ minItems: 1
925
+ maxItems: 20
926
+
927
+ TransformStep:
928
+ type: object
929
+ required: [op]
930
+ properties:
931
+ op:
932
+ type: string
933
+ enum: [map, filter, reduce, sort, distinct, take, skip, flatten]
934
+ expression:
935
+ type: string
936
+ description: JSONPath or jq-style expression
937
+ nullable: true
938
+ params:
939
+ type: object
940
+ additionalProperties: true
941
+ description: Op-specific params (free-form)
942
+
943
+ RetryPolicy:
944
+ type: object
945
+ properties:
946
+ maxRetries:
947
+ type: integer
948
+ minimum: 0
949
+ maximum: 10
950
+ default: 3
951
+ initialDelayMs:
952
+ type: integer
953
+ minimum: 100
954
+ maximum: 60000
955
+ default: 1000
956
+ backoffMultiplier:
957
+ type: number
958
+ format: float
959
+ minimum: 1.0
960
+ maximum: 5.0
961
+ default: 2.0
962
+ retryOn:
963
+ type: array
964
+ items:
965
+ type: integer
966
+ description: HTTP status codes that trigger retry
967
+ minimum: 400
968
+ maximum: 599
969
+ uniqueItems: true
970
+ default: [500, 502, 503, 504]
971
+
972
+ ActionResult:
973
+ type: object
974
+ required: [requestId, status]
975
+ properties:
976
+ requestId:
977
+ type: string
978
+ format: uuid
979
+ status:
980
+ type: string
981
+ enum: [accepted, completed, failed, scheduled]
982
+ completedAt:
983
+ type: string
984
+ format: date-time
985
+ nullable: true
986
+ output:
987
+ description: Free-form result payload
988
+ nullable: true
989
+ errors:
990
+ type: array
991
+ items:
992
+ $ref: '#/components/schemas/ErrorDetail'
993
+ default: []
994
+
995
+ # ·· Shared error schemas ·····································
996
+ ErrorDetail:
997
+ type: object
998
+ required: [code, message]
999
+ properties:
1000
+ code:
1001
+ type: string
1002
+ pattern: '^[A-Z_]{3,50}$'
1003
+ message:
1004
+ type: string
1005
+ maxLength: 1000
1006
+ field:
1007
+ type: string
1008
+ nullable: true
1009
+ description: 'JSON pointer to the offending field'
1010
+ pattern: '^(/[a-zA-Z0-9_~]+)+$'
1011
+ meta:
1012
+ type: object
1013
+ additionalProperties: true
1014
+ nullable: true
1015
+
1016
+ ValidationErrorBatch:
1017
+ type: object
1018
+ required: [status, errors]
1019
+ properties:
1020
+ status:
1021
+ type: integer
1022
+ enum: [422]
1023
+ traceId:
1024
+ type: string
1025
+ pattern: '^[a-f0-9]{32}$'
1026
+ errors:
1027
+ type: array
1028
+ items:
1029
+ $ref: '#/components/schemas/ErrorDetail'
1030
+ minItems: 1