@stonecrop/schema 0.10.16 → 0.11.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 (81) hide show
  1. package/README.md +87 -26
  2. package/dist/cli.cjs +41 -0
  3. package/dist/cli.cjs.map +1 -0
  4. package/dist/cli.js +1 -1
  5. package/dist/converter/heuristics.js +8 -6
  6. package/dist/converter/index.js +20 -3
  7. package/dist/converter-DNwpowpq.js +459 -0
  8. package/dist/converter-DNwpowpq.js.map +1 -0
  9. package/dist/doctype.js +90 -5
  10. package/dist/field.js +5 -3
  11. package/dist/fieldtype.js +0 -2
  12. package/dist/index-0qWNlDQ5.js +450 -0
  13. package/dist/index-0qWNlDQ5.js.map +1 -0
  14. package/dist/index-2UVTfbcY.js +448 -0
  15. package/dist/index-2UVTfbcY.js.map +1 -0
  16. package/dist/index-9QjWZ_PC.js +449 -0
  17. package/dist/index-9QjWZ_PC.js.map +1 -0
  18. package/dist/index-BCADNO5M.js +449 -0
  19. package/dist/index-BCADNO5M.js.map +1 -0
  20. package/dist/index-BRQJGVFR.js +453 -0
  21. package/dist/index-BRQJGVFR.js.map +1 -0
  22. package/dist/index-BatnoC-J.js +429 -0
  23. package/dist/index-BatnoC-J.js.map +1 -0
  24. package/dist/index-BhMd2_xl.js +454 -0
  25. package/dist/index-BhMd2_xl.js.map +1 -0
  26. package/dist/index-CLc5mUMQ.js +478 -0
  27. package/dist/index-CLc5mUMQ.js.map +1 -0
  28. package/dist/index-COrltkHl.js +401 -0
  29. package/dist/index-COrltkHl.js.map +1 -0
  30. package/dist/index-CvN9xK1B.js +453 -0
  31. package/dist/index-CvN9xK1B.js.map +1 -0
  32. package/dist/index-CzoRIy1-.js +408 -0
  33. package/dist/index-CzoRIy1-.js.map +1 -0
  34. package/dist/index-DIb_Z0wI.js +476 -0
  35. package/dist/index-DIb_Z0wI.js.map +1 -0
  36. package/dist/index-DNROIEMe.js +449 -0
  37. package/dist/index-DNROIEMe.js.map +1 -0
  38. package/dist/index-DUFcQC-H.js +449 -0
  39. package/dist/index-DUFcQC-H.js.map +1 -0
  40. package/dist/index-DmD8vmuB.js +449 -0
  41. package/dist/index-DmD8vmuB.js.map +1 -0
  42. package/dist/index-XZZbfFxT.js +434 -0
  43. package/dist/index-XZZbfFxT.js.map +1 -0
  44. package/dist/index-aeXXzPET.cjs +2 -0
  45. package/dist/index-aeXXzPET.cjs.map +1 -0
  46. package/dist/index-neVkjlgB.js +446 -0
  47. package/dist/index-neVkjlgB.js.map +1 -0
  48. package/dist/index-xdlVWldg.js +408 -0
  49. package/dist/index-xdlVWldg.js.map +1 -0
  50. package/dist/index.cjs +2 -0
  51. package/dist/index.cjs.map +1 -0
  52. package/dist/index.js +23 -29
  53. package/dist/jsonschema.js +225 -0
  54. package/dist/schema.d.ts +206 -35
  55. package/dist/src/cli.js +210 -0
  56. package/dist/src/converter/heuristics.d.ts.map +1 -1
  57. package/dist/src/converter/heuristics.js +256 -0
  58. package/dist/src/converter/index.d.ts.map +1 -1
  59. package/dist/src/converter/index.js +164 -0
  60. package/dist/src/converter/scalars.js +86 -0
  61. package/dist/src/converter/types.d.ts +2 -0
  62. package/dist/src/converter/types.d.ts.map +1 -1
  63. package/dist/src/converter/types.js +5 -0
  64. package/dist/src/doctype.d.ts +176 -12
  65. package/dist/src/doctype.d.ts.map +1 -1
  66. package/dist/src/doctype.js +67 -0
  67. package/dist/src/field.d.ts +3 -2
  68. package/dist/src/field.d.ts.map +1 -1
  69. package/dist/src/field.js +108 -0
  70. package/dist/src/fieldtype.d.ts +0 -1
  71. package/dist/src/fieldtype.d.ts.map +1 -1
  72. package/dist/src/fieldtype.js +75 -0
  73. package/dist/src/index.d.ts +5 -9
  74. package/dist/src/index.d.ts.map +1 -1
  75. package/dist/src/index.js +12 -0
  76. package/dist/src/jsonschema.d.ts +95 -0
  77. package/dist/src/jsonschema.d.ts.map +1 -0
  78. package/dist/src/naming.js +106 -0
  79. package/dist/src/tsdoc-metadata.json +11 -0
  80. package/dist/src/validation.js +60 -0
  81. package/package.json +1 -1
