@powerhousedao/builder-tools 5.1.0-dev.4 → 5.1.0-dev.40

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 (60) hide show
  1. package/dist/connect-utils/env-config.d.ts +14 -0
  2. package/dist/connect-utils/env-config.d.ts.map +1 -1
  3. package/dist/connect-utils/env-config.js +5 -0
  4. package/dist/connect-utils/env-config.js.map +1 -1
  5. package/dist/connect-utils/vite-config.js +1 -1
  6. package/dist/connect-utils/vite-config.js.map +1 -1
  7. package/dist/connect-utils/vite-plugins/ph-external-packages.d.ts.map +1 -1
  8. package/dist/connect-utils/vite-plugins/ph-external-packages.js +10 -26
  9. package/dist/connect-utils/vite-plugins/ph-external-packages.js.map +1 -1
  10. package/dist/document-model-editor/components/code-editors/graphql-editor.js +1 -1
  11. package/dist/document-model-editor/components/code-editors/graphql-editor.js.map +1 -1
  12. package/dist/document-model-editor/components/module.d.ts +1 -0
  13. package/dist/document-model-editor/components/module.d.ts.map +1 -1
  14. package/dist/document-model-editor/components/module.js +2 -2
  15. package/dist/document-model-editor/components/module.js.map +1 -1
  16. package/dist/document-model-editor/components/modules.d.ts +2 -1
  17. package/dist/document-model-editor/components/modules.d.ts.map +1 -1
  18. package/dist/document-model-editor/components/modules.js +2 -2
  19. package/dist/document-model-editor/components/modules.js.map +1 -1
  20. package/dist/document-model-editor/components/operation.d.ts +1 -0
  21. package/dist/document-model-editor/components/operation.d.ts.map +1 -1
  22. package/dist/document-model-editor/components/operation.js +6 -3
  23. package/dist/document-model-editor/components/operation.js.map +1 -1
  24. package/dist/document-model-editor/components/operations.d.ts +2 -1
  25. package/dist/document-model-editor/components/operations.d.ts.map +1 -1
  26. package/dist/document-model-editor/components/operations.js +2 -2
  27. package/dist/document-model-editor/components/operations.js.map +1 -1
  28. package/dist/document-model-editor/components/state-error.d.ts +5 -0
  29. package/dist/document-model-editor/components/state-error.d.ts.map +1 -0
  30. package/dist/document-model-editor/components/state-error.js +21 -0
  31. package/dist/document-model-editor/components/state-error.js.map +1 -0
  32. package/dist/document-model-editor/components/state-schemas.d.ts.map +1 -1
  33. package/dist/document-model-editor/components/state-schemas.js +45 -19
  34. package/dist/document-model-editor/components/state-schemas.js.map +1 -1
  35. package/dist/document-model-editor/constants/documents.d.ts +1 -0
  36. package/dist/document-model-editor/constants/documents.d.ts.map +1 -1
  37. package/dist/document-model-editor/constants/documents.js +2 -1
  38. package/dist/document-model-editor/constants/documents.js.map +1 -1
  39. package/dist/document-model-editor/context/schema-context.d.ts +7 -2
  40. package/dist/document-model-editor/context/schema-context.d.ts.map +1 -1
  41. package/dist/document-model-editor/context/schema-context.js +35 -23
  42. package/dist/document-model-editor/context/schema-context.js.map +1 -1
  43. package/dist/document-model-editor/editor.d.ts.map +1 -1
  44. package/dist/document-model-editor/editor.js +40 -29
  45. package/dist/document-model-editor/editor.js.map +1 -1
  46. package/dist/document-model-editor/utils/helpers.d.ts +77 -1
  47. package/dist/document-model-editor/utils/helpers.d.ts.map +1 -1
  48. package/dist/document-model-editor/utils/helpers.js +575 -12
  49. package/dist/document-model-editor/utils/helpers.js.map +1 -1
  50. package/dist/style.css +47 -0
  51. package/dist/test/helpers.test.d.ts +2 -0
  52. package/dist/test/helpers.test.d.ts.map +1 -0
  53. package/dist/test/helpers.test.js +805 -0
  54. package/dist/test/helpers.test.js.map +1 -0
  55. package/dist/test/index.d.ts +2 -0
  56. package/dist/test/index.d.ts.map +1 -0
  57. package/dist/test/index.js +2 -0
  58. package/dist/test/index.js.map +1 -0
  59. package/dist/tsconfig.tsbuildinfo +1 -1
  60. package/package.json +10 -8
@@ -1,6 +1,7 @@
1
1
  import { getPHCustomScalarByTypeName } from "@powerhousedao/document-engineering/graphql";
2
2
  import { pascalCase } from "change-case";
