@soda-gql/core 0.11.10 → 0.11.12

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.
package/dist/index.cjs CHANGED
@@ -454,541 +454,652 @@ const createColocateHelper = () => {
454
454
  };
455
455
 
456
456
  //#endregion
457
- //#region packages/core/src/composer/directive-builder.ts
457
+ //#region packages/core/src/utils/promise.ts
458
458
  /**
459
- * Directive builder utilities for creating field-level directives.
460
- *
461
- * Provides type-safe methods for creating directive references that can be
462
- * applied to field selections. The builder follows a similar pattern to
463
- * the variable builder ($var).
464
- *
459
+ * Promise detection utilities for cross-realm compatibility.
465
460
  * @module
466
461
  */
467
462
  /**
468
- * Creates a directive method factory for a specific directive.
463
+ * Check if a value is Promise-like (has .then method).
469
464
  *
470
- * @param name - The directive name (without @)
471
- * @param locations - Valid locations where the directive can be applied
472
- * @returns A function that creates DirectiveRef instances
465
+ * This function uses duck typing instead of `instanceof Promise` to work across
466
+ * VM sandbox boundaries where Promises created in a different realm have a
467
+ * different constructor.
473
468
  *
474
469
  * @example
475
470
  * ```typescript
476
- * const skipMethod = createDirectiveMethod("skip", ["FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT"] as const);
477
- * const skipDirective = skipMethod({ if: true });
478
- * ```
479
- */
480
- const createDirectiveMethod = (name, locations) => {
481
- return (args) => new DirectiveRef({
482
- name,
483
- arguments: args,
484
- locations
485
- });
486
- };
487
- /**
488
- * Creates a typed directive method with argument type specifiers.
489
- * Enables enum value output in directive arguments.
471
+ * // Works with native Promises
472
+ * isPromiseLike(Promise.resolve(42)); // true
490
473
  *
491
- * @param name - The directive name (without @)
492
- * @param locations - Valid locations where the directive can be applied
493
- * @param argSpecs - Type specifiers for directive arguments
494
- * @returns A function that creates DirectiveRef instances with argument type info
474
+ * // Works with VM sandbox Promises (instanceof would fail)
475
+ * const vmPromise = vm.runInContext("Promise.resolve(42)", context);
476
+ * isPromiseLike(vmPromise); // true (instanceof Promise would be false)
495
477
  *
496
- * @example
497
- * ```typescript
498
- * const authMethod = createTypedDirectiveMethod(
499
- * "auth",
500
- * ["FIELD"] as const,
501
- * { role: { kind: "enum", name: "Role", modifier: "!" } }
502
- * );
503
- * const authDirective = authMethod({ role: "ADMIN" });
478
+ * // Rejects non-Promises
479
+ * isPromiseLike({ then: "not a function" }); // false
480
+ * isPromiseLike(null); // false
481
+ * isPromiseLike(42); // false
504
482
  * ```
505
483
  */
