grafast 0.1.1-beta.11 → 0.1.1-beta.13

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 (41) hide show
  1. package/README.md +0 -3
  2. package/dist/args.js +8 -2
  3. package/dist/constraints.d.ts +15 -1
  4. package/dist/constraints.js +40 -1
  5. package/dist/engine/LayerPlan.d.ts +1 -0
  6. package/dist/engine/LayerPlan.js +62 -9
  7. package/dist/engine/OperationPlan.js +372 -274
  8. package/dist/engine/OutputPlan.d.ts +6 -5
  9. package/dist/engine/OutputPlan.js +423 -444
  10. package/dist/engine/executeBucket.js +36 -77
  11. package/dist/error.d.ts +13 -0
  12. package/dist/grafastGraphql.d.ts +5 -5
  13. package/dist/graphqlCollectFields.d.ts +6 -0
  14. package/dist/graphqlCollectFields.js +14 -8
  15. package/dist/interfaces.d.ts +18 -0
  16. package/dist/interfaces.js +0 -2
  17. package/dist/mermaid.js +20 -11
  18. package/dist/operationPlan-input.js +8 -1
  19. package/dist/planJSONInterfaces.d.ts +2 -0
  20. package/dist/prepare.js +3 -3
  21. package/dist/step.d.ts +5 -5
  22. package/dist/step.js +53 -2
  23. package/dist/steps/__flag.d.ts +3 -2
  24. package/dist/steps/__inputObject.d.ts +1 -0
  25. package/dist/steps/__inputObject.js +36 -0
  26. package/dist/steps/__item.d.ts +3 -3
  27. package/dist/steps/__trackedValue.d.ts +8 -0
  28. package/dist/steps/__trackedValue.js +40 -0
  29. package/dist/steps/__value.d.ts +2 -1
  30. package/dist/steps/constant.d.ts +2 -0
  31. package/dist/steps/constant.js +12 -0
  32. package/dist/steps/graphqlResolver.js +8 -5
  33. package/dist/steps/listen.d.ts +2 -2
  34. package/dist/steps/object.d.ts +1 -1
  35. package/dist/steps/sideEffect.d.ts +0 -1
  36. package/dist/thereCanBeOnlyOne.js +1 -1
  37. package/dist/utils.d.ts +4 -0
  38. package/dist/utils.js +7 -0
  39. package/dist/version.d.ts +1 -1
  40. package/dist/version.js +1 -1
  41. package/package.json +1 -1
@@ -170,10 +170,6 @@ class OperationPlan {
170
170
  this.operationType = operation.operation;
171
171
  this.phase = "plan";
172
172
  this.rootLayerPlan = new LayerPlan_js_1.LayerPlan(this, null, REASON_ROOT);
173
- // This doesn't do anything, it only exists to align plans more closely
174
- // with an older version of the planner (to reduce the diff).
175
- const rootSelectionSetStep = (0, withGlobalLayerPlan_js_1.withGlobalLayerPlan)(this.rootLayerPlan, exports.POLYMORPHIC_ROOT_PATHS, newValueStepCallback);
176
- this.rootLayerPlan.setRootStep(rootSelectionSetStep);
177
173
  // Set up the shared steps for variables, context and rootValue
178
174
  [this.variableValuesStep, this.trackedVariableValuesStep] = this.track(variableValues, this.variableValuesConstraints, this.operation.variableDefinitions);
179
175
  [this.contextStep, this.trackedContextStep] = this.track(context, this.contextConstraints);
@@ -399,7 +395,7 @@ class OperationPlan {
399
395
  typeName: this.queryType.name,
400
396
  }, locationDetails);
401
397
  this.rootOutputPlan = outputPlan;
402
- this.planSelectionSet(outputPlan, [], exports.POLYMORPHIC_ROOT_PATH, exports.POLYMORPHIC_ROOT_PATHS, this.trackedRootValueStep, rootType, this.operation.selectionSet.selections);
398
+ this.planSelectionSet(outputPlan, [], exports.POLYMORPHIC_ROOT_PATH, exports.POLYMORPHIC_ROOT_PATHS, this.trackedRootValueStep, rootType, this.operation.selectionSet.selections, true);
403
399
  if (this.loc !== null)
404
400
  this.loc.pop();
405
401
  }
@@ -425,7 +421,7 @@ class OperationPlan {
425
421
  typeName: rootType.name,
426
422
  }, locationDetails);
427
423
  this.rootOutputPlan = outputPlan;
428
- this.planSelectionSet(outputPlan, [], exports.POLYMORPHIC_ROOT_PATH, exports.POLYMORPHIC_ROOT_PATHS, this.trackedRootValueStep, rootType, this.operation.selectionSet.selections, true);
424
+ this.planSelectionSet(outputPlan, [], exports.POLYMORPHIC_ROOT_PATH, exports.POLYMORPHIC_ROOT_PATHS, this.trackedRootValueStep, rootType, this.operation.selectionSet.selections, true, true);
429
425
  if (this.loc !== null)
430
426
  this.loc.pop();
431
427
  }
@@ -440,7 +436,7 @@ class OperationPlan {
440
436
  throw new index_js_1.SafeError("No subscription type found in schema");
441
437
  }
442
438
  const selectionSet = this.operation.selectionSet;
443
- const groupedFieldSet = (0, withGlobalLayerPlan_js_1.withGlobalLayerPlan)(this.rootLayerPlan, exports.POLYMORPHIC_ROOT_PATHS, graphqlCollectFields_js_1.graphqlCollectFields, null, this, this.trackedRootValueStep.id, rootType, selectionSet.selections);
439
+ const groupedFieldSet = (0, withGlobalLayerPlan_js_1.withGlobalLayerPlan)(this.rootLayerPlan, exports.POLYMORPHIC_ROOT_PATHS, graphqlCollectFields_js_1.graphqlCollectFields, null, this, this.trackedRootValueStep.id, rootType, selectionSet.selections, (0, graphqlCollectFields_js_1.newSelectionSetDigest)(false));
444
440
  if (groupedFieldSet.deferred !== undefined) {
445
441
  throw new index_js_1.SafeError("@defer forbidden on subscription root selection set");
446
442
  }