package/dist/schema.d.ts CHANGED
@@ -58,6 +58,23 @@ export declare function camelToLabel(camelCase: string): string;
58
58
  */
59
59
  export declare function camelToSnake(camelCase: string): string;
60
60
 
61
+ /**
62
+ * Cardinality for relationship links.
63
+ * @public
64
+ */
65
+ export declare const Cardinality: z.ZodEnum<{
66
+ one: "one";
67
+ atMostOne: "atMostOne";
68
+ noneOrMany: "noneOrMany";
69
+ atLeastOne: "atLeastOne";
70
+ }>;
71
+
72
+ /**
73
+ * Cardinality type inferred from Zod schema
74
+ * @public
75
+ */
76
+ export declare type Cardinality = z.infer<typeof Cardinality>;
77
+
61
78
  /**
62
79
  * Classify a single GraphQL field into a Stonecrop field definition.
63
80
  *
@@ -123,6 +140,21 @@ export declare interface ConvertedGraphQLDoctype extends Omit<DoctypeMeta, 'fiel
123
140
  */
124
141
  export declare function convertGraphQLSchema(source: IntrospectionSource, options?: GraphQLConversionOptions): ConvertedGraphQLDoctype[];
125
142
 
143
+ /**
144
+ * Custom fetch strategy - uses a custom handler function.
145
+ * @public
146
+ */
147
+ export declare const CustomFetch: z.ZodObject<{
148
+ method: z.ZodLiteral<"custom">;
149
+ handler: z.ZodString;
150
+ }, z.core.$strip>;
151
+
152
+ /**
153
+ * Custom fetch strategy type
154
+ * @public
155
+ */
156
+ export declare type CustomFetch = z.infer<typeof CustomFetch>;
157
+
126
158
  /**
127
159
  * Interface for data clients that fetch doctype metadata and records.
128
160
  * Implemented by \@stonecrop/graphql-client's StonecropClient.
@@ -141,23 +173,23 @@ export declare interface DataClient<T extends DoctypeRef = DoctypeRef, M = Docty
141
173
  getMeta(context: DoctypeContext): Promise<M | null>;
142
174
  /**
143
175
  * Fetch a single record by ID
176
+ *
177
+ * When `includeNested` is set, builds a query with sub-selections for descendant
178
+ * links and returns ancestor + merged descendants. When omitted, returns flat scalar data.
179
+ *
144
180
  * @param doctype - Doctype reference (name and optional slug)
145
181
  * @param recordId - Record ID to fetch
182
+ * @param options - Query options
146
183
  * @returns Record data or null if not found
147
184
  */
148
- getRecord(doctype: T, recordId: string): Promise<Record<string, unknown> | null>;
185
+ getRecord(doctype: T, recordId: string, options?: GetRecordOptions): Promise<Record<string, unknown> | null>;
149
186
  /**
150
187
  * Fetch multiple records
151
188
  * @param doctype - Doctype reference (name and optional slug)
152
- * @param options - Optional filters, pagination, sorting
189
+ * @param options - Query options
153
190
  * @returns Array of record data
154
191
  */
155
- getRecords(doctype: T, options?: {
156
- filters?: Record<string, unknown>;
157
- orderBy?: string;
158
- limit?: number;
159
- offset?: number;
160
- }): Promise<Record<string, unknown>[]>;
192
+ getRecords(doctype: T, options?: GetRecordsOptions): Promise<Record<string, unknown>[]>;
161
193
  /**
162
194
  * Execute a doctype action (e.g., SUBMIT, APPROVE, save).
163
195
  * All state changes flow through this single mutation endpoint.
@@ -221,7 +253,7 @@ export declare interface DoctypeContext {
221
253
  * Doctype metadata - complete definition of a doctype
222
254
  * @public
223
255
  */
