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.
- package/README.md +0 -3
- package/dist/args.js +8 -2
- package/dist/constraints.d.ts +15 -1
- package/dist/constraints.js +40 -1
- package/dist/engine/LayerPlan.d.ts +1 -0
- package/dist/engine/LayerPlan.js +62 -9
- package/dist/engine/OperationPlan.js +372 -274
- package/dist/engine/OutputPlan.d.ts +6 -5
- package/dist/engine/OutputPlan.js +423 -444
- package/dist/engine/executeBucket.js +36 -77
- package/dist/error.d.ts +13 -0
- package/dist/grafastGraphql.d.ts +5 -5
- package/dist/graphqlCollectFields.d.ts +6 -0
- package/dist/graphqlCollectFields.js +14 -8
- package/dist/interfaces.d.ts +18 -0
- package/dist/interfaces.js +0 -2
- package/dist/mermaid.js +20 -11
- package/dist/operationPlan-input.js +8 -1
- package/dist/planJSONInterfaces.d.ts +2 -0
- package/dist/prepare.js +3 -3
- package/dist/step.d.ts +5 -5
- package/dist/step.js +53 -2
- package/dist/steps/__flag.d.ts +3 -2
- package/dist/steps/__inputObject.d.ts +1 -0
- package/dist/steps/__inputObject.js +36 -0
- package/dist/steps/__item.d.ts +3 -3
- package/dist/steps/__trackedValue.d.ts +8 -0
- package/dist/steps/__trackedValue.js +40 -0
- package/dist/steps/__value.d.ts +2 -1
- package/dist/steps/constant.d.ts +2 -0
- package/dist/steps/constant.js +12 -0
- package/dist/steps/graphqlResolver.js +8 -5
- package/dist/steps/listen.d.ts +2 -2
- package/dist/steps/object.d.ts +1 -1
- package/dist/steps/sideEffect.d.ts +0 -1
- package/dist/thereCanBeOnlyOne.js +1 -1
- package/dist/utils.d.ts +4 -0
- package/dist/utils.js +7 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- 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
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
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
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
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
|
-
|
|
750
|
-
|
|
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
|
-
|
|
772
|
-
|
|
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 $
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
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 $
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
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
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
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
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
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
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
type
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
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 (
|
|
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
|
|
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),
|