@@ -497,7 +493,7 @@ class OperationPlan {
497
493
  streamItemPlan = this.stepTracker.getStepById(streamItemPlan.id);
498
494
  const outputPlan = new OutputPlan_js_1.OutputPlan(subscriptionEventLayerPlan, this.rootValueStep, { mode: "root", typeName: rootType.name }, locationDetails);
499
495
  this.rootOutputPlan = outputPlan;
500
- this.planSelectionSet(outputPlan, [], exports.POLYMORPHIC_ROOT_PATH, exports.POLYMORPHIC_ROOT_PATHS, streamItemPlan, rootType, selectionSet.selections);
496
+ this.planSelectionSet(outputPlan, [], exports.POLYMORPHIC_ROOT_PATH, exports.POLYMORPHIC_ROOT_PATHS, streamItemPlan, rootType, selectionSet.selections, false);
501
497
  }
502
498
  else {
503
499
  const subscribeStep = (0, withGlobalLayerPlan_js_1.withGlobalLayerPlan)(this.rootLayerPlan, exports.POLYMORPHIC_ROOT_PATHS, () => {
@@ -541,7 +537,7 @@ class OperationPlan {
541
537
  streamItemPlan = this.stepTracker.getStepById(streamItemPlan.id);
542
538
  const outputPlan = new OutputPlan_js_1.OutputPlan(subscriptionEventLayerPlan, this.rootValueStep, { mode: "root", typeName: rootType.name }, locationDetails);
543
539
  this.rootOutputPlan = outputPlan;
544
- this.planSelectionSet(outputPlan, [], exports.POLYMORPHIC_ROOT_PATH, exports.POLYMORPHIC_ROOT_PATHS, streamItemPlan, rootType, selectionSet.selections);
540
+ this.planSelectionSet(outputPlan, [], exports.POLYMORPHIC_ROOT_PATH, exports.POLYMORPHIC_ROOT_PATHS, streamItemPlan, rootType, selectionSet.selections, true);
545
541
  }
546
542
  if (this.loc !== null)
547
543
  this.loc.pop();
@@ -571,187 +567,208 @@ class OperationPlan {
571
567
  outputPlan, path, polymorphicPath, polymorphicPaths, parentStep, objectType, objectTypeFields, isMutation, groupedFieldSet) {
572
568
  // `__typename` shouldn't bump the mutation index since it has no side effects.
573
569
  let mutationIndex = -1;
570
+ const $sideEffect = outputPlan.layerPlan.latestSideEffectStep;
574
571
  for (const [responseKey, fieldNodes] of groupedFieldSet.fields.entries()) {
575
- // All grouped fields are equivalent, as mandated by GraphQL validation rules. Thus we can take the first one.
576
- const field = fieldNodes[0];
577
- const fieldName = field.name.value;
578
- const locationDetails = {
579
- parentTypeName: objectType.name,
580
- fieldName,
581
- node: fieldNodes,
582
- };
583
- // explicit matches are the fastest: https://jsben.ch/ajZNf
584
- if (fieldName === "__typename") {
585
- outputPlan.addChild(objectType, responseKey, {
586
- type: "__typename",
587
- locationDetails,
588
- });
589
- continue;
590
- }
591
- else if (fieldName === "__schema" || fieldName === "__type") {
592
- const variableNames = (0, utils_js_1.findVariableNamesUsed)(this, field);
593
- outputPlan.addChild(objectType, responseKey, {
594
- type: "outputPlan",
595
- isNonNull: fieldName === "__schema",
596
- outputPlan: new OutputPlan_js_1.OutputPlan(outputPlan.layerPlan, this.rootValueStep, {
597
- mode: "introspection",
598
- field,
599
- variableNames,
600
- // PERF: if variableNames.length === 0 we should be able to optimize this!
601
- introspectionCacheByVariableValues: new lru_1.default({
602
- maxLength: 3,
603
- }),
604
- }, locationDetails),
605
- locationDetails,
606
- });
607
- continue;
608
- }
609
- const objectField = objectTypeFields[fieldName];
610
- if (!objectField) {
611
- // Field does not exist; this should have been caught by validation
612
- // but the spec says to just skip it.
613
- continue;
614
- }
615
- const fieldType = objectField.type;
616
- const rawPlanResolver = objectField.extensions?.grafast?.plan;
617
- if (rawPlanResolver?.constructor?.name === "AsyncFunction") {
618
- throw new Error(`Plans must be synchronous, but this schema has an async function at '${objectType.name}.${fieldName}.plan': ${rawPlanResolver.toString()}`);
619
- }
620
- const namedReturnType = getNamedType(fieldType);
621
- const resolvedResolver = objectField.resolve;
622
- const subscriber = objectField.subscribe;
623
- const usesDefaultResolver = resolvedResolver == null || resolvedResolver === defaultFieldResolver;
624
- const resolver = usesDefaultResolver ? null : resolvedResolver;
625
- // Apply a default plan to fields that do not have a plan nor a resolver.
626
- const planResolver = rawPlanResolver ??
627
- (usesDefaultResolver ? makeDefaultPlan(fieldName) : undefined);
628
- /*
629
- * When considering resolvers on fields, there's three booleans to
630
- * consider:
631
- *
632
- * - typeIsPlanned: Does the type the field is defined on expect a plan?
633
- * - NOTE: the root types (Query, Mutation, Subscription) implicitly
634
- * expect the "root plan"
635
- * - fieldHasPlan: Does the field define a `plan()` method?
636
- * - resultIsPlanned: Does the named type that the field returns (the
637
- * "named field type") expect a plan?
638
- * - NOTE: only object types, unions and interfaces may expect plans;
639
- * but not all of them do.
640
- * - NOTE: a union/interface expects a plan iff ANY of its object
641
- * types expect plans
642
- * - NOTE: if ANY object type in an interface/union expects a plan
643
- * then ALL object types within the interface/union must expect
644
- * plans.
645
- * - NOTE: scalars and enums never expect a plan.
646
- *
647
- * These booleans impact:
648
- *
649
- * - Whether there must be a `plan()` declaration and what the "parent"
650
- * argument is to the same
651
- * - If typeIsPlanned:
652
- * - Assert: `fieldHasPlan` must be true
653
- * - Pass through the parent plan
654
- * - Else, if resultIsPlanned:
655
- * - Assert: `fieldHasPlan` must be true
656
- * - Pass through a `__ValueStep` representing the parent value.
657
- * - Else, if fieldHasPlan:
658
- * - Pass through a `__ValueStep` representing the parent value.
659
- * - Else
660
- * - No action necessary.
661
- * - If the field may define `resolve()` and what the "parent" argument
662
- * is to the same
663
- * - If resultIsPlanned
664
- * - Assert: there must not be a `resolve()`
665
- * - Grafast provides pure resolver.
666
- * - Else if fieldHasPlan (which may be implied by typeIsPlanned
667
- * above)
668
- * - If `resolve()` is not set:
669
- * - grafast will return the value from the plan directly
670
- * - Otherwise:
671
- * - Grafast will wrap this resolver and will call `resolve()` (or
672
- * default resolver) with the plan result. IMPORTANT: you may
673
- * want to use an `ObjectStep` so that the parent object is of
674
- * the expected shape; e.g. your plan might return
675
- * `object({username: $username})` for a `User.username` field.
676
- * - Else
677
- * - Leave `resolve()` untouched - do not even wrap it.
678
- * - (Failing that, use a __ValueStep and return the result
679
- * directly.)
680
- */
681
- if (resolver !== null) {
682
- this.pure = false;
683
- }
684
- const resultIsPlanned = (0, utils_js_1.isTypePlanned)(this.schema, namedReturnType);
685
- const fieldHasPlan = !!planResolver;
686
- if (resultIsPlanned &&
687
- !fieldHasPlan &&
688
- !objectType.extensions?.grafast?.assertStep) {
689
- throw new Error(`Field ${objectType.name}.${fieldName} returns a ${namedReturnType.name} which expects a plan to be available; however this field has no plan() method to produce such a plan; please add 'extensions.grafast.plan' to this field.`);
690
- }
691
- if (resultIsPlanned && resolver) {
692
- throw new Error(`Field ${objectType.name}.${fieldName} returns a ${namedReturnType.name} which expects a plan to be available; this means that ${objectType.name}.${fieldName} is forbidden from defining a GraphQL resolver.`);
693
- }
694
- let step;
695
- let haltTree = false;
696
- const fieldLayerPlan = isMutation
697
- ? new LayerPlan_js_1.LayerPlan(this, outputPlan.layerPlan, {
698
- type: "mutationField",
699
- mutationIndex: ++mutationIndex,
700
- })
701
- : outputPlan.layerPlan;
702
- const objectFieldArgs = objectField.args;
703
- const trackedArguments = objectFieldArgs.length > 0
704
- ? (0, withGlobalLayerPlan_js_1.withGlobalLayerPlan)(this.rootLayerPlan, exports.POLYMORPHIC_ROOT_PATHS, this.getTrackedArguments, this, objectFieldArgs, field)
705
- : NO_ARGS;
706
- const fieldPath = [...path, responseKey];
707
- if (typeof planResolver === "function") {
708
- ({ step, haltTree } = this.planField(fieldLayerPlan, fieldPath, polymorphicPaths, fieldNodes, planResolver, "autoApplyAfterParentPlan", parentStep, objectField, trackedArguments));
709
- }
710
- else {
711
- // No plan resolver (or plan resolver fallback) so there must be a
712
- // `resolve` method, so we'll feed the full parent step into the
713
- // resolver.
714
- assert.ok(resolver !== null, "GraphileInternalError<81652257-617a-4d1a-8306-903d0e3d2ddf>: The field has no resolver, so planResolver should exist (either as the field.plan or as the default plan resolver).");
715
- // ENHANCEMENT: should we do `step = parentStep.object()` (i.e.
716
- // `$pgSelectSingle.record()`) or similar for "opaque" steps to become
717
- // suitable for consumption by resolvers?
718
- // Maybe `parentStep.forResolver()` or `parentStep.hydrate()` or `parentStep.toFullObject()`?
719
- step = parentStep;
720
- }
721
- if (resolver !== null) {
722
- step = (0, withGlobalLayerPlan_js_1.withGlobalLayerPlan)(fieldLayerPlan, polymorphicPaths, () => {
723
- const $args = (0, index_js_1.object)(field.arguments?.reduce((memo, arg) => {
724
- memo[arg.name.value] = trackedArguments.get(arg.name.value);
725
- return memo;
726
- }, Object.create(null)) ?? Object.create(null));
727
- return (0, graphqlResolver_js_1.graphqlResolver)(resolver, subscriber, step, $args, {
728
- fieldName,
729
- fieldNodes,
730
- fragments: this.fragments,
731
- operation: this.operation,
732
- parentType: objectType,
733
- returnType: fieldType,
734
- schema: this.schema,
572
+ let resolverEmulation = groupedFieldSet.resolverEmulation;
573
+ try {
574
+ // All grouped fields are equivalent, as mandated by GraphQL validation rules. Thus we can take the first one.
575
+ const field = fieldNodes[0];
576
+ const fieldName = field.name.value;
577
+ const locationDetails = {
578
+ parentTypeName: objectType.name,
579
+ fieldName,
580
+ node: fieldNodes,
581
+ };
582
+ // explicit matches are the fastest: https://jsben.ch/ajZNf
583
+ if (fieldName === "__typename") {
584
+ outputPlan.addChild(objectType, responseKey, {
585
+ type: "__typename",
586
+ locationDetails,
735
587
  });
736
- });
737
- }
738
- // May have changed due to deduplicate
739
- step = this.stepTracker.getStepById(step.id);
740
- if (haltTree) {
741
- const isNonNull = isNonNullType(fieldType);
742
- outputPlan.addChild(objectType, responseKey, {
743
- type: "outputPlan",
744
- outputPlan: new OutputPlan_js_1.OutputPlan(fieldLayerPlan, step, OUTPUT_PLAN_TYPE_NULL, locationDetails),
745
- isNonNull,
746
- locationDetails,
747
- });
588
+ continue;
589
+ }
590
+ else if (fieldName === "__schema" || fieldName === "__type") {
591
+ const variableNames = (0, utils_js_1.findVariableNamesUsed)(this, field);
592
+ outputPlan.addChild(objectType, responseKey, {
593
+ type: "outputPlan",
594
+ isNonNull: fieldName === "__schema",
595
+ outputPlan: new OutputPlan_js_1.OutputPlan(outputPlan.layerPlan, this.rootValueStep, {
596
+ mode: "introspection",
597
+ field,
598
+ variableNames,
599
+ // PERF: if variableNames.length === 0 we should be able to optimize this!
600
+ introspectionCacheByVariableValues: new lru_1.default({
601
+ maxLength: 3,
602
+ }),
603
+ }, locationDetails),
604
+ locationDetails,
605
+ });
606
+ continue;
607
+ }
608
+ const objectField = objectTypeFields[fieldName];
609
+ if (!objectField) {
610
+ // Field does not exist; this should have been caught by validation
611
+ // but the spec says to just skip it.
612
+ continue;
613
+ }
614
+ const fieldType = objectField.type;
615
+ const rawPlanResolver = objectField.extensions?.grafast?.plan;
616
+ if (rawPlanResolver) {
617
+ resolverEmulation = false;
618
+ }
619
+ if (rawPlanResolver?.constructor?.name === "AsyncFunction") {
620
+ throw new Error(`Plans must be synchronous, but this schema has an async function at '${objectType.name}.${fieldName}.plan': ${rawPlanResolver.toString()}`);
621
+ }
622
+ const namedReturnType = getNamedType(fieldType);
623
+ const resolvedResolver = objectField.resolve;
624
+ const subscriber = objectField.subscribe;
625
+ const usesDefaultResolver = resolvedResolver == null || resolvedResolver === defaultFieldResolver;
626
+ const isPolymorphic = isUnionType(namedReturnType) || isInterfaceType(namedReturnType);
627
+ // We should use a resolver if:
628
+ // 1. they give us a non-default resolver
629
+ // 2. we're emulating resolvers AND the field is polymorphic
630
+ const resolver = resolvedResolver && !usesDefaultResolver
631
+ ? resolvedResolver
632
+ : resolverEmulation && isPolymorphic
633
+ ? defaultFieldResolver
634
+ : null;
635
+ // Apply a default plan to fields that do not have a plan nor a resolver.
636
+ const planResolver = rawPlanResolver ??
637
+ (resolver ? undefined : makeDefaultPlan(fieldName));
638
+ /*
639
+ * When considering resolvers on fields, there's three booleans to
640
+ * consider:
641
+ *
642
+ * - typeIsPlanned: Does the type the field is defined on expect a plan?
643
+ * - NOTE: the root types (Query, Mutation, Subscription) implicitly
644
+ * expect the "root plan"
645
+ * - fieldHasPlan: Does the field define a `plan()` method?
646
+ * - resultIsPlanned: Does the named type that the field returns (the
647
+ * "named field type") expect a plan?
648
+ * - NOTE: only object types, unions and interfaces may expect plans;
649
+ * but not all of them do.
650
+ * - NOTE: a union/interface expects a plan iff ANY of its object
651
+ * types expect plans
652
+ * - NOTE: if ANY object type in an interface/union expects a plan
653
+ * then ALL object types within the interface/union must expect
654
+ * plans.
655
+ * - NOTE: scalars and enums never expect a plan.
656
+ *
657
+ * These booleans impact:
658
+ *
659
+ * - Whether there must be a `plan()` declaration and what the "parent"
660
+ * argument is to the same
661
+ * - If typeIsPlanned:
662
+ * - Assert: `fieldHasPlan` must be true
663
+ * - Pass through the parent plan
664
+ * - Else, if resultIsPlanned:
665
+ * - Assert: `fieldHasPlan` must be true
666
+ * - Pass through a `__ValueStep` representing the parent value.
667
+ * - Else, if fieldHasPlan:
668
+ * - Pass through a `__ValueStep` representing the parent value.
669
+ * - Else
670
+ * - No action necessary.
671
+ * - If the field may define `resolve()` and what the "parent" argument
672
+ * is to the same
673
+ * - If resultIsPlanned
674
+ * - Assert: there must not be a `resolve()`
675
+ * - Grafast provides pure resolver.
676
+ * - Else if fieldHasPlan (which may be implied by typeIsPlanned
677
+ * above)
678
+ * - If `resolve()` is not set:
679
+ * - grafast will return the value from the plan directly
680
+ * - Otherwise:
681
+ * - Grafast will wrap this resolver and will call `resolve()` (or
682
+ * default resolver) with the plan result. IMPORTANT: you may
683
+ * want to use an `ObjectStep` so that the parent object is of
684
+ * the expected shape; e.g. your plan might return
685
+ * `object({username: $username})` for a `User.username` field.
686
+ * - Else
687
+ * - Leave `resolve()` untouched - do not even wrap it.
688
+ * - (Failing that, use a __ValueStep and return the result
689
+ * directly.)
690
+ */
691
+ if (resolver !== null) {
692
+ this.pure = false;
693
+ if (!rawPlanResolver) {
694
+ resolverEmulation = true;
695
+ }
696
+ }
697
+ const resultIsPlanned = (0, utils_js_1.isTypePlanned)(this.schema, namedReturnType);
698
+ const fieldHasPlan = !!planResolver;
699
+ if (resultIsPlanned &&
700
+ !fieldHasPlan &&
701
+ !objectType.extensions?.grafast?.assertStep) {
702
+ throw new Error(`Field ${objectType.name}.${fieldName} returns a ${namedReturnType.name} which expects a plan to be available; however this field has no plan() method to produce such a plan; please add 'extensions.grafast.plan' to this field.`);
703
+ }
704
+ if (resultIsPlanned && resolver) {
705
+ throw new Error(`Field ${objectType.name}.${fieldName} returns a ${namedReturnType.name} which expects a plan to be available; this means that ${objectType.name}.${fieldName} is forbidden from defining a GraphQL resolver.`);
706
+ }
707
+ let step;
708
+ let haltTree = false;
709
+ const fieldLayerPlan = isMutation
710
+ ? new LayerPlan_js_1.LayerPlan(this, outputPlan.layerPlan, {
711
+ type: "mutationField",
712
+ mutationIndex: ++mutationIndex,
713
+ })
714
+ : outputPlan.layerPlan;
715
+ const objectFieldArgs = objectField.args;
716
+ const trackedArguments = objectFieldArgs.length > 0
717
+ ? (0, withGlobalLayerPlan_js_1.withGlobalLayerPlan)(this.rootLayerPlan, exports.POLYMORPHIC_ROOT_PATHS, this.getTrackedArguments, this, objectFieldArgs, field)
718
+ : NO_ARGS;
719
+ const fieldPath = [...path, responseKey];
720
+ if (typeof planResolver === "function") {
721
+ ({ step, haltTree } = this.planField(fieldLayerPlan, fieldPath, polymorphicPaths, fieldNodes, planResolver, "autoApplyAfterParentPlan", parentStep, objectField, trackedArguments));
722
+ }
723
+ else {
724
+ // No plan resolver (or plan resolver fallback) so there must be a
725
+ // `resolve` method, so we'll feed the full parent step into the
726
+ // resolver.
727
+ assert.ok(resolver !== null, "GraphileInternalError<81652257-617a-4d1a-8306-903d0e3d2ddf>: The field has no resolver, so planResolver should exist (either as the field.plan or as the default plan resolver).");
728
+ // ENHANCEMENT: should we do `step = parentStep.object()` (i.e.
729
+ // `$pgSelectSingle.record()`) or similar for "opaque" steps to become
730
+ // suitable for consumption by resolvers?
731
+ // Maybe `parentStep.forResolver()` or `parentStep.hydrate()` or `parentStep.toFullObject()`?
732
+ step = parentStep;
733
+ }
734
+ if (resolver !== null) {
735
+ step = (0, withGlobalLayerPlan_js_1.withGlobalLayerPlan)(fieldLayerPlan, polymorphicPaths, () => {
736
+ const $args = (0, index_js_1.object)(field.arguments?.reduce((memo, arg) => {
737
+ memo[arg.name.value] = trackedArguments.get(arg.name.value);
738
+ return memo;
739
+ }, Object.create(null)) ?? Object.create(null));
740
+ return (0, graphqlResolver_js_1.graphqlResolver)(resolver, subscriber, step, $args, {
741
+ fieldName,
742
+ fieldNodes,
743
+ fragments: this.fragments,
744
+ operation: this.operation,
745
+ parentType: objectType,
746
+ returnType: fieldType,
747
+ schema: this.schema,
748
+ });
749
+ });
750
+ }
751
+ // May have changed due to deduplicate
752
+ step = this.stepTracker.getStepById(step.id);
753
+ if (haltTree) {
754
+ const isNonNull = isNonNullType(fieldType);
755
+ outputPlan.addChild(objectType, responseKey, {
756
+ type: "outputPlan",
757
+ outputPlan: new OutputPlan_js_1.OutputPlan(fieldLayerPlan, step, OUTPUT_PLAN_TYPE_NULL, locationDetails),
758
+ isNonNull,
759
+ locationDetails,
760
+ });
761
+ }
762
+ else {
763
+ this.planIntoOutputPlan(outputPlan, fieldLayerPlan, fieldPath, polymorphicPath, polymorphicPaths,
764
+ // If one field has a selection set, they all have a selection set (guaranteed by validation).
765
+ field.selectionSet != null
766
+ ? fieldNodes.flatMap((n) => n.selectionSet.selections)
767
+ : undefined, objectType, responseKey, fieldType, step, locationDetails, resolverEmulation);
768
+ }
748
769
  }
749
- else {
750
- this.planIntoOutputPlan(outputPlan, fieldLayerPlan, fieldPath, polymorphicPath, polymorphicPaths,
751
- // If one field has a selection set, they all have a selection set (guaranteed by validation).
752
- field.selectionSet != null
753
- ? fieldNodes.flatMap((n) => n.selectionSet.selections)
754
- : undefined, objectType, responseKey, fieldType, step, locationDetails);
770
+ finally {
771
+ outputPlan.layerPlan.latestSideEffectStep = $sideEffect;
755
772
  }
756
773
  }
757
774
  if (groupedFieldSet.deferred !== undefined) {
@@ -768,8 +785,14 @@ class OperationPlan {
768
785
  // LOGGING: the location details should be tweaked to reference this
769
786
  // fragment
770
787
  outputPlan.locationDetails);
771
- outputPlan.deferredOutputPlans.push(deferredOutputPlan);
772
- this.processGroupedFieldSet(deferredOutputPlan, path, polymorphicPath, polymorphicPaths, parentStep, objectType, objectTypeFields, isMutation, deferred);
788
+ const $sideEffect = deferredOutputPlan.layerPlan.latestSideEffectStep;
789
+ try {
790
+ outputPlan.deferredOutputPlans.push(deferredOutputPlan);
791
+ this.processGroupedFieldSet(deferredOutputPlan, path, polymorphicPath, polymorphicPaths, parentStep, objectType, objectTypeFields, isMutation, deferred);
792
+ }
793
+ finally {
794
+ deferredOutputPlan.layerPlan.latestSideEffectStep = $sideEffect;
795
+ }
773
796
  }
774
797
  }
775
798
  }
@@ -782,14 +805,14 @@ class OperationPlan {
782
805
  * @param selections - The GraphQL selections (fields, fragment spreads, inline fragments) to evaluate
783
806
  * @param isMutation - If true this selection set should be executed serially rather than in parallel (each field gets its own LayerPlan)
784
807
  */
785
- planSelectionSet(outputPlan, path, polymorphicPath, polymorphicPaths, parentStep, objectType, selections, isMutation = false) {
808
+ planSelectionSet(outputPlan, path, polymorphicPath, polymorphicPaths, parentStep, objectType, selections, resolverEmulation, isMutation = false) {
786
809
  if (this.loc !== null) {
787
810
  this.loc.push(`planSelectionSet(${objectType.name} @ ${outputPlan.layerPlan.id} @ ${path.join(".")} @ ${polymorphicPath ?? ""})`);
788
811
  }
789
812
  if (index_js_1.isDev) {
790
813
  assertObjectType(objectType);
791
814
  }
792
- const groupedFieldSet = (0, withGlobalLayerPlan_js_1.withGlobalLayerPlan)(outputPlan.layerPlan, polymorphicPaths, graphqlCollectFields_js_1.graphqlCollectFields, null, this, parentStep.id, objectType, selections, isMutation);
815
+ const groupedFieldSet = (0, withGlobalLayerPlan_js_1.withGlobalLayerPlan)(outputPlan.layerPlan, polymorphicPaths, graphqlCollectFields_js_1.graphqlCollectFields, null, this, parentStep.id, objectType, selections, (0, graphqlCollectFields_js_1.newSelectionSetDigest)(resolverEmulation), isMutation);
793
816
  const objectTypeFields = objectType.getFields();
794
817
  this.processGroupedFieldSet(outputPlan, path, polymorphicPath, polymorphicPaths, parentStep, objectType, objectTypeFields, isMutation, groupedFieldSet);
795
818
  if (this.loc !== null)
@@ -800,7 +823,7 @@ class OperationPlan {
800
823
  // Typically this is parentOutputPlan.layerPlan; but in the case of mutationFields it isn't.
801
824
  parentLayerPlan,
802
825
  // This is the LAYER-RELATIVE path, not the absolute path! It resets!
803
- path, polymorphicPath, polymorphicPaths, selections, parentObjectType, responseKey, fieldType, $step, locationDetails, listDepth = 0) {
826
+ path, polymorphicPath, polymorphicPaths, selections, parentObjectType, responseKey, fieldType, $step, locationDetails, resolverEmulation, listDepth = 0) {
804
827
  const nullableFieldType = getNullableType(fieldType);
805
828
  const isNonNull = nullableFieldType !== fieldType;
806
829
  if (isListType(nullableFieldType)) {
@@ -812,40 +835,58 @@ class OperationPlan {
812
835
  locationDetails,
813
836
  });
814
837
  const $__item = this.itemStepForListStep(parentLayerPlan, $step, listDepth);
815
- const $item = (0, step_js_1.isListCapableStep)($step)
816
- ? (0, withGlobalLayerPlan_js_1.withGlobalLayerPlan)($__item.layerPlan, $__item.polymorphicPaths, $step.listItem, $step, $__item)
817
- : $__item;
818
- this.planIntoOutputPlan(listOutputPlan, $item.layerPlan, path, polymorphicPath, polymorphicPaths, selections, null, null, nullableFieldType.ofType, $item, locationDetails, listDepth + 1);
838
+ const $sideEffect = $__item.layerPlan.latestSideEffectStep;
839
+ try {
840
+ const $item = (0, step_js_1.isListCapableStep)($step)
841
+ ? (0, withGlobalLayerPlan_js_1.withGlobalLayerPlan)($__item.layerPlan, $__item.polymorphicPaths, $step.listItem, $step, $__item)
842
+ : $__item;
843
+ this.planIntoOutputPlan(listOutputPlan, $item.layerPlan, path, polymorphicPath, polymorphicPaths, selections, null, null, nullableFieldType.ofType, $item, locationDetails, resolverEmulation, listDepth + 1);
844
+ }
845
+ finally {
846
+ $__item.layerPlan.latestSideEffectStep = $sideEffect;
847
+ }
819
848
  }
820
849
  else if (isScalarType(nullableFieldType)) {
821
850
  const scalarPlanResolver = nullableFieldType.extensions?.grafast?.plan;
822
851
  if (scalarPlanResolver?.constructor?.name === "AsyncFunction") {
823
852
  throw new Error(`Plans must be synchronous, but this schema has an async function at '${nullableFieldType.name}.plan': ${scalarPlanResolver.toString()}`);
824
853
  }
825
- const $leaf = typeof scalarPlanResolver === "function"
826
- ? (0, withGlobalLayerPlan_js_1.withGlobalLayerPlan)(parentLayerPlan, polymorphicPaths, scalarPlanResolver, null, $step, this.scalarPlanInfo)
827
- : $step;
828
- parentOutputPlan.addChild(parentObjectType, responseKey, {
829
- type: "outputPlan",
830
- isNonNull,
831
- outputPlan: new OutputPlan_js_1.OutputPlan(parentLayerPlan, $leaf, {
832
- mode: "leaf",
833
- // stepId: $leaf.id,
834
- graphqlType: nullableFieldType,
835
- }, locationDetails),
836
- locationDetails,
837
- });
854
+ const $sideEffect = parentLayerPlan.latestSideEffectStep;
855
+ try {
856
+ const $leaf = typeof scalarPlanResolver === "function"
857
+ ? (0, withGlobalLayerPlan_js_1.withGlobalLayerPlan)(parentLayerPlan, polymorphicPaths, scalarPlanResolver, null, $step, this.scalarPlanInfo)
858
+ : $step;
859
+ parentOutputPlan.addChild(parentObjectType, responseKey, {
860
+ type: "outputPlan",
861
+ isNonNull,
862
+ outputPlan: new OutputPlan_js_1.OutputPlan(parentLayerPlan, $leaf, {
863
+ mode: "leaf",
864
+ // stepId: $leaf.id,
865
+ graphqlType: nullableFieldType,
866
+ }, locationDetails),
867
+ locationDetails,
868
+ });
869
+ }
870
+ finally {
871
+ parentLayerPlan.latestSideEffectStep = $sideEffect;
872
+ }
838
873
  }
839
874
  else if (isEnumType(nullableFieldType)) {
840
- parentOutputPlan.addChild(parentObjectType, responseKey, {
841
- type: "outputPlan",
842
- isNonNull,
843
- outputPlan: new OutputPlan_js_1.OutputPlan(parentLayerPlan, $step, {
844
- mode: "leaf",
845
- graphqlType: nullableFieldType,
846
- }, locationDetails),
847
- locationDetails,
848
- });
875
+ const $sideEffect = parentLayerPlan.latestSideEffectStep;
876
+ try {
877
+ parentOutputPlan.addChild(parentObjectType, responseKey, {
878
+ type: "outputPlan",
879
+ isNonNull,
880
+ outputPlan: new OutputPlan_js_1.OutputPlan(parentLayerPlan, $step, {
881
+ mode: "leaf",
882
+ graphqlType: nullableFieldType,
883
+ }, locationDetails),
884
+ locationDetails,
885
+ });
886
+ }
887
+ finally {
888
+ parentLayerPlan.latestSideEffectStep = $sideEffect;
889
+ }
849
890
  }
850
891
  else if (isObjectType(nullableFieldType)) {
851
892
  if (index_js_1.isDev) {
@@ -893,18 +934,24 @@ class OperationPlan {
893
934
  objectLayerPlan.setRootStep($step);
894
935
  }
895
936
  }
896
- const objectOutputPlan = new OutputPlan_js_1.OutputPlan(objectLayerPlan, $step, {
897
- mode: "object",
898
- deferLabel: undefined,
899
- typeName: nullableFieldType.name,
900
- }, locationDetails);
901
- parentOutputPlan.addChild(parentObjectType, responseKey, {
902
- type: "outputPlan",
903
- outputPlan: objectOutputPlan,
904
- isNonNull,
905
- locationDetails,
906
- });
907
- this.planSelectionSet(objectOutputPlan, path, polymorphicPath, polymorphicPaths, $step, nullableFieldType, selections);
937
+ const $sideEffect = objectLayerPlan.latestSideEffectStep;
938
+ try {
939
+ const objectOutputPlan = new OutputPlan_js_1.OutputPlan(objectLayerPlan, $step, {
940
+ mode: "object",
941
+ deferLabel: undefined,
942
+ typeName: nullableFieldType.name,
943
+ }, locationDetails);
944
+ parentOutputPlan.addChild(parentObjectType, responseKey, {
945
+ type: "outputPlan",
946
+ outputPlan: objectOutputPlan,
947
+ isNonNull,
948
+ locationDetails,
949
+ });
950
+ this.planSelectionSet(objectOutputPlan, path, polymorphicPath, polymorphicPaths, $step, nullableFieldType, selections, resolverEmulation);
951
+ }
952
+ finally {
953
+ objectLayerPlan.latestSideEffectStep = $sideEffect;
954
+ }
908
955
  }
909
956
  else {
910
957
  // Polymorphic
@@ -970,48 +1017,54 @@ class OperationPlan {
970
1017
  const polyBase = polymorphicPath ?? "";
971
1018
  const $oldStep = $step;
972
1019
  for (const type of allPossibleObjectTypes) {
973
- /* TODO: this $oldStep / $step dance turned out to be necessary
974
- * because `$step` was being replaced in the following query against
975
- * the pggql_test `polymorpic` schema; but I'm not sure this is the
976
- * right behavior - shouldn't deduplication have been temporarily
977
- * disabled during this?
978
- *
979
- * ```graphql
980
- * {
981
- * allSingleTableItems {
982
- * nodes {
983
- * id
984
- * type
985
- * singleTableItemsByParentIdList {
986
- * id
987
- * position
988
- * }
989
- * }
990
- * }
991
- * }
992
- * ```
993
- */
994
- const $step = this.stepTracker.getStepById($oldStep.id);
995
- // Bit of a hack, but saves passing it around through all the arguments
996
- const newPolymorphicPath = `${polyBase}>${type.name}`;
997
- polymorphicLayerPlan.reason.polymorphicPaths.add(newPolymorphicPath);
998
- const newPolymorphicPaths = new Set();
999
- newPolymorphicPaths.add(newPolymorphicPath);
1000
- const $root = (0, withGlobalLayerPlan_js_1.withGlobalLayerPlan)(polymorphicLayerPlan, newPolymorphicPaths, $step.planForType, $step, type);
1001
- const objectOutputPlan = new OutputPlan_js_1.OutputPlan(polymorphicLayerPlan, $root, {
1002
- mode: "object",
1003
- deferLabel: undefined,
1004
- typeName: type.name,
1005
- }, locationDetails);
1006
- // find all selections compatible with `type`
1007
- const fieldNodes = (0, graphqlMergeSelectionSets_js_1.fieldSelectionsForType)(this, type, selections);
1008
- this.planSelectionSet(objectOutputPlan, path, newPolymorphicPath, newPolymorphicPaths, $root, type, fieldNodes, false);
1009
- polymorphicOutputPlan.addChild(type, null, {
1010
- type: "outputPlan",
1011
- isNonNull,
1012
- outputPlan: objectOutputPlan,
1013
- locationDetails,
1014
- });
1020
+ const $sideEffect = polymorphicLayerPlan.latestSideEffectStep;
1021
+ try {
1022
+ /* TODO: this $oldStep / $step dance turned out to be necessary
1023
+ * because `$step` was being replaced in the following query against
1024
+ * the pggql_test `polymorpic` schema; but I'm not sure this is the
1025
+ * right behavior - shouldn't deduplication have been temporarily
1026
+ * disabled during this?
1027
+ *
1028
+ * ```graphql
1029
+ * {
1030
+ * allSingleTableItems {
1031
+ * nodes {
1032
+ * id
1033
+ * type
1034
+ * singleTableItemsByParentIdList {
1035
+ * id
1036
+ * position
1037
+ * }
1038
+ * }
1039
+ * }
1040
+ * }
1041
+ * ```
1042
+ */
1043
+ const $step = this.stepTracker.getStepById($oldStep.id);
1044
+ // Bit of a hack, but saves passing it around through all the arguments
1045
+ const newPolymorphicPath = `${polyBase}>${type.name}`;
1046
+ polymorphicLayerPlan.reason.polymorphicPaths.add(newPolymorphicPath);
1047
+ const newPolymorphicPaths = new Set();
1048
+ newPolymorphicPaths.add(newPolymorphicPath);
1049
+ const $root = (0, withGlobalLayerPlan_js_1.withGlobalLayerPlan)(polymorphicLayerPlan, newPolymorphicPaths, $step.planForType, $step, type);
1050
+ const objectOutputPlan = new OutputPlan_js_1.OutputPlan(polymorphicLayerPlan, $root, {
1051
+ mode: "object",
1052
+ deferLabel: undefined,
1053
+ typeName: type.name,
1054
+ }, locationDetails);
1055
+ // find all selections compatible with `type`
1056
+ const fieldNodes = (0, graphqlMergeSelectionSets_js_1.fieldSelectionsForType)(this, type, selections);
1057
+ this.planSelectionSet(objectOutputPlan, path, newPolymorphicPath, newPolymorphicPaths, $root, type, fieldNodes, false);
1058
+ polymorphicOutputPlan.addChild(type, null, {
1059
+ type: "outputPlan",
1060
+ isNonNull,
1061
+ outputPlan: objectOutputPlan,
1062
+ locationDetails,
1063
+ });
1064
+ }
1065
+ finally {
1066
+ polymorphicLayerPlan.latestSideEffectStep = $sideEffect;
1067
+ }
1015
1068
  }
1016
1069
  }
1017
1070
  }
@@ -1065,6 +1118,7 @@ class OperationPlan {
1065
1118
  // PERF: this should be handled in the parent?
1066
1119
  const parentStep = this.stepTracker.getStepById(rawParentStep.id);
1067
1120
  const previousStepCount = this.stepTracker.stepCount;
1121
+ const previousSideEffectStep = layerPlan.latestSideEffectStep;
1068
1122
  if (this.loc !== null)
1069
1123
  this.loc.push(`planField(${path.join(".")})`);
1070
1124
  try {
@@ -1132,6 +1186,7 @@ class OperationPlan {
1132
1186
  }
1133
1187
  try {
1134
1188
  this.stepTracker.purgeBackTo(previousStepCount);
1189
+ layerPlan.latestSideEffectStep = previousSideEffectStep;
1135
1190
  }
1136
1191
  catch (e2) {
1137
1192
  console.error(`Cleanup error occurred whilst trying to recover from field planning error: ${e2.stack}`);
@@ -1226,7 +1281,8 @@ class OperationPlan {
1226
1281
  continue;
1227
1282
  for (const key in step) {
1228
1283
  const val = step[key];
1229
- if (val instanceof index_js_1.ExecutableStep) {
1284
+ if (key !== ("implicitSideEffectStep") &&
1285
+ val instanceof index_js_1.ExecutableStep) {
1230
1286
  errors.push(new Error(`ERROR: ExecutableStep ${step} has illegal reference via property '${key}' to step ${val}. You must not reference steps directly, instead add the step as a dependency (\`depId = this.addDependency($step)\`) and use \`this.getDep(depId)\` to look the step up (or, if the step is not a dependency, store the step ID and use \`this.getStep(stepId)\` each time you need to reference it). Failure to comply could result in subtle breakage during optimisation.`));
1231
1287
  }
1232
1288
  }
@@ -2190,15 +2246,17 @@ class OperationPlan {
2190
2246
  const sideEffectSteps = layerPlan.pendingSteps.filter((s) => s.hasSideEffects);
2191
2247
  const pending = new Set(layerPlan.pendingSteps);
2192
2248
  const processed = new Set();
2249
+ let latestSideEffectStep = null;
2193
2250
  const processSideEffectPlan = (step) => {
2194
2251
  if (processed.has(step) || isPrepopulatedStep(step)) {
2195
2252
  return;
2196
2253
  }
2254
+ const sstep = (0, utils_js_1.sudo)(step);
2197
2255
  processed.add(step);
2198
2256
  pending.delete(step);
2199
2257
  const sideEffectDeps = [];
2200
2258
  const rest = [];
2201
- for (const dep of (0, utils_js_1.sudo)(step).dependencies) {
2259
+ for (const dep of sstep.dependencies) {
2202
2260
  if (dep.layerPlan !== layerPlan) {
2203
2261
  continue;
2204
2262
  }
@@ -2221,6 +2279,13 @@ class OperationPlan {
2221
2279
  for (const dep of rest) {
2222
2280
  processSideEffectPlan(dep);
2223
2281
  }
2282
+ if (latestSideEffectStep !== null &&
2283
+ !(0, utils_js_1.stepADependsOnStepB)(sstep, latestSideEffectStep)) {
2284
+ sstep.implicitSideEffectStep = latestSideEffectStep;
2285
+ }
2286
+ if (step.hasSideEffects) {
2287
+ latestSideEffectStep = step;
2288
+ }
2224
2289
  const phase = /*#__INLINE__*/ newLayerPlanPhase();
2225
2290
  phase.checkTimeout = true;
2226
2291
  phase.normalSteps = [{ step }];
@@ -2255,6 +2320,11 @@ class OperationPlan {
2255
2320
  for (const step of nextSteps) {
2256
2321
  processed.add(step);
2257
2322
  pending.delete(step);
2323
+ const sstep = (0, utils_js_1.sudo)(step);
2324
+ if (latestSideEffectStep !== null &&
2325
+ !(0, utils_js_1.stepADependsOnStepB)(sstep, latestSideEffectStep)) {
2326
+ sstep.implicitSideEffectStep = latestSideEffectStep;
2327
+ }
2258
2328
  if (step.isSyncAndSafe && (0, step_js_1.isUnbatchedExecutableStep)(step)) {
2259
2329
  if (phase.unbatchedSyncAndSafeSteps !== undefined) {
2260
2330
  phase.unbatchedSyncAndSafeSteps.push({
@@ -2289,6 +2359,11 @@ class OperationPlan {
2289
2359
  if (readyToExecute(step)) {
2290
2360
  processed.add(step);
2291
2361
  pending.delete(step);
2362
+ const sstep = (0, utils_js_1.sudo)(step);
2363
+ if (latestSideEffectStep !== null &&
2364
+ !(0, utils_js_1.stepADependsOnStepB)(sstep, latestSideEffectStep)) {
2365
+ sstep.implicitSideEffectStep = latestSideEffectStep;
2366
+ }
2292
2367
  foundOne = true;
2293
2368
  if (phase.unbatchedSyncAndSafeSteps !== undefined) {
2294
2369
  phase.unbatchedSyncAndSafeSteps.push({
@@ -2327,10 +2402,31 @@ class OperationPlan {
2327
2402
  for (const dep of (0, utils_js_1.sudo)(step).dependencies) {
2328
2403
  ensurePlanAvailableInLayer(dep, layerPlan);
2329
2404
  }
2405
+ if (step.implicitSideEffectStep) {
2406
+ ensurePlanAvailableInLayer(step.implicitSideEffectStep, layerPlan);
2407
+ }
2330
2408
  }
2331
2409
  const $root = layerPlan.rootStep;
2332
2410
  if ($root) {
2333
2411
  ensurePlanAvailableInLayer($root, layerPlan);
2412
+ // If $root explicitly dependends on `layerPlan.parentSideEffectStep`
2413
+ // then we should remove the implicit layerPlan dependency (e.g. so
2414
+ // that if you `trap()` the error it does not interfere).
2415
+ if (layerPlan.parentSideEffectStep) {
2416
+ if (layerPlan.parentSideEffectStep === $root ||
2417
+ layerPlan.parentSideEffectStep === $root.implicitSideEffectStep ||
2418
+ (0, utils_js_1.stepADependsOnStepB)($root, layerPlan.parentSideEffectStep)) {
2419
+ layerPlan.parentSideEffectStep = null;
2420
+ }
2421
+ }
2422
+ }
2423
+ const $sideEffect = layerPlan.parentSideEffectStep;
2424
+ if ($sideEffect) {
2425
+ // We read it from the parent layer plan at newBucket time, but we
2426
+ // don't need to actually scale it up/down.
2427
+ //
2428
+ // If parentSideEffectStep exists then parentLayerPlan must exist.
2429
+ ensurePlanAvailableInLayer($sideEffect, layerPlan.parentLayerPlan);
2334
2430
  }
2335
2431
  // Copy polymorphic parentStepId
2336
2432
  if (layerPlan.reason.type === "polymorphic") {
@@ -2390,6 +2486,7 @@ class OperationPlan {
2390
2486
  metaString: metaString ? (0, index_js_1.stripAnsi)(metaString) : metaString,
2391
2487
  isUnary: step._isUnary,
2392
2488
  bucketId: step.layerPlan.id,
2489
+ implicitSideEffectStepId: step.implicitSideEffectStep?.id ?? null,
2393
2490
  dependencyIds: sstep.dependencies.map((d) => d.id),
2394
2491
  dependencyForbiddenFlags: sstep.dependencyForbiddenFlags.slice(),
2395
2492
  dependencyOnReject: sstep.dependencyOnReject.map((or) => or ? String(or) : or),
@@ -2458,6 +2555,7 @@ class OperationPlan {
2458
2555
  return {
2459
2556
  id: lp.id,
2460
2557
  reason: printBucketReason(lp.reason),
2558
+ parentSideEffectStepId: lp.parentSideEffectStep?.id ?? null,
2461
2559
  copyStepIds: lp.copyStepIds,
2462
2560
  phases: lp.phases.map(printPhase),
2463
2561
  steps: lp.steps.map(printStep),