224
- declare const DoctypeMeta: z.ZodObject<{
256
+ export declare const DoctypeMeta: z.ZodObject<{
225
257
  name: z.ZodString;
226
258
  slug: z.ZodOptional<z.ZodString>;
227
259
  tableName: z.ZodOptional<z.ZodString>;
@@ -242,7 +274,6 @@ declare const DoctypeMeta: z.ZodObject<{
242
274
  JSON: "JSON";
243
275
  Code: "Code";
244
276
  Link: "Link";
245
- Doctype: "Doctype";
246
277
  Attach: "Attach";
247
278
  Currency: "Currency";
248
279
  Quantity: "Quantity";
@@ -267,13 +298,37 @@ declare const DoctypeMeta: z.ZodObject<{
267
298
  options: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>, z.ZodRecord<z.ZodString, z.ZodUnknown>]>>;
268
299
  cardinality: z.ZodOptional<z.ZodEnum<{
269
300
  one: "one";
270
- many: "many";
301
+ atMostOne: "atMostOne";
302
+ noneOrMany: "noneOrMany";
303
+ atLeastOne: "atLeastOne";
271
304
  }>>;
272
305
  mask: z.ZodOptional<z.ZodString>;
273
306
  validation: z.ZodOptional<z.ZodObject<{
274
307
  errorMessage: z.ZodString;
275
308
  }, z.core.$loose>>;
276
309
  }, z.core.$strip>>;
310
+ links: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
311
+ target: z.ZodString;
312
+ cardinality: z.ZodEnum<{
313
+ one: "one";
314
+ atMostOne: "atMostOne";
315
+ noneOrMany: "noneOrMany";
316
+ atLeastOne: "atLeastOne";
317
+ }>;
318
+ backlink: z.ZodOptional<z.ZodString>;
319
+ component: z.ZodOptional<z.ZodString>;
320
+ fieldname: z.ZodOptional<z.ZodString>;
321
+ fetch: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
322
+ method: z.ZodLiteral<"sync">;
323
+ limit: z.ZodOptional<z.ZodNumber>;
324
+ }, z.core.$strip>, z.ZodObject<{
325
+ method: z.ZodLiteral<"lazy">;
326
+ }, z.core.$strip>, z.ZodObject<{
327
+ method: z.ZodLiteral<"custom">;
328
+ handler: z.ZodString;
329
+ }, z.core.$strip>], "method">>;
330
+ blockWorkflows: z.ZodOptional<z.ZodBoolean>;
331
+ }, z.core.$strip>>>;
277
332
  workflow: z.ZodOptional<z.ZodObject<{
278
333
  states: z.ZodOptional<z.ZodArray<z.ZodString>>;
279
334
  actions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
@@ -286,17 +341,13 @@ declare const DoctypeMeta: z.ZodObject<{
286
341
  }, z.core.$strip>>>;
287
342
  }, z.core.$strip>>;
288
343
  inherits: z.ZodOptional<z.ZodString>;
289
- listDoctype: z.ZodOptional<z.ZodString>;
290
- parentDoctype: z.ZodOptional<z.ZodString>;
291
344
  }, z.core.$strip>;
292
345
 
293
346
  /**
294
347
  * Doctype metadata type inferred from Zod schema
295
348
  * @public
296
349
  */
297
- declare type DoctypeMeta = z.infer<typeof DoctypeMeta>;
298
- export { DoctypeMeta }
299
- export { DoctypeMeta as DoctypeMetaType }
350
+ export declare type DoctypeMeta = z.infer<typeof DoctypeMeta>;
300
351
 