3
- import { Kind, print, visit } from "graphql";
3
+ import { getVariableValues, GraphQLError, GraphQLInputObjectType, GraphQLList, GraphQLNonNull, isEnumType, isListType, isNonNullType, isObjectType, isScalarType, Kind, parse, print, visit, } from "graphql";
4
+ import { buildASTSchema, getOperationAST } from "graphql/utilities";
4
5
  import { z } from "zod";
5
6
  import { BOOLEAN_GQL_PRIMITIVE_NAME, FLOAT_GQL_PRIMITIVE_NAME, gqlPrimitiveNodeNamesList, ID_GQL_PRIMITIVE_NAME, INT_GQL_PRIMITIVE_NAME, STRING_GQL_PRIMITIVE_NAME, } from "../constants/graphql-kinds.js";
6
7
  import { safeParseSdl } from "../context/schema-context.js";
@@ -27,6 +28,17 @@ export function makeOperationInitialDoc(name) {
27
28
  }`;
28
29
  return inputSdl;
29
30
  }
31
+ export function makeEmptyOperationSchema(operationName) {
32
+ const pascalName = pascalCase(operationName);
33
+ return `input ${pascalName}Input {\n _empty: Boolean\n}`;
34
+ }
35
+ export function isEmptyOperationSchema(schema) {
36
+ if (!schema)
37
+ return false;
38
+ // Check if schema only contains _empty: Boolean field
39
+ return (/_empty:\s*Boolean/.test(schema) &&
40
+ !schema.replace(/_empty:\s*Boolean/, "").match(/\w+:\s*\w+/));
41
+ }
30
42
  export function safeParseJsonRecord(json) {
31
43
  try {
32
44
  return JSON.parse(json);
@@ -45,15 +57,18 @@ export function makeMinimalObjectForStateType(args) {
45
57
  if (!stateTypeDefinitionFields?.length) {
46
58
  return existingValue;
47
59
  }
60
+ // Initialize visitedTypes with the root type to detect self-recursive types
61
+ const visitedTypes = new Set([stateTypeDefinitionNode.name.value]);
48
62
  const minimalObject = makeMinimalValuesForObjectFields({
49
63
  schemaDocumentNode: sharedSchemaDocumentNode,
50
64
  fieldDefinitionNodes: stateTypeDefinitionFields,
51
65
  existingValueObject,
66
+ visitedTypes,
52
67
  });
53
68
  return JSON.stringify(minimalObject, null, 2);
54
69
  }
55
70
  export function makeMinimalValuesForObjectFields(args) {
56
- const { schemaDocumentNode, existingValueObject, fieldDefinitionNodes } = args;
71
+ const { schemaDocumentNode, existingValueObject, fieldDefinitionNodes, visitedTypes, } = args;
57
72
  const newJson = {};
58
73
  for (const astNode of fieldDefinitionNodes) {
59
74
  const fieldName = getASTNodeName(astNode);
@@ -65,13 +80,14 @@ export function makeMinimalValuesForObjectFields(args) {
65
80
  astNode,
66
81
  schemaDocumentNode,
67
82
  existingValueObject,
83
+ visitedTypes,
68
84
  });
69
85
  newJson[astNode.name.value] = minimalValue;
70
86
  }
71
87
  return newJson;
72
88
  }
73
89
  function makeMinimalValueForASTNode(args) {
74
- const { fieldName, astNode, schemaDocumentNode, existingValueObject } = args;
90
+ const { fieldName, astNode, schemaDocumentNode, existingValueObject, visitedTypes, } = args;
75
91
  const existingFieldValue = existingValueObject?.[fieldName];
76
92
  let node = astNode;
77
93
  if (isFieldDefinitionNode(astNode)) {
@@ -82,7 +98,7 @@ function makeMinimalValueForASTNode(args) {
82
98
  node = getASTNodeTypeNode(node);
83
99
  }
84
100
  if (isListTypeNode(node)) {
85
- return makeMinimalValueForGqlListNode(node, existingFieldValue, isNonNull);
101
+ return makeMinimalValueForGqlListNode(node, existingFieldValue, isNonNull, schemaDocumentNode, visitedTypes);
86
102
  }
87
103
  if (isGqlPrimitiveNode(node)) {
88
104
  return makeMinimalValueForGQLPrimitiveNode(node, existingFieldValue, isNonNull);
@@ -95,10 +111,10 @@ function makeMinimalValueForASTNode(args) {
95
111
  return makeMinimalValueForGqlScalar(namedTypeDefinitionNode, existingFieldValue, isNonNull);
96
112
  }
97
113
  if (isUnionTypeDefinitionNode(namedTypeDefinitionNode)) {
98
- return makeMinimalValueForGqlUnion(namedTypeDefinitionNode, existingFieldValue, schemaDocumentNode, existingValueObject, isNonNull);
114
+ return makeMinimalValueForGqlUnion(namedTypeDefinitionNode, existingFieldValue, schemaDocumentNode, existingValueObject, isNonNull, visitedTypes);
99
115
  }
100
116
  if (isObjectTypeDefinitionNode(namedTypeDefinitionNode)) {
101
- return makeMinimalValueForGqlObject(namedTypeDefinitionNode, schemaDocumentNode, existingValueObject, existingFieldValue, isNonNull);
117
+ return makeMinimalValueForGqlObject(namedTypeDefinitionNode, schemaDocumentNode, existingValueObject, existingFieldValue, isNonNull, visitedTypes);
102
118
  }
103
119
  return existingFieldValue;
104
120
  }
@@ -243,7 +259,7 @@ function makeMinimalValueForGqlScalar(scalarTypeDefinitionNode, existingFieldVal
243
259
  }
244
260
  return existingFieldValue;
245
261
  }
246
- function makeMinimalValueForGqlUnion(namedTypeDefinitionNode, existingFieldValue, schemaDocumentNode, existingValueObject, isNonNull) {
262
+ function makeMinimalValueForGqlUnion(namedTypeDefinitionNode, existingFieldValue, schemaDocumentNode, existingValueObject, isNonNull, visitedTypes) {
247
263
  if (!isNonNull && !existingFieldValue) {
248
264
  return null;
249
265
  }
@@ -259,18 +275,31 @@ function makeMinimalValueForGqlUnion(namedTypeDefinitionNode, existingFieldValue
259
275
  if (!isObjectTypeDefinitionNode(firstNamedTypeObjectDefinitionNode)) {
260
276
  return null;
261
277
  }
262
- return makeMinimalValueForGqlObject(firstNamedTypeObjectDefinitionNode, schemaDocumentNode, existingValueObject, existingFieldValue, isNonNull);
278
+ return makeMinimalValueForGqlObject(firstNamedTypeObjectDefinitionNode, schemaDocumentNode, existingValueObject, existingFieldValue, isNonNull, visitedTypes);
263
279
  }
264
- function makeMinimalValueForGqlListNode(listTypeNode, existingFieldValue, isNonNull) {
280
+ function makeMinimalValueForGqlListNode(listTypeNode, existingFieldValue, isNonNull, schemaDocumentNode, visitedTypes) {
265
281
  if (!isNonNull && !Array.isArray(existingFieldValue)) {
266
282
  return null;
267
283
  }
268
284
  if (isNonNull && !Array.isArray(existingFieldValue)) {
269
285
  return [];
270
286
  }
271
- return existingFieldValue;
287
+ // Process each array item recursively
288
+ const arrayValue = existingFieldValue;
289
+ return arrayValue.map((item, index) => makeMinimalValueForASTNode({
290
+ fieldName: String(index),
291
+ astNode: listTypeNode.type,
292
+ schemaDocumentNode,
293
+ existingValueObject: { [index]: item },
294
+ visitedTypes,
295
+ }));
272
296
  }
273
- function makeMinimalValueForGqlObject(objectTypeDefinitionNode, schemaDocumentNode, existingValueObject, existingFieldValue, isNonNull) {
297
+ function makeMinimalValueForGqlObject(objectTypeDefinitionNode, schemaDocumentNode, _existingValueObject, existingFieldValue, isNonNull, visitedTypes) {
298
+ const typeName = objectTypeDefinitionNode.name.value;
299
+ // Check for recursive types to prevent infinite recursion
300
+ if (visitedTypes?.has(typeName)) {
301
+ return null;
302
+ }
274
303
  if (!isNonNull && !existingFieldValue) {
275
304
  return null;
276
305
  }
@@ -278,10 +307,20 @@ function makeMinimalValueForGqlObject(objectTypeDefinitionNode, schemaDocumentNo
278
307
  if (!fields?.length) {
279
308
  return {};
280
309
  }
310
+ // Track this type to detect cycles
311
+ const newVisitedTypes = new Set(visitedTypes);
312
+ newVisitedTypes.add(typeName);
313
+ // Use existingFieldValue (the actual nested object) instead of existingValueObject (parent)
314
+ const nestedExistingValue = existingFieldValue &&
315
+ typeof existingFieldValue === "object" &&
316
+ !Array.isArray(existingFieldValue)
317
+ ? existingFieldValue
318
+ : null;
281
319
  return makeMinimalValuesForObjectFields({
282
320
  schemaDocumentNode,
283
- existingValueObject,
321
+ existingValueObject: nestedExistingValue,
284
322
  fieldDefinitionNodes: fields,
323
+ visitedTypes: newVisitedTypes,
285
324
  });
286
325
  }
287
326
  function getNamedTypeDefinitionNode(astNodeTypeNode, schemaDocumentNode) {
@@ -354,4 +393,528 @@ export function handleModelNameChange(params) {
354
393
  }
355
394
  updateModelSchemaNames(params);
356
395
  }
396
+ /**
397
+ * Converts an output object type into an equivalent input object type.
398
+ * Intended for structural validation of state objects.
399
+ */
400
+ export function objectTypeToInputType(schema, objectType, options) {
401
+ const suffix = options?.nameSuffix ?? "Input";
402
+ const cache = options?.cache ?? new Map();
403
+ const inputTypeName = `${objectType.name}${suffix}`;
404
+ if (cache.has(inputTypeName)) {
405
+ return cache.get(inputTypeName);
406
+ }
407
+ const inputType = new GraphQLInputObjectType({
408
+ name: inputTypeName,
409
+ fields: () => {
410
+ const fields = objectType.getFields();
411
+ const inputFields = {};
412
+ for (const fieldName in fields) {
413
+ const field = fields[fieldName];
414
+ if (field.args.length > 0) {
415
+ throw new Error(`Cannot convert field "${objectType.name}.${fieldName}" with arguments into input type`);
416
+ }
417
+ inputFields[fieldName] = {
418
+ type: outputTypeToInputType(schema, field.type, suffix, cache),
419
+ };
420
+ }
421
+ return inputFields;
422
+ },
423
+ });
424
+ cache.set(inputTypeName, inputType);
425
+ return inputType;
426
+ }
427
+ function outputTypeToInputType(schema, type, suffix, cache) {
428
+ if (isNonNullType(type)) {
429
+ return new GraphQLNonNull(outputTypeToInputType(schema, type.ofType, suffix, cache));
430
+ }
431
+ if (isListType(type)) {
432
+ return new GraphQLList(outputTypeToInputType(schema, type.ofType, suffix, cache));
433
+ }
434
+ if (isScalarType(type) || isEnumType(type)) {
435
+ return type;
436
+ }
437
+ if (isObjectType(type)) {
438
+ return objectTypeToInputType(schema, type, {
439
+ nameSuffix: suffix,
440
+ cache,
441
+ });
442
+ }
443
+ throw new Error(`Unsupported output type: ${type.toString()}`);
444
+ }
445
+ export function validateStateObject(sharedSchemaDocumentNode, stateTypeDefinitionNode, stateValue) {
446
+ let stateObjectJson;
447
+ try {
448
+ stateObjectJson = JSON.parse(stateValue);
449
+ }
450
+ catch (error) {
451
+ return [new Error("Invalid JSON object", { cause: error })];
452
+ }
453
+ // 2) Build a quick index of type definitions from the shared schema
454
+ const typeDefByName = indexTypeDefinitions(sharedSchemaDocumentNode);
455
+ // Ensure the passed node exists in the shared schema (optional but helpful)
456
+ const stateTypeName = stateTypeDefinitionNode.name.value;
457
+ if (!typeDefByName.has(stateTypeName)) {
458
+ return [
459
+ new Error(`State type "${stateTypeName}" was not found in sharedSchemaDocumentNode`),
460
+ ];
461
+ }
462
+ // 3) Generate input types needed to validate this state object
463
+ const inputSuffix = "Input";
464
+ const generatedInputDefs = generateInputTypesForObjectTree(stateTypeName, typeDefByName, inputSuffix);
465
+ // 4) Build a schema that includes the generated input types
466
+ const augmentedDoc = {
467
+ ...sharedSchemaDocumentNode,
468
+ definitions: [
469
+ ...sharedSchemaDocumentNode.definitions,
470
+ ...generatedInputDefs,
471
+ ],
472
+ };
473
+ let schema;
474
+ try {
475
+ schema = buildASTSchema(augmentedDoc, { assumeValidSDL: false });
476
+ }
477
+ catch (e) {
478
+ return [new Error("Failed to build schema from SDL", { cause: e })];
479
+ }
480
+ // 5) Validate by coercing variables against the generated input type
481
+ const inputTypeName = `${stateTypeName}${inputSuffix}`;
482
+ const opDoc = parse(`query($v: ${inputTypeName}!) { __typename }`);
483
+ const op = getOperationAST(opDoc);
484
+ if (!op) {
485
+ return [new Error("Failed to create validation operation AST")];
486
+ }
487
+ const { errors } = getVariableValues(schema, op.variableDefinitions ?? [], {
488
+ v: stateObjectJson,
489
+ });
490
+ // Detect recursive types first - these take priority over NON_NULL errors
491
+ const recursiveTypeErrors = detectRecursiveTypes(stateTypeDefinitionNode, typeDefByName);
492
+ // Get the field names that have recursive types
493
+ const recursiveFieldNames = new Set(recursiveTypeErrors.map((e) => e.field));
494
+ const validationErrors = errors
495
+ ? graphQLErrorsToStateValidationErrors(errors).filter((e) => {
496
+ // Filter out NON_NULL errors caused by recursive types
497
+ if (e instanceof StateValidationError && e.kind === "NON_NULL") {
498
+ // Check if this error is in a path that starts with a recursive field
499
+ const rootField = e.path[0];
500
+ if (typeof rootField === "string" &&
501
+ recursiveFieldNames.has(rootField)) {
502
+ return false;
503
+ }
504
+ }
505
+ return true;
506
+ })
507
+ : [];
508
+ // Add recursive type errors first (they have priority)
509
+ validationErrors.unshift(...recursiveTypeErrors);
510
+ const missingOptionalErrors = detectMissingOptionalFields(sharedSchemaDocumentNode, stateTypeDefinitionNode, stateObjectJson);
511
+ validationErrors.push(...missingOptionalErrors);
512
+ return validationErrors;
513
+ }
514
+ /**
515
+ * Indexes object/input/enum/scalar/interface/union type definition nodes by name.
516
+ * Note: only AST definitions that have a "name" field are indexed.
517
+ */
518
+ function indexTypeDefinitions(doc) {
519
+ const map = new Map();
520
+ for (const def of doc.definitions) {
521
+ if (def.kind === Kind.OBJECT_TYPE_DEFINITION ||
522
+ def.kind === Kind.INPUT_OBJECT_TYPE_DEFINITION ||
523
+ def.kind === Kind.ENUM_TYPE_DEFINITION ||
524
+ def.kind === Kind.SCALAR_TYPE_DEFINITION ||
525
+ def.kind === Kind.INTERFACE_TYPE_DEFINITION ||
526
+ def.kind === Kind.UNION_TYPE_DEFINITION ||
527
+ def.kind === Kind.OBJECT_TYPE_EXTENSION ||
528
+ def.kind === Kind.INPUT_OBJECT_TYPE_EXTENSION ||
529
+ def.kind === Kind.ENUM_TYPE_EXTENSION ||
530
+ def.kind === Kind.INTERFACE_TYPE_EXTENSION ||
531
+ def.kind === Kind.UNION_TYPE_EXTENSION) {
532
+ // Extensions also have names; we index them too (but see note below).
533
+ // In production, you may want to merge extensions into base definitions.
534
+ // For state validation, prefer definitions (not extensions) if both exist.
535
+ const name = def.name.value;
536
+ if (!map.has(name)) {
537
+ map.set(name, def);
538
+ }
539
+ }
540
+ }
541
+ return map;
542
+ }
543
+ /**
544
+ * Generates InputObjectTypeDefinitionNode(s) for a root object type and any nested
545
+ * object types reachable via fields, converting object references to their *Input equivalents*.
546
+ */
547
+ function generateInputTypesForObjectTree(rootObjectTypeName, typeDefByName, inputSuffix) {
548
+ const generated = new Map();
549
+ const visiting = new Set();
550
+ const ensureInputForObject = (objectTypeName) => {
551
+ const inputName = `${objectTypeName}${inputSuffix}`;
552
+ if (generated.has(inputName))
553
+ return;
554
+ if (visiting.has(objectTypeName)) {
555
+ // Recursive reference; we rely on GraphQLInputObjectType lazy field resolution via AST schema build.
556
+ // Still, we must avoid infinite loops while generating AST nodes.
557
+ return;
558
+ }
559
+ visiting.add(objectTypeName);
560
+ const def = typeDefByName.get(objectTypeName);
561
+ if (!def) {
562
+ throw new GraphQLError(`Unknown referenced type "${objectTypeName}"`);
563
+ }
564
+ if (def.kind !== Kind.OBJECT_TYPE_DEFINITION &&
565
+ def.kind !== Kind.OBJECT_TYPE_EXTENSION) {
566
+ throw new GraphQLError(`Type "${objectTypeName}" is not an object type; cannot generate input from kind "${def.kind}"`);
567
+ }
568
+ const objDef = def;
569
+ // Convert each field type to an input-acceptable TypeNode.
570
+ const inputFields = objDef.fields?.map((f) => {
571
+ return {
572
+ kind: Kind.INPUT_VALUE_DEFINITION,
573
+ name: f.name,
574
+ description: f.description,
575
+ directives: [], // output-field directives don't automatically translate to input fields
576
+ type: convertOutputTypeNodeToInputTypeNode(f.type, typeDefByName, inputSuffix, ensureInputForObject),
577
+ defaultValue: undefined,
578
+ };
579
+ }) ?? [];
580
+ const inputDef = {
581
+ kind: Kind.INPUT_OBJECT_TYPE_DEFINITION,
582
+ name: { kind: Kind.NAME, value: inputName },
583
+ description: objDef.description,
584
+ directives: [],
585
+ fields: inputFields,
586
+ };
587
+ generated.set(inputName, inputDef);
588
+ visiting.delete(objectTypeName);
589
+ };
590
+ // Kick off generation for root
591
+ ensureInputForObject(rootObjectTypeName);
592
+ return Array.from(generated.values());
593
+ }
594
+ function convertOutputTypeNodeToInputTypeNode(typeNode, typeDefByName, inputSuffix, ensureInputForObject) {
595
+ switch (typeNode.kind) {
596
+ case Kind.NON_NULL_TYPE:
597
+ return {
598
+ kind: Kind.NON_NULL_TYPE,
599
+ type: convertOutputTypeNodeToInputTypeNode(typeNode.type, typeDefByName, inputSuffix, ensureInputForObject),
600
+ };
601
+ case Kind.LIST_TYPE:
602
+ return {
603
+ kind: Kind.LIST_TYPE,
604
+ type: convertOutputTypeNodeToInputTypeNode(typeNode.type, typeDefByName, inputSuffix, ensureInputForObject),
605
+ };
606
+ case Kind.NAMED_TYPE: {
607
+ const named = typeNode;
608
+ const name = named.name.value;
609
+ const def = typeDefByName.get(name);
610
+ // If it's an object type, we must reference its generated input twin.
611
+ if (def?.kind === Kind.OBJECT_TYPE_DEFINITION ||
612
+ def?.kind === Kind.OBJECT_TYPE_EXTENSION) {
613
+ ensureInputForObject(name);
614
+ return {
615
+ kind: Kind.NAMED_TYPE,
616
+ name: { kind: Kind.NAME, value: `${name}${inputSuffix}` },
617
+ };
618
+ }
619
+ // Scalars/enums/input objects are valid as-is in input positions.
620
+ if (!def ||
621
+ def.kind === Kind.SCALAR_TYPE_DEFINITION ||
622
+ def.kind === Kind.ENUM_TYPE_DEFINITION ||
623
+ def.kind === Kind.INPUT_OBJECT_TYPE_DEFINITION ||
624
+ def.kind === Kind.INPUT_OBJECT_TYPE_EXTENSION) {
625
+ return named;
626
+ }
627
+ // Interfaces/unions are not valid input types.
628
+ if (def.kind === Kind.INTERFACE_TYPE_DEFINITION ||
629
+ def.kind === Kind.UNION_TYPE_DEFINITION) {
630
+ throw new GraphQLError(`Type "${name}" (${def.kind}) cannot be used in an input type`);
631
+ }
632
+ // Anything else is unexpected.
633
+ throw new GraphQLError(`Unsupported named type "${name}" of kind "${def.kind}"`);
634
+ }
635
+ default:
636
+ // Exhaustiveness guard
637
+ throw new GraphQLError(`Unsupported TypeNode kind: ${JSON.stringify(typeNode)}`);
638
+ }
639
+ }
640
+ export class StateValidationError extends Error {
641
+ payload;
642
+ originalMessage;
643
+ constructor(payload, originalMessage) {
644
+ // Keep Error.message stable but not user-facing; UI should render from payload.
645
+ super(payload.kind);
646
+ this.name = "StateValidationError";
647
+ this.payload = payload;
648
+ this.originalMessage = originalMessage;
649
+ Object.setPrototypeOf(this, new.target.prototype);
650
+ }
651
+ get kind() {
652
+ return this.payload.kind;
653
+ }
654
+ get path() {
655
+ return this.payload.path;
656
+ }
657
+ get field() {
658
+ return this.payload.field;
659
+ }
660
+ }
661
+ function extractInputPath(message) {
662
+ const match = message.match(/at\s+"([^"]+)"/);
663
+ if (!match)
664
+ return [];
665
+ const parts = match[1].split(".").filter(Boolean);
666
+ const withoutVar = parts[0] === "v" ? parts.slice(1) : parts;
667
+ return withoutVar.map((p) => (/^\d+$/.test(p) ? Number(p) : p));
668
+ }
669
+ const RE_MISSING_REQUIRED_FIELD = /Field "([^"]+)" of required type "([^"]+)" was not provided\./;
670
+ const RE_UNKNOWN_FIELD = /Field "([^"]+)" is not defined by type "([^"]+)"\.(?: Did you mean "([^"]+)"\?)?/;
671
+ function extractMissingRequiredField(message) {
672
+ const m = message.match(RE_MISSING_REQUIRED_FIELD);
673
+ if (!m)
674
+ return null;
675
+ return { field: m[1], expectedType: m[2] };
676
+ }
677
+ function extractUnknownField(message) {
678
+ const m = message.match(RE_UNKNOWN_FIELD);
679
+ if (!m)
680
+ return null;
681
+ return { field: m[1], typeName: m[2], didYouMean: m[3] };
682
+ }
683
+ function lastFieldFromPath(path) {
684
+ for (let i = path.length - 1; i >= 0; i--) {
685
+ if (typeof path[i] === "string")
686
+ return path[i];
687
+ }
688
+ return undefined;
689
+ }
690
+ function extractExpectedType(message) {
691
+ // NON_NULL: Expected non-nullable type "Int!" not to be null.
692
+ let m = message.match(/Expected non-nullable type "([^"]+)"/);
693
+ if (m?.[1])
694
+ return m[1];
695
+ // Sometimes: Expected type "X" ...
696
+ m = message.match(/Expected type "([^"]+)"/);
697
+ if (m?.[1])
698
+ return m[1];
699
+ // Scalar coercion: "; String cannot represent ..."
700
+ m = message.match(/;\s*([_A-Za-z][_0-9A-Za-z]*)\s+cannot represent/i);
701
+ if (m?.[1])
702
+ return m[1];
703
+ return undefined;
704
+ }
705
+ export function graphQLErrorsToStateValidationErrors(errors) {
706
+ const out = [];
707
+ for (const e of errors) {
708
+ const originalMessage = e.message;
709
+ // 1) Missing required field (no `at "v.x"` path usually)
710
+ const missing = extractMissingRequiredField(originalMessage);
711
+ if (missing) {
712
+ out.push(new StateValidationError({
713
+ kind: "MISSING",
714
+ path: [missing.field],
715
+ field: missing.field,
716
+ expectedType: missing.expectedType, // if your payload supports it
717
+ }, originalMessage));
718
+ continue;
719
+ }
720
+ // 2) Unknown field (extra key)
721
+ const unknown = extractUnknownField(originalMessage);
722
+ if (unknown) {
723
+ out.push(new StateValidationError({
724
+ kind: "UNKNOWN_FIELD",
725
+ path: [unknown.field],
726
+ field: unknown.field,
727
+ didYouMean: unknown.didYouMean, // optional
728
+ }, originalMessage));
729
+ continue;
730
+ }
731
+ // 3) Usual `at "v.path"` extraction (NON_NULL / TYPE)
732
+ const path = extractInputPath(originalMessage);
733
+ const field = lastFieldFromPath(path) ?? "value";
734
+ const expectedType = extractExpectedType(originalMessage);
735
+ if (originalMessage.includes("Expected non-nullable type") &&
736
+ originalMessage.includes("not to be null")) {
737
+ out.push(new StateValidationError({ kind: "NON_NULL", path, field, expectedType }, originalMessage));
738
+ continue;
739
+ }
740
+ if (originalMessage.includes("cannot represent") ||
741
+ originalMessage.includes("Expected type")) {
742
+ out.push(new StateValidationError({
743
+ kind: "TYPE",
744
+ path,
745
+ field,
746
+ expectedType,
747
+ details: originalMessage,
748
+ }, originalMessage));
749
+ continue;
750
+ }
751
+ out.push(new StateValidationError({ kind: "TYPE", path, field, details: originalMessage }, originalMessage));
752
+ }
753
+ return out;
754
+ }
755
+ /**
756
+ * Finds all fields defined in the schema that are missing from the state object.
757
+ * Returns information about each missing field including whether it's required.
758
+ */
759
+ export function findMissingFields(sharedSchemaDocumentNode, rootTypeNode, value, basePath = []) {
760
+ let stateObjectJson;
761
+ try {
762
+ stateObjectJson =
763
+ typeof value === "string"
764
+ ? JSON.parse(value)
765
+ : value;
766
+ }
767
+ catch {
768
+ return [];
769
+ }
770
+ const typeByName = indexObjectTypes(sharedSchemaDocumentNode);
771
+ const missingFields = [];
772
+ for (const field of rootTypeNode.fields ?? []) {
773
+ const fieldName = field.name.value;
774
+ const fieldPath = [...basePath, fieldName];
775
+ const isRequired = field.type.kind === Kind.NON_NULL_TYPE;
776
+ // Unwrap NonNull to get the underlying type
777
+ const underlyingType = isRequired
778
+ ? field.type.type
779
+ : field.type;
780
+ // Check if field is missing from the state object
781
+ if (!(fieldName in stateObjectJson)) {
782
+ missingFields.push({
783
+ fieldName,
784
+ path: fieldPath,
785
+ isRequired,
786
+ type: underlyingType,
787
+ });
788
+ continue;
789
+ }
790
+ // If present and object-typed → recurse to check nested missing fields
791
+ const namedType = unwrapNamedType(field.type);
792
+ const childType = namedType ? typeByName.get(namedType) : undefined;
793
+ if (childType &&
794
+ typeof stateObjectJson[fieldName] === "object" &&
795
+ stateObjectJson[fieldName] !== null &&
796
+ !Array.isArray(stateObjectJson[fieldName])) {
797
+ const nestedMissing = findMissingFields(sharedSchemaDocumentNode, childType, stateObjectJson[fieldName], fieldPath);
798
+ missingFields.push(...nestedMissing);
799
+ }
800
+ }
801
+ return missingFields;
802
+ }
803
+ /**
804
+ * Detects optional fields defined in the schema that are missing from the state object.
805
+ * Returns StateValidationError[] for each missing optional field.
806
+ */
807
+ export function detectMissingOptionalFields(sharedSchemaDocumentNode, rootTypeNode, value) {
808
+ const missingFields = findMissingFields(sharedSchemaDocumentNode, rootTypeNode, value);
809
+ // Only report optional (not required) fields as MISSING_OPTIONAL errors
810
+ // Required fields are already caught by GraphQL validation
811
+ return missingFields
812
+ .filter((field) => !field.isRequired)
813
+ .map((field) => new StateValidationError({
814
+ kind: "MISSING_OPTIONAL",
815
+ path: field.path,
816
+ field: field.fieldName,
817
+ expectedType: typeNodeToString(field.type),
818
+ }));
819
+ }
820
+ /**
821
+ * Detects fields that have recursive types (types that reference themselves directly or indirectly).
822
+ * Returns a RECURSIVE_TYPE error for each field that contains a recursive type.
823
+ */
824
+ function detectRecursiveTypes(stateTypeDefinitionNode, typeDefByName) {
825
+ const errors = [];
826
+ for (const field of stateTypeDefinitionNode.fields ?? []) {
827
+ const fieldName = field.name.value;
828
+ const namedTypeName = getNamedTypeName(field.type);
829
+ if (!namedTypeName)
830
+ continue;
831
+ // Check if this field's type is recursive
832
+ if (isRecursiveType(namedTypeName, typeDefByName, new Set())) {
833
+ errors.push(new StateValidationError({
834
+ kind: "RECURSIVE_TYPE",
835
+ path: [fieldName],
836
+ field: fieldName,
837
+ typeName: namedTypeName,
838
+ }));
839
+ }
840
+ }
841
+ return errors;
842
+ }
843
+ /**
844
+ * Gets the named type name from a TypeNode, unwrapping NonNull and List types.
845
+ */
846
+ function getNamedTypeName(typeNode) {
847
+ if (typeNode.kind === Kind.NAMED_TYPE) {
848
+ return typeNode.name.value;
849
+ }
850
+ if (typeNode.kind === Kind.NON_NULL_TYPE ||
851
+ typeNode.kind === Kind.LIST_TYPE) {
852
+ return getNamedTypeName(typeNode.type);
853
+ }
854
+ return null;
855
+ }
856
+ /**
857
+ * Checks if a type is recursive (references itself directly or indirectly through required fields).
858
+ */
859
+ function isRecursiveType(typeName, typeDefByName, visitedTypes) {
860
+ if (visitedTypes.has(typeName)) {
861
+ return true;
862
+ }
863
+ const typeDef = typeDefByName.get(typeName);
864
+ if (!typeDef || typeDef.kind !== Kind.OBJECT_TYPE_DEFINITION) {
865
+ return false;
866
+ }
867
+ visitedTypes.add(typeName);
868
+ for (const field of typeDef.fields ?? []) {
869
+ // Only check required fields for recursion (NonNull types)
870
+ if (field.type.kind !== Kind.NON_NULL_TYPE)
871
+ continue;
872
+ const innerType = field.type.type;
873
+ let fieldTypeName = null;
874
+ if (innerType.kind === Kind.NAMED_TYPE) {
875
+ fieldTypeName = innerType.name.value;
876
+ }
877
+ else if (innerType.kind === Kind.LIST_TYPE) {
878
+ // For list types like [Item!]!, get the inner type
879
+ fieldTypeName = getNamedTypeName(innerType);
880
+ }
881
+ if (fieldTypeName &&
882
+ isRecursiveType(fieldTypeName, typeDefByName, visitedTypes)) {
883
+ return true;
884
+ }
885
+ }
886
+ visitedTypes.delete(typeName);
887
+ return false;
888
+ }
889
+ /**
890
+ * Converts a TypeNode to its string representation (e.g., "String", "Int!", "[String]!")
891
+ */
892
+ function typeNodeToString(typeNode) {
893
+ switch (typeNode.kind) {
894
+ case Kind.NAMED_TYPE:
895
+ return typeNode.name.value;
896
+ case Kind.NON_NULL_TYPE:
897
+ return `${typeNodeToString(typeNode.type)}!`;
898
+ case Kind.LIST_TYPE:
899
+ return `[${typeNodeToString(typeNode.type)}]`;
900
+ }
901
+ }
902
+ function indexObjectTypes(doc) {
903
+ const map = new Map();
904
+ for (const def of doc.definitions) {
905
+ if (def.kind === Kind.OBJECT_TYPE_DEFINITION) {
906
+ map.set(def.name.value, def);
907
+ }
908
+ }
909
+ return map;
910
+ }
911
+ function unwrapNamedType(typeNode) {
912
+ if (typeNode.kind === Kind.NAMED_TYPE)
913
+ return typeNode.name.value;
914
+ if (typeNode.kind === Kind.NON_NULL_TYPE)
915
+ return unwrapNamedType(typeNode.type);
916
+ if (typeNode.kind === Kind.LIST_TYPE)
917
+ return unwrapNamedType(typeNode.type);
918
+ return undefined;
919
+ }
357
920
  //# sourceMappingURL=helpers.js.map