grafast 0.1.1-beta.2 → 0.1.1-beta.21
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 +2 -3
- package/dist/args.d.ts +4 -7
- package/dist/args.js +53 -38
- package/dist/assert.js +2 -3
- package/dist/bucket.d.ts +1 -89
- package/dist/config.d.ts +0 -17
- package/dist/config.js +1 -95
- package/dist/constraints.d.ts +15 -1
- package/dist/constraints.js +41 -3
- package/dist/deferred.js +1 -2
- package/dist/dev.d.ts +1 -4
- package/dist/dev.js +18 -3
- package/dist/engine/LayerPlan.d.ts +21 -111
- package/dist/engine/LayerPlan.js +196 -78
- package/dist/engine/OperationPlan.d.ts +30 -104
- package/dist/engine/OperationPlan.js +763 -471
- package/dist/engine/OutputPlan.d.ts +9 -20
- package/dist/engine/OutputPlan.js +432 -444
- package/dist/engine/StepTracker.d.ts +1 -95
- package/dist/engine/StepTracker.js +94 -20
- package/dist/engine/executeBucket.d.ts +5 -7
- package/dist/engine/executeBucket.js +646 -244
- package/dist/engine/executeOutputPlan.d.ts +0 -53
- package/dist/engine/executeOutputPlan.js +7 -2
- package/dist/engine/lib/defaultPlanResolver.d.ts +6 -0
- package/dist/engine/lib/defaultPlanResolver.js +11 -0
- package/dist/engine/lib/withGlobalLayerPlan.d.ts +2 -0
- package/dist/engine/lib/withGlobalLayerPlan.js +7 -4
- package/dist/engine/lock.d.ts +4 -0
- package/dist/engine/lock.js +82 -0
- package/dist/envelop.js +5 -4
- package/dist/error.d.ts +18 -49
- package/dist/error.js +50 -39
- package/dist/establishOperationPlan.js +57 -42
- package/dist/execute.d.ts +5 -4
- package/dist/execute.js +31 -15
- package/dist/exportAs.d.ts +1 -18
- package/dist/exportAs.js +2 -3
- package/dist/global.js +2 -3
- package/dist/grafastGraphql.d.ts +6 -1
- package/dist/grafastGraphql.js +35 -16
- package/dist/grafastPrint.d.ts +3 -0
- package/dist/grafastPrint.js +67 -6
- package/dist/graphqlCollectFields.d.ts +7 -34
- package/dist/graphqlCollectFields.js +24 -13
- package/dist/graphqlMergeSelectionSets.js +1 -2
- package/dist/index.d.ts +44 -29
- package/dist/index.js +91 -18
- package/dist/input.d.ts +1 -9
- package/dist/input.js +75 -28
- package/dist/inspect.d.ts +9 -3
- package/dist/inspect.js +3 -3
- package/dist/interfaces.d.ts +238 -93
- package/dist/interfaces.js +20 -3
- package/dist/makeGrafastSchema.d.ts +21 -9
- package/dist/makeGrafastSchema.js +353 -133
- package/dist/mermaid.d.ts +0 -17
- package/dist/mermaid.js +131 -19
- package/dist/middleware.d.ts +7 -0
- package/dist/middleware.js +24 -0
- package/dist/multistep.d.ts +19 -0
- package/dist/multistep.js +54 -0
- package/dist/operationPlan-input.d.ts +5 -4
- package/dist/operationPlan-input.js +140 -368
- package/dist/planJSONInterfaces.d.ts +12 -2
- package/dist/polymorphic.js +4 -5
- package/dist/prepare.d.ts +1 -7
- package/dist/prepare.js +100 -74
- package/dist/step.d.ts +118 -172
- package/dist/step.js +262 -183
- package/dist/steps/__flag.d.ts +65 -0
- package/dist/steps/__flag.js +261 -0
- package/dist/steps/__inputDefault.d.ts +14 -0
- package/dist/steps/__inputDefault.js +48 -0
- package/dist/steps/__inputDynamicScalar.d.ts +4 -6
- package/dist/steps/__inputDynamicScalar.js +4 -2
- package/dist/steps/__inputList.d.ts +5 -9
- package/dist/steps/__inputList.js +4 -1
- package/dist/steps/__inputObject.d.ts +7 -11
- package/dist/steps/__inputObject.js +49 -3
- package/dist/steps/__inputStaticLeaf.d.ts +2 -4
- package/dist/steps/__inputStaticLeaf.js +3 -1
- package/dist/steps/__item.d.ts +10 -12
- package/dist/steps/__item.js +8 -2
- package/dist/steps/__trackedValue.d.ts +7 -55
- package/dist/steps/__trackedValue.js +69 -8
- package/dist/steps/__value.d.ts +5 -4
- package/dist/steps/__value.js +5 -4
- package/dist/steps/access.d.ts +10 -10
- package/dist/steps/access.js +50 -17
- package/dist/steps/applyInput.d.ts +44 -0
- package/dist/steps/applyInput.js +206 -0
- package/dist/steps/applyTransforms.d.ts +2 -25
- package/dist/steps/applyTransforms.js +53 -31
- package/dist/steps/bakedInput.d.ts +23 -0
- package/dist/steps/bakedInput.js +90 -0
- package/dist/steps/condition.d.ts +31 -0
- package/dist/steps/condition.js +94 -0
- package/dist/steps/connection.d.ts +56 -42
- package/dist/steps/connection.js +97 -44
- package/dist/steps/constant.d.ts +7 -7
- package/dist/steps/constant.js +86 -7
- package/dist/steps/each.d.ts +3 -3
- package/dist/steps/each.js +2 -3
- package/dist/steps/error.d.ts +4 -4
- package/dist/steps/error.js +7 -6
- package/dist/steps/filter.d.ts +3 -2
- package/dist/steps/filter.js +1 -2
- package/dist/steps/first.d.ts +10 -9
- package/dist/steps/first.js +9 -13
- package/dist/steps/graphqlResolver.d.ts +3 -76
- package/dist/steps/graphqlResolver.js +89 -72
- package/dist/steps/groupBy.d.ts +3 -2
- package/dist/steps/groupBy.js +1 -2
- package/dist/steps/index.d.ts +12 -3
- package/dist/steps/index.js +48 -6
- package/dist/steps/lambda.d.ts +9 -9
- package/dist/steps/lambda.js +18 -9
- package/dist/steps/last.d.ts +9 -8
- package/dist/steps/last.js +7 -6
- package/dist/steps/list.d.ts +20 -9
- package/dist/steps/list.js +56 -13
- package/dist/steps/listTransform.d.ts +18 -22
- package/dist/steps/listTransform.js +58 -27
- package/dist/steps/listen.d.ts +6 -11
- package/dist/steps/listen.js +14 -16
- package/dist/steps/load.d.ts +42 -33
- package/dist/steps/load.js +60 -27
- package/dist/steps/node.d.ts +17 -10
- package/dist/steps/node.js +18 -6
- package/dist/steps/object.d.ts +33 -27
- package/dist/steps/object.js +53 -32
- package/dist/steps/partitionByIndex.d.ts +3 -2
- package/dist/steps/partitionByIndex.js +1 -2
- package/dist/steps/polymorphicBranch.d.ts +27 -0
- package/dist/steps/polymorphicBranch.js +62 -0
- package/dist/steps/proxy.d.ts +11 -12
- package/dist/steps/proxy.js +8 -5
- package/dist/steps/remapKeys.d.ts +10 -10
- package/dist/steps/remapKeys.js +14 -7
- package/dist/steps/reverse.d.ts +8 -8
- package/dist/steps/reverse.js +10 -7
- package/dist/steps/setter.d.ts +9 -18
- package/dist/steps/setter.js +24 -14
- package/dist/steps/sideEffect.d.ts +28 -0
- package/dist/steps/sideEffect.js +44 -0
- package/dist/stringifyPayload.js +1 -2
- package/dist/stripAnsi.js +2 -2
- package/dist/subscribe.d.ts +7 -1
- package/dist/subscribe.js +24 -9
- package/dist/thereCanBeOnlyOne.js +1 -1
- package/dist/timeSource.d.ts +0 -1
- package/dist/utils.d.ts +56 -55
- package/dist/utils.js +179 -59
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +5 -8
|
@@ -14,13 +14,15 @@ const inspect_js_1 = require("../inspect.js");
|
|
|
14
14
|
const interfaces_js_1 = require("../interfaces.js");
|
|
15
15
|
const operationPlan_input_js_1 = require("../operationPlan-input.js");
|
|
16
16
|
const step_js_1 = require("../step.js");
|
|
17
|
-
const
|
|
17
|
+
const connection_js_1 = require("../steps/connection.js");
|
|
18
18
|
const constant_js_1 = require("../steps/constant.js");
|
|
19
19
|
const graphqlResolver_js_1 = require("../steps/graphqlResolver.js");
|
|
20
20
|
const timeSource_js_1 = require("../timeSource.js");
|
|
21
21
|
const utils_js_1 = require("../utils.js");
|
|
22
22
|
const LayerPlan_js_1 = require("./LayerPlan.js");
|
|
23
|
+
const defaultPlanResolver_js_1 = require("./lib/defaultPlanResolver.js");
|
|
23
24
|
const withGlobalLayerPlan_js_1 = require("./lib/withGlobalLayerPlan.js");
|
|
25
|
+
const lock_js_1 = require("./lock.js");
|
|
24
26
|
const OutputPlan_js_1 = require("./OutputPlan.js");
|
|
25
27
|
const StepTracker_js_1 = require("./StepTracker.js");
|
|
26
28
|
const atpe = typeof process !== "undefined" && process.env.ALWAYS_THROW_PLANNING_ERRORS;
|
|
@@ -49,22 +51,19 @@ const EMPTY_ARRAY = Object.freeze([]);
|
|
|
49
51
|
exports.POLYMORPHIC_ROOT_PATH = null;
|
|
50
52
|
exports.POLYMORPHIC_ROOT_PATHS = null;
|
|
51
53
|
Object.freeze(exports.POLYMORPHIC_ROOT_PATHS);
|
|
52
|
-
/** In development we might run additional checks */
|
|
53
|
-
const isDev = typeof process !== "undefined" &&
|
|
54
|
-
(process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test");
|
|
55
54
|
/** How many times will we try re-optimizing before giving up */
|
|
56
55
|
const MAX_OPTIMIZATION_LOOPS = 10;
|
|
57
56
|
const REASON_ROOT = Object.freeze({ type: "root" });
|
|
58
57
|
const OUTPUT_PLAN_TYPE_NULL = Object.freeze({ mode: "null" });
|
|
59
58
|
const OUTPUT_PLAN_TYPE_ARRAY = Object.freeze({ mode: "array" });
|
|
60
|
-
const newValueStepCallback = () => new index_js_1.__ValueStep();
|
|
59
|
+
const newValueStepCallback = (isImmutable) => new index_js_1.__ValueStep(isImmutable);
|
|
61
60
|
const NO_ARGS = {
|
|
62
61
|
get() {
|
|
63
62
|
throw new Error(`This field doesn't have any arguments`);
|
|
64
63
|
},
|
|
65
64
|
};
|
|
66
65
|
class OperationPlan {
|
|
67
|
-
constructor(schema, operation, fragments, variableValues, context, rootValue, planningTimeout) {
|
|
66
|
+
constructor(schema, operation, fragments, variableValuesConstraints, variableValues, contextConstraints, context, rootValueConstraints, rootValue, planningTimeout) {
|
|
68
67
|
this.schema = schema;
|
|
69
68
|
this.operation = operation;
|
|
70
69
|
this.fragments = fragments;
|
|
@@ -83,29 +82,16 @@ class OperationPlan {
|
|
|
83
82
|
* 7. ready
|
|
84
83
|
*
|
|
85
84
|
* Once in 'ready' state we can execute the plan.
|
|
86
|
-
*
|
|
87
|
-
* @internal
|
|
88
85
|
*/
|
|
89
86
|
this.phase = "init";
|
|
90
87
|
/**
|
|
91
88
|
* Gets updated as we work our way through the plan, useful for making errors more helpful.
|
|
92
89
|
*/
|
|
93
|
-
this.loc = isDev
|
|
90
|
+
this.loc = index_js_1.isDev
|
|
94
91
|
? []
|
|
95
92
|
: null /* forbid loc in production */;
|
|
96
|
-
this.modifierStepCount = 0;
|
|
97
|
-
this.modifierDepthCount = 0;
|
|
98
|
-
this.modifierSteps = [];
|
|
99
93
|
/** @internal */
|
|
100
94
|
this.stepTracker = new StepTracker_js_1.StepTracker(this);
|
|
101
|
-
this.maxDeduplicatedStepId = -1;
|
|
102
|
-
this.maxValidatedStepId = -1;
|
|
103
|
-
/** Constraints based on evaluating variables. @internal */
|
|
104
|
-
this.variableValuesConstraints = [];
|
|
105
|
-
/** Constraints based on evaluating context. @internal */
|
|
106
|
-
this.contextConstraints = [];
|
|
107
|
-
/** Constraints based on evaluating rootValue. @internal */
|
|
108
|
-
this.rootValueConstraints = [];
|
|
109
95
|
/**
|
|
110
96
|
* @internal
|
|
111
97
|
*/
|
|
@@ -118,8 +104,10 @@ class OperationPlan {
|
|
|
118
104
|
this.previousLap = this.startTime;
|
|
119
105
|
this.laps = [];
|
|
120
106
|
this.optimizeMeta = new Map();
|
|
107
|
+
/** @internal */
|
|
108
|
+
this.valueNodeToStaticValueCache = new Map();
|
|
121
109
|
/** @internal Use plan.getStep(id) instead. */
|
|
122
|
-
this.getStep = isDev
|
|
110
|
+
this.getStep = index_js_1.isDev
|
|
123
111
|
? (id, requestingStep) => {
|
|
124
112
|
if (!["plan", "validate", "optimize"].includes(this.phase)) {
|
|
125
113
|
throw new Error(`Getting a step during the '${this.phase}' phase is forbidden - please do so before or during the optimize phase.`);
|
|
@@ -141,6 +129,10 @@ class OperationPlan {
|
|
|
141
129
|
return step[interfaces_js_1.$$proxy] ?? step;
|
|
142
130
|
};
|
|
143
131
|
this.polymorphicLayerPlanByPathByLayerPlan = new Map();
|
|
132
|
+
this._cacheStepStoreByLayerPlanAndActionKey = Object.create(null);
|
|
133
|
+
this.variableValuesConstraints = variableValuesConstraints;
|
|
134
|
+
this.contextConstraints = contextConstraints;
|
|
135
|
+
this.rootValueConstraints = rootValueConstraints;
|
|
144
136
|
this.scalarPlanInfo = { schema: this.schema };
|
|
145
137
|
const queryType = schema.getQueryType();
|
|
146
138
|
assert.ok(queryType, "Schema must have a query type");
|
|
@@ -174,10 +166,6 @@ class OperationPlan {
|
|
|
174
166
|
this.operationType = operation.operation;
|
|
175
167
|
this.phase = "plan";
|
|
176
168
|
this.rootLayerPlan = new LayerPlan_js_1.LayerPlan(this, null, REASON_ROOT);
|
|
177
|
-
// This doesn't do anything, it only exists to align plans more closely
|
|
178
|
-
// with an older version of the planner (to reduce the diff).
|
|
179
|
-
const rootSelectionSetStep = (0, withGlobalLayerPlan_js_1.withGlobalLayerPlan)(this.rootLayerPlan, exports.POLYMORPHIC_ROOT_PATHS, newValueStepCallback);
|
|
180
|
-
this.rootLayerPlan.setRootStep(rootSelectionSetStep);
|
|
181
169
|
// Set up the shared steps for variables, context and rootValue
|
|
182
170
|
[this.variableValuesStep, this.trackedVariableValuesStep] = this.track(variableValues, this.variableValuesConstraints, this.operation.variableDefinitions);
|
|
183
171
|
[this.contextStep, this.trackedContextStep] = this.track(context, this.contextConstraints);
|
|
@@ -193,13 +181,15 @@ class OperationPlan {
|
|
|
193
181
|
this.hoistSteps();
|
|
194
182
|
this.checkTimeout();
|
|
195
183
|
this.lap("hoistSteps", "planOperation");
|
|
196
|
-
if (isDev) {
|
|
184
|
+
if (index_js_1.isDev) {
|
|
197
185
|
this.phase = "validate";
|
|
186
|
+
this.resetCache();
|
|
198
187
|
// Helpfully check steps don't do forbidden things.
|
|
199
188
|
this.validateSteps();
|
|
200
189
|
this.lap("validateSteps");
|
|
201
190
|
}
|
|
202
191
|
this.phase = "optimize";
|
|
192
|
+
this.resetCache();
|
|
203
193
|
// Get rid of temporary steps before `optimize` triggers side-effects.
|
|
204
194
|
// (Critical due to steps that may have been discarded due to field errors
|
|
205
195
|
// or similar.)
|
|
@@ -212,7 +202,11 @@ class OperationPlan {
|
|
|
212
202
|
});
|
|
213
203
|
this.checkTimeout();
|
|
214
204
|
this.lap("optimizeSteps");
|
|
205
|
+
this.inlineSteps();
|
|
206
|
+
this.checkTimeout();
|
|
207
|
+
this.lap("inlineSteps");
|
|
215
208
|
this.phase = "finalize";
|
|
209
|
+
this.resetCache();
|
|
216
210
|
this.stepTracker.finalizeSteps();
|
|
217
211
|
// Get rid of steps that are no longer needed after optimising outputPlans
|
|
218
212
|
// (we shouldn't see any new steps or dependencies after here)
|
|
@@ -249,6 +243,7 @@ class OperationPlan {
|
|
|
249
243
|
});
|
|
250
244
|
this.lap("finalizeOutputPlans");
|
|
251
245
|
this.phase = "ready";
|
|
246
|
+
this.resetCache();
|
|
252
247
|
// this.walkFinalizedPlans();
|
|
253
248
|
// this.preparePrefetches();
|
|
254
249
|
const allMetaKeys = new Set();
|
|
@@ -323,21 +318,6 @@ class OperationPlan {
|
|
|
323
318
|
}
|
|
324
319
|
return this.stepTracker.addStep(plan);
|
|
325
320
|
}
|
|
326
|
-
/**
|
|
327
|
-
* Adds a plan to the known steps and returns the number to use as the plan
|
|
328
|
-
* id. ONLY to be used from Step, user code should never call this directly.
|
|
329
|
-
*
|
|
330
|
-
* @internal
|
|
331
|
-
*/
|
|
332
|
-
_addModifierStep(step) {
|
|
333
|
-
if (!["plan", "validate", "optimize"].includes(this.phase)) {
|
|
334
|
-
throw new Error(`Creating a step during the '${this.phase}' phase is forbidden.`);
|
|
335
|
-
}
|
|
336
|
-
const modifierStepId = `${this.modifierDepthCount}-${this
|
|
337
|
-
.modifierStepCount++}`;
|
|
338
|
-
this.modifierSteps.push(step);
|
|
339
|
-
return modifierStepId;
|
|
340
|
-
}
|
|
341
321
|
/**
|
|
342
322
|
* Get a plan without specifying who requested it; this disables all the
|
|
343
323
|
* caller checks. Only intended to be called from internal code.
|
|
@@ -400,7 +380,7 @@ class OperationPlan {
|
|
|
400
380
|
typeName: this.queryType.name,
|
|
401
381
|
}, locationDetails);
|
|
402
382
|
this.rootOutputPlan = outputPlan;
|
|
403
|
-
this.planSelectionSet(outputPlan, [], exports.POLYMORPHIC_ROOT_PATH, exports.POLYMORPHIC_ROOT_PATHS, this.trackedRootValueStep, rootType, this.operation.selectionSet.selections);
|
|
383
|
+
this.planSelectionSet(outputPlan, [], exports.POLYMORPHIC_ROOT_PATH, exports.POLYMORPHIC_ROOT_PATHS, this.trackedRootValueStep, rootType, this.operation.selectionSet.selections, true);
|
|
404
384
|
if (this.loc !== null)
|
|
405
385
|
this.loc.pop();
|
|
406
386
|
}
|
|
@@ -426,7 +406,7 @@ class OperationPlan {
|
|
|
426
406
|
typeName: rootType.name,
|
|
427
407
|
}, locationDetails);
|
|
428
408
|
this.rootOutputPlan = outputPlan;
|
|
429
|
-
this.planSelectionSet(outputPlan, [], exports.POLYMORPHIC_ROOT_PATH, exports.POLYMORPHIC_ROOT_PATHS, this.trackedRootValueStep, rootType, this.operation.selectionSet.selections, true);
|
|
409
|
+
this.planSelectionSet(outputPlan, [], exports.POLYMORPHIC_ROOT_PATH, exports.POLYMORPHIC_ROOT_PATHS, this.trackedRootValueStep, rootType, this.operation.selectionSet.selections, true, true);
|
|
430
410
|
if (this.loc !== null)
|
|
431
411
|
this.loc.pop();
|
|
432
412
|
}
|
|
@@ -441,7 +421,7 @@ class OperationPlan {
|
|
|
441
421
|
throw new index_js_1.SafeError("No subscription type found in schema");
|
|
442
422
|
}
|
|
443
423
|
const selectionSet = this.operation.selectionSet;
|
|
444
|
-
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);
|
|
424
|
+
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));
|
|
445
425
|
if (groupedFieldSet.deferred !== undefined) {
|
|
446
426
|
throw new index_js_1.SafeError("@defer forbidden on subscription root selection set");
|
|
447
427
|
}
|
|
@@ -476,14 +456,10 @@ class OperationPlan {
|
|
|
476
456
|
: NO_ARGS;
|
|
477
457
|
if (subscriptionPlanResolver !== undefined) {
|
|
478
458
|
// PERF: optimize this
|
|
479
|
-
const { haltTree, step: subscribeStep } = this.planField(this.rootLayerPlan, path, exports.POLYMORPHIC_ROOT_PATHS,
|
|
459
|
+
const { haltTree, step: subscribeStep } = this.planField(rootType.name, fieldName, this.rootLayerPlan, path, exports.POLYMORPHIC_ROOT_PATHS, subscriptionPlanResolver, "subscribePlan", this.trackedRootValueStep, fieldSpec, trackedArguments, true);
|
|
480
460
|
if (haltTree) {
|
|
481
461
|
throw new index_js_1.SafeError("Failed to setup subscription");
|
|
482
462
|
}
|
|
483
|
-
const stepOptions = {
|
|
484
|
-
stream: { initialCount: 0 },
|
|
485
|
-
};
|
|
486
|
-
subscribeStep._stepOptions = stepOptions;
|
|
487
463
|
this.rootLayerPlan.setRootStep(subscribeStep);
|
|
488
464
|
const subscriptionEventLayerPlan = new LayerPlan_js_1.LayerPlan(this, this.rootLayerPlan, {
|
|
489
465
|
type: "subscription",
|
|
@@ -498,7 +474,7 @@ class OperationPlan {
|
|
|
498
474
|
streamItemPlan = this.stepTracker.getStepById(streamItemPlan.id);
|
|
499
475
|
const outputPlan = new OutputPlan_js_1.OutputPlan(subscriptionEventLayerPlan, this.rootValueStep, { mode: "root", typeName: rootType.name }, locationDetails);
|
|
500
476
|
this.rootOutputPlan = outputPlan;
|
|
501
|
-
this.planSelectionSet(outputPlan, [], exports.POLYMORPHIC_ROOT_PATH, exports.POLYMORPHIC_ROOT_PATHS, streamItemPlan, rootType, selectionSet.selections);
|
|
477
|
+
this.planSelectionSet(outputPlan, [], exports.POLYMORPHIC_ROOT_PATH, exports.POLYMORPHIC_ROOT_PATHS, streamItemPlan, rootType, selectionSet.selections, false);
|
|
502
478
|
}
|
|
503
479
|
else {
|
|
504
480
|
const subscribeStep = (0, withGlobalLayerPlan_js_1.withGlobalLayerPlan)(this.rootLayerPlan, exports.POLYMORPHIC_ROOT_PATHS, () => {
|
|
@@ -524,10 +500,8 @@ class OperationPlan {
|
|
|
524
500
|
},
|
|
525
501
|
});
|
|
526
502
|
});
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
};
|
|
530
|
-
subscribeStep._stepOptions = stepOptions;
|
|
503
|
+
subscribeStep._stepOptions.stream = {};
|
|
504
|
+
subscribeStep._stepOptions.walkIterable = true;
|
|
531
505
|
this.rootLayerPlan.setRootStep(subscribeStep);
|
|
532
506
|
const subscriptionEventLayerPlan = new LayerPlan_js_1.LayerPlan(this, this.rootLayerPlan, {
|
|
533
507
|
type: "subscription",
|
|
@@ -542,7 +516,7 @@ class OperationPlan {
|
|
|
542
516
|
streamItemPlan = this.stepTracker.getStepById(streamItemPlan.id);
|
|
543
517
|
const outputPlan = new OutputPlan_js_1.OutputPlan(subscriptionEventLayerPlan, this.rootValueStep, { mode: "root", typeName: rootType.name }, locationDetails);
|
|
544
518
|
this.rootOutputPlan = outputPlan;
|
|
545
|
-
this.planSelectionSet(outputPlan, [], exports.POLYMORPHIC_ROOT_PATH, exports.POLYMORPHIC_ROOT_PATHS, streamItemPlan, rootType, selectionSet.selections);
|
|
519
|
+
this.planSelectionSet(outputPlan, [], exports.POLYMORPHIC_ROOT_PATH, exports.POLYMORPHIC_ROOT_PATHS, streamItemPlan, rootType, selectionSet.selections, true);
|
|
546
520
|
}
|
|
547
521
|
if (this.loc !== null)
|
|
548
522
|
this.loc.pop();
|
|
@@ -551,7 +525,7 @@ class OperationPlan {
|
|
|
551
525
|
* Gets the item plan for a given parent list plan - this ensures we only
|
|
552
526
|
* create one item plan per parent plan.
|
|
553
527
|
*/
|
|
554
|
-
itemStepForListStep(parentLayerPlan, listStep, depth
|
|
528
|
+
itemStepForListStep(parentLayerPlan, listStep, depth, stream) {
|
|
555
529
|
const itemStepId = this.itemStepIdByListStepId[listStep.id];
|
|
556
530
|
if (itemStepId !== undefined) {
|
|
557
531
|
return this.stepTracker.getStepById(itemStepId);
|
|
@@ -560,7 +534,7 @@ class OperationPlan {
|
|
|
560
534
|
const layerPlan = new LayerPlan_js_1.LayerPlan(this, parentLayerPlan, {
|
|
561
535
|
type: "listItem",
|
|
562
536
|
parentStep: listStep,
|
|
563
|
-
stream
|
|
537
|
+
stream,
|
|
564
538
|
});
|
|
565
539
|
const itemPlan = (0, withGlobalLayerPlan_js_1.withGlobalLayerPlan)(layerPlan, listStep.polymorphicPaths, () => new index_js_1.__ItemStep(listStep, depth));
|
|
566
540
|
layerPlan.setRootStep(itemPlan);
|
|
@@ -572,187 +546,246 @@ class OperationPlan {
|
|
|
572
546
|
outputPlan, path, polymorphicPath, polymorphicPaths, parentStep, objectType, objectTypeFields, isMutation, groupedFieldSet) {
|
|
573
547
|
// `__typename` shouldn't bump the mutation index since it has no side effects.
|
|
574
548
|
let mutationIndex = -1;
|
|
549
|
+
const $sideEffect = outputPlan.layerPlan.latestSideEffectStep;
|
|
575
550
|
for (const [responseKey, fieldNodes] of groupedFieldSet.fields.entries()) {
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
}
|
|
592
|
-
else if (fieldName === "__schema" || fieldName === "__type") {
|
|
593
|
-
const variableNames = (0, utils_js_1.findVariableNamesUsed)(this, field);
|
|
594
|
-
outputPlan.addChild(objectType, responseKey, {
|
|
595
|
-
type: "outputPlan",
|
|
596
|
-
isNonNull: fieldName === "__schema",
|
|
597
|
-
outputPlan: new OutputPlan_js_1.OutputPlan(outputPlan.layerPlan, this.rootValueStep, {
|
|
598
|
-
mode: "introspection",
|
|
599
|
-
field,
|
|
600
|
-
variableNames,
|
|
601
|
-
// PERF: if variableNames.length === 0 we should be able to optimize this!
|
|
602
|
-
introspectionCacheByVariableValues: new lru_1.default({
|
|
603
|
-
maxLength: 3,
|
|
604
|
-
}),
|
|
605
|
-
}, locationDetails),
|
|
606
|
-
locationDetails,
|
|
607
|
-
});
|
|
608
|
-
continue;
|
|
609
|
-
}
|
|
610
|
-
const objectField = objectTypeFields[fieldName];
|
|
611
|
-
if (!objectField) {
|
|
612
|
-
// Field does not exist; this should have been caught by validation
|
|
613
|
-
// but the spec says to just skip it.
|
|
614
|
-
continue;
|
|
615
|
-
}
|
|
616
|
-
const fieldType = objectField.type;
|
|
617
|
-
const rawPlanResolver = objectField.extensions?.grafast?.plan;
|
|
618
|
-
if (rawPlanResolver?.constructor?.name === "AsyncFunction") {
|
|
619
|
-
throw new Error(`Plans must be synchronous, but this schema has an async function at '${objectType.name}.${fieldName}.plan': ${rawPlanResolver.toString()}`);
|
|
620
|
-
}
|
|
621
|
-
const namedReturnType = getNamedType(fieldType);
|
|
622
|
-
const resolvedResolver = objectField.resolve;
|
|
623
|
-
const subscriber = objectField.subscribe;
|
|
624
|
-
const usesDefaultResolver = resolvedResolver == null || resolvedResolver === defaultFieldResolver;
|
|
625
|
-
const resolver = usesDefaultResolver ? null : resolvedResolver;
|
|
626
|
-
// Apply a default plan to fields that do not have a plan nor a resolver.
|
|
627
|
-
const planResolver = rawPlanResolver ??
|
|
628
|
-
(usesDefaultResolver ? makeDefaultPlan(fieldName) : undefined);
|
|
629
|
-
/*
|
|
630
|
-
* When considering resolvers on fields, there's three booleans to
|
|
631
|
-
* consider:
|
|
632
|
-
*
|
|
633
|
-
* - typeIsPlanned: Does the type the field is defined on expect a plan?
|
|
634
|
-
* - NOTE: the root types (Query, Mutation, Subscription) implicitly
|
|
635
|
-
* expect the "root plan"
|
|
636
|
-
* - fieldHasPlan: Does the field define a `plan()` method?
|
|
637
|
-
* - resultIsPlanned: Does the named type that the field returns (the
|
|
638
|
-
* "named field type") expect a plan?
|
|
639
|
-
* - NOTE: only object types, unions and interfaces may expect plans;
|
|
640
|
-
* but not all of them do.
|
|
641
|
-
* - NOTE: a union/interface expects a plan iff ANY of its object
|
|
642
|
-
* types expect plans
|
|
643
|
-
* - NOTE: if ANY object type in an interface/union expects a plan
|
|
644
|
-
* then ALL object types within the interface/union must expect
|
|
645
|
-
* plans.
|
|
646
|
-
* - NOTE: scalars and enums never expect a plan.
|
|
647
|
-
*
|
|
648
|
-
* These booleans impact:
|
|
649
|
-
*
|
|
650
|
-
* - Whether there must be a `plan()` declaration and what the "parent"
|
|
651
|
-
* argument is to the same
|
|
652
|
-
* - If typeIsPlanned:
|
|
653
|
-
* - Assert: `fieldHasPlan` must be true
|
|
654
|
-
* - Pass through the parent plan
|
|
655
|
-
* - Else, if resultIsPlanned:
|
|
656
|
-
* - Assert: `fieldHasPlan` must be true
|
|
657
|
-
* - Pass through a `__ValueStep` representing the parent value.
|
|
658
|
-
* - Else, if fieldHasPlan:
|
|
659
|
-
* - Pass through a `__ValueStep` representing the parent value.
|
|
660
|
-
* - Else
|
|
661
|
-
* - No action necessary.
|
|
662
|
-
* - If the field may define `resolve()` and what the "parent" argument
|
|
663
|
-
* is to the same
|
|
664
|
-
* - If resultIsPlanned
|
|
665
|
-
* - Assert: there must not be a `resolve()`
|
|
666
|
-
* - Grafast provides pure resolver.
|
|
667
|
-
* - Else if fieldHasPlan (which may be implied by typeIsPlanned
|
|
668
|
-
* above)
|
|
669
|
-
* - If `resolve()` is not set:
|
|
670
|
-
* - grafast will return the value from the plan directly
|
|
671
|
-
* - Otherwise:
|
|
672
|
-
* - Grafast will wrap this resolver and will call `resolve()` (or
|
|
673
|
-
* default resolver) with the plan result. IMPORTANT: you may
|
|
674
|
-
* want to use an `ObjectStep` so that the parent object is of
|
|
675
|
-
* the expected shape; e.g. your plan might return
|
|
676
|
-
* `object({username: $username})` for a `User.username` field.
|
|
677
|
-
* - Else
|
|
678
|
-
* - Leave `resolve()` untouched - do not even wrap it.
|
|
679
|
-
* - (Failing that, use a __ValueStep and return the result
|
|
680
|
-
* directly.)
|
|
681
|
-
*/
|
|
682
|
-
if (resolver !== null) {
|
|
683
|
-
this.pure = false;
|
|
684
|
-
}
|
|
685
|
-
const resultIsPlanned = (0, utils_js_1.isTypePlanned)(this.schema, namedReturnType);
|
|
686
|
-
const fieldHasPlan = !!planResolver;
|
|
687
|
-
if (resultIsPlanned &&
|
|
688
|
-
!fieldHasPlan &&
|
|
689
|
-
!objectType.extensions?.grafast?.assertStep) {
|
|
690
|
-
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.`);
|
|
691
|
-
}
|
|
692
|
-
if (resultIsPlanned && resolver) {
|
|
693
|
-
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.`);
|
|
694
|
-
}
|
|
695
|
-
let step;
|
|
696
|
-
let haltTree = false;
|
|
697
|
-
const fieldLayerPlan = isMutation
|
|
698
|
-
? new LayerPlan_js_1.LayerPlan(this, outputPlan.layerPlan, {
|
|
699
|
-
type: "mutationField",
|
|
700
|
-
mutationIndex: ++mutationIndex,
|
|
701
|
-
})
|
|
702
|
-
: outputPlan.layerPlan;
|
|
703
|
-
const objectFieldArgs = objectField.args;
|
|
704
|
-
const trackedArguments = objectFieldArgs.length > 0
|
|
705
|
-
? (0, withGlobalLayerPlan_js_1.withGlobalLayerPlan)(this.rootLayerPlan, exports.POLYMORPHIC_ROOT_PATHS, this.getTrackedArguments, this, objectFieldArgs, field)
|
|
706
|
-
: NO_ARGS;
|
|
707
|
-
const fieldPath = [...path, responseKey];
|
|
708
|
-
if (typeof planResolver === "function") {
|
|
709
|
-
({ step, haltTree } = this.planField(fieldLayerPlan, fieldPath, polymorphicPaths, fieldNodes, planResolver, "autoApplyAfterParentPlan", parentStep, objectField, trackedArguments));
|
|
710
|
-
}
|
|
711
|
-
else {
|
|
712
|
-
// No plan resolver (or plan resolver fallback) so there must be a
|
|
713
|
-
// `resolve` method, so we'll feed the full parent step into the
|
|
714
|
-
// resolver.
|
|
715
|
-
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).");
|
|
716
|
-
// ENHANCEMENT: should we do `step = parentStep.object()` (i.e.
|
|
717
|
-
// `$pgSelectSingle.record()`) or similar for "opaque" steps to become
|
|
718
|
-
// suitable for consumption by resolvers?
|
|
719
|
-
// Maybe `parentStep.forResolver()` or `parentStep.hydrate()` or `parentStep.toFullObject()`?
|
|
720
|
-
step = parentStep;
|
|
721
|
-
}
|
|
722
|
-
if (resolver !== null) {
|
|
723
|
-
step = (0, withGlobalLayerPlan_js_1.withGlobalLayerPlan)(fieldLayerPlan, polymorphicPaths, () => {
|
|
724
|
-
const $args = (0, index_js_1.object)(field.arguments?.reduce((memo, arg) => {
|
|
725
|
-
memo[arg.name.value] = trackedArguments.get(arg.name.value);
|
|
726
|
-
return memo;
|
|
727
|
-
}, Object.create(null)) ?? Object.create(null));
|
|
728
|
-
return (0, graphqlResolver_js_1.graphqlResolver)(resolver, subscriber, step, $args, {
|
|
729
|
-
fieldName,
|
|
730
|
-
fieldNodes,
|
|
731
|
-
fragments: this.fragments,
|
|
732
|
-
operation: this.operation,
|
|
733
|
-
parentType: objectType,
|
|
734
|
-
returnType: fieldType,
|
|
735
|
-
schema: this.schema,
|
|
551
|
+
let resolverEmulation = groupedFieldSet.resolverEmulation;
|
|
552
|
+
try {
|
|
553
|
+
// All grouped fields are equivalent, as mandated by GraphQL validation rules. Thus we can take the first one.
|
|
554
|
+
const field = fieldNodes[0];
|
|
555
|
+
const fieldName = field.name.value;
|
|
556
|
+
const locationDetails = {
|
|
557
|
+
parentTypeName: objectType.name,
|
|
558
|
+
fieldName,
|
|
559
|
+
node: fieldNodes,
|
|
560
|
+
};
|
|
561
|
+
// explicit matches are the fastest: https://jsben.ch/ajZNf
|
|
562
|
+
if (fieldName === "__typename") {
|
|
563
|
+
outputPlan.addChild(objectType, responseKey, {
|
|
564
|
+
type: "__typename",
|
|
565
|
+
locationDetails,
|
|
736
566
|
});
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
567
|
+
continue;
|
|
568
|
+
}
|
|
569
|
+
else if (fieldName === "__schema" || fieldName === "__type") {
|
|
570
|
+
const variableNames = (0, utils_js_1.findVariableNamesUsed)(this, field);
|
|
571
|
+
outputPlan.addChild(objectType, responseKey, {
|
|
572
|
+
type: "outputPlan",
|
|
573
|
+
isNonNull: fieldName === "__schema",
|
|
574
|
+
outputPlan: new OutputPlan_js_1.OutputPlan(outputPlan.layerPlan, this.rootValueStep, {
|
|
575
|
+
mode: "introspection",
|
|
576
|
+
field,
|
|
577
|
+
variableNames,
|
|
578
|
+
// PERF: if variableNames.length === 0 we should be able to optimize this!
|
|
579
|
+
introspectionCacheByVariableValues: new lru_1.default({
|
|
580
|
+
maxLength: 3,
|
|
581
|
+
}),
|
|
582
|
+
}, locationDetails),
|
|
583
|
+
locationDetails,
|
|
584
|
+
});
|
|
585
|
+
continue;
|
|
586
|
+
}
|
|
587
|
+
const objectField = objectTypeFields[fieldName];
|
|
588
|
+
if (!objectField) {
|
|
589
|
+
// Field does not exist; this should have been caught by validation
|
|
590
|
+
// but the spec says to just skip it.
|
|
591
|
+
continue;
|
|
592
|
+
}
|
|
593
|
+
const fieldType = objectField.type;
|
|
594
|
+
const rawPlanResolver = objectField.extensions?.grafast?.plan;
|
|
595
|
+
if (rawPlanResolver) {
|
|
596
|
+
resolverEmulation = false;
|
|
597
|
+
(0, utils_js_1.assertNotAsync)(rawPlanResolver, `${objectType.name}.${fieldName}.plan`);
|
|
598
|
+
}
|
|
599
|
+
const namedReturnType = getNamedType(fieldType);
|
|
600
|
+
const resolvedResolver = objectField.resolve;
|
|
601
|
+
const subscriber = objectField.subscribe;
|
|
602
|
+
const usesDefaultResolver = resolvedResolver == null || resolvedResolver === defaultFieldResolver;
|
|
603
|
+
const isPolymorphic = isUnionType(namedReturnType) || isInterfaceType(namedReturnType);
|
|
604
|
+
// We should use a resolver if:
|
|
605
|
+
// 1. they give us a non-default resolver
|
|
606
|
+
// 2. we're emulating resolvers AND the field is polymorphic
|
|
607
|
+
const resolver = resolvedResolver && !usesDefaultResolver
|
|
608
|
+
? resolvedResolver
|
|
609
|
+
: resolverEmulation && isPolymorphic
|
|
610
|
+
? defaultFieldResolver
|
|
611
|
+
: null;
|
|
612
|
+
// Apply a default plan to fields that do not have a plan nor a resolver.
|
|
613
|
+
const planResolver = rawPlanResolver ?? (resolver ? undefined : defaultPlanResolver_js_1.defaultPlanResolver);
|
|
614
|
+
/*
|
|
615
|
+
* When considering resolvers on fields, there's three booleans to
|
|
616
|
+
* consider:
|
|
617
|
+
*
|
|
618
|
+
* - typeIsPlanned: Does the type the field is defined on expect a plan?
|
|
619
|
+
* - NOTE: the root types (Query, Mutation, Subscription) implicitly
|
|
620
|
+
* expect the "root plan"
|
|
621
|
+
* - fieldHasPlan: Does the field define a `plan()` method?
|
|
622
|
+
* - resultIsPlanned: Does the named type that the field returns (the
|
|
623
|
+
* "named field type") expect a plan?
|
|
624
|
+
* - NOTE: only object types, unions and interfaces may expect plans;
|
|
625
|
+
* but not all of them do.
|
|
626
|
+
* - NOTE: a union/interface expects a plan iff ANY of its object
|
|
627
|
+
* types expect plans
|
|
628
|
+
* - NOTE: if ANY object type in an interface/union expects a plan
|
|
629
|
+
* then ALL object types within the interface/union must expect
|
|
630
|
+
* plans.
|
|
631
|
+
* - NOTE: scalars and enums never expect a plan.
|
|
632
|
+
*
|
|
633
|
+
* These booleans impact:
|
|
634
|
+
*
|
|
635
|
+
* - Whether there must be a `plan()` declaration and what the "parent"
|
|
636
|
+
* argument is to the same
|
|
637
|
+
* - If typeIsPlanned:
|
|
638
|
+
* - Assert: `fieldHasPlan` must be true
|
|
639
|
+
* - Pass through the parent plan
|
|
640
|
+
* - Else, if resultIsPlanned:
|
|
641
|
+
* - Assert: `fieldHasPlan` must be true
|
|
642
|
+
* - Pass through a `__ValueStep` representing the parent value.
|
|
643
|
+
* - Else, if fieldHasPlan:
|
|
644
|
+
* - Pass through a `__ValueStep` representing the parent value.
|
|
645
|
+
* - Else
|
|
646
|
+
* - No action necessary.
|
|
647
|
+
* - If the field may define `resolve()` and what the "parent" argument
|
|
648
|
+
* is to the same
|
|
649
|
+
* - If resultIsPlanned
|
|
650
|
+
* - Assert: there must not be a `resolve()`
|
|
651
|
+
* - Grafast provides pure resolver.
|
|
652
|
+
* - Else if fieldHasPlan (which may be implied by typeIsPlanned
|
|
653
|
+
* above)
|
|
654
|
+
* - If `resolve()` is not set:
|
|
655
|
+
* - grafast will return the value from the plan directly
|
|
656
|
+
* - Otherwise:
|
|
657
|
+
* - Grafast will wrap this resolver and will call `resolve()` (or
|
|
658
|
+
* default resolver) with the plan result. IMPORTANT: you may
|
|
659
|
+
* want to use an `ObjectStep` so that the parent object is of
|
|
660
|
+
* the expected shape; e.g. your plan might return
|
|
661
|
+
* `object({username: $username})` for a `User.username` field.
|
|
662
|
+
* - Else
|
|
663
|
+
* - Leave `resolve()` untouched - do not even wrap it.
|
|
664
|
+
* - (Failing that, use a __ValueStep and return the result
|
|
665
|
+
* directly.)
|
|
666
|
+
*/
|
|
667
|
+
if (resolver !== null) {
|
|
668
|
+
this.pure = false;
|
|
669
|
+
if (!rawPlanResolver) {
|
|
670
|
+
resolverEmulation = true;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
const resultIsPlanned = (0, utils_js_1.isTypePlanned)(this.schema, namedReturnType);
|
|
674
|
+
const fieldHasPlan = !!planResolver;
|
|
675
|
+
if (resultIsPlanned &&
|
|
676
|
+
!fieldHasPlan &&
|
|
677
|
+
!objectType.extensions?.grafast?.assertStep) {
|
|
678
|
+
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.`);
|
|
679
|
+
}
|
|
680
|
+
if (resultIsPlanned && resolver) {
|
|
681
|
+
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.`);
|
|
682
|
+
}
|
|
683
|
+
let step;
|
|
684
|
+
let haltTree = false;
|
|
685
|
+
const fieldLayerPlan = isMutation
|
|
686
|
+
? new LayerPlan_js_1.LayerPlan(this, outputPlan.layerPlan, {
|
|
687
|
+
type: "mutationField",
|
|
688
|
+
mutationIndex: ++mutationIndex,
|
|
689
|
+
})
|
|
690
|
+
: outputPlan.layerPlan;
|
|
691
|
+
const objectFieldArgs = objectField.args;
|
|
692
|
+
const trackedArguments = objectFieldArgs.length > 0
|
|
693
|
+
? (0, withGlobalLayerPlan_js_1.withGlobalLayerPlan)(this.rootLayerPlan, exports.POLYMORPHIC_ROOT_PATHS, this.getTrackedArguments, this, objectFieldArgs, field)
|
|
694
|
+
: NO_ARGS;
|
|
695
|
+
const fieldPath = [...path, responseKey];
|
|
696
|
+
let streamDetails = null;
|
|
697
|
+
const isList = isListType(getNullableType(fieldType));
|
|
698
|
+
if (isList) {
|
|
699
|
+
// read the @stream directive, if present
|
|
700
|
+
// TODO: Check SameStreamDirective still exists in @stream spec at release.
|
|
701
|
+
/*
|
|
702
|
+
* `SameStreamDirective`
|
|
703
|
+
* (https://github.com/graphql/graphql-spec/blob/26fd78c4a89a79552dcc0c7e0140a975ce654400/spec/Section%205%20--%20Validation.md#L450-L458)
|
|
704
|
+
* ensures that every field that has `@stream` must have the same
|
|
705
|
+
* `@stream` arguments; so we can just check the first node in the
|
|
706
|
+
* merged set to see our stream options. NOTE: if this changes before
|
|
707
|
+
* release then we may need to find the stream with the largest
|
|
708
|
+
* `initialCount` to figure what to do; something like:
|
|
709
|
+
*
|
|
710
|
+
* const streamDirective = firstField.directives?.filter(
|
|
711
|
+
* (d) => d.name.value === "stream",
|
|
712
|
+
* ).sort(
|
|
713
|
+
* (a, z) => getArg(z, 'initialCount', 0) - getArg(a, 'initialCount', 0)
|
|
714
|
+
* )[0]
|
|
715
|
+
*/
|
|
716
|
+
for (const n of fieldNodes) {
|
|
717
|
+
const streamDirective = n.directives?.find((d) => d.name.value === "stream");
|
|
718
|
+
if (streamDirective === undefined) {
|
|
719
|
+
// Undo any previous stream details; the non-@stream wins.
|
|
720
|
+
streamDetails = null;
|
|
721
|
+
break;
|
|
722
|
+
}
|
|
723
|
+
else if (streamDetails !== null) {
|
|
724
|
+
// Validation promises the values are the same
|
|
725
|
+
continue;
|
|
726
|
+
}
|
|
727
|
+
else {
|
|
728
|
+
// Create streamDetails
|
|
729
|
+
streamDetails = this.withRootLayerPlan(() => ({
|
|
730
|
+
initialCount: this.internalDependency((0, utils_js_1.directiveArgument)(this, streamDirective, "initialCount", graphql.Kind.INT) ?? (0, constant_js_1.constant)(0)),
|
|
731
|
+
if: this.internalDependency((0, utils_js_1.directiveArgument)(this, streamDirective, "if", graphql.Kind.BOOLEAN) ?? (0, constant_js_1.constant)(true)),
|
|
732
|
+
label: this.internalDependency((0, utils_js_1.directiveArgument)(this, streamDirective, "label", graphql.Kind.STRING) ?? (0, constant_js_1.constant)(undefined)),
|
|
733
|
+
}));
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
if (typeof planResolver === "function") {
|
|
738
|
+
({ step, haltTree } = this.planField(objectType.name, fieldName, fieldLayerPlan, fieldPath, polymorphicPaths, planResolver, "plan", parentStep, objectField, trackedArguments, isList ? (streamDetails ?? false) : null));
|
|
739
|
+
}
|
|
740
|
+
else {
|
|
741
|
+
// No plan resolver (or plan resolver fallback) so there must be a
|
|
742
|
+
// `resolve` method, so we'll feed the full parent step into the
|
|
743
|
+
// resolver.
|
|
744
|
+
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).");
|
|
745
|
+
// ENHANCEMENT: should we do `step = parentStep.object()` (i.e.
|
|
746
|
+
// `$pgSelectSingle.record()`) or similar for "opaque" steps to become
|
|
747
|
+
// suitable for consumption by resolvers?
|
|
748
|
+
// Maybe `parentStep.forResolver()` or `parentStep.hydrate()` or `parentStep.toFullObject()`?
|
|
749
|
+
step = parentStep;
|
|
750
|
+
}
|
|
751
|
+
if (resolver !== null) {
|
|
752
|
+
step = (0, withGlobalLayerPlan_js_1.withGlobalLayerPlan)(fieldLayerPlan, polymorphicPaths, () => {
|
|
753
|
+
const $args = (0, index_js_1.object)(field.arguments?.reduce((memo, arg) => {
|
|
754
|
+
memo[arg.name.value] = trackedArguments.get(arg.name.value);
|
|
755
|
+
return memo;
|
|
756
|
+
}, Object.create(null)) ?? Object.create(null));
|
|
757
|
+
return (0, graphqlResolver_js_1.graphqlResolver)(resolver, subscriber, step, $args, {
|
|
758
|
+
fieldName,
|
|
759
|
+
fieldNodes,
|
|
760
|
+
fragments: this.fragments,
|
|
761
|
+
operation: this.operation,
|
|
762
|
+
parentType: objectType,
|
|
763
|
+
returnType: fieldType,
|
|
764
|
+
schema: this.schema,
|
|
765
|
+
});
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
// May have changed due to deduplicate
|
|
769
|
+
step = this.stepTracker.getStepById(step.id);
|
|
770
|
+
if (haltTree) {
|
|
771
|
+
const isNonNull = isNonNullType(fieldType);
|
|
772
|
+
outputPlan.addChild(objectType, responseKey, {
|
|
773
|
+
type: "outputPlan",
|
|
774
|
+
outputPlan: new OutputPlan_js_1.OutputPlan(fieldLayerPlan, step, OUTPUT_PLAN_TYPE_NULL, locationDetails),
|
|
775
|
+
isNonNull,
|
|
776
|
+
locationDetails,
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
else {
|
|
780
|
+
this.planIntoOutputPlan(outputPlan, fieldLayerPlan, fieldPath, polymorphicPath, polymorphicPaths,
|
|
781
|
+
// If one field has a selection set, they all have a selection set (guaranteed by validation).
|
|
782
|
+
field.selectionSet != null
|
|
783
|
+
? fieldNodes.flatMap((n) => n.selectionSet.selections)
|
|
784
|
+
: undefined, objectType, responseKey, fieldType, step, locationDetails, resolverEmulation, 0, streamDetails);
|
|
785
|
+
}
|
|
749
786
|
}
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
// If one field has a selection set, they all have a selection set (guaranteed by validation).
|
|
753
|
-
field.selectionSet != null
|
|
754
|
-
? fieldNodes.flatMap((n) => n.selectionSet.selections)
|
|
755
|
-
: undefined, objectType, responseKey, fieldType, step, locationDetails);
|
|
787
|
+
finally {
|
|
788
|
+
outputPlan.layerPlan.latestSideEffectStep = $sideEffect;
|
|
756
789
|
}
|
|
757
790
|
}
|
|
758
791
|
if (groupedFieldSet.deferred !== undefined) {
|
|
@@ -769,8 +802,14 @@ class OperationPlan {
|
|
|
769
802
|
// LOGGING: the location details should be tweaked to reference this
|
|
770
803
|
// fragment
|
|
771
804
|
outputPlan.locationDetails);
|
|
772
|
-
|
|
773
|
-
|
|
805
|
+
const $sideEffect = deferredOutputPlan.layerPlan.latestSideEffectStep;
|
|
806
|
+
try {
|
|
807
|
+
outputPlan.deferredOutputPlans.push(deferredOutputPlan);
|
|
808
|
+
this.processGroupedFieldSet(deferredOutputPlan, path, polymorphicPath, polymorphicPaths, parentStep, objectType, objectTypeFields, isMutation, deferred);
|
|
809
|
+
}
|
|
810
|
+
finally {
|
|
811
|
+
deferredOutputPlan.layerPlan.latestSideEffectStep = $sideEffect;
|
|
812
|
+
}
|
|
774
813
|
}
|
|
775
814
|
}
|
|
776
815
|
}
|
|
@@ -783,80 +822,122 @@ class OperationPlan {
|
|
|
783
822
|
* @param selections - The GraphQL selections (fields, fragment spreads, inline fragments) to evaluate
|
|
784
823
|
* @param isMutation - If true this selection set should be executed serially rather than in parallel (each field gets its own LayerPlan)
|
|
785
824
|
*/
|
|
786
|
-
planSelectionSet(outputPlan, path, polymorphicPath, polymorphicPaths, parentStep, objectType, selections, isMutation = false) {
|
|
825
|
+
planSelectionSet(outputPlan, path, polymorphicPath, polymorphicPaths, parentStep, objectType, selections, resolverEmulation, isMutation = false) {
|
|
787
826
|
if (this.loc !== null) {
|
|
788
827
|
this.loc.push(`planSelectionSet(${objectType.name} @ ${outputPlan.layerPlan.id} @ ${path.join(".")} @ ${polymorphicPath ?? ""})`);
|
|
789
828
|
}
|
|
790
|
-
if (isDev) {
|
|
829
|
+
if (index_js_1.isDev) {
|
|
791
830
|
assertObjectType(objectType);
|
|
792
831
|
}
|
|
793
|
-
const groupedFieldSet = (0, withGlobalLayerPlan_js_1.withGlobalLayerPlan)(outputPlan.layerPlan, polymorphicPaths, graphqlCollectFields_js_1.graphqlCollectFields, null, this, parentStep.id, objectType, selections, isMutation);
|
|
832
|
+
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);
|
|
794
833
|
const objectTypeFields = objectType.getFields();
|
|
795
834
|
this.processGroupedFieldSet(outputPlan, path, polymorphicPath, polymorphicPaths, parentStep, objectType, objectTypeFields, isMutation, groupedFieldSet);
|
|
796
835
|
if (this.loc !== null)
|
|
797
836
|
this.loc.pop();
|
|
798
837
|
}
|
|
838
|
+
internalDependency($step) {
|
|
839
|
+
this.stepTracker.internalDependencies.add($step);
|
|
840
|
+
return $step;
|
|
841
|
+
}
|
|
799
842
|
// Similar to the old 'planFieldReturnType'
|
|
800
843
|
planIntoOutputPlan(parentOutputPlan,
|
|
801
844
|
// Typically this is parentOutputPlan.layerPlan; but in the case of mutationFields it isn't.
|
|
802
845
|
parentLayerPlan,
|
|
803
846
|
// This is the LAYER-RELATIVE path, not the absolute path! It resets!
|
|
804
|
-
path, polymorphicPath, polymorphicPaths, selections, parentObjectType, responseKey, fieldType, $step, locationDetails, listDepth
|
|
847
|
+
path, polymorphicPath, polymorphicPaths, selections, parentObjectType, responseKey, fieldType, $step, locationDetails, resolverEmulation, listDepth, streamDetails) {
|
|
805
848
|
const nullableFieldType = getNullableType(fieldType);
|
|
806
849
|
const isNonNull = nullableFieldType !== fieldType;
|
|
807
850
|
if (isListType(nullableFieldType)) {
|
|
808
|
-
const
|
|
851
|
+
const $list = (0, withGlobalLayerPlan_js_1.withGlobalLayerPlan)(parentLayerPlan, polymorphicPaths, connection_js_1.itemsOrStep, null, $step);
|
|
852
|
+
if ($list !== $step) {
|
|
853
|
+
$list._stepOptions.stream = $step._stepOptions.stream;
|
|
854
|
+
}
|
|
855
|
+
$list._stepOptions.walkIterable = true;
|
|
856
|
+
const listOutputPlan = new OutputPlan_js_1.OutputPlan(parentLayerPlan, $list, OUTPUT_PLAN_TYPE_ARRAY, locationDetails);
|
|
809
857
|
parentOutputPlan.addChild(parentObjectType, responseKey, {
|
|
810
858
|
type: "outputPlan",
|
|
811
859
|
outputPlan: listOutputPlan,
|
|
812
860
|
isNonNull,
|
|
813
861
|
locationDetails,
|
|
814
862
|
});
|
|
815
|
-
const
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
863
|
+
const stream = streamDetails
|
|
864
|
+
? {
|
|
865
|
+
// These are already marked as internal dependencies
|
|
866
|
+
initialCountStepId: streamDetails.initialCount.id,
|
|
867
|
+
ifStepId: streamDetails.if.id,
|
|
868
|
+
labelStepId: streamDetails.label.id,
|
|
869
|
+
}
|
|
870
|
+
: undefined;
|
|
871
|
+
let $__item = this.itemStepForListStep(parentLayerPlan, $list, listDepth, stream);
|
|
872
|
+
const $sideEffect = $__item.layerPlan.latestSideEffectStep;
|
|
873
|
+
try {
|
|
874
|
+
let $item;
|
|
875
|
+
if ((0, step_js_1.isListCapableStep)($list)) {
|
|
876
|
+
$item = (0, withGlobalLayerPlan_js_1.withGlobalLayerPlan)($__item.layerPlan, $__item.polymorphicPaths, $list.listItem, $list, $__item);
|
|
877
|
+
this.deduplicateSteps();
|
|
878
|
+
// Refetch steps following deduplicate
|
|
879
|
+
$__item = this.stepTracker.getStepById($__item.id);
|
|
880
|
+
$item = this.stepTracker.getStepById($item.id);
|
|
881
|
+
}
|
|
882
|
+
else {
|
|
883
|
+
$item = $__item;
|
|
884
|
+
}
|
|
885
|
+
this.planIntoOutputPlan(listOutputPlan, $item.layerPlan, path, polymorphicPath, polymorphicPaths, selections, null, null, nullableFieldType.ofType, $item, locationDetails, resolverEmulation, listDepth + 1, null);
|
|
886
|
+
}
|
|
887
|
+
finally {
|
|
888
|
+
$__item.layerPlan.latestSideEffectStep = $sideEffect;
|
|
889
|
+
}
|
|
820
890
|
}
|
|
821
891
|
else if (isScalarType(nullableFieldType)) {
|
|
822
892
|
const scalarPlanResolver = nullableFieldType.extensions?.grafast?.plan;
|
|
823
|
-
|
|
824
|
-
|
|
893
|
+
const fnDescription = `${nullableFieldType.name}.plan`;
|
|
894
|
+
(0, utils_js_1.assertNotAsync)(scalarPlanResolver, fnDescription);
|
|
895
|
+
const $sideEffect = parentLayerPlan.latestSideEffectStep;
|
|
896
|
+
try {
|
|
897
|
+
const $leaf = typeof scalarPlanResolver === "function"
|
|
898
|
+
? (0, utils_js_1.assertNotPromise)((0, withGlobalLayerPlan_js_1.withGlobalLayerPlan)(parentLayerPlan, polymorphicPaths, scalarPlanResolver, null, $step, this.scalarPlanInfo), scalarPlanResolver, fnDescription)
|
|
899
|
+
: $step;
|
|
900
|
+
parentOutputPlan.addChild(parentObjectType, responseKey, {
|
|
901
|
+
type: "outputPlan",
|
|
902
|
+
isNonNull,
|
|
903
|
+
outputPlan: new OutputPlan_js_1.OutputPlan(parentLayerPlan, $leaf, {
|
|
904
|
+
mode: "leaf",
|
|
905
|
+
// stepId: $leaf.id,
|
|
906
|
+
graphqlType: nullableFieldType,
|
|
907
|
+
}, locationDetails),
|
|
908
|
+
locationDetails,
|
|
909
|
+
});
|
|
910
|
+
}
|
|
911
|
+
finally {
|
|
912
|
+
parentLayerPlan.latestSideEffectStep = $sideEffect;
|
|
825
913
|
}
|
|
826
|
-
const $leaf = typeof scalarPlanResolver === "function"
|
|
827
|
-
? (0, withGlobalLayerPlan_js_1.withGlobalLayerPlan)(parentLayerPlan, polymorphicPaths, scalarPlanResolver, null, $step, this.scalarPlanInfo)
|
|
828
|
-
: $step;
|
|
829
|
-
parentOutputPlan.addChild(parentObjectType, responseKey, {
|
|
830
|
-
type: "outputPlan",
|
|
831
|
-
isNonNull,
|
|
832
|
-
outputPlan: new OutputPlan_js_1.OutputPlan(parentLayerPlan, $leaf, {
|
|
833
|
-
mode: "leaf",
|
|
834
|
-
// stepId: $leaf.id,
|
|
835
|
-
graphqlType: nullableFieldType,
|
|
836
|
-
}, locationDetails),
|
|
837
|
-
locationDetails,
|
|
838
|
-
});
|
|
839
914
|
}
|
|
840
915
|
else if (isEnumType(nullableFieldType)) {
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
916
|
+
const $sideEffect = parentLayerPlan.latestSideEffectStep;
|
|
917
|
+
try {
|
|
918
|
+
parentOutputPlan.addChild(parentObjectType, responseKey, {
|
|
919
|
+
type: "outputPlan",
|
|
920
|
+
isNonNull,
|
|
921
|
+
outputPlan: new OutputPlan_js_1.OutputPlan(parentLayerPlan, $step, {
|
|
922
|
+
mode: "leaf",
|
|
923
|
+
graphqlType: nullableFieldType,
|
|
924
|
+
}, locationDetails),
|
|
925
|
+
locationDetails,
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
finally {
|
|
929
|
+
parentLayerPlan.latestSideEffectStep = $sideEffect;
|
|
930
|
+
}
|
|
850
931
|
}
|
|
851
932
|
else if (isObjectType(nullableFieldType)) {
|
|
852
|
-
if (isDev) {
|
|
933
|
+
if (index_js_1.isDev) {
|
|
853
934
|
// Check that the plan we're dealing with is the one the user declared
|
|
854
935
|
/** Either an assertion function or a step class */
|
|
855
936
|
const stepAssertion = nullableFieldType.extensions?.grafast?.assertStep;
|
|
856
937
|
if (stepAssertion != null) {
|
|
857
938
|
try {
|
|
858
|
-
if (stepAssertion === index_js_1.
|
|
859
|
-
stepAssertion.prototype instanceof index_js_1.
|
|
939
|
+
if (stepAssertion === index_js_1.Step ||
|
|
940
|
+
stepAssertion.prototype instanceof index_js_1.Step) {
|
|
860
941
|
if (!($step instanceof stepAssertion)) {
|
|
861
942
|
throw new Error(`Step mis-match: expected ${stepAssertion.name}, but instead found ${$step.constructor.name} (${$step})`);
|
|
862
943
|
}
|
|
@@ -866,7 +947,7 @@ class OperationPlan {
|
|
|
866
947
|
}
|
|
867
948
|
}
|
|
868
949
|
catch (e) {
|
|
869
|
-
throw new Error(`The step returned by '${path.join(".")}' is not compatible with the GraphQL object type '${nullableFieldType.name}': ${e.message}`, { cause: e });
|
|
950
|
+
throw new Error(`The step returned by '${path.join(".")}${"[]".repeat(listDepth)}' is not compatible with the GraphQL object type '${nullableFieldType.name}': ${e.message}`, { cause: e });
|
|
870
951
|
}
|
|
871
952
|
}
|
|
872
953
|
if (!selections) {
|
|
@@ -894,18 +975,24 @@ class OperationPlan {
|
|
|
894
975
|
objectLayerPlan.setRootStep($step);
|
|
895
976
|
}
|
|
896
977
|
}
|
|
897
|
-
const
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
978
|
+
const $sideEffect = objectLayerPlan.latestSideEffectStep;
|
|
979
|
+
try {
|
|
980
|
+
const objectOutputPlan = new OutputPlan_js_1.OutputPlan(objectLayerPlan, $step, {
|
|
981
|
+
mode: "object",
|
|
982
|
+
deferLabel: undefined,
|
|
983
|
+
typeName: nullableFieldType.name,
|
|
984
|
+
}, locationDetails);
|
|
985
|
+
parentOutputPlan.addChild(parentObjectType, responseKey, {
|
|
986
|
+
type: "outputPlan",
|
|
987
|
+
outputPlan: objectOutputPlan,
|
|
988
|
+
isNonNull,
|
|
989
|
+
locationDetails,
|
|
990
|
+
});
|
|
991
|
+
this.planSelectionSet(objectOutputPlan, path, polymorphicPath, polymorphicPaths, $step, nullableFieldType, selections, resolverEmulation);
|
|
992
|
+
}
|
|
993
|
+
finally {
|
|
994
|
+
objectLayerPlan.latestSideEffectStep = $sideEffect;
|
|
995
|
+
}
|
|
909
996
|
}
|
|
910
997
|
else {
|
|
911
998
|
// Polymorphic
|
|
@@ -971,48 +1058,54 @@ class OperationPlan {
|
|
|
971
1058
|
const polyBase = polymorphicPath ?? "";
|
|
972
1059
|
const $oldStep = $step;
|
|
973
1060
|
for (const type of allPossibleObjectTypes) {
|
|
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
|
-
|
|
1011
|
-
type
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1061
|
+
const $sideEffect = polymorphicLayerPlan.latestSideEffectStep;
|
|
1062
|
+
try {
|
|
1063
|
+
/* TODO: this $oldStep / $step dance turned out to be necessary
|
|
1064
|
+
* because `$step` was being replaced in the following query against
|
|
1065
|
+
* the graphilecrystaltest `polymorpic` schema; but I'm not sure this is the
|
|
1066
|
+
* right behavior - shouldn't deduplication have been temporarily
|
|
1067
|
+
* disabled during this?
|
|
1068
|
+
*
|
|
1069
|
+
* ```graphql
|
|
1070
|
+
* {
|
|
1071
|
+
* allSingleTableItems {
|
|
1072
|
+
* nodes {
|
|
1073
|
+
* id
|
|
1074
|
+
* type
|
|
1075
|
+
* singleTableItemsByParentIdList {
|
|
1076
|
+
* id
|
|
1077
|
+
* position
|
|
1078
|
+
* }
|
|
1079
|
+
* }
|
|
1080
|
+
* }
|
|
1081
|
+
* }
|
|
1082
|
+
* ```
|
|
1083
|
+
*/
|
|
1084
|
+
const $step = this.stepTracker.getStepById($oldStep.id);
|
|
1085
|
+
// Bit of a hack, but saves passing it around through all the arguments
|
|
1086
|
+
const newPolymorphicPath = `${polyBase}>${type.name}`;
|
|
1087
|
+
polymorphicLayerPlan.reason.polymorphicPaths.add(newPolymorphicPath);
|
|
1088
|
+
const newPolymorphicPaths = new Set();
|
|
1089
|
+
newPolymorphicPaths.add(newPolymorphicPath);
|
|
1090
|
+
const $root = (0, withGlobalLayerPlan_js_1.withGlobalLayerPlan)(polymorphicLayerPlan, newPolymorphicPaths, $step.planForType, $step, type);
|
|
1091
|
+
const objectOutputPlan = new OutputPlan_js_1.OutputPlan(polymorphicLayerPlan, $root, {
|
|
1092
|
+
mode: "object",
|
|
1093
|
+
deferLabel: undefined,
|
|
1094
|
+
typeName: type.name,
|
|
1095
|
+
}, locationDetails);
|
|
1096
|
+
// find all selections compatible with `type`
|
|
1097
|
+
const fieldNodes = (0, graphqlMergeSelectionSets_js_1.fieldSelectionsForType)(this, type, selections);
|
|
1098
|
+
this.planSelectionSet(objectOutputPlan, path, newPolymorphicPath, newPolymorphicPaths, $root, type, fieldNodes, false);
|
|
1099
|
+
polymorphicOutputPlan.addChild(type, null, {
|
|
1100
|
+
type: "outputPlan",
|
|
1101
|
+
isNonNull,
|
|
1102
|
+
outputPlan: objectOutputPlan,
|
|
1103
|
+
locationDetails,
|
|
1104
|
+
});
|
|
1105
|
+
}
|
|
1106
|
+
finally {
|
|
1107
|
+
polymorphicLayerPlan.latestSideEffectStep = $sideEffect;
|
|
1108
|
+
}
|
|
1016
1109
|
}
|
|
1017
1110
|
}
|
|
1018
1111
|
}
|
|
@@ -1061,15 +1154,23 @@ class OperationPlan {
|
|
|
1061
1154
|
return layerPlan;
|
|
1062
1155
|
}
|
|
1063
1156
|
}
|
|
1064
|
-
planField(layerPlan, path, polymorphicPaths,
|
|
1157
|
+
planField(typeName, fieldName, layerPlan, path, polymorphicPaths, planResolver, applyAfterMode, rawParentStep, field, trackedArguments,
|
|
1158
|
+
// If 'true' this is a subscription rather than a stream
|
|
1159
|
+
// If 'false' this is a list but it will never stream
|
|
1160
|
+
// If 'null' this is neither subscribe field nor list field
|
|
1161
|
+
// Otherwise, it's a list field that has the `@stream` directive applied
|
|
1162
|
+
streamDetails, deduplicate = true) {
|
|
1163
|
+
const coordinate = `${typeName}.${fieldName}`;
|
|
1065
1164
|
// The step may have been de-duped whilst sibling steps were planned
|
|
1066
1165
|
// PERF: this should be handled in the parent?
|
|
1067
1166
|
const parentStep = this.stepTracker.getStepById(rawParentStep.id);
|
|
1068
1167
|
const previousStepCount = this.stepTracker.stepCount;
|
|
1168
|
+
const previousSideEffectStep = layerPlan.latestSideEffectStep;
|
|
1069
1169
|
if (this.loc !== null)
|
|
1070
1170
|
this.loc.push(`planField(${path.join(".")})`);
|
|
1071
1171
|
try {
|
|
1072
|
-
let step = (0, withGlobalLayerPlan_js_1.withGlobalLayerPlan)(layerPlan, polymorphicPaths, operationPlan_input_js_1.withFieldArgsForArguments, null, this,
|
|
1172
|
+
let step = (0, withGlobalLayerPlan_js_1.withGlobalLayerPlan)(layerPlan, polymorphicPaths, operationPlan_input_js_1.withFieldArgsForArguments, null, this, trackedArguments, field, parentStep, applyAfterMode, coordinate, (fieldArgs) => planResolver(parentStep, fieldArgs, {
|
|
1173
|
+
fieldName,
|
|
1073
1174
|
field,
|
|
1074
1175
|
schema: this.schema,
|
|
1075
1176
|
}));
|
|
@@ -1083,31 +1184,22 @@ class OperationPlan {
|
|
|
1083
1184
|
haltTree = true;
|
|
1084
1185
|
}
|
|
1085
1186
|
(0, step_js_1.assertExecutableStep)(step);
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
const streamDirective = fieldNodes[0].directives?.find((d) => d.name.value === "stream");
|
|
1103
|
-
const stepOptions = {
|
|
1104
|
-
stream: !haltTree && streamDirective
|
|
1105
|
-
? {
|
|
1106
|
-
initialCount: Number((0, graphqlCollectFields_js_1.evalDirectiveArg)(fieldNodes[0], "stream", "initialCount", this.trackedVariableValuesStep, null)) || 0,
|
|
1107
|
-
}
|
|
1108
|
-
: null,
|
|
1109
|
-
};
|
|
1110
|
-
step._stepOptions = stepOptions;
|
|
1187
|
+
if (streamDetails === true) {
|
|
1188
|
+
// subscription
|
|
1189
|
+
step._stepOptions.stream = {};
|
|
1190
|
+
step._stepOptions.walkIterable = true;
|
|
1191
|
+
}
|
|
1192
|
+
else if (streamDetails === false) {
|
|
1193
|
+
step._stepOptions.walkIterable = true;
|
|
1194
|
+
}
|
|
1195
|
+
else if (streamDetails != null) {
|
|
1196
|
+
step._stepOptions.stream = {
|
|
1197
|
+
initialCountStepId: streamDetails.initialCount.id,
|
|
1198
|
+
ifStepId: streamDetails.if.id,
|
|
1199
|
+
labelStepId: streamDetails.label.id,
|
|
1200
|
+
};
|
|
1201
|
+
step._stepOptions.walkIterable = true;
|
|
1202
|
+
}
|
|
1111
1203
|
if (deduplicate) {
|
|
1112
1204
|
// Now that the field has been planned (including arguments, but NOT
|
|
1113
1205
|
// including selection set) we can deduplicate it to see if any of its
|
|
@@ -1133,6 +1225,7 @@ class OperationPlan {
|
|
|
1133
1225
|
}
|
|
1134
1226
|
try {
|
|
1135
1227
|
this.stepTracker.purgeBackTo(previousStepCount);
|
|
1228
|
+
layerPlan.latestSideEffectStep = previousSideEffectStep;
|
|
1136
1229
|
}
|
|
1137
1230
|
catch (e2) {
|
|
1138
1231
|
console.error(`Cleanup error occurred whilst trying to recover from field planning error: ${e2.stack}`);
|
|
@@ -1140,7 +1233,6 @@ class OperationPlan {
|
|
|
1140
1233
|
}
|
|
1141
1234
|
const step = (0, withGlobalLayerPlan_js_1.withGlobalLayerPlan)(layerPlan, polymorphicPaths, index_js_1.error, null, e);
|
|
1142
1235
|
const haltTree = true;
|
|
1143
|
-
this.modifierSteps = [];
|
|
1144
1236
|
// PERF: consider deleting all steps that were allocated during this. For
|
|
1145
1237
|
// now we'll just rely on tree-shaking.
|
|
1146
1238
|
return { step, haltTree };
|
|
@@ -1161,7 +1253,7 @@ class OperationPlan {
|
|
|
1161
1253
|
const trackedArgumentValues = Object.create(null);
|
|
1162
1254
|
for (const argumentDefinition of argumentDefinitions) {
|
|
1163
1255
|
const argumentName = argumentDefinition.name;
|
|
1164
|
-
if (isDev && trackedArgumentValues[argumentName]) {
|
|
1256
|
+
if (index_js_1.isDev && trackedArgumentValues[argumentName]) {
|
|
1165
1257
|
throw new index_js_1.SafeError(`Argument name '${argumentName}' seen twice; aborting.`);
|
|
1166
1258
|
}
|
|
1167
1259
|
const argumentType = argumentDefinition.type;
|
|
@@ -1178,42 +1270,12 @@ class OperationPlan {
|
|
|
1178
1270
|
},
|
|
1179
1271
|
};
|
|
1180
1272
|
}
|
|
1181
|
-
withModifiers(cb) {
|
|
1182
|
-
// Stash previous modifiers
|
|
1183
|
-
const previousModifierDepthCount = this.modifierDepthCount++;
|
|
1184
|
-
const previousCount = this.modifierStepCount;
|
|
1185
|
-
const previousStack = this.modifierSteps.splice(0, this.modifierSteps.length);
|
|
1186
|
-
this.modifierStepCount = 0;
|
|
1187
|
-
let result;
|
|
1188
|
-
let plansToApply;
|
|
1189
|
-
try {
|
|
1190
|
-
result = cb();
|
|
1191
|
-
}
|
|
1192
|
-
finally {
|
|
1193
|
-
// Remove the modifier plans from operationPlan and sort them ready for application.
|
|
1194
|
-
plansToApply = this.modifierSteps
|
|
1195
|
-
.splice(0, this.modifierSteps.length)
|
|
1196
|
-
.reverse();
|
|
1197
|
-
// Restore previous modifiers
|
|
1198
|
-
this.modifierStepCount = previousCount;
|
|
1199
|
-
for (const mod of previousStack) {
|
|
1200
|
-
this.modifierSteps.push(mod);
|
|
1201
|
-
}
|
|
1202
|
-
// Restore previous depth
|
|
1203
|
-
this.modifierDepthCount = previousModifierDepthCount;
|
|
1204
|
-
}
|
|
1205
|
-
// Apply the plans.
|
|
1206
|
-
for (let i = 0, l = plansToApply.length; i < l; i++) {
|
|
1207
|
-
plansToApply[i].apply();
|
|
1208
|
-
}
|
|
1209
|
-
return result;
|
|
1210
|
-
}
|
|
1211
1273
|
/**
|
|
1212
1274
|
* Sets up tracking for the given value (variableValues, context, rootValue).
|
|
1213
1275
|
*/
|
|
1214
1276
|
track(value, constraints, variableDefinitions) {
|
|
1215
|
-
const valueStep = (0, withGlobalLayerPlan_js_1.withGlobalLayerPlan)(this.rootLayerPlan, exports.POLYMORPHIC_ROOT_PATHS, newValueStepCallback);
|
|
1216
|
-
const trackedObjectStep = (0, withGlobalLayerPlan_js_1.withGlobalLayerPlan)(this.rootLayerPlan, exports.POLYMORPHIC_ROOT_PATHS, () => new index_js_1.__TrackedValueStep(value, valueStep, constraints, [], variableDefinitions));
|
|
1277
|
+
const valueStep = (0, withGlobalLayerPlan_js_1.withGlobalLayerPlan)(this.rootLayerPlan, exports.POLYMORPHIC_ROOT_PATHS, newValueStepCallback, null, variableDefinitions != null);
|
|
1278
|
+
const trackedObjectStep = (0, withGlobalLayerPlan_js_1.withGlobalLayerPlan)(this.rootLayerPlan, exports.POLYMORPHIC_ROOT_PATHS, () => new index_js_1.__TrackedValueStep(value, valueStep, constraints, [], variableDefinitions != null, variableDefinitions));
|
|
1217
1279
|
return [valueStep, trackedObjectStep];
|
|
1218
1280
|
}
|
|
1219
1281
|
/**
|
|
@@ -1227,7 +1289,8 @@ class OperationPlan {
|
|
|
1227
1289
|
continue;
|
|
1228
1290
|
for (const key in step) {
|
|
1229
1291
|
const val = step[key];
|
|
1230
|
-
if (
|
|
1292
|
+
if (key !== "implicitSideEffectStep" &&
|
|
1293
|
+
val instanceof index_js_1.Step) {
|
|
1231
1294
|
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.`));
|
|
1232
1295
|
}
|
|
1233
1296
|
}
|
|
@@ -1269,7 +1332,7 @@ class OperationPlan {
|
|
|
1269
1332
|
}
|
|
1270
1333
|
else {
|
|
1271
1334
|
// used for: hoist
|
|
1272
|
-
for (const $processFirst of step.dependencies) {
|
|
1335
|
+
for (const $processFirst of (0, utils_js_1.sudo)(step).dependencies) {
|
|
1273
1336
|
if (!processed.has($processFirst)) {
|
|
1274
1337
|
this.processStep(actionDescription, order, isReadonly, callback, processed, $processFirst);
|
|
1275
1338
|
}
|
|
@@ -1311,6 +1374,8 @@ class OperationPlan {
|
|
|
1311
1374
|
* @internal
|
|
1312
1375
|
*/
|
|
1313
1376
|
processSteps(actionDescription, order, isReadonly, callback) {
|
|
1377
|
+
if (index_js_1.isDev)
|
|
1378
|
+
this.stepTracker.lockNewSteps();
|
|
1314
1379
|
const previousStepCount = this.stepTracker.stepCount;
|
|
1315
1380
|
const processed = new Set();
|
|
1316
1381
|
for (let i = 0; i < this.stepTracker.stepCount; i++) {
|
|
@@ -1319,7 +1384,7 @@ class OperationPlan {
|
|
|
1319
1384
|
if (!step || step.id !== i || processed.has(step))
|
|
1320
1385
|
continue;
|
|
1321
1386
|
const resultStep = this.processStep(actionDescription, order, isReadonly, callback, processed, step);
|
|
1322
|
-
if (isDev) {
|
|
1387
|
+
if (index_js_1.isDev) {
|
|
1323
1388
|
const plansAdded = this.stepTracker.stepCount - previousStepCount;
|
|
1324
1389
|
// NOTE: whilst processing steps new steps may be added, thus we must loop
|
|
1325
1390
|
// ascending and we must re-evaluate this.stepTracker.stepCount on each loop
|
|
@@ -1328,8 +1393,13 @@ class OperationPlan {
|
|
|
1328
1393
|
throw new Error(`Whilst processing steps as part of ${actionDescription}Plans, ${plansAdded} new steps have been created... That seems like it's likely a bug in the relevant method of one of your steps. The last plan processed was ${resultStep}`);
|
|
1329
1394
|
}
|
|
1330
1395
|
}
|
|
1331
|
-
if (isReadonly
|
|
1332
|
-
|
|
1396
|
+
if ((isReadonly || index_js_1.isDev) &&
|
|
1397
|
+
this.stepTracker.stepCount > previousStepCount) {
|
|
1398
|
+
if (isReadonly) {
|
|
1399
|
+
throwNoNewStepsError(this, actionDescription, step, previousStepCount, "Creating steps in isReadonly mode is forbidden");
|
|
1400
|
+
}
|
|
1401
|
+
if (index_js_1.isDev)
|
|
1402
|
+
this.stepTracker.lockNewSteps();
|
|
1333
1403
|
}
|
|
1334
1404
|
}
|
|
1335
1405
|
if (!isReadonly &&
|
|
@@ -1348,17 +1418,48 @@ class OperationPlan {
|
|
|
1348
1418
|
*/
|
|
1349
1419
|
getPeers(step) {
|
|
1350
1420
|
if (step.hasSideEffects) {
|
|
1421
|
+
// Plans with side effects have no peers.
|
|
1422
|
+
return EMPTY_ARRAY;
|
|
1423
|
+
}
|
|
1424
|
+
if (step._stepOptions.stream) {
|
|
1425
|
+
// Streams have no peers - we cannot reference the stream more
|
|
1426
|
+
// than once (and we aim to not cache the stream because we want its
|
|
1427
|
+
// entries to be garbage collected).
|
|
1428
|
+
//
|
|
1429
|
+
// HOWEVER! There may be lifecycle parts that need to be called... So
|
|
1430
|
+
// call the function with an empty array; ignore the result.
|
|
1351
1431
|
return EMPTY_ARRAY;
|
|
1352
1432
|
}
|
|
1353
|
-
|
|
1433
|
+
// NOTE: Streams have no peers - we cannot reference the stream more than
|
|
1434
|
+
// once (and we aim to not cache the stream because we want its entries to
|
|
1435
|
+
// be garbage collected) - however if we're already fetching the list then
|
|
1436
|
+
// we shouldn't fetch it again via stream... We should deduplicate a stream
|
|
1437
|
+
// to return a non-stream.
|
|
1438
|
+
if (step._stepOptions.stream != null) {
|
|
1439
|
+
// TODO: remove this if block when we implement the new stream/defer -
|
|
1440
|
+
// deduplicating a stream should be fine. (Not subscriptions though - may
|
|
1441
|
+
// need a check for that!)
|
|
1442
|
+
return EMPTY_ARRAY;
|
|
1443
|
+
}
|
|
1444
|
+
if (this.stepTracker.internalDependencies.has(step)) {
|
|
1445
|
+
// PERF: we need to set up correct tracking, then internal deps can be deduped
|
|
1446
|
+
return EMPTY_ARRAY;
|
|
1447
|
+
}
|
|
1448
|
+
const sstep = (0, utils_js_1.sudo)(step);
|
|
1449
|
+
const { dependencies: deps, dependencyForbiddenFlags: flags, dependencyOnReject: onReject, layerPlan: layerPlan, constructor: stepConstructor, peerKey, } = sstep;
|
|
1450
|
+
// const streamInitialCount = sstep._stepOptions.stream?.initialCount;
|
|
1354
1451
|
const dependencyCount = deps.length;
|
|
1355
1452
|
if (dependencyCount === 0) {
|
|
1356
1453
|
let allPeers = null;
|
|
1357
|
-
|
|
1454
|
+
const stepsWithNoDependencies = this.stepTracker.stepsWithNoDependenciesByConstructor.get(step.constructor) ?? new Set();
|
|
1455
|
+
for (const possiblyPeer of stepsWithNoDependencies) {
|
|
1358
1456
|
if (possiblyPeer !== step &&
|
|
1359
1457
|
!possiblyPeer.hasSideEffects &&
|
|
1360
1458
|
possiblyPeer.layerPlan === layerPlan &&
|
|
1361
|
-
possiblyPeer.
|
|
1459
|
+
possiblyPeer._stepOptions.stream == null &&
|
|
1460
|
+
possiblyPeer.peerKey === peerKey
|
|
1461
|
+
// && possiblyPeer._stepOptions.stream?.initialCount === streamInitialCount
|
|
1462
|
+
) {
|
|
1362
1463
|
if (allPeers === null) {
|
|
1363
1464
|
allPeers = [possiblyPeer];
|
|
1364
1465
|
}
|
|
@@ -1381,15 +1482,24 @@ class OperationPlan {
|
|
|
1381
1482
|
}
|
|
1382
1483
|
const minDepth = Math.max(deferBoundaryDepth, dep.layerPlan.depth);
|
|
1383
1484
|
let allPeers = null;
|
|
1384
|
-
for (const { dependencyIndex: peerDependencyIndex, step:
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1485
|
+
for (const { dependencyIndex: peerDependencyIndex, step: rawPossiblyPeer, } of dep.dependents) {
|
|
1486
|
+
if (peerDependencyIndex !== 0 ||
|
|
1487
|
+
rawPossiblyPeer === step ||
|
|
1488
|
+
rawPossiblyPeer.hasSideEffects ||
|
|
1489
|
+
rawPossiblyPeer._stepOptions.stream != null ||
|
|
1490
|
+
rawPossiblyPeer.constructor !== stepConstructor ||
|
|
1491
|
+
rawPossiblyPeer.peerKey !== peerKey
|
|
1492
|
+
// || rawPossiblyPeer._stepOptions.stream?.initialCount !== streamInitialCount
|
|
1493
|
+
) {
|
|
1494
|
+
continue;
|
|
1495
|
+
}
|
|
1496
|
+
const possiblyPeer = (0, utils_js_1.sudo)(rawPossiblyPeer);
|
|
1497
|
+
const { layerPlan: peerLayerPlan, dependencyForbiddenFlags: peerFlags, dependencyOnReject: peerOnReject, } = possiblyPeer;
|
|
1498
|
+
if (peerLayerPlan.depth >= minDepth &&
|
|
1391
1499
|
possiblyPeer.dependencies.length === dependencyCount &&
|
|
1392
|
-
peerLayerPlan === ancestry[peerLayerPlan.depth]
|
|
1500
|
+
peerLayerPlan === ancestry[peerLayerPlan.depth] &&
|
|
1501
|
+
peerFlags[0] === flags[0] &&
|
|
1502
|
+
peerOnReject[0] === onReject[0]) {
|
|
1393
1503
|
if (allPeers === null) {
|
|
1394
1504
|
allPeers = [possiblyPeer];
|
|
1395
1505
|
}
|
|
@@ -1413,7 +1523,8 @@ class OperationPlan {
|
|
|
1413
1523
|
*/
|
|
1414
1524
|
let minDepth = deferBoundaryDepth;
|
|
1415
1525
|
const possiblePeers = [];
|
|
1416
|
-
|
|
1526
|
+
// Loop backwards since last dependency is most likely to be most unique
|
|
1527
|
+
for (let dependencyIndex = dependencyCount - 1; dependencyIndex >= 0; dependencyIndex--) {
|
|
1417
1528
|
const dep = deps[dependencyIndex];
|
|
1418
1529
|
const dl = dep.dependents.length;
|
|
1419
1530
|
if (dl === 1) {
|
|
@@ -1425,13 +1536,23 @@ class OperationPlan {
|
|
|
1425
1536
|
// Check the final dependency - this is likely to have the fewest
|
|
1426
1537
|
// dependents (since it was added last it's more likely to be
|
|
1427
1538
|
// unique).
|
|
1428
|
-
for (const { dependencyIndex: peerDependencyIndex, step:
|
|
1429
|
-
if (
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1539
|
+
for (const { dependencyIndex: peerDependencyIndex, step: rawPossiblyPeer, } of dep.dependents) {
|
|
1540
|
+
if (peerDependencyIndex !== dependencyIndex ||
|
|
1541
|
+
rawPossiblyPeer === step ||
|
|
1542
|
+
rawPossiblyPeer.hasSideEffects ||
|
|
1543
|
+
rawPossiblyPeer._stepOptions.stream != null ||
|
|
1544
|
+
rawPossiblyPeer.constructor !== stepConstructor ||
|
|
1545
|
+
rawPossiblyPeer.peerKey !== peerKey
|
|
1546
|
+
// || rawPossiblyPeer._stepOptions.stream?.initialCount !== streamInitialCount
|
|
1547
|
+
) {
|
|
1548
|
+
continue;
|
|
1549
|
+
}
|
|
1550
|
+
const possiblyPeer = (0, utils_js_1.sudo)(rawPossiblyPeer);
|
|
1551
|
+
const { layerPlan: peerLayerPlan, dependencyForbiddenFlags: peerFlags, dependencyOnReject: peerOnReject, dependencies: peerDependencies, } = possiblyPeer;
|
|
1552
|
+
if (peerDependencies.length === dependencyCount &&
|
|
1553
|
+
peerLayerPlan === ancestry[peerLayerPlan.depth] &&
|
|
1554
|
+
peerFlags[0] === flags[0] &&
|
|
1555
|
+
peerOnReject[0] === onReject[0]) {
|
|
1435
1556
|
possiblePeers.push(possiblyPeer);
|
|
1436
1557
|
}
|
|
1437
1558
|
}
|
|
@@ -1451,7 +1572,7 @@ class OperationPlan {
|
|
|
1451
1572
|
// We know the final dependency matches and the dependency count
|
|
1452
1573
|
// matches - check the other dependencies match.
|
|
1453
1574
|
for (let i = 0; i < dependencyCount - 1; i++) {
|
|
1454
|
-
if (deps[i] !== possiblyPeer.dependencies[i]) {
|
|
1575
|
+
if (deps[i] !== (0, utils_js_1.sudo)(possiblyPeer).dependencies[i]) {
|
|
1455
1576
|
continue outerloop;
|
|
1456
1577
|
}
|
|
1457
1578
|
}
|
|
@@ -1530,6 +1651,9 @@ class OperationPlan {
|
|
|
1530
1651
|
// unsafe. Should we do so, we should remove this.
|
|
1531
1652
|
break;
|
|
1532
1653
|
}
|
|
1654
|
+
else if (step instanceof index_js_1.__FlagStep) {
|
|
1655
|
+
break;
|
|
1656
|
+
}
|
|
1533
1657
|
else {
|
|
1534
1658
|
return;
|
|
1535
1659
|
}
|
|
@@ -1541,7 +1665,7 @@ class OperationPlan {
|
|
|
1541
1665
|
case "nullableBoundary": {
|
|
1542
1666
|
// Safe to hoist _unless_ it depends on the root step of the nullableBoundary.
|
|
1543
1667
|
const $root = step.layerPlan.reason.parentStep;
|
|
1544
|
-
if (step.dependencies.includes($root)) {
|
|
1668
|
+
if ((0, utils_js_1.sudo)(step).dependencies.includes($root)) {
|
|
1545
1669
|
return;
|
|
1546
1670
|
}
|
|
1547
1671
|
else {
|
|
@@ -1567,6 +1691,9 @@ class OperationPlan {
|
|
|
1567
1691
|
// break;
|
|
1568
1692
|
return;
|
|
1569
1693
|
}
|
|
1694
|
+
else if (step instanceof index_js_1.__FlagStep) {
|
|
1695
|
+
break;
|
|
1696
|
+
}
|
|
1570
1697
|
else {
|
|
1571
1698
|
// Plans that rely on external state shouldn't be hoisted because
|
|
1572
1699
|
// their results may change after a mutation, so the mutation should
|
|
@@ -1580,7 +1707,7 @@ class OperationPlan {
|
|
|
1580
1707
|
}
|
|
1581
1708
|
}
|
|
1582
1709
|
// Finally, check that none of its dependencies are in the same bucket.
|
|
1583
|
-
const deps = step.dependencies;
|
|
1710
|
+
const deps = (0, utils_js_1.sudo)(step).dependencies;
|
|
1584
1711
|
if (deps.some((dep) => dep.layerPlan === step.layerPlan)) {
|
|
1585
1712
|
return;
|
|
1586
1713
|
}
|
|
@@ -1646,6 +1773,11 @@ class OperationPlan {
|
|
|
1646
1773
|
if (this.isImmoveable(step)) {
|
|
1647
1774
|
return step;
|
|
1648
1775
|
}
|
|
1776
|
+
if (step._isUnary) {
|
|
1777
|
+
step._isUnaryLocked = true;
|
|
1778
|
+
// Don't push unary steps down
|
|
1779
|
+
return step;
|
|
1780
|
+
}
|
|
1649
1781
|
switch (step.layerPlan.reason.type) {
|
|
1650
1782
|
case "root":
|
|
1651
1783
|
case "subscription":
|
|
@@ -1783,34 +1915,25 @@ class OperationPlan {
|
|
|
1783
1915
|
return step;
|
|
1784
1916
|
}
|
|
1785
1917
|
_deduplicateInnerLogic(step) {
|
|
1786
|
-
if (step.hasSideEffects) {
|
|
1787
|
-
// Never deduplicate plans with side effects.
|
|
1788
|
-
return null;
|
|
1789
|
-
}
|
|
1790
1918
|
if (step instanceof index_js_1.__ItemStep) {
|
|
1791
1919
|
// __ItemStep cannot be deduplicated
|
|
1792
1920
|
return null;
|
|
1793
1921
|
}
|
|
1794
|
-
// OPTIMIZE: Past Benjie thought we might want to revisit this under the
|
|
1795
|
-
// new Grafast system. No idea what he had in mind there though...
|
|
1796
|
-
if (step._stepOptions.stream) {
|
|
1797
|
-
// Never deduplicate streaming plans, we cannot reference the stream more
|
|
1798
|
-
// than once (and we aim to not cache the stream because we want its
|
|
1799
|
-
// entries to be garbage collected).
|
|
1800
|
-
return null;
|
|
1801
|
-
}
|
|
1802
1922
|
const peers = this.getPeers(step);
|
|
1803
|
-
// Even if there
|
|
1923
|
+
// Even if there are no peers, we must still deduplicate because
|
|
1804
1924
|
// `deduplicate` is called to indicate that the field is done being
|
|
1805
1925
|
// planned, which the step class may want to acknowledge by locking certain
|
|
1806
1926
|
// facets of its functionality (such as adding filters). We'll simplify its
|
|
1807
1927
|
// work though by giving it an empty array to filter.
|
|
1928
|
+
const wasLocked = index_js_1.isDev && (0, lock_js_1.unlock)(step);
|
|
1808
1929
|
const equivalentSteps = step.deduplicate(peers);
|
|
1930
|
+
if (wasLocked)
|
|
1931
|
+
(0, lock_js_1.lock)(step);
|
|
1809
1932
|
if (equivalentSteps.length === 0) {
|
|
1810
1933
|
// No other equivalents
|
|
1811
1934
|
return null;
|
|
1812
1935
|
}
|
|
1813
|
-
if (isDev) {
|
|
1936
|
+
if (index_js_1.isDev) {
|
|
1814
1937
|
if (!equivalentSteps.every((replacementStep) => peers.includes(replacementStep))) {
|
|
1815
1938
|
throw new Error(`deduplicatePlan error: '${step}.deduplicate' may only return steps from its peers peers (peers = ${peers}), but it returned ${equivalentSteps}. This is currently forbidden because it could cause confusion during the optimization process, instead apply this change in 'optimize', or make sure that any child selections aren't applied until the optimize/finalize phase so that no mapping is required during deduplicate.`);
|
|
1816
1939
|
}
|
|
@@ -1832,7 +1955,14 @@ class OperationPlan {
|
|
|
1832
1955
|
return { stepsAtMinDepth, equivalentSteps };
|
|
1833
1956
|
}
|
|
1834
1957
|
deduplicateStep(step) {
|
|
1958
|
+
// Conditions applied to this step are now finalized, though we may still
|
|
1959
|
+
// tell the step to do more stuff (like fetch extra data), it can no longer
|
|
1960
|
+
// change its order, conditions, etc.
|
|
1835
1961
|
step.isArgumentsFinalized = true;
|
|
1962
|
+
// If a step is unary at this point, it must always remain unary.
|
|
1963
|
+
if (step._isUnary) {
|
|
1964
|
+
step._isUnaryLocked = true;
|
|
1965
|
+
}
|
|
1836
1966
|
if (step.deduplicate == null)
|
|
1837
1967
|
return step;
|
|
1838
1968
|
const result = this._deduplicateInnerLogic(step);
|
|
@@ -1851,14 +1981,24 @@ class OperationPlan {
|
|
|
1851
1981
|
}
|
|
1852
1982
|
winner.polymorphicPaths = polymorphicPaths;
|
|
1853
1983
|
}
|
|
1984
|
+
// Equivalent steps cannot be streaming; so we can no longer stream either.
|
|
1985
|
+
if (equivalentSteps.length > 0) {
|
|
1986
|
+
winner._stepOptions.stream = null;
|
|
1987
|
+
}
|
|
1854
1988
|
// Give the steps a chance to pass their responsibilities to the winner.
|
|
1855
1989
|
if (winner !== step) {
|
|
1990
|
+
const wasLocked = index_js_1.isDev && (0, lock_js_1.unlock)(step);
|
|
1856
1991
|
step.deduplicatedWith?.(winner);
|
|
1992
|
+
if (wasLocked)
|
|
1993
|
+
(0, lock_js_1.lock)(step);
|
|
1857
1994
|
this.stepTracker.replaceStep(step, winner);
|
|
1858
1995
|
}
|
|
1859
1996
|
for (const target of equivalentSteps) {
|
|
1860
1997
|
if (winner !== target) {
|
|
1998
|
+
const wasLocked = index_js_1.isDev && (0, lock_js_1.unlock)(target);
|
|
1861
1999
|
target.deduplicatedWith?.(winner);
|
|
2000
|
+
if (wasLocked)
|
|
2001
|
+
(0, lock_js_1.lock)(target);
|
|
1862
2002
|
this.stepTracker.replaceStep(target, winner);
|
|
1863
2003
|
}
|
|
1864
2004
|
}
|
|
@@ -1896,12 +2036,12 @@ class OperationPlan {
|
|
|
1896
2036
|
*/
|
|
1897
2037
|
deduplicateStepsProcess(processed, start, step) {
|
|
1898
2038
|
processed.add(step);
|
|
1899
|
-
for (const dep of step.dependencies) {
|
|
2039
|
+
for (const dep of (0, utils_js_1.sudo)(step).dependencies) {
|
|
1900
2040
|
if (dep.id >= start && !processed.has(dep)) {
|
|
1901
2041
|
this.deduplicateStepsProcess(processed, start, dep);
|
|
1902
2042
|
}
|
|
1903
2043
|
}
|
|
1904
|
-
(0, withGlobalLayerPlan_js_1.withGlobalLayerPlan)(step.layerPlan, step.polymorphicPaths, this.deduplicateStep, this, step);
|
|
2044
|
+
(0, withGlobalLayerPlan_js_1.withGlobalLayerPlan)(step.layerPlan, step.polymorphicPaths, this.phase === "plan" ? this.deduplicateStep : this.hoistAndDeduplicate, this, step);
|
|
1905
2045
|
}
|
|
1906
2046
|
/**
|
|
1907
2047
|
* Gives us a chance to replace nearly-duplicate plans with other existing
|
|
@@ -1955,27 +2095,6 @@ class OperationPlan {
|
|
|
1955
2095
|
pushDownSteps() {
|
|
1956
2096
|
this.processSteps("pushDown", "dependents-first", false, this.pushDown);
|
|
1957
2097
|
}
|
|
1958
|
-
getStepOptionsForStep(step) {
|
|
1959
|
-
return step._stepOptions;
|
|
1960
|
-
/*
|
|
1961
|
-
// NOTE: streams can only be merged if their parameters are compatible
|
|
1962
|
-
// (namely they need to have equivalent `initialCount`)
|
|
1963
|
-
const streamLayerPlan = step.childLayerPlans.find(
|
|
1964
|
-
(lp) => lp.reason.type === "stream",
|
|
1965
|
-
);
|
|
1966
|
-
|
|
1967
|
-
// PERF: if step isn't streamable, don't create a streamLayerPlan because
|
|
1968
|
-
// there's no point (and then we can leverage better optimisations).
|
|
1969
|
-
|
|
1970
|
-
return {
|
|
1971
|
-
stream:
|
|
1972
|
-
streamLayerPlan && isStreamableStep(step)
|
|
1973
|
-
? // && streamLayerPlan.parentStep === step
|
|
1974
|
-
{ initialCount: streamLayerPlan.reason.initialCount }
|
|
1975
|
-
: null,
|
|
1976
|
-
};
|
|
1977
|
-
*/
|
|
1978
|
-
}
|
|
1979
2098
|
/**
|
|
1980
2099
|
* Calls the 'optimize' method on a plan, which may cause the plan to
|
|
1981
2100
|
* communicate with its (deep) dependencies, and even to replace itself with
|
|
@@ -1993,7 +2112,7 @@ class OperationPlan {
|
|
|
1993
2112
|
return step;
|
|
1994
2113
|
}
|
|
1995
2114
|
// We know if it's streaming or not based on the LayerPlan it's contained within.
|
|
1996
|
-
const stepOptions =
|
|
2115
|
+
// const stepOptions = step._stepOptions;
|
|
1997
2116
|
let meta;
|
|
1998
2117
|
if (step.optimizeMetaKey !== undefined) {
|
|
1999
2118
|
meta = this.optimizeMeta.get(step.optimizeMetaKey);
|
|
@@ -2002,10 +2121,17 @@ class OperationPlan {
|
|
|
2002
2121
|
this.optimizeMeta.set(step.optimizeMetaKey, meta);
|
|
2003
2122
|
}
|
|
2004
2123
|
}
|
|
2124
|
+
const wasLocked = index_js_1.isDev && (0, lock_js_1.unlock)(step);
|
|
2005
2125
|
const replacementStep = step.optimize({
|
|
2006
|
-
|
|
2126
|
+
stream: step._stepOptions.stream
|
|
2127
|
+
? {
|
|
2128
|
+
// We could add more details here, but for now we don't really need them?
|
|
2129
|
+
}
|
|
2130
|
+
: null,
|
|
2007
2131
|
meta,
|
|
2008
2132
|
});
|
|
2133
|
+
if (wasLocked)
|
|
2134
|
+
(0, lock_js_1.lock)(step);
|
|
2009
2135
|
if (!replacementStep) {
|
|
2010
2136
|
throw new Error(`Bug in ${step}'s class: the 'optimize' method must return a step. Hint: did you forget 'return this;'?`);
|
|
2011
2137
|
}
|
|
@@ -2051,14 +2177,67 @@ class OperationPlan {
|
|
|
2051
2177
|
.slice(0, 10)
|
|
2052
2178
|
.join(", ")}`);
|
|
2053
2179
|
}
|
|
2180
|
+
inlineSteps() {
|
|
2181
|
+
flagLoop: for (const $flag of this.stepTracker.activeSteps) {
|
|
2182
|
+
if ($flag instanceof index_js_1.__FlagStep) {
|
|
2183
|
+
// We can only inline it if it's not used by an output plan or layer plan
|
|
2184
|
+
{
|
|
2185
|
+
const usages = this.stepTracker.outputPlansByRootStep.get($flag);
|
|
2186
|
+
if (usages?.size) {
|
|
2187
|
+
continue;
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
{
|
|
2191
|
+
const usages = this.stepTracker.layerPlansByRootStep.get($flag);
|
|
2192
|
+
if (usages?.size) {
|
|
2193
|
+
continue;
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
{
|
|
2197
|
+
const usages = this.stepTracker.layerPlansByParentStep.get($flag);
|
|
2198
|
+
if (usages?.size) {
|
|
2199
|
+
continue;
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2202
|
+
// We're only going to inline one if we can inline all.
|
|
2203
|
+
const toInline = [];
|
|
2204
|
+
for (const dependent of $flag.dependents) {
|
|
2205
|
+
const { step, dependencyIndex } = dependent;
|
|
2206
|
+
const $dependent = (0, utils_js_1.sudo)(step);
|
|
2207
|
+
const inlineDetails = $flag.inline($dependent.getDepOptions(dependencyIndex));
|
|
2208
|
+
if (inlineDetails === null) {
|
|
2209
|
+
continue flagLoop;
|
|
2210
|
+
}
|
|
2211
|
+
toInline.push({
|
|
2212
|
+
$dependent,
|
|
2213
|
+
dependencyIndex,
|
|
2214
|
+
inlineDetails,
|
|
2215
|
+
dependent,
|
|
2216
|
+
});
|
|
2217
|
+
}
|
|
2218
|
+
// All of them pass the check, let's inline them
|
|
2219
|
+
for (const todo of toInline) {
|
|
2220
|
+
const { $dependent, dependencyIndex, inlineDetails: { onReject, acceptFlags = interfaces_js_1.DEFAULT_ACCEPT_FLAGS }, } = todo;
|
|
2221
|
+
(0, utils_js_1.writeableArray)($dependent.dependencyOnReject)[dependencyIndex] =
|
|
2222
|
+
onReject;
|
|
2223
|
+
(0, utils_js_1.writeableArray)($dependent.dependencyForbiddenFlags)[dependencyIndex] =
|
|
2224
|
+
interfaces_js_1.ALL_FLAGS & ~acceptFlags;
|
|
2225
|
+
}
|
|
2226
|
+
const $flagDep = (0, utils_js_1.sudo)($flag).dependencies[0];
|
|
2227
|
+
this.stepTracker.replaceStep($flag, $flagDep);
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2054
2231
|
/** Finalizes each step */
|
|
2055
2232
|
finalizeSteps() {
|
|
2056
2233
|
const initialStepCount = this.stepTracker.stepCount;
|
|
2057
|
-
assert.strictEqual(this.modifierSteps.length, 0, "No modifier steps expected when performing finalizeSteps");
|
|
2058
2234
|
for (const step of this.stepTracker.activeSteps) {
|
|
2235
|
+
const wasLocked = index_js_1.isDev && (0, lock_js_1.unlock)(step);
|
|
2059
2236
|
step.finalize();
|
|
2237
|
+
if (wasLocked)
|
|
2238
|
+
(0, lock_js_1.lock)(step);
|
|
2060
2239
|
(0, step_js_1.assertFinalized)(step);
|
|
2061
|
-
if (isDev && this.stepTracker.stepCount !== initialStepCount) {
|
|
2240
|
+
if (index_js_1.isDev && this.stepTracker.stepCount !== initialStepCount) {
|
|
2062
2241
|
throw new Error(`When calling ${step}.finalize() a new plan was created; this is forbidden!`);
|
|
2063
2242
|
}
|
|
2064
2243
|
}
|
|
@@ -2074,9 +2253,13 @@ class OperationPlan {
|
|
|
2074
2253
|
if (currentLayerPlan.copyStepIds.includes(dep.id)) {
|
|
2075
2254
|
break;
|
|
2076
2255
|
}
|
|
2077
|
-
|
|
2256
|
+
const targetStep = this.stepTracker.getStepById(dep.id);
|
|
2257
|
+
if (index_js_1.isDev && targetStep !== dep) {
|
|
2078
2258
|
throw new Error(`GrafastInternalError<b291b110-396a-4c9c-814a-5466af3c50e8>: Plan mismatch; are we using a replaced step? Step ID: ${dep.id}; step: ${dep}; stepById: ${this.stepTracker.getStepById(dep.id)}`);
|
|
2079
2259
|
}
|
|
2260
|
+
if (targetStep._isUnary) {
|
|
2261
|
+
targetStep._isUnaryLocked = true;
|
|
2262
|
+
}
|
|
2080
2263
|
currentLayerPlan.copyStepIds.push(dep.id);
|
|
2081
2264
|
currentLayerPlan = currentLayerPlan.parentLayerPlan;
|
|
2082
2265
|
if (!currentLayerPlan) {
|
|
@@ -2101,15 +2284,17 @@ class OperationPlan {
|
|
|
2101
2284
|
const sideEffectSteps = layerPlan.pendingSteps.filter((s) => s.hasSideEffects);
|
|
2102
2285
|
const pending = new Set(layerPlan.pendingSteps);
|
|
2103
2286
|
const processed = new Set();
|
|
2287
|
+
let latestSideEffectStep = null;
|
|
2104
2288
|
const processSideEffectPlan = (step) => {
|
|
2105
2289
|
if (processed.has(step) || isPrepopulatedStep(step)) {
|
|
2106
2290
|
return;
|
|
2107
2291
|
}
|
|
2292
|
+
const sstep = (0, utils_js_1.sudo)(step);
|
|
2108
2293
|
processed.add(step);
|
|
2109
2294
|
pending.delete(step);
|
|
2110
2295
|
const sideEffectDeps = [];
|
|
2111
2296
|
const rest = [];
|
|
2112
|
-
for (const dep of
|
|
2297
|
+
for (const dep of sstep.dependencies) {
|
|
2113
2298
|
if (dep.layerPlan !== layerPlan) {
|
|
2114
2299
|
continue;
|
|
2115
2300
|
}
|
|
@@ -2132,6 +2317,13 @@ class OperationPlan {
|
|
|
2132
2317
|
for (const dep of rest) {
|
|
2133
2318
|
processSideEffectPlan(dep);
|
|
2134
2319
|
}
|
|
2320
|
+
if (latestSideEffectStep !== null &&
|
|
2321
|
+
!(0, utils_js_1.stepADependsOnStepB)(sstep, latestSideEffectStep)) {
|
|
2322
|
+
sstep.implicitSideEffectStep = latestSideEffectStep;
|
|
2323
|
+
}
|
|
2324
|
+
if (step.hasSideEffects) {
|
|
2325
|
+
latestSideEffectStep = step;
|
|
2326
|
+
}
|
|
2135
2327
|
const phase = /*#__INLINE__*/ newLayerPlanPhase();
|
|
2136
2328
|
phase.checkTimeout = true;
|
|
2137
2329
|
phase.normalSteps = [{ step }];
|
|
@@ -2142,7 +2334,7 @@ class OperationPlan {
|
|
|
2142
2334
|
processSideEffectPlan(sideEffectStep);
|
|
2143
2335
|
}
|
|
2144
2336
|
const readyToExecute = (step) => {
|
|
2145
|
-
for (const dep of step.dependencies) {
|
|
2337
|
+
for (const dep of (0, utils_js_1.sudo)(step).dependencies) {
|
|
2146
2338
|
if (dep.layerPlan === layerPlan && pending.has(dep)) {
|
|
2147
2339
|
return false;
|
|
2148
2340
|
}
|
|
@@ -2166,7 +2358,12 @@ class OperationPlan {
|
|
|
2166
2358
|
for (const step of nextSteps) {
|
|
2167
2359
|
processed.add(step);
|
|
2168
2360
|
pending.delete(step);
|
|
2169
|
-
|
|
2361
|
+
const sstep = (0, utils_js_1.sudo)(step);
|
|
2362
|
+
if (latestSideEffectStep !== null &&
|
|
2363
|
+
!(0, utils_js_1.stepADependsOnStepB)(sstep, latestSideEffectStep)) {
|
|
2364
|
+
sstep.implicitSideEffectStep = latestSideEffectStep;
|
|
2365
|
+
}
|
|
2366
|
+
if (step.isSyncAndSafe && (0, step_js_1.isUnbatchedStep)(step)) {
|
|
2170
2367
|
if (phase.unbatchedSyncAndSafeSteps !== undefined) {
|
|
2171
2368
|
phase.unbatchedSyncAndSafeSteps.push({
|
|
2172
2369
|
step,
|
|
@@ -2196,10 +2393,15 @@ class OperationPlan {
|
|
|
2196
2393
|
do {
|
|
2197
2394
|
foundOne = false;
|
|
2198
2395
|
for (const step of pending) {
|
|
2199
|
-
if (step.isSyncAndSafe && (0, step_js_1.
|
|
2396
|
+
if (step.isSyncAndSafe && (0, step_js_1.isUnbatchedStep)(step)) {
|
|
2200
2397
|
if (readyToExecute(step)) {
|
|
2201
2398
|
processed.add(step);
|
|
2202
2399
|
pending.delete(step);
|
|
2400
|
+
const sstep = (0, utils_js_1.sudo)(step);
|
|
2401
|
+
if (latestSideEffectStep !== null &&
|
|
2402
|
+
!(0, utils_js_1.stepADependsOnStepB)(sstep, latestSideEffectStep)) {
|
|
2403
|
+
sstep.implicitSideEffectStep = latestSideEffectStep;
|
|
2404
|
+
}
|
|
2203
2405
|
foundOne = true;
|
|
2204
2406
|
if (phase.unbatchedSyncAndSafeSteps !== undefined) {
|
|
2205
2407
|
phase.unbatchedSyncAndSafeSteps.push({
|
|
@@ -2235,13 +2437,34 @@ class OperationPlan {
|
|
|
2235
2437
|
if (step_js_1.$$noExec in step) {
|
|
2236
2438
|
continue;
|
|
2237
2439
|
}
|
|
2238
|
-
for (const dep of step.dependencies) {
|
|
2440
|
+
for (const dep of (0, utils_js_1.sudo)(step).dependencies) {
|
|
2239
2441
|
ensurePlanAvailableInLayer(dep, layerPlan);
|
|
2240
2442
|
}
|
|
2443
|
+
if (step.implicitSideEffectStep) {
|
|
2444
|
+
ensurePlanAvailableInLayer(step.implicitSideEffectStep, layerPlan);
|
|
2445
|
+
}
|
|
2241
2446
|
}
|
|
2242
2447
|
const $root = layerPlan.rootStep;
|
|
2243
2448
|
if ($root) {
|
|
2244
2449
|
ensurePlanAvailableInLayer($root, layerPlan);
|
|
2450
|
+
// If $root explicitly dependends on `layerPlan.parentSideEffectStep`
|
|
2451
|
+
// then we should remove the implicit layerPlan dependency (e.g. so
|
|
2452
|
+
// that if you `trap()` the error it does not interfere).
|
|
2453
|
+
if (layerPlan.parentSideEffectStep) {
|
|
2454
|
+
if (layerPlan.parentSideEffectStep === $root ||
|
|
2455
|
+
layerPlan.parentSideEffectStep === $root.implicitSideEffectStep ||
|
|
2456
|
+
(0, utils_js_1.stepADependsOnStepB)($root, layerPlan.parentSideEffectStep)) {
|
|
2457
|
+
layerPlan.parentSideEffectStep = null;
|
|
2458
|
+
}
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
const $sideEffect = layerPlan.parentSideEffectStep;
|
|
2462
|
+
if ($sideEffect) {
|
|
2463
|
+
// We read it from the parent layer plan at newBucket time, but we
|
|
2464
|
+
// don't need to actually scale it up/down.
|
|
2465
|
+
//
|
|
2466
|
+
// If parentSideEffectStep exists then parentLayerPlan must exist.
|
|
2467
|
+
ensurePlanAvailableInLayer($sideEffect, layerPlan.parentLayerPlan);
|
|
2245
2468
|
}
|
|
2246
2469
|
// Copy polymorphic parentStepId
|
|
2247
2470
|
if (layerPlan.reason.type === "polymorphic") {
|
|
@@ -2252,6 +2475,18 @@ class OperationPlan {
|
|
|
2252
2475
|
if (layerPlan.reason.type === "listItem") {
|
|
2253
2476
|
const parentStep = layerPlan.reason.parentStep;
|
|
2254
2477
|
ensurePlanAvailableInLayer(parentStep, layerPlan.parentLayerPlan);
|
|
2478
|
+
const stream = layerPlan.reason.stream;
|
|
2479
|
+
if (stream != null) {
|
|
2480
|
+
if (stream.initialCountStepId) {
|
|
2481
|
+
ensurePlanAvailableInLayer(this.stepTracker.getStepById(stream.initialCountStepId), layerPlan.parentLayerPlan);
|
|
2482
|
+
}
|
|
2483
|
+
if (stream.ifStepId) {
|
|
2484
|
+
ensurePlanAvailableInLayer(this.stepTracker.getStepById(stream.ifStepId), layerPlan.parentLayerPlan);
|
|
2485
|
+
}
|
|
2486
|
+
if (stream.labelStepId) {
|
|
2487
|
+
ensurePlanAvailableInLayer(this.stepTracker.getStepById(stream.labelStepId), layerPlan.parentLayerPlan);
|
|
2488
|
+
}
|
|
2489
|
+
}
|
|
2255
2490
|
}
|
|
2256
2491
|
}
|
|
2257
2492
|
// Populate copyPlanIds for output plans' rootStepId
|
|
@@ -2294,18 +2529,26 @@ class OperationPlan {
|
|
|
2294
2529
|
generatePlanJSON() {
|
|
2295
2530
|
function printStep(step) {
|
|
2296
2531
|
const metaString = step.toStringMeta();
|
|
2532
|
+
const sstep = (0, utils_js_1.sudo)(step);
|
|
2297
2533
|
return {
|
|
2298
2534
|
id: step.id,
|
|
2299
2535
|
stepClass: step.constructor.name,
|
|
2300
2536
|
metaString: metaString ? (0, index_js_1.stripAnsi)(metaString) : metaString,
|
|
2537
|
+
isUnary: step._isUnary,
|
|
2301
2538
|
bucketId: step.layerPlan.id,
|
|
2302
|
-
|
|
2539
|
+
implicitSideEffectStepId: step.implicitSideEffectStep?.id ?? null,
|
|
2540
|
+
dependencyIds: sstep.dependencies.map((d) => d.id),
|
|
2541
|
+
dependencyForbiddenFlags: sstep.dependencyForbiddenFlags.slice(),
|
|
2542
|
+
dependencyOnReject: sstep.dependencyOnReject.map((or) => or ? String(or) : or),
|
|
2303
2543
|
polymorphicPaths: step.polymorphicPaths
|
|
2304
2544
|
? [...step.polymorphicPaths]
|
|
2305
2545
|
: undefined,
|
|
2306
2546
|
isSyncAndSafe: step.isSyncAndSafe || undefined,
|
|
2307
2547
|
supportsUnbatched: typeof step.unbatchedExecute === "function" || undefined,
|
|
2308
2548
|
hasSideEffects: step.hasSideEffects || undefined,
|
|
2549
|
+
stream: step._stepOptions.stream
|
|
2550
|
+
? { initialCountStepId: step._stepOptions.stream.initialCountStepId }
|
|
2551
|
+
: undefined,
|
|
2309
2552
|
extra: step.planJSONExtra(),
|
|
2310
2553
|
};
|
|
2311
2554
|
}
|
|
@@ -2365,6 +2608,7 @@ class OperationPlan {
|
|
|
2365
2608
|
return {
|
|
2366
2609
|
id: lp.id,
|
|
2367
2610
|
reason: printBucketReason(lp.reason),
|
|
2611
|
+
parentSideEffectStepId: lp.parentSideEffectStep?.id ?? null,
|
|
2368
2612
|
copyStepIds: lp.copyStepIds,
|
|
2369
2613
|
phases: lp.phases.map(printPhase),
|
|
2370
2614
|
steps: lp.steps.map(printStep),
|
|
@@ -2383,7 +2627,7 @@ class OperationPlan {
|
|
|
2383
2627
|
const process = (lp, known) => {
|
|
2384
2628
|
for (const step of this.stepTracker.activeSteps) {
|
|
2385
2629
|
if (step.layerPlan === lp) {
|
|
2386
|
-
for (const dep of step.dependencies) {
|
|
2630
|
+
for (const dep of (0, utils_js_1.sudo)(step).dependencies) {
|
|
2387
2631
|
if (!known.includes(dep.layerPlan)) {
|
|
2388
2632
|
// Naughty naughty
|
|
2389
2633
|
subroutineStep.addDependency(dep);
|
|
@@ -2423,13 +2667,61 @@ class OperationPlan {
|
|
|
2423
2667
|
}
|
|
2424
2668
|
return matches;
|
|
2425
2669
|
}
|
|
2670
|
+
/**
|
|
2671
|
+
* Cache a generated step by a given identifier (cacheKey) such that we don't
|
|
2672
|
+
* need to regenerate it on future calls, significantly reducing the load on
|
|
2673
|
+
* deduplication later.
|
|
2674
|
+
*
|
|
2675
|
+
* Note: automatically extends the cached step into other (relevant)
|
|
2676
|
+
* polymorphic paths; if this shouldn't be the case then don't use cacheStep
|
|
2677
|
+
* and instead rely on deduplication as usual.
|
|
2678
|
+
*
|
|
2679
|
+
* @experimental
|
|
2680
|
+
*/
|
|
2681
|
+
cacheStep(ownerStep, actionKey, cacheKey, cb) {
|
|
2682
|
+
const layerPlan = (0, withGlobalLayerPlan_js_1.currentLayerPlan)();
|
|
2683
|
+
const paths = (0, withGlobalLayerPlan_js_1.currentPolymorphicPaths)();
|
|
2684
|
+
const cache = (this._cacheStepStoreByLayerPlanAndActionKey[`${actionKey}|${layerPlan.id}|${ownerStep.id}`] ??= new Map());
|
|
2685
|
+
const cacheIt = () => {
|
|
2686
|
+
const stepToCache = cb();
|
|
2687
|
+
if (!(stepToCache instanceof index_js_1.Step)) {
|
|
2688
|
+
throw new Error(`The callback passed to cacheStep must always return an ExecutableStep; but this call from ${ownerStep} returned instead ${(0, inspect_js_1.inspect)(stepToCache)}`);
|
|
2689
|
+
}
|
|
2690
|
+
cache.set(cacheKey, stepToCache.id);
|
|
2691
|
+
return stepToCache;
|
|
2692
|
+
};
|
|
2693
|
+
if (!cache.has(cacheKey)) {
|
|
2694
|
+
return cacheIt();
|
|
2695
|
+
}
|
|
2696
|
+
const cachedStepId = cache.get(cacheKey);
|
|
2697
|
+
const cachedStep = this.stepTracker.stepById[cachedStepId];
|
|
2698
|
+
if (cachedStep) {
|
|
2699
|
+
// Fix poly paths
|
|
2700
|
+
if (paths && cachedStep.polymorphicPaths) {
|
|
2701
|
+
const polymorphicPaths = new Set([
|
|
2702
|
+
...cachedStep.polymorphicPaths,
|
|
2703
|
+
...paths,
|
|
2704
|
+
]);
|
|
2705
|
+
cachedStep.polymorphicPaths = polymorphicPaths;
|
|
2706
|
+
}
|
|
2707
|
+
return cachedStep;
|
|
2708
|
+
}
|
|
2709
|
+
else {
|
|
2710
|
+
return cacheIt();
|
|
2711
|
+
}
|
|
2712
|
+
}
|
|
2713
|
+
/**
|
|
2714
|
+
* Clears the cache, typically due to side effects having taken place. Called
|
|
2715
|
+
* from setting hasSideEffects on an ExecutableStep, among other places.
|
|
2716
|
+
*/
|
|
2717
|
+
resetCache() {
|
|
2718
|
+
this._cacheStepStoreByLayerPlanAndActionKey = Object.create(null);
|
|
2719
|
+
}
|
|
2720
|
+
withRootLayerPlan(cb) {
|
|
2721
|
+
return (0, withGlobalLayerPlan_js_1.withGlobalLayerPlan)(this.rootLayerPlan, exports.POLYMORPHIC_ROOT_PATHS, cb);
|
|
2722
|
+
}
|
|
2426
2723
|
}
|
|
2427
2724
|
exports.OperationPlan = OperationPlan;
|
|
2428
|
-
function makeDefaultPlan(fieldName) {
|
|
2429
|
-
return ($step) => typeof $step.get === "function"
|
|
2430
|
-
? $step.get(fieldName)
|
|
2431
|
-
: (0, access_js_1.access)($step, [fieldName]);
|
|
2432
|
-
}
|
|
2433
2725
|
function makeMetaByMetaKeysFactory(allMetaKeysList) {
|
|
2434
2726
|
const l = allMetaKeysList.length;
|
|
2435
2727
|
// Optimize the common cases
|