301
352
  /**
302
353
  * Base interface for doctype metadata passed to DataClient methods.
@@ -310,6 +361,29 @@ export declare interface DoctypeRef {
310
361
  slug?: string;
311
362
  }
312
363
 
364
+ /**
365
+ * Fetch strategy for link data loading.
366
+ * - sync: fetched in the initial query
367
+ * - lazy: fetched on demand in a separate query
368
+ * - custom: uses a custom handler function
369
+ * @public
370
+ */
371
+ export declare const FetchStrategy: z.ZodDiscriminatedUnion<[z.ZodObject<{
372
+ method: z.ZodLiteral<"sync">;
373
+ limit: z.ZodOptional<z.ZodNumber>;
374
+ }, z.core.$strip>, z.ZodObject<{
375
+ method: z.ZodLiteral<"lazy">;
376
+ }, z.core.$strip>, z.ZodObject<{
377
+ method: z.ZodLiteral<"custom">;
378
+ handler: z.ZodString;
379
+ }, z.core.$strip>], "method">;
380
+
381
+ /**
382
+ * Fetch strategy type
383
+ * @public
384
+ */
385
+ export declare type FetchStrategy = z.infer<typeof FetchStrategy>;
386
+
313
387
  /**
314
388
  * Unified field metadata - the single source of truth for field definitions.
315
389
  * Works for both forms (AForm) and tables (ATable).
@@ -318,7 +392,7 @@ export declare interface DoctypeRef {
318
392
  *
319
393
  * @public
320
394
  */