506
- const createTypedDirectiveMethod = (name, locations, argSpecs) => {
507
- return (args) => new DirectiveRef({
508
- name,
509
- arguments: args,
510
- locations,
511
- argumentSpecs: argSpecs
512
- });
484
+ const isPromiseLike = (value) => {
485
+ return value !== null && typeof value === "object" && "then" in value && typeof value.then === "function";
513
486
  };
487
+
488
+ //#endregion
489
+ //#region packages/core/src/types/element/lazy-evaluator.ts
514
490
  /**
515
- * Standard directive locations for @skip and @include.
491
+ * Creates a lazy evaluator with caching, async support, and dependency ordering.
492
+ *
493
+ * @param define - Factory function that produces the value
494
+ * @param getDeps - Optional function returning dependencies to evaluate first
495
+ * @param createDepGenerator - Function to create evaluation generator for a dependency
496
+ * @returns An executor generator function
516
497
  */
517
- const CONDITIONAL_DIRECTIVE_LOCATIONS = [
518
- "FIELD",
519
- "FRAGMENT_SPREAD",
520
- "INLINE_FRAGMENT"
521
- ];
498
+ const createLazyEvaluator = (define, getDeps, createDepGenerator) => {
499
+ let cache = null;
500
+ let promise = null;
501
+ return function* execute(context) {
502
+ if (cache) return cache.value;
503
+ if (promise) {
504
+ yield promise;
505
+ return cache.value;
506
+ }
507
+ if (getDeps) for (const dep of getDeps()) yield* createDepGenerator(dep);
508
+ const defined = define(context);
509
+ if (!isPromiseLike(defined)) return (cache = { value: defined }).value;
510
+ promise = defined.then((value) => {
511
+ cache = { value };
512
+ promise = null;
513
+ });
514
+ yield promise;
515
+ return cache.value;
516
+ };
517
+ };
522
518
  /**
523
- * Creates the standard GraphQL directives (@skip, @include).
524
- * These are always available regardless of schema definition.
525
- *
526
- * @returns Object containing skip and include directive methods
527
- *
528
- * @example
529
- * ```typescript
530
- * const $dir = createStandardDirectives();
531
- * const skipDirective = $dir.skip({ if: true });
532
- * ```
519
+ * Creates an evaluation generator from an executor.
520
+ * Wraps the executor's generator and discards its return value.
533
521
  */
534
- const createStandardDirectives = () => ({
535
- skip: createDirectiveMethod("skip", CONDITIONAL_DIRECTIVE_LOCATIONS),
536
- include: createDirectiveMethod("include", CONDITIONAL_DIRECTIVE_LOCATIONS)
537
- });
522
+ function* createEvaluationGenerator(executor, context) {
523
+ yield* executor(context);
524
+ }
538
525
  /**
539
- * Creates a directive builder with standard directives and optional custom directives.
540
- *
541
- * @param customDirectives - Additional directive methods from schema (generated by codegen)
542
- * @returns Combined directive builder with all available directives
543
- *
544
- * @internal Used by codegen to create schema-specific directive builders
526
+ * Executes the evaluator synchronously.
527
+ * Throws if async operation is encountered.
545
528
  */
546
- const createDirectiveBuilder = (customDirectives) => {
547
- return {
548
- ...createStandardDirectives(),
549
- ...customDirectives ?? {}
550
- };
529
+ const evaluateSync = (executor, context) => {
530
+ const result = executor(context).next();
531
+ if (!result.done) throw new Error("Async operation is not supported in sync evaluation.");
532
+ return result.value;
551
533
  };
534
+
535
+ //#endregion
536
+ //#region packages/core/src/types/element/gql-element.ts
537
+ const GQL_ELEMENT_FACTORY = Symbol("GQL_ELEMENT_FACTORY");
538
+ const GQL_ELEMENT_CONTEXT = Symbol("GQL_ELEMENT_CONTEXT");
552
539
  /**
553
- * Type guard to check if a value is a DirectiveRef.
540
+ * Abstract base class for all GraphQL elements (Fragment, Operation).
554
541
  *
555
- * @param value - Value to check
556
- * @returns True if value is a DirectiveRef instance
542
+ * Uses lazy evaluation with caching - definition is computed on first access.
543
+ * Subclasses should not be instantiated directly; use static `create` methods.
544
+ *
545
+ * @template TDefinition - The shape of the evaluated definition
546
+ * @template TInfer - Type inference metadata (access via `$infer`)
557
547
  */
558
- const isDirectiveRef = (value) => {
559
- return value instanceof DirectiveRef;
548
+ var GqlElement = class GqlElement {
549
+ [GQL_ELEMENT_FACTORY];
550
+ [GQL_ELEMENT_CONTEXT] = null;
551
+ constructor(define, getDeps) {
552
+ this[GQL_ELEMENT_FACTORY] = createLazyEvaluator(define, getDeps, GqlElement.createEvaluationGenerator);
553
+ Object.defineProperty(this, "$infer", { get() {
554
+ throw new Error("This property is only for type meta. Do not access this property directly.");
555
+ } });
556
+ }
557
+ attach(attachmentOrAttachments) {
558
+ const attachments = Array.isArray(attachmentOrAttachments) ? attachmentOrAttachments : [attachmentOrAttachments];
559
+ for (const attachment of attachments) {
560
+ let cache = null;
561
+ const self = this;
562
+ Object.defineProperty(this, attachment.name, { get() {
563
+ if (cache) return cache;
564
+ GqlElement.evaluateInstantly(self);
565
+ return cache = attachment.createValue(self);
566
+ } });
567
+ }
568
+ return this;
569
+ }
570
+ /**
571
+ * Sets the canonical context for an element. Used by the builder.
572
+ * @internal
573
+ */
574
+ static setContext(element, context) {
575
+ element[GQL_ELEMENT_CONTEXT] = context;
576
+ }
577
+ /**
578
+ * Gets the canonical context of an element, if set.
579
+ * @internal
580
+ */
581
+ static getContext(element) {
582
+ return element[GQL_ELEMENT_CONTEXT];
583
+ }
584
+ /**
585
+ * Creates a generator for async evaluation. Used by the builder.
586
+ * @internal
587
+ */
588
+ static createEvaluationGenerator(element) {
589
+ return createEvaluationGenerator(element[GQL_ELEMENT_FACTORY], element[GQL_ELEMENT_CONTEXT]);
590
+ }
591
+ static evaluateInstantly(element) {
592
+ return evaluateSync(element[GQL_ELEMENT_FACTORY], element[GQL_ELEMENT_CONTEXT]);
593
+ }
594
+ /**
595
+ * Forces synchronous evaluation. Throws if async operation is needed.
596
+ * @internal
597
+ */
598
+ static evaluateSync(element) {
599
+ GqlElement.evaluateInstantly(element);
600
+ }
601
+ /**
602
+ * Evaluates and returns the element's definition.
603
+ * Throws if async operation is needed.
604
+ * @internal
605
+ */
606
+ static get(element) {
607
+ return GqlElement.evaluateInstantly(element);
608
+ }
560
609
  };
561
610
 
562
611
  //#endregion
563
- //#region packages/core/src/composer/field-path-context.ts
612
+ //#region packages/core/src/types/element/define.ts
564
613
  /**
565
- * Shared mutable container for field path context.
566
- * Only synchronous access is supported.
614
+ * Define element for storing arbitrary value factories.
615
+ * @module
567
616
  */
568
- const fieldPathContext = { current: null };
569
617
  /**
570
- * Get the current field path.
571
- * Returns null if not in a field building context.
618
+ * Represents a factory-based value definition.
619
+ *
620
+ * Define elements are created via `gql(({ define }) => define(() => value))`.
621
+ * The factory is evaluated lazily and the result is cached.
622
+ *
623
+ * @template TValue - The type of value produced by the factory
572
624
  *
573
625
  * @example
574
626
  * ```typescript
575
- * import { getCurrentFieldPath } from '@soda-gql/core';
576
- *
577
- * // Inside a field builder or model spread:
578
- * const path = getCurrentFieldPath();
579
- * console.log(path?.full); // "$.user.posts[].author"
627
+ * // Store a primitive value
628
+ * const myNumber = gql.default(({ define }) => define(() => 42));
629
+ * console.log(myNumber.value); // 42
630
+ *
631
+ * // Store a plain object
632
+ * const myConfig = gql.default(({ define }) => define(() => ({
633
+ * apiUrl: "https://api.example.com",
634
+ * timeout: 5000,
635
+ * })));
636
+ * console.log(myConfig.value.apiUrl); // "https://api.example.com"
580
637
  * ```
581
638
  */
582
- const getCurrentFieldPath = () => fieldPathContext.current;
583
- /**
584
- * Run a function with a specific field path context.
585
- * Restores the previous path after the function completes.
586
- *
587
- * @internal
588
- */
589
- const withFieldPath = (path, fn) => {
590
- const previousPath = fieldPathContext.current;
591
- fieldPathContext.current = path;
592
- try {
593
- return fn();
594
- } finally {
595
- fieldPathContext.current = previousPath;
639
+ var GqlDefine = class GqlDefine extends GqlElement {
640
+ constructor(define) {
641
+ super(define);
642
+ }
643
+ /**
644
+ * The evaluated value from the factory.
645
+ * Triggers lazy evaluation on first access.
646
+ */
647
+ get value() {
648
+ return GqlElement.get(this).factoryResult;
649
+ }
650
+ /**
651
+ * Creates a new GqlDefine instance.
652
+ *
653
+ * Prefer using the `gql(({ define }) => define(() => value))` API instead.
654
+ *
655
+ * @param factory - Function that produces the value. Can be sync or async.
656
+ * @returns A new GqlDefine instance wrapping the factory result.
657
+ *
658
+ * @example
659
+ * ```typescript
660
+ * // Sync factory
661
+ * const syncDefine = GqlDefine.create(() => 42);
662
+ *
663
+ * // Async factory
664
+ * const asyncDefine = GqlDefine.create(async () => {
665
+ * const data = await fetch('/api/config');
666
+ * return data.json();
667
+ * });
668
+ * ```
669
+ */
670
+ static create(factory) {
671
+ return new GqlDefine((_context) => {
672
+ const result = factory();
673
+ if (isPromiseLike(result)) return result.then((value) => ({ factoryResult: value }));
674
+ return { factoryResult: result };
675
+ });
596
676
  }
597
677
  };
678
+
679
+ //#endregion
680
+ //#region packages/core/src/types/element/fragment.ts
598
681
  /**
599
- * Append a new segment to the current path.
682
+ * Represents a reusable GraphQL field selection on a specific type.
600
683
  *
601
- * @internal
684
+ * Fragments are created via `gql(({ fragment }) => fragment.TypeName({ ... }))`.
685
+ * Use `spread()` to include the fragment's fields in an operation.
686
+ *
687
+ * @template TTypeName - The GraphQL type this fragment selects from
688
+ * @template TVariables - Variables required when spreading
689
+ * @template TFields - The selected fields structure
690
+ * @template TOutput - Inferred output type from selected fields
602
691
  */
603
- const appendToPath = (parent, segment) => {
604
- const listSuffix = segment.isList ? "[]" : "";
605
- const newSegment = {
606
- field: segment.field,
607
- parent: segment.parentType,
608
- isList: segment.isList
609
- };
610
- if (!parent) return {
611
- full: `$.${segment.field}${listSuffix}`,
612
- segments: [newSegment]
613
- };
614
- return {
615
- full: `${parent.full}.${segment.field}${listSuffix}`,
616
- segments: [...parent.segments, newSegment]
617
- };
692
+ var Fragment = class Fragment extends GqlElement {
693
+ constructor(define) {
694
+ super(define);
695
+ }
696
+ /** The GraphQL type name this fragment selects from. */
697
+ get typename() {
698
+ return GqlElement.get(this).typename;
699
+ }
700
+ /** Optional unique key for prebuilt type lookup. */
701
+ get key() {
702
+ return GqlElement.get(this).key;
703
+ }
704
+ /** The schema label this fragment belongs to. */
705
+ get schemaLabel() {
706
+ return GqlElement.get(this).schemaLabel;
707
+ }
708
+ /** Variable definitions for this fragment. */
709
+ get variableDefinitions() {
710
+ return GqlElement.get(this).variableDefinitions;
711
+ }
712
+ /**
713
+ * Spreads this fragment's fields into a parent selection.
714
+ * Pass variables if the fragment defines any.
715
+ */
716
+ get spread() {
717
+ return GqlElement.get(this).spread;
718
+ }
719
+ /**
720
+ * Creates a new Fragment instance.
721
+ * Prefer using the `gql(({ fragment }) => ...)` API instead.
722
+ * @internal
723
+ */
724
+ static create(define) {
725
+ return new Fragment(define);
726
+ }
618
727
  };
728
+
729
+ //#endregion
730
+ //#region packages/core/src/types/element/operation.ts
619
731
  /**
620
- * Check if a type specifier represents a list type.
621
- * Matches patterns like "Type:![]!", "Type:![]?", "Type:?[]!", etc.
732
+ * Represents a GraphQL operation (query, mutation, or subscription).
622
733
  *
623
- * @internal
734
+ * Operations are created via `gql(({ query }) => query.operation({ ... }))`.
735
+ * Produces a TypedDocumentNode for type-safe execution with GraphQL clients.
736
+ *
737
+ * @template TOperationType - 'query' | 'mutation' | 'subscription'
738
+ * @template TOperationName - The unique operation name
739
+ * @template TVariableNames - Tuple of variable names
740
+ * @template TVariables - Variable types for the operation
741
+ * @template TFields - Selected fields structure
742
+ * @template TData - Inferred response data type
624
743
  */
625
- const isListType = (typeString) => {
626
- return typeString.includes("[]");
744
+ var Operation = class Operation extends GqlElement {
745
+ constructor(define) {
746
+ super(define);
747
+ }
748
+ /** The operation type: 'query', 'mutation', or 'subscription'. */
749
+ get operationType() {
750
+ return GqlElement.get(this).operationType;
751
+ }
752
+ /** The unique name of this operation. */
753
+ get operationName() {
754
+ return GqlElement.get(this).operationName;
755
+ }
756
+ /** The schema label this operation belongs to. */
757
+ get schemaLabel() {
758
+ return GqlElement.get(this).schemaLabel;
759
+ }
760
+ /** List of variable names defined for this operation. */
761
+ get variableNames() {
762
+ return GqlElement.get(this).variableNames;
763
+ }
764
+ /**
765
+ * Returns the field selections. Used for document reconstruction.
766
+ * @internal
767
+ */
768
+ get documentSource() {
769
+ return GqlElement.get(this).documentSource;
770
+ }
771
+ /** The TypedDocumentNode for use with GraphQL clients. */
772
+ get document() {
773
+ return GqlElement.get(this).document;
774
+ }
775
+ /** Custom metadata attached to this operation, if any. */
776
+ get metadata() {
777
+ return GqlElement.get(this).metadata;
778
+ }
779
+ /**
780
+ * Creates a new Operation instance.
781
+ * Prefer using the `gql(({ query }) => ...)` API instead.
782
+ * @internal
783
+ */
784
+ static create(define) {
785
+ return new Operation(define);
786
+ }
627
787
  };
628
788
 
629
789
  //#endregion
630
- //#region packages/core/src/utils/map-values.ts
631
- function mapValues(obj, fn) {
632
- return Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, fn(value, key)]));
633
- }
634
-
635
- //#endregion
636
- //#region packages/core/src/composer/fields-builder.ts
637
- const cacheMapBySchema = /* @__PURE__ */ new WeakMap();
638
- const ensureCacheMapBySchema = (schema) => {
639
- const cachedCacheMap = cacheMapBySchema.get(schema);
640
- if (cachedCacheMap) return cachedCacheMap;
641
- const cacheMap = /* @__PURE__ */ new Map();
642
- cacheMapBySchema.set(schema, cacheMap);
643
- return cacheMap;
644
- };
790
+ //#region packages/core/src/composer/compat.ts
645
791
  /**
646
- * Creates field selection factories for a given object type.
792
+ * Compat composer factory for creating GraphQL operation specifications.
793
+ * @module
794
+ */
795
+ /**
796
+ * Creates a factory for composing compat operation specifications.
647
797
  *
648
- * Returns an object with a factory for each field defined on the type.
649
- * Factories are cached per schema+type to avoid recreation.
798
+ * Returns a function that creates a `GqlDefine<CompatSpec<...>>` storing
799
+ * the operation specification with unevaluated fieldsBuilder.
650
800
  *
651
801
  * @param schema - The GraphQL schema definition
652
- * @param typeName - The object type name to create factories for
653
- * @returns Object mapping field names to their selection factories
802
+ * @param operationType - The operation type ('query' | 'mutation' | 'subscription')
803
+ * @returns Compat operation composer function
654
804
  *
655
- * @internal Used by operation and fragment composers
805
+ * @internal Used by `createGqlElementComposer`
656
806
  */
657
- const createFieldFactories = (schema, typeName) => {
658
- const cacheMap = ensureCacheMapBySchema(schema);
659
- const cached = cacheMap.get(typeName);
660
- if (cached) return cached;
661
- const factories = createFieldFactoriesInner(schema, typeName);
662
- cacheMap.set(typeName, factories);
663
- return factories;
664
- };
665
- const createFieldFactoriesInner = (schema, typeName) => {
666
- const typeDef = schema.object[typeName];
667
- if (!typeDef) throw new Error(`Type ${typeName} is not defined in schema objects`);
668
- const entries = Object.entries(typeDef.fields).map(([fieldName, type]) => {
669
- const factory = (fieldArgs, extras) => {
670
- const wrap = (value) => require_schema_builder.wrapByKey(extras?.alias ?? fieldName, value);
671
- const directives = extras?.directives ?? [];
672
- if (type.kind === "object") {
673
- const factoryReturn = ((nest) => {
674
- const nestedFields = withFieldPath(appendToPath(getCurrentFieldPath(), {
675
- field: fieldName,
676
- parentType: typeName,
677
- isList: isListType(type.modifier)
678
- }), () => nest({ f: createFieldFactories(schema, type.name) }));
679
- return wrap({
680
- parent: typeName,
681
- field: fieldName,
682
- type,
683
- args: fieldArgs ?? {},
684
- directives,
685
- object: nestedFields,
686
- union: null
687
- });
688
- });
689
- return factoryReturn;
690
- }
691
- if (type.kind === "union") {
692
- const factoryReturn = ((nest) => {
693
- const nestedUnion = withFieldPath(appendToPath(getCurrentFieldPath(), {
694
- field: fieldName,
695
- parentType: typeName,
696
- isList: isListType(type.modifier)
697
- }), () => mapValues(nest, (builder, memberName) => {
698
- if (!builder) throw new Error(`Builder is undefined for member name: ${memberName}`);
699
- return builder({ f: createFieldFactories(schema, memberName) });
700
- }));
701
- return wrap({
702
- parent: typeName,
703
- field: fieldName,
704
- type,
705
- args: fieldArgs ?? {},
706
- directives,
707
- object: null,
708
- union: nestedUnion
709
- });
710
- });
711
- return factoryReturn;
712
- }
713
- if (type.kind === "scalar" || type.kind === "enum" || type.kind === "typename") return wrap({
714
- parent: typeName,
715
- field: fieldName,
716
- type,
717
- args: fieldArgs ?? {},
718
- directives,
719
- object: null,
720
- union: null
721
- });
722
- throw new Error(`Unsupported field type: ${type}`);
723
- };
724
- return [fieldName, factory];
725
- });
726
- return Object.fromEntries(entries);
807
+ const createCompatComposer = (schema, operationType) => {
808
+ if (schema.operations[operationType] === null) throw new Error(`Operation type ${operationType} is not defined in schema roots`);
809
+ return (options) => {
810
+ return GqlDefine.create(() => ({
811
+ schema,
812
+ operationType,
813
+ operationName: options.name,
814
+ variables: options.variables ?? {},
815
+ fieldsBuilder: options.fields
816
+ }));
817
+ };
727
818
  };
728
819
 
729
820
  //#endregion
730
- //#region packages/core/src/utils/promise.ts
821
+ //#region packages/core/src/composer/directive-builder.ts
731
822
  /**
732
- * Promise detection utilities for cross-realm compatibility.
823
+ * Directive builder utilities for creating field-level directives.
824
+ *
825
+ * Provides type-safe methods for creating directive references that can be
826
+ * applied to field selections. The builder follows a similar pattern to
827
+ * the variable builder ($var).
828
+ *
733
829
  * @module
734
830
  */
735
831
  /**
736
- * Check if a value is Promise-like (has .then method).
832
+ * Creates a directive method factory for a specific directive.
737
833
  *
738
- * This function uses duck typing instead of `instanceof Promise` to work across
739
- * VM sandbox boundaries where Promises created in a different realm have a
740
- * different constructor.
834
+ * @param name - The directive name (without @)
835
+ * @param locations - Valid locations where the directive can be applied
836
+ * @returns A function that creates DirectiveRef instances
741
837
  *
742
838
  * @example
743
839
  * ```typescript
744
- * // Works with native Promises
745
- * isPromiseLike(Promise.resolve(42)); // true
840
+ * const skipMethod = createDirectiveMethod("skip", ["FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT"] as const);
841
+ * const skipDirective = skipMethod({ if: true });
842
+ * ```
843
+ */
844
+ const createDirectiveMethod = (name, locations) => {
845
+ return (args) => new DirectiveRef({
846
+ name,
847
+ arguments: args,
848
+ locations
849
+ });
850
+ };
851
+ /**
852
+ * Creates a typed directive method with argument type specifiers.
853
+ * Enables enum value output in directive arguments.
746
854
  *
747
- * // Works with VM sandbox Promises (instanceof would fail)
748
- * const vmPromise = vm.runInContext("Promise.resolve(42)", context);
749
- * isPromiseLike(vmPromise); // true (instanceof Promise would be false)
855
+ * @param name - The directive name (without @)
856
+ * @param locations - Valid locations where the directive can be applied
857
+ * @param argSpecs - Type specifiers for directive arguments
858
+ * @returns A function that creates DirectiveRef instances with argument type info
750
859
  *
751
- * // Rejects non-Promises
752
- * isPromiseLike({ then: "not a function" }); // false
753
- * isPromiseLike(null); // false
754
- * isPromiseLike(42); // false
860
+ * @example
861
+ * ```typescript
862
+ * const authMethod = createTypedDirectiveMethod(
863
+ * "auth",
864
+ * ["FIELD"] as const,
865
+ * { role: { kind: "enum", name: "Role", modifier: "!" } }
866
+ * );
867
+ * const authDirective = authMethod({ role: "ADMIN" });
755
868
  * ```
756
869
  */
757
- const isPromiseLike = (value) => {
758
- return value !== null && typeof value === "object" && "then" in value && typeof value.then === "function";
870
+ const createTypedDirectiveMethod = (name, locations, argSpecs) => {
871
+ return (args) => new DirectiveRef({
872
+ name,
873
+ arguments: args,
874
+ locations,
875
+ argumentSpecs: argSpecs
876
+ });
759
877
  };
760
-
761
- //#endregion
762
- //#region packages/core/src/types/element/lazy-evaluator.ts
763
878
  /**
764
- * Creates a lazy evaluator with caching, async support, and dependency ordering.
879
+ * Standard directive locations for @skip and @include.
880
+ */
881
+ const CONDITIONAL_DIRECTIVE_LOCATIONS = [
882
+ "FIELD",
883
+ "FRAGMENT_SPREAD",
884
+ "INLINE_FRAGMENT"
885
+ ];
886
+ /**
887
+ * Creates the standard GraphQL directives (@skip, @include).
888
+ * These are always available regardless of schema definition.
765
889
  *
766
- * @param define - Factory function that produces the value
767
- * @param getDeps - Optional function returning dependencies to evaluate first
768
- * @param createDepGenerator - Function to create evaluation generator for a dependency
769
- * @returns An executor generator function
890
+ * @returns Object containing skip and include directive methods
891
+ *
892
+ * @example
893
+ * ```typescript
894
+ * const $dir = createStandardDirectives();
895
+ * const skipDirective = $dir.skip({ if: true });
896
+ * ```
770
897
  */
771
- const createLazyEvaluator = (define, getDeps, createDepGenerator) => {
772
- let cache = null;
773
- let promise = null;
774
- return function* execute(context) {
775
- if (cache) return cache.value;
776
- if (promise) {
777
- yield promise;
778
- return cache.value;
779
- }
780
- if (getDeps) for (const dep of getDeps()) yield* createDepGenerator(dep);
781
- const defined = define(context);
782
- if (!isPromiseLike(defined)) return (cache = { value: defined }).value;
783
- promise = defined.then((value) => {
784
- cache = { value };
785
- promise = null;
786
- });
787
- yield promise;
788
- return cache.value;
898
+ const createStandardDirectives = () => ({
899
+ skip: createDirectiveMethod("skip", CONDITIONAL_DIRECTIVE_LOCATIONS),
900
+ include: createDirectiveMethod("include", CONDITIONAL_DIRECTIVE_LOCATIONS)
901
+ });
902
+ /**
903
+ * Creates a directive builder with standard directives and optional custom directives.
904
+ *
905
+ * @param customDirectives - Additional directive methods from schema (generated by codegen)
906
+ * @returns Combined directive builder with all available directives
907
+ *
908
+ * @internal Used by codegen to create schema-specific directive builders
909
+ */
910
+ const createDirectiveBuilder = (customDirectives) => {
911
+ return {
912
+ ...createStandardDirectives(),
913
+ ...customDirectives ?? {}
789
914
  };
790
915
  };
791
916
  /**
792
- * Creates an evaluation generator from an executor.
793
- * Wraps the executor's generator and discards its return value.
917
+ * Type guard to check if a value is a DirectiveRef.
918
+ *
919
+ * @param value - Value to check
920
+ * @returns True if value is a DirectiveRef instance
794
921
  */
795
- function* createEvaluationGenerator(executor, context) {
796
- yield* executor(context);
797
- }
922
+ const isDirectiveRef = (value) => {
923
+ return value instanceof DirectiveRef;
924
+ };
925
+
926
+ //#endregion
927
+ //#region packages/core/src/types/metadata/adapter.ts
798
928
  /**
799
- * Executes the evaluator synchronously.
800
- * Throws if async operation is encountered.
929
+ * Creates the default adapter instance.
930
+ * @internal
801
931
  */
802
- const evaluateSync = (executor, context) => {
803
- const result = executor(context).next();
804
- if (!result.done) throw new Error("Async operation is not supported in sync evaluation.");
805
- return result.value;
806
- };
932
+ const createDefaultAdapter = () => ({ aggregateFragmentMetadata: (fragments) => fragments.map((m) => m.metadata) });
933
+ /**
934
+ * The default adapter instance.
935
+ */
936
+ const defaultMetadataAdapter = createDefaultAdapter();
807
937
 
808
938
  //#endregion
809
- //#region packages/core/src/types/element/gql-element.ts
810
- const GQL_ELEMENT_FACTORY = Symbol("GQL_ELEMENT_FACTORY");
811
- const GQL_ELEMENT_CONTEXT = Symbol("GQL_ELEMENT_CONTEXT");
939
+ //#region packages/core/src/utils/map-values.ts
940
+ function mapValues(obj, fn) {
941
+ return Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, fn(value, key)]));
942
+ }
943
+
944
+ //#endregion
945
+ //#region packages/core/src/composer/field-path-context.ts
812
946
  /**
813
- * Abstract base class for all GraphQL elements (Fragment, Operation).
947
+ * Shared mutable container for field path context.
948
+ * Only synchronous access is supported.
949
+ */
950
+ const fieldPathContext = { current: null };
951
+ /**
952
+ * Get the current field path.
953
+ * Returns null if not in a field building context.
814
954
  *
815
- * Uses lazy evaluation with caching - definition is computed on first access.
816
- * Subclasses should not be instantiated directly; use static `create` methods.
955
+ * @example
956
+ * ```typescript
957
+ * import { getCurrentFieldPath } from '@soda-gql/core';
817
958
  *
818
- * @template TDefinition - The shape of the evaluated definition
819
- * @template TInfer - Type inference metadata (access via `$infer`)
959
+ * // Inside a field builder or model spread:
960
+ * const path = getCurrentFieldPath();
961
+ * console.log(path?.full); // "$.user.posts[].author"
962
+ * ```
820
963
  */
821
- var GqlElement = class GqlElement {
822
- [GQL_ELEMENT_FACTORY];
823
- [GQL_ELEMENT_CONTEXT] = null;
824
- constructor(define, getDeps) {
825
- this[GQL_ELEMENT_FACTORY] = createLazyEvaluator(define, getDeps, GqlElement.createEvaluationGenerator);
826
- Object.defineProperty(this, "$infer", { get() {
827
- throw new Error("This property is only for type meta. Do not access this property directly.");
828
- } });
829
- }
830
- attach(attachmentOrAttachments) {
831
- const attachments = Array.isArray(attachmentOrAttachments) ? attachmentOrAttachments : [attachmentOrAttachments];
832
- for (const attachment of attachments) {
833
- let cache = null;
834
- const self = this;
835
- Object.defineProperty(this, attachment.name, { get() {
836
- if (cache) return cache;
837
- GqlElement.evaluateInstantly(self);
838
- return cache = attachment.createValue(self);
839
- } });
840
- }
841
- return this;
842
- }
843
- /**
844
- * Sets the canonical context for an element. Used by the builder.
845
- * @internal
846
- */
847
- static setContext(element, context) {
848
- element[GQL_ELEMENT_CONTEXT] = context;
849
- }
850
- /**
851
- * Gets the canonical context of an element, if set.
852
- * @internal
853
- */
854
- static getContext(element) {
855
- return element[GQL_ELEMENT_CONTEXT];
856
- }
857
- /**
858
- * Creates a generator for async evaluation. Used by the builder.
859
- * @internal
860
- */
861
- static createEvaluationGenerator(element) {
862
- return createEvaluationGenerator(element[GQL_ELEMENT_FACTORY], element[GQL_ELEMENT_CONTEXT]);
863
- }
864
- static evaluateInstantly(element) {
865
- return evaluateSync(element[GQL_ELEMENT_FACTORY], element[GQL_ELEMENT_CONTEXT]);
866
- }
867
- /**
868
- * Forces synchronous evaluation. Throws if async operation is needed.
869
- * @internal
870
- */
871
- static evaluateSync(element) {
872
- GqlElement.evaluateInstantly(element);
873
- }
874
- /**
875
- * Evaluates and returns the element's definition.
876
- * Throws if async operation is needed.
877
- * @internal
878
- */
879
- static get(element) {
880
- return GqlElement.evaluateInstantly(element);
964
+ const getCurrentFieldPath = () => fieldPathContext.current;
965
+ /**
966
+ * Run a function with a specific field path context.
967
+ * Restores the previous path after the function completes.
968
+ *
969
+ * @internal
970
+ */
971
+ const withFieldPath = (path, fn) => {
972
+ const previousPath = fieldPathContext.current;
973
+ fieldPathContext.current = path;
974
+ try {
975
+ return fn();
976
+ } finally {
977
+ fieldPathContext.current = previousPath;
881
978
  }
882
979
  };
883
-
884
- //#endregion
885
- //#region packages/core/src/types/element/fragment.ts
886
980
  /**
887
- * Represents a reusable GraphQL field selection on a specific type.
888
- *
889
- * Fragments are created via `gql(({ fragment }) => fragment.TypeName({ ... }))`.
890
- * Use `spread()` to include the fragment's fields in an operation.
981
+ * Append a new segment to the current path.
982
+ *
983
+ * @internal
984
+ */
985
+ const appendToPath = (parent, segment) => {
986
+ const listSuffix = segment.isList ? "[]" : "";
987
+ const newSegment = {
988
+ field: segment.field,
989
+ parent: segment.parentType,
990
+ isList: segment.isList
991
+ };
992
+ if (!parent) return {
993
+ full: `$.${segment.field}${listSuffix}`,
994
+ segments: [newSegment]
995
+ };
996
+ return {
997
+ full: `${parent.full}.${segment.field}${listSuffix}`,
998
+ segments: [...parent.segments, newSegment]
999
+ };
1000
+ };
1001
+ /**
1002
+ * Check if a type specifier represents a list type.
1003
+ * Matches patterns like "Type:![]!", "Type:![]?", "Type:?[]!", etc.
891
1004
  *
892
- * @template TTypeName - The GraphQL type this fragment selects from
893
- * @template TVariables - Variables required when spreading
894
- * @template TFields - The selected fields structure
895
- * @template TOutput - Inferred output type from selected fields
1005
+ * @internal
896
1006
  */
897
- var Fragment = class Fragment extends GqlElement {
898
- constructor(define) {
899
- super(define);
900
- }
901
- /** The GraphQL type name this fragment selects from. */
902
- get typename() {
903
- return GqlElement.get(this).typename;
904
- }
905
- /** Optional unique key for prebuilt type lookup. */
906
- get key() {
907
- return GqlElement.get(this).key;
908
- }
909
- /** The schema label this fragment belongs to. */
910
- get schemaLabel() {
911
- return GqlElement.get(this).schemaLabel;
912
- }
913
- /** Variable definitions for this fragment. */
914
- get variableDefinitions() {
915
- return GqlElement.get(this).variableDefinitions;
916
- }
917
- /**
918
- * Spreads this fragment's fields into a parent selection.
919
- * Pass variables if the fragment defines any.
920
- */
921
- get spread() {
922
- return GqlElement.get(this).spread;
923
- }
924
- /**
925
- * Creates a new Fragment instance.
926
- * Prefer using the `gql(({ fragment }) => ...)` API instead.
927
- * @internal
928
- */
929
- static create(define) {
930
- return new Fragment(define);
931
- }
1007
+ const isListType = (typeString) => {
1008
+ return typeString.includes("[]");
932
1009
  };
933
1010
 
934
1011
  //#endregion
935
- //#region packages/core/src/types/element/operation.ts
1012
+ //#region packages/core/src/composer/fields-builder.ts
1013
+ const cacheMapBySchema = /* @__PURE__ */ new WeakMap();
1014
+ const ensureCacheMapBySchema = (schema) => {
1015
+ const cachedCacheMap = cacheMapBySchema.get(schema);
1016
+ if (cachedCacheMap) return cachedCacheMap;
1017
+ const cacheMap = /* @__PURE__ */ new Map();
1018
+ cacheMapBySchema.set(schema, cacheMap);
1019
+ return cacheMap;
1020
+ };
936
1021
  /**
937
- * Represents a GraphQL operation (query, mutation, or subscription).
1022
+ * Creates field selection factories for a given object type.
938
1023
  *
939
- * Operations are created via `gql(({ query }) => query.operation({ ... }))`.
940
- * Produces a TypedDocumentNode for type-safe execution with GraphQL clients.
1024
+ * Returns an object with a factory for each field defined on the type.
1025
+ * Factories are cached per schema+type to avoid recreation.
941
1026
  *
942
- * @template TOperationType - 'query' | 'mutation' | 'subscription'
943
- * @template TOperationName - The unique operation name
944
- * @template TVariableNames - Tuple of variable names
945
- * @template TVariables - Variable types for the operation
946
- * @template TFields - Selected fields structure
947
- * @template TData - Inferred response data type
1027
+ * @param schema - The GraphQL schema definition
1028
+ * @param typeName - The object type name to create factories for
1029
+ * @returns Object mapping field names to their selection factories
1030
+ *
1031
+ * @internal Used by operation and fragment composers
948
1032
  */
949
- var Operation = class Operation extends GqlElement {
950
- constructor(define) {
951
- super(define);
952
- }
953
- /** The operation type: 'query', 'mutation', or 'subscription'. */
954
- get operationType() {
955
- return GqlElement.get(this).operationType;
956
- }
957
- /** The unique name of this operation. */
958
- get operationName() {
959
- return GqlElement.get(this).operationName;
960
- }
961
- /** The schema label this operation belongs to. */
962
- get schemaLabel() {
963
- return GqlElement.get(this).schemaLabel;
964
- }
965
- /** List of variable names defined for this operation. */
966
- get variableNames() {
967
- return GqlElement.get(this).variableNames;
968
- }
969
- /**
970
- * Returns the field selections. Used for document reconstruction.
971
- * @internal
972
- */
973
- get documentSource() {
974
- return GqlElement.get(this).documentSource;
975
- }
976
- /** The TypedDocumentNode for use with GraphQL clients. */
977
- get document() {
978
- return GqlElement.get(this).document;
979
- }
980
- /** Custom metadata attached to this operation, if any. */
981
- get metadata() {
982
- return GqlElement.get(this).metadata;
983
- }
984
- /**
985
- * Creates a new Operation instance.
986
- * Prefer using the `gql(({ query }) => ...)` API instead.
987
- * @internal
988
- */
989
- static create(define) {
990
- return new Operation(define);
991
- }
1033
+ const createFieldFactories = (schema, typeName) => {
1034
+ const cacheMap = ensureCacheMapBySchema(schema);
1035
+ const cached = cacheMap.get(typeName);
1036
+ if (cached) return cached;
1037
+ const factories = createFieldFactoriesInner(schema, typeName);
1038
+ cacheMap.set(typeName, factories);
1039
+ return factories;
1040
+ };
1041
+ const createFieldFactoriesInner = (schema, typeName) => {
1042
+ const typeDef = schema.object[typeName];
1043
+ if (!typeDef) throw new Error(`Type ${typeName} is not defined in schema objects`);
1044
+ const entries = Object.entries(typeDef.fields).map(([fieldName, type]) => {
1045
+ const factory = (fieldArgs, extras) => {
1046
+ const wrap = (value) => require_schema_builder.wrapByKey(extras?.alias ?? fieldName, value);
1047
+ const directives = extras?.directives ?? [];
1048
+ if (type.kind === "object") {
1049
+ const factoryReturn = ((nest) => {
1050
+ const nestedFields = withFieldPath(appendToPath(getCurrentFieldPath(), {
1051
+ field: fieldName,
1052
+ parentType: typeName,
1053
+ isList: isListType(type.modifier)
1054
+ }), () => nest({ f: createFieldFactories(schema, type.name) }));
1055
+ return wrap({
1056
+ parent: typeName,
1057
+ field: fieldName,
1058
+ type,
1059
+ args: fieldArgs ?? {},
1060
+ directives,
1061
+ object: nestedFields,
1062
+ union: null
1063
+ });
1064
+ });
1065
+ return factoryReturn;
1066
+ }
1067
+ if (type.kind === "union") {
1068
+ const factoryReturn = ((nest) => {
1069
+ const nestedUnion = withFieldPath(appendToPath(getCurrentFieldPath(), {
1070
+ field: fieldName,
1071
+ parentType: typeName,
1072
+ isList: isListType(type.modifier)
1073
+ }), () => mapValues(nest, (builder, memberName) => {
1074
+ if (!builder) throw new Error(`Builder is undefined for member name: ${memberName}`);
1075
+ return builder({ f: createFieldFactories(schema, memberName) });
1076
+ }));
1077
+ return wrap({
1078
+ parent: typeName,
1079
+ field: fieldName,
1080
+ type,
1081
+ args: fieldArgs ?? {},
1082
+ directives,
1083
+ object: null,
1084
+ union: nestedUnion
1085
+ });
1086
+ });
1087
+ return factoryReturn;
1088
+ }
1089
+ if (type.kind === "scalar" || type.kind === "enum" || type.kind === "typename") return wrap({
1090
+ parent: typeName,
1091
+ field: fieldName,
1092
+ type,
1093
+ args: fieldArgs ?? {},
1094
+ directives,
1095
+ object: null,
1096
+ union: null
1097
+ });
1098
+ throw new Error(`Unsupported field type: ${type}`);
1099
+ };
1100
+ return [fieldName, factory];
1101
+ });
1102
+ return Object.fromEntries(entries);
992
1103
  };
993
1104
 
994
1105
  //#endregion
@@ -1063,6 +1174,142 @@ const createVarAssignments = (definitions, providedValues) => {
1063
1174
  */
1064
1175
  const createVarRefs = (definitions) => mapValues(definitions, (_, name) => createVarRefFromVariable(name));
1065
1176
 
1177
+ //#endregion
1178
+ //#region packages/core/src/composer/operation-core.ts
1179
+ /**
1180
+ * Builds an operation artifact from the provided parameters.
1181
+ *
1182
+ * This function contains the core logic for:
1183
+ * - Creating variable refs and field factories
1184
+ * - Evaluating fields with fragment usage tracking
1185
+ * - Building the document
1186
+ * - Handling metadata (sync and async)
1187
+ * - Applying document transformations
1188
+ *
1189
+ * @param params - Operation building parameters
1190
+ * @returns Operation artifact or Promise of artifact (if async metadata)
1191
+ *
1192
+ * @internal Used by operation.ts and extend.ts
1193
+ */
1194
+ const buildOperationArtifact = (params) => {
1195
+ const { schema, operationType, operationTypeName, operationName, variables, fieldsFactory, adapter, metadata: metadataBuilder, transformDocument: operationTransformDocument, adapterTransformDocument } = params;
1196
+ const $ = createVarRefs(variables);
1197
+ const f = createFieldFactories(schema, operationTypeName);
1198
+ const { result: fields, usages: fragmentUsages } = withFragmentUsageCollection(() => fieldsFactory({
1199
+ f,
1200
+ $
1201
+ }));
1202
+ const document = buildDocument({
1203
+ operationName,
1204
+ operationType,
1205
+ variables,
1206
+ fields,
1207
+ schema
1208
+ });
1209
+ const variableNames = Object.keys(variables);
1210
+ if (!fragmentUsages.some((u) => u.metadataBuilder) && !metadataBuilder && !adapterTransformDocument && !operationTransformDocument) return {
1211
+ operationType,
1212
+ operationName,
1213
+ schemaLabel: schema.label,
1214
+ variableNames,
1215
+ documentSource: () => fields,
1216
+ document,
1217
+ metadata: void 0
1218
+ };
1219
+ const aggregateFragmentMetadata = (resolvedFragmentMetadata) => {
1220
+ const fragmentMetaInfos = fragmentUsages.map((usage, index) => ({
1221
+ metadata: resolvedFragmentMetadata[index],
1222
+ fieldPath: usage.path
1223
+ }));
1224
+ return adapter.aggregateFragmentMetadata(fragmentMetaInfos);
1225
+ };
1226
+ const fragmentMetadataResults = fragmentUsages.map((usage) => usage.metadataBuilder ? usage.metadataBuilder() : void 0);
1227
+ const hasAsyncFragmentMetadata = fragmentMetadataResults.some((r) => isPromiseLike(r));
1228
+ const buildOperationMetadata = (aggregatedFragmentMetadata) => {
1229
+ const schemaLevel = adapter.schemaLevel;
1230
+ return metadataBuilder?.({
1231
+ $,
1232
+ document,
1233
+ fragmentMetadata: aggregatedFragmentMetadata,
1234
+ schemaLevel
1235
+ });
1236
+ };
1237
+ const makeCreateArtifact = (aggregated$1) => {
1238
+ return ({ metadata }) => {
1239
+ let finalDocument = operationTransformDocument ? operationTransformDocument({
1240
+ document,
1241
+ metadata
1242
+ }) : document;
1243
+ if (adapterTransformDocument) finalDocument = adapterTransformDocument({
1244
+ document: finalDocument,
1245
+ operationName,
1246
+ operationType,
1247
+ variableNames,
1248
+ schemaLevel: adapter.schemaLevel,
1249
+ fragmentMetadata: aggregated$1
1250
+ });
1251
+ return {
1252
+ operationType,
1253
+ operationName,
1254
+ schemaLabel: schema.label,
1255
+ variableNames,
1256
+ documentSource: () => fields,
1257
+ document: finalDocument,
1258
+ metadata
1259
+ };
1260
+ };
1261
+ };
1262
+ if (hasAsyncFragmentMetadata) return Promise.all(fragmentMetadataResults).then(async (resolvedFragmentMetadata) => {
1263
+ const aggregated$1 = aggregateFragmentMetadata(resolvedFragmentMetadata);
1264
+ const operationMetadata = await buildOperationMetadata(aggregated$1);
1265
+ return makeCreateArtifact(aggregated$1)({ metadata: operationMetadata });
1266
+ });
1267
+ const aggregated = aggregateFragmentMetadata(fragmentMetadataResults);
1268
+ const createArtifact = makeCreateArtifact(aggregated);
1269
+ const operationMetadataResult = buildOperationMetadata(aggregated);
1270
+ if (isPromiseLike(operationMetadataResult)) return operationMetadataResult.then((metadata) => createArtifact({ metadata }));
1271
+ return createArtifact({ metadata: operationMetadataResult });
1272
+ };
1273
+
1274
+ //#endregion
1275
+ //#region packages/core/src/composer/extend.ts
1276
+ /**
1277
+ * Extend composer factory for creating Operations from compat specs.
1278
+ * @module
1279
+ */
1280
+ /**
1281
+ * Creates a factory for extending compat specs into full operations.
1282
+ *
1283
+ * The extend function takes a compat spec (created by `query.compat()`) and
1284
+ * optional metadata/transformDocument options, then creates a full Operation.
1285
+ *
1286
+ * @param schema - The GraphQL schema definition
1287
+ * @param adapter - Optional metadata adapter for custom metadata handling
1288
+ * @param transformDocument - Optional document transformer
1289
+ * @returns Extend composer function
1290
+ *
1291
+ * @internal Used by `createGqlElementComposer`
1292
+ */
1293
+ const createExtendComposer = (schema, adapter, transformDocument) => {
1294
+ const resolvedAdapter = adapter ?? defaultMetadataAdapter;
1295
+ return (compat, options) => {
1296
+ const { operationType, operationName, variables, fieldsBuilder } = compat.value;
1297
+ const operationTypeName = schema.operations[operationType];
1298
+ return Operation.create((() => buildOperationArtifact({
1299
+ schema,
1300
+ operationType,
1301
+ operationTypeName,
1302
+ operationName,
1303
+ variables,
1304
+ fieldsFactory: fieldsBuilder,
1305
+ adapter: resolvedAdapter,
1306
+ metadata: options?.metadata,
1307
+ transformDocument: options?.transformDocument,
1308
+ adapterTransformDocument: transformDocument
1309
+ })));
1310
+ };
1311
+ };
1312
+
1066
1313
  //#endregion
1067
1314
  //#region packages/core/src/composer/fragment.ts
1068
1315
  /**
@@ -1109,18 +1356,6 @@ const createGqlFragmentComposers = (schema, _adapter) => {
1109
1356
  return mapValues(schema.object, (_, typename) => createFragmentComposer(typename));
1110
1357
  };
1111
1358
 
1112
- //#endregion
1113
- //#region packages/core/src/types/metadata/adapter.ts
1114
- /**
1115
- * Creates the default adapter instance.
1116
- * @internal
1117
- */
1118
- const createDefaultAdapter = () => ({ aggregateFragmentMetadata: (fragments) => fragments.map((m) => m.metadata) });
1119
- /**
1120
- * The default adapter instance.
1121
- */
1122
- const defaultMetadataAdapter = createDefaultAdapter();
1123
-
1124
1359
  //#endregion
1125
1360
  //#region packages/core/src/composer/operation.ts
1126
1361
  /**
@@ -1149,86 +1384,18 @@ const createOperationComposerFactory = (schema, adapter, transformDocument) => {
1149
1384
  const operationTypeName = schema.operations[operationType];
1150
1385
  if (operationTypeName === null) throw new Error(`Operation type ${operationType} is not defined in schema roots`);
1151
1386
  return (options) => {
1152
- return Operation.create(() => {
1153
- const { name: operationName } = options;
1154
- const variables = options.variables ?? {};
1155
- const $ = createVarRefs(variables);
1156
- const f = createFieldFactories(schema, operationTypeName);
1157
- const { result: fields, usages: fragmentUsages } = withFragmentUsageCollection(() => options.fields({
1158
- f,
1159
- $
1160
- }));
1161
- const document = buildDocument({
1162
- operationName,
1163
- operationType,
1164
- variables,
1165
- fields,
1166
- schema
1167
- });
1168
- const variableNames = Object.keys(variables);
1169
- if (!fragmentUsages.some((u) => u.metadataBuilder) && !options.metadata && !transformDocument && !options.transformDocument) return {
1170
- operationType,
1171
- operationName,
1172
- schemaLabel: schema.label,
1173
- variableNames,
1174
- documentSource: () => fields,
1175
- document,
1176
- metadata: void 0
1177
- };
1178
- const aggregateFragmentMetadata = (resolvedFragmentMetadata) => {
1179
- const fragmentMetaInfos = fragmentUsages.map((usage, index) => ({
1180
- metadata: resolvedFragmentMetadata[index],
1181
- fieldPath: usage.path
1182
- }));
1183
- return resolvedAdapter.aggregateFragmentMetadata(fragmentMetaInfos);
1184
- };
1185
- const fragmentMetadataResults = fragmentUsages.map((usage) => usage.metadataBuilder ? usage.metadataBuilder() : void 0);
1186
- const hasAsyncFragmentMetadata = fragmentMetadataResults.some((r) => isPromiseLike(r));
1187
- const buildOperationMetadata = (aggregatedFragmentMetadata) => {
1188
- const schemaLevel = resolvedAdapter.schemaLevel;
1189
- return options.metadata?.({
1190
- $,
1191
- document,
1192
- fragmentMetadata: aggregatedFragmentMetadata,
1193
- schemaLevel
1194
- });
1195
- };
1196
- const makeCreateDefinition = (aggregated$1) => {
1197
- return ({ metadata }) => {
1198
- let finalDocument = options.transformDocument ? options.transformDocument({
1199
- document,
1200
- metadata
1201
- }) : document;
1202
- if (transformDocument) finalDocument = transformDocument({
1203
- document: finalDocument,
1204
- operationName,
1205
- operationType,
1206
- variableNames,
1207
- schemaLevel: resolvedAdapter.schemaLevel,
1208
- fragmentMetadata: aggregated$1
1209
- });
1210
- return {
1211
- operationType,
1212
- operationName,
1213
- schemaLabel: schema.label,
1214
- variableNames,
1215
- documentSource: () => fields,
1216
- document: finalDocument,
1217
- metadata
1218
- };
1219
- };
1220
- };
1221
- if (hasAsyncFragmentMetadata) return Promise.all(fragmentMetadataResults).then(async (resolvedFragmentMetadata) => {
1222
- const aggregated$1 = aggregateFragmentMetadata(resolvedFragmentMetadata);
1223
- const operationMetadata = await buildOperationMetadata(aggregated$1);
1224
- return makeCreateDefinition(aggregated$1)({ metadata: operationMetadata });
1225
- });
1226
- const aggregated = aggregateFragmentMetadata(fragmentMetadataResults);
1227
- const createDefinition = makeCreateDefinition(aggregated);
1228
- const operationMetadataResult = buildOperationMetadata(aggregated);
1229
- if (isPromiseLike(operationMetadataResult)) return operationMetadataResult.then((metadata) => createDefinition({ metadata }));
1230
- return createDefinition({ metadata: operationMetadataResult });
1231
- });
1387
+ return Operation.create((() => buildOperationArtifact({
1388
+ schema,
1389
+ operationType,
1390
+ operationTypeName,
1391
+ operationName: options.name,
1392
+ variables: options.variables ?? {},
1393
+ fieldsFactory: options.fields,
1394
+ adapter: resolvedAdapter,
1395
+ metadata: options.metadata,
1396
+ transformDocument: options.transformDocument,
1397
+ adapterTransformDocument: transformDocument
1398
+ })));
1232
1399
  };
1233
1400
  };
1234
1401
  };
@@ -1488,9 +1655,20 @@ const createGqlElementComposer = (schema, options) => {
1488
1655
  const createOperationComposer = createOperationComposerFactory(schema, metadataAdapter, transformDocument);
1489
1656
  const transformedContext = require_context_transformer.applyContextTransformer({
1490
1657
  fragment,
1491
- query: { operation: createOperationComposer("query") },
1492
- mutation: { operation: createOperationComposer("mutation") },
1493
- subscription: { operation: createOperationComposer("subscription") },
1658
+ query: {
1659
+ operation: createOperationComposer("query"),
1660
+ compat: createCompatComposer(schema, "query")
1661
+ },
1662
+ mutation: {
1663
+ operation: createOperationComposer("mutation"),
1664
+ compat: createCompatComposer(schema, "mutation")
1665
+ },
1666
+ subscription: {
1667
+ operation: createOperationComposer("subscription"),
1668
+ compat: createCompatComposer(schema, "subscription")
1669
+ },
1670
+ define: (factory) => GqlDefine.create(factory),
1671
+ extend: createExtendComposer(schema, metadataAdapter, transformDocument),
1494
1672
  $var: createVarBuilder(inputTypeMethods),
1495
1673
  $dir: directiveMethods ?? createStandardDirectives(),
1496
1674
  $colocate: createColocateHelper(),
@@ -1760,6 +1938,7 @@ const generateInputTypeFromSpecifiers = (schema, specifiers, options = {}) => {
1760
1938
 
1761
1939
  //#endregion
1762
1940
  exports.Fragment = Fragment;
1941
+ exports.GqlDefine = GqlDefine;
1763
1942
  exports.GqlElement = GqlElement;
1764
1943
  exports.Operation = Operation;
1765
1944
  exports.VarRef = VarRef;
@@ -1768,14 +1947,17 @@ exports.applyTypeModifier = applyTypeModifier;
1768
1947
  exports.buildArgumentValue = buildArgumentValue;
1769
1948
  exports.buildConstValueNode = buildConstValueNode;
1770
1949
  exports.buildDocument = buildDocument;
1950
+ exports.buildOperationArtifact = buildOperationArtifact;
1771
1951
  exports.buildOperationTypeNode = buildOperationTypeNode;
1772
1952
  exports.buildWithTypeModifier = buildWithTypeModifier;
1773
1953
  exports.calculateFieldType = calculateFieldType;
1774
1954
  exports.calculateFieldsType = calculateFieldsType;
1775
1955
  exports.createColocateHelper = createColocateHelper;
1956
+ exports.createCompatComposer = createCompatComposer;
1776
1957
  exports.createDefaultAdapter = createDefaultAdapter;
1777
1958
  exports.createDirectiveBuilder = createDirectiveBuilder;
1778
1959
  exports.createDirectiveMethod = createDirectiveMethod;
1960
+ exports.createExtendComposer = createExtendComposer;
1779
1961
  exports.createFieldFactories = createFieldFactories;
1780
1962
  exports.createGqlElementComposer = createGqlElementComposer;
1781
1963
  exports.createGqlFragmentComposers = createGqlFragmentComposers;