321
- declare const FieldMeta: z.ZodObject<{
395
+ export declare const FieldMeta: z.ZodObject<{
322
396
  fieldname: z.ZodString;
323
397
  fieldtype: z.ZodEnum<{
324
398
  Data: "Data";
@@ -335,7 +409,6 @@ declare const FieldMeta: z.ZodObject<{
335
409
  JSON: "JSON";
336
410
  Code: "Code";
337
411
  Link: "Link";
338
- Doctype: "Doctype";
339
412
  Attach: "Attach";
340
413
  Currency: "Currency";
341
414
  Quantity: "Quantity";
@@ -360,7 +433,9 @@ declare const FieldMeta: z.ZodObject<{
360
433
  options: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>, z.ZodRecord<z.ZodString, z.ZodUnknown>]>>;
361
434
  cardinality: z.ZodOptional<z.ZodEnum<{
362
435
  one: "one";
363
- many: "many";
436
+ atMostOne: "atMostOne";
437
+ noneOrMany: "noneOrMany";
438
+ atLeastOne: "atLeastOne";
364
439
  }>>;
365
440
  mask: z.ZodOptional<z.ZodString>;
366
441
  validation: z.ZodOptional<z.ZodObject<{
@@ -373,9 +448,7 @@ declare const FieldMeta: z.ZodObject<{
373
448
  * Field metadata type inferred from Zod schema
374
449
  * @public
375
450
  */
376
- declare type FieldMeta = z.infer<typeof FieldMeta>;
377
- export { FieldMeta }
378
- export { FieldMeta as FieldMetaType }
451
+ export declare type FieldMeta = z.infer<typeof FieldMeta>;
379
452
 
380
453
  /**
381
454
  * Field options - flexible bag for type-specific configuration.
@@ -388,15 +461,13 @@ export { FieldMeta as FieldMetaType }
388
461
  *
389
462
  * @public
390
463
  */
391
- declare const FieldOptions: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>, z.ZodRecord<z.ZodString, z.ZodUnknown>]>;
464
+ export declare const FieldOptions: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>, z.ZodRecord<z.ZodString, z.ZodUnknown>]>;
392
465
 
393
466
  /**
394
467
  * Field options type inferred from Zod schema
395
468
  * @public
396
469
  */
397
- declare type FieldOptions = z.infer<typeof FieldOptions>;
398
- export { FieldOptions }
399
- export { FieldOptions as FieldOptionsType }
470
+ export declare type FieldOptions = z.infer<typeof FieldOptions>;
400
471
 
401
472
  /**
402
473
  * Field template for TYPE_MAP entries.
@@ -437,6 +508,40 @@ export declare type FieldValidation = z.infer<typeof FieldValidation>;
437
508
  */
438
509
  export declare function getDefaultComponent(fieldtype: StonecropFieldType): string;
439
510
 
511
+ /**
512
+ * Options for fetching a single record
513
+ * @public
514
+ */
515
+ export declare interface GetRecordOptions {
516
+ /**
517
+ * Include nested link sub-selections.
518
+ * - `true`: include all descendant links
519
+ * - `string[]`: include only named links
520
+ * - `false` / omitted: scalar fields only (default)
521
+ */
522
+ includeNested?: boolean | string[];
523
+ /**
524
+ * Maximum depth for recursive sub-selections.
525
+ * No default — unlimited when omitted.
526
+ */
527
+ maxDepth?: number;
528
+ }
529
+
530
+ /**
531
+ * Options for fetching multiple records
532
+ * @public
533
+ */
534
+ export declare interface GetRecordsOptions {
535
+ /** Filter expression (field-value pairs) */
536
+ filters?: Record<string, unknown>;
537
+ /** Order by expression (e.g. 'NAME_ASC') */
538
+ orderBy?: string;
539
+ /** Maximum number of records to return */
540
+ limit?: number;
541
+ /** Number of records to skip */
542
+ offset?: number;
543
+ }
544
+
440
545
  /**
441
546
  * Mapping from standard GraphQL scalar types to Stonecrop field types.
442
547
  * These are defined by the GraphQL specification and are always available.
@@ -456,6 +561,8 @@ export declare interface GraphQLConversionFieldMeta extends FieldMeta {
456
561
  _graphqlType?: string;
457
562
  /** Marks fields that couldn't be automatically mapped */
458
563
  _unmapped?: boolean;
564
+ /** Marks relationship fields that belong in `links`, not `fields` */
565
+ _isLink?: boolean;
459
566
  }
460
567
 
461
568
  /**
@@ -579,6 +686,53 @@ export declare const INTERNAL_SCALARS: Set<string>;
579
686
  */
580
687
  export declare type IntrospectionSource = IntrospectionQuery | string;
581
688
 
689
+ /**
690
+ * Lazy fetch strategy - data is fetched on demand in a separate query.
691
+ * @public
692
+ */
693
+ export declare const LazyFetch: z.ZodObject<{
694
+ method: z.ZodLiteral<"lazy">;
695
+ }, z.core.$strip>;
696
+
697
+ /**
698
+ * Lazy fetch strategy type
699
+ * @public
700
+ */
701
+ export declare type LazyFetch = z.infer<typeof LazyFetch>;
702
+
703
+ /**
704
+ * Link declaration - describes a relationship from one doctype to another.
705
+ * @public
706
+ */
707
+ export declare const LinkDeclaration: z.ZodObject<{
708
+ target: z.ZodString;
709
+ cardinality: z.ZodEnum<{
710
+ one: "one";
711
+ atMostOne: "atMostOne";
712
+ noneOrMany: "noneOrMany";
713
+ atLeastOne: "atLeastOne";
714
+ }>;
715
+ backlink: z.ZodOptional<z.ZodString>;
716
+ component: z.ZodOptional<z.ZodString>;
717
+ fieldname: z.ZodOptional<z.ZodString>;
718
+ fetch: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
719
+ method: z.ZodLiteral<"sync">;
720
+ limit: z.ZodOptional<z.ZodNumber>;
721
+ }, z.core.$strip>, z.ZodObject<{
722
+ method: z.ZodLiteral<"lazy">;
723
+ }, z.core.$strip>, z.ZodObject<{
724
+ method: z.ZodLiteral<"custom">;
725
+ handler: z.ZodString;
726
+ }, z.core.$strip>], "method">>;
727
+ blockWorkflows: z.ZodOptional<z.ZodBoolean>;
728
+ }, z.core.$strip>;
729
+
730
+ /**
731
+ * Link declaration type inferred from Zod schema
732
+ * @public
733
+ */
734
+ export declare type LinkDeclaration = z.infer<typeof LinkDeclaration>;
735
+
582
736
  /**
583
737
  * Parse and validate a doctype, throwing on failure
584
738
  * @param data - Data to parse
@@ -610,6 +764,13 @@ export declare function parseField(data: unknown): FieldMeta;
610
764
  */
611
765
  export declare function pascalToSnake(pascal: string): string;
612
766
 
767
+ /**
768
+ * Serialized function type - a function serialized to a string.
769
+ * Used for custom fetch handlers.
770
+ * @public
771
+ */
772
+ export declare type SerializedFunction = string;
773
+
613
774
  /**
614
775
  * Converts snake_case to camelCase
615
776
  * @param snakeCase - Snake case string
@@ -641,7 +802,7 @@ export declare function snakeToLabel(snakeCase: string): string;
641
802
  * These are consistent across forms and tables.
642
803
  * @public
643
804
  */
644
- declare const StonecropFieldType: z.ZodEnum<{
805
+ export declare const StonecropFieldType: z.ZodEnum<{
645
806
  Data: "Data";
646
807
  Text: "Text";
647
808
  Int: "Int";
@@ -656,7 +817,6 @@ declare const StonecropFieldType: z.ZodEnum<{
656
817
  JSON: "JSON";
657
818
  Code: "Code";
658
819
  Link: "Link";
659
- Doctype: "Doctype";
660
820
  Attach: "Attach";
661
821
  Currency: "Currency";
662
822
  Quantity: "Quantity";
@@ -667,9 +827,22 @@ declare const StonecropFieldType: z.ZodEnum<{
667
827
  * Stonecrop field type enum inferred from Zod schema
668
828
  * @public
669
829
  */
670
- declare type StonecropFieldType = z.infer<typeof StonecropFieldType>;
671
- export { StonecropFieldType }
672
- export { StonecropFieldType as StonecropFieldTypeValue }
830
+ export declare type StonecropFieldType = z.infer<typeof StonecropFieldType>;
831
+
832
+ /**
833
+ * Sync fetch strategy - data is fetched in the initial query.
834
+ * @public
835
+ */
836
+ export declare const SyncFetch: z.ZodObject<{
837
+ method: z.ZodLiteral<"sync">;
838
+ limit: z.ZodOptional<z.ZodNumber>;
839
+ }, z.core.$strip>;
840
+
841
+ /**
842
+ * Sync fetch strategy type
843
+ * @public
844
+ */
845
+ export declare type SyncFetch = z.infer<typeof SyncFetch>;
673
846
 
674
847
  /**
675
848
  * Convert table name to PascalCase doctype name
@@ -748,7 +921,7 @@ export declare const WELL_KNOWN_SCALARS: Record<string, FieldTemplate>;
748
921
  * Workflow metadata - states and actions for a doctype
749
922
  * @public
750
923
  */
751
- declare const WorkflowMeta: z.ZodObject<{
924
+ export declare const WorkflowMeta: z.ZodObject<{
752
925
  states: z.ZodOptional<z.ZodArray<z.ZodString>>;
753
926
  actions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
754
927
  label: z.ZodString;
@@ -764,8 +937,6 @@ declare const WorkflowMeta: z.ZodObject<{
764
937
  * Workflow metadata type inferred from Zod schema
765
938
  * @public
766
939
  */
767
- declare type WorkflowMeta = z.infer<typeof WorkflowMeta>;
768
- export { WorkflowMeta }
769
- export { WorkflowMeta as WorkflowMetaType }
940
+ export declare type WorkflowMeta = z.infer<typeof WorkflowMeta>;
770
941
 
771
942
  export { }
@@ -0,0 +1,210 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Stonecrop Schema CLI
4
+ *
5
+ * Converts GraphQL introspection results to Stonecrop doctype JSON schemas.
6
+ *
7
+ * Usage:
8
+ * stonecrop-schema generate --endpoint <url> --output <dir>
9
+ * stonecrop-schema generate --introspection <file.json> --output <dir>
10
+ * stonecrop-schema generate --sdl <file.graphql> --output <dir>
11
+ *
12
+ * @packageDocumentation
13
+ */
14
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
15
+ import { resolve, join } from 'node:path';
16
+ import { parseArgs } from 'node:util';
17
+ import { getIntrospectionQuery } from 'graphql';
18
+ import { convertGraphQLSchema } from './converter/index';
19
+ import { validateDoctype } from './validation';
20
+ /**
21
+ * Fetch an introspection result from a live GraphQL endpoint.
22
+ *
23
+ * @param endpoint - The GraphQL endpoint URL
24
+ * @param headers - Optional HTTP headers
25
+ * @returns The introspection query result
26
+ */
27
+ async function fetchIntrospection(endpoint, headers) {
28
+ const response = await fetch(endpoint, {
29
+ method: 'POST',
30
+ headers: {
31
+ 'Content-Type': 'application/json',
32
+ ...headers,
33
+ },
34
+ body: JSON.stringify({
35
+ query: getIntrospectionQuery(),
36
+ }),
37
+ });
38
+ if (!response.ok) {
39
+ throw new Error(`Failed to fetch introspection: ${response.status} ${response.statusText}`);
40
+ }
41
+ const json = (await response.json());
42
+ if (json.errors?.length) {
43
+ throw new Error(`GraphQL errors: ${json.errors.map(e => e.message).join(', ')}`);
44
+ }
45
+ if (!json.data) {
46
+ throw new Error('No data in introspection response');
47
+ }
48
+ return json.data;
49
+ }
50
+ async function main() {
51
+ const { values, positionals } = parseArgs({
52
+ allowPositionals: true,
53
+ options: {
54
+ endpoint: { type: 'string', short: 'e' },
55
+ introspection: { type: 'string', short: 'i' },
56
+ sdl: { type: 'string', short: 's' },
57
+ output: { type: 'string', short: 'o' },
58
+ include: { type: 'string' },
59
+ exclude: { type: 'string' },
60
+ overrides: { type: 'string' },
61
+ 'custom-scalars': { type: 'string' },
62
+ 'include-unmapped': { type: 'boolean', default: false },
63
+ help: { type: 'boolean', short: 'h' },
64
+ },
65
+ });
66
+ const command = positionals[0];
67
+ if (values.help || !command) {
68
+ printHelp();
69
+ process.exit(command ? 0 : 1);
70
+ }
71
+ if (command !== 'generate') {
72
+ console.error(`Unknown command: ${command}`);
73
+ console.error('Available commands: generate');
74
+ process.exit(1);
75
+ }
76
+ // Determine source
77
+ const sourceCount = [values.endpoint, values.introspection, values.sdl].filter(Boolean).length;
78
+ if (sourceCount !== 1) {
79
+ console.error('Exactly one of --endpoint, --introspection, or --sdl must be provided');
80
+ process.exit(1);
81
+ }
82
+ if (!values.output) {
83
+ console.error('--output <dir> is required');
84
+ process.exit(1);
85
+ }
86
+ const outputDir = resolve(values.output);
87
+ // Build conversion options
88
+ const options = {
89
+ includeUnmappedMeta: values['include-unmapped'],
90
+ };
91
+ if (values.include) {
92
+ options.include = values.include.split(',').map(s => s.trim());
93
+ }
94
+ if (values.exclude) {
95
+ options.exclude = values.exclude.split(',').map(s => s.trim());
96
+ }
97
+ if (values.overrides) {
98
+ const overridesPath = resolve(values.overrides);
99
+ const overridesContent = readFileSync(overridesPath, 'utf-8');
100
+ options.typeOverrides = JSON.parse(overridesContent);
101
+ }
102
+ if (values['custom-scalars']) {
103
+ const scalarsPath = resolve(values['custom-scalars']);
104
+ const scalarsContent = readFileSync(scalarsPath, 'utf-8');
105
+ options.customScalars = JSON.parse(scalarsContent);
106
+ }
107
+ // Resolve source
108
+ let source;
109
+ if (values.endpoint) {
110
+ console.log(`Fetching introspection from ${values.endpoint}...`);
111
+ source = await fetchIntrospection(values.endpoint);
112
+ }
113
+ else if (values.introspection) {
114
+ const filePath = resolve(values.introspection);
115
+ const content = readFileSync(filePath, 'utf-8');
116
+ const parsed = JSON.parse(content);
117
+ // Handle both { data: { __schema: ... } } and { __schema: ... } formats
118
+ source = parsed.data ?? parsed;
119
+ }
120
+ else {
121
+ const filePath = resolve(values.sdl);
122
+ source = readFileSync(filePath, 'utf-8');
123
+ }
124
+ // Convert
125
+ const doctypes = convertGraphQLSchema(source, options);
126
+ if (doctypes.length === 0) {
127
+ console.warn('No entity types found in the schema. Check your include/exclude filters.');
128
+ process.exit(0);
129
+ }
130
+ // Write output
131
+ if (!existsSync(outputDir)) {
132
+ mkdirSync(outputDir, { recursive: true });
133
+ }
134
+ let warnings = 0;
135
+ let errors = 0;
136
+ for (const doctype of doctypes) {
137
+ const fileName = `${doctype.slug}.json`;
138
+ const filePath = join(outputDir, fileName);
139
+ const json = JSON.stringify(doctype, null, '\t');
140
+ writeFileSync(filePath, json + '\n', 'utf-8');
141
+ // Validate the output
142
+ const validation = validateDoctype(doctype);
143
+ if (!validation.success) {
144
+ errors++;
145
+ console.error(` ERROR: ${fileName} failed validation:`);
146
+ for (const err of validation.errors) {
147
+ console.error(` ${err.path.join('.')}: ${err.message}`);
148
+ }
149
+ }
150
+ else {
151
+ // Check for unmapped fields
152
+ const unmappedFields = doctype.fields.filter((f) => f._unmapped);
153
+ if (unmappedFields.length > 0) {
154
+ warnings++;
155
+ console.warn(` WARN: ${fileName} has ${unmappedFields.length} unmapped field(s): ${unmappedFields
156
+ .map((f) => f.fieldname)
157
+ .join(', ')}`);
158
+ }
159
+ }
160
+ }
161
+ console.log(`\nGenerated ${doctypes.length} doctype(s) in ${outputDir}` +
162
+ (warnings ? ` (${warnings} with warnings)` : '') +
163
+ (errors ? ` (${errors} with errors)` : ''));
164
+ if (errors > 0) {
165
+ process.exit(1);
166
+ }
167
+ }
168
+ function printHelp() {
169
+ console.log(`
170
+ stonecrop-schema - Convert GraphQL schemas to Stonecrop doctypes
171
+
172
+ USAGE:
173
+ stonecrop-schema generate [options]
174
+
175
+ SOURCE (exactly one required):
176
+ --endpoint, -e <url> Fetch introspection from a live GraphQL endpoint
177
+ --introspection, -i <file> Read from a saved introspection JSON file
178
+ --sdl, -s <file> Read from a GraphQL SDL (.graphql) file
179
+
180
+ OUTPUT:
181
+ --output, -o <dir> Directory to write doctype JSON files (required)
182
+
183
+ OPTIONS:
184
+ --include <types> Comma-separated list of type names to include
185
+ --exclude <types> Comma-separated list of type names to exclude
186
+ --overrides <file> JSON file with per-type field overrides
187
+ --custom-scalars <file> JSON file mapping custom scalar names to field templates
188
+ --include-unmapped Include _graphqlType metadata on unmapped fields
189
+ --help, -h Show this help message
190
+
191
+ EXAMPLES:
192
+ # From a live PostGraphile server
193
+ stonecrop-schema generate -e http://localhost:5000/graphql -o ./schemas
194
+
195
+ # From a saved introspection result
196
+ stonecrop-schema generate -i introspection.json -o ./schemas
197
+
198
+ # From an SDL file with custom scalars
199
+ stonecrop-schema generate -s schema.graphql -o ./schemas \\
200
+ --custom-scalars custom-scalars.json
201
+
202
+ # Only convert specific types
203
+ stonecrop-schema generate -e http://localhost:5000/graphql -o ./schemas \\
204
+ --include "User,Post,Comment"
205
+ `);
206
+ }
207
+ main().catch(err => {
208
+ console.error('Error:', err.message);
209
+ process.exit(1);
210
+ });
@@ -1 +1 @@
1
- {"version":3,"file":"heuristics.d.ts","sourceRoot":"","sources":["../../../src/converter/heuristics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAMN,KAAK,iBAAiB,EACtB,KAAK,YAAY,EAGjB,MAAM,SAAS,CAAA;AAGhB,OAAO,KAAK,EAAE,0BAA0B,EAAE,wBAAwB,EAAE,MAAM,SAAS,CAAA;AA+BnF;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,GAAG,OAAO,CA8BtF;AAQD;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CACnC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,EACtC,WAAW,EAAE,iBAAiB,GAC5B,OAAO,CAET;AAsED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,iBAAiB,CAChC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,EACrC,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,EACxB,OAAO,GAAE,wBAA6B,GACpC,0BAA0B,CA2F5B"}
1
+ {"version":3,"file":"heuristics.d.ts","sourceRoot":"","sources":["../../../src/converter/heuristics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAMN,KAAK,iBAAiB,EACtB,KAAK,YAAY,EAGjB,MAAM,SAAS,CAAA;AAGhB,OAAO,KAAK,EAAE,0BAA0B,EAAE,wBAAwB,EAAE,MAAM,SAAS,CAAA;AA+BnF;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,GAAG,OAAO,CA8BtF;AAQD;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CACnC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,EACtC,WAAW,EAAE,iBAAiB,GAC5B,OAAO,CAET;AAsED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,iBAAiB,CAChC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,EACrC,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,EACxB,OAAO,GAAE,wBAA6B,GACpC,0BAA0B,CA6F5B"}