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.
Files changed (157) hide show
  1. package/README.md +2 -3
  2. package/dist/args.d.ts +4 -7
  3. package/dist/args.js +53 -38
  4. package/dist/assert.js +2 -3
  5. package/dist/bucket.d.ts +1 -89
  6. package/dist/config.d.ts +0 -17
  7. package/dist/config.js +1 -95
  8. package/dist/constraints.d.ts +15 -1
  9. package/dist/constraints.js +41 -3
  10. package/dist/deferred.js +1 -2
  11. package/dist/dev.d.ts +1 -4
  12. package/dist/dev.js +18 -3
  13. package/dist/engine/LayerPlan.d.ts +21 -111
  14. package/dist/engine/LayerPlan.js +196 -78
  15. package/dist/engine/OperationPlan.d.ts +30 -104
  16. package/dist/engine/OperationPlan.js +763 -471
  17. package/dist/engine/OutputPlan.d.ts +9 -20
  18. package/dist/engine/OutputPlan.js +432 -444
  19. package/dist/engine/StepTracker.d.ts +1 -95
  20. package/dist/engine/StepTracker.js +94 -20
  21. package/dist/engine/executeBucket.d.ts +5 -7
  22. package/dist/engine/executeBucket.js +646 -244
  23. package/dist/engine/executeOutputPlan.d.ts +0 -53
  24. package/dist/engine/executeOutputPlan.js +7 -2
  25. package/dist/engine/lib/defaultPlanResolver.d.ts +6 -0
  26. package/dist/engine/lib/defaultPlanResolver.js +11 -0
  27. package/dist/engine/lib/withGlobalLayerPlan.d.ts +2 -0
  28. package/dist/engine/lib/withGlobalLayerPlan.js +7 -4
  29. package/dist/engine/lock.d.ts +4 -0
  30. package/dist/engine/lock.js +82 -0
  31. package/dist/envelop.js +5 -4
  32. package/dist/error.d.ts +18 -49
  33. package/dist/error.js +50 -39
  34. package/dist/establishOperationPlan.js +57 -42
  35. package/dist/execute.d.ts +5 -4
  36. package/dist/execute.js +31 -15
  37. package/dist/exportAs.d.ts +1 -18
  38. package/dist/exportAs.js +2 -3
  39. package/dist/global.js +2 -3
  40. package/dist/grafastGraphql.d.ts +6 -1
  41. package/dist/grafastGraphql.js +35 -16
  42. package/dist/grafastPrint.d.ts +3 -0
  43. package/dist/grafastPrint.js +67 -6
  44. package/dist/graphqlCollectFields.d.ts +7 -34
  45. package/dist/graphqlCollectFields.js +24 -13
  46. package/dist/graphqlMergeSelectionSets.js +1 -2
  47. package/dist/index.d.ts +44 -29
  48. package/dist/index.js +91 -18
  49. package/dist/input.d.ts +1 -9
  50. package/dist/input.js +75 -28
  51. package/dist/inspect.d.ts +9 -3
  52. package/dist/inspect.js +3 -3
  53. package/dist/interfaces.d.ts +238 -93
  54. package/dist/interfaces.js +20 -3
  55. package/dist/makeGrafastSchema.d.ts +21 -9
  56. package/dist/makeGrafastSchema.js +353 -133
  57. package/dist/mermaid.d.ts +0 -17
  58. package/dist/mermaid.js +131 -19
  59. package/dist/middleware.d.ts +7 -0
  60. package/dist/middleware.js +24 -0
  61. package/dist/multistep.d.ts +19 -0
  62. package/dist/multistep.js +54 -0
  63. package/dist/operationPlan-input.d.ts +5 -4
  64. package/dist/operationPlan-input.js +140 -368
  65. package/dist/planJSONInterfaces.d.ts +12 -2
  66. package/dist/polymorphic.js +4 -5
  67. package/dist/prepare.d.ts +1 -7
  68. package/dist/prepare.js +100 -74
  69. package/dist/step.d.ts +118 -172
  70. package/dist/step.js +262 -183
  71. package/dist/steps/__flag.d.ts +65 -0
  72. package/dist/steps/__flag.js +261 -0
  73. package/dist/steps/__inputDefault.d.ts +14 -0
  74. package/dist/steps/__inputDefault.js +48 -0
  75. package/dist/steps/__inputDynamicScalar.d.ts +4 -6
  76. package/dist/steps/__inputDynamicScalar.js +4 -2
  77. package/dist/steps/__inputList.d.ts +5 -9
  78. package/dist/steps/__inputList.js +4 -1
  79. package/dist/steps/__inputObject.d.ts +7 -11
  80. package/dist/steps/__inputObject.js +49 -3
  81. package/dist/steps/__inputStaticLeaf.d.ts +2 -4
  82. package/dist/steps/__inputStaticLeaf.js +3 -1
  83. package/dist/steps/__item.d.ts +10 -12
  84. package/dist/steps/__item.js +8 -2
  85. package/dist/steps/__trackedValue.d.ts +7 -55
  86. package/dist/steps/__trackedValue.js +69 -8
  87. package/dist/steps/__value.d.ts +5 -4
  88. package/dist/steps/__value.js +5 -4
  89. package/dist/steps/access.d.ts +10 -10
  90. package/dist/steps/access.js +50 -17
  91. package/dist/steps/applyInput.d.ts +44 -0
  92. package/dist/steps/applyInput.js +206 -0
  93. package/dist/steps/applyTransforms.d.ts +2 -25
  94. package/dist/steps/applyTransforms.js +53 -31
  95. package/dist/steps/bakedInput.d.ts +23 -0
  96. package/dist/steps/bakedInput.js +90 -0
  97. package/dist/steps/condition.d.ts +31 -0
  98. package/dist/steps/condition.js +94 -0
  99. package/dist/steps/connection.d.ts +56 -42
  100. package/dist/steps/connection.js +97 -44
  101. package/dist/steps/constant.d.ts +7 -7
  102. package/dist/steps/constant.js +86 -7
  103. package/dist/steps/each.d.ts +3 -3
  104. package/dist/steps/each.js +2 -3
  105. package/dist/steps/error.d.ts +4 -4
  106. package/dist/steps/error.js +7 -6
  107. package/dist/steps/filter.d.ts +3 -2
  108. package/dist/steps/filter.js +1 -2
  109. package/dist/steps/first.d.ts +10 -9
  110. package/dist/steps/first.js +9 -13
  111. package/dist/steps/graphqlResolver.d.ts +3 -76
  112. package/dist/steps/graphqlResolver.js +89 -72
  113. package/dist/steps/groupBy.d.ts +3 -2
  114. package/dist/steps/groupBy.js +1 -2
  115. package/dist/steps/index.d.ts +12 -3
  116. package/dist/steps/index.js +48 -6
  117. package/dist/steps/lambda.d.ts +9 -9
  118. package/dist/steps/lambda.js +18 -9
  119. package/dist/steps/last.d.ts +9 -8
  120. package/dist/steps/last.js +7 -6
  121. package/dist/steps/list.d.ts +20 -9
  122. package/dist/steps/list.js +56 -13
  123. package/dist/steps/listTransform.d.ts +18 -22
  124. package/dist/steps/listTransform.js +58 -27
  125. package/dist/steps/listen.d.ts +6 -11
  126. package/dist/steps/listen.js +14 -16
  127. package/dist/steps/load.d.ts +42 -33
  128. package/dist/steps/load.js +60 -27
  129. package/dist/steps/node.d.ts +17 -10
  130. package/dist/steps/node.js +18 -6
  131. package/dist/steps/object.d.ts +33 -27
  132. package/dist/steps/object.js +53 -32
  133. package/dist/steps/partitionByIndex.d.ts +3 -2
  134. package/dist/steps/partitionByIndex.js +1 -2
  135. package/dist/steps/polymorphicBranch.d.ts +27 -0
  136. package/dist/steps/polymorphicBranch.js +62 -0
  137. package/dist/steps/proxy.d.ts +11 -12
  138. package/dist/steps/proxy.js +8 -5
  139. package/dist/steps/remapKeys.d.ts +10 -10
  140. package/dist/steps/remapKeys.js +14 -7
  141. package/dist/steps/reverse.d.ts +8 -8
  142. package/dist/steps/reverse.js +10 -7
  143. package/dist/steps/setter.d.ts +9 -18
  144. package/dist/steps/setter.js +24 -14
  145. package/dist/steps/sideEffect.d.ts +28 -0
  146. package/dist/steps/sideEffect.js +44 -0
  147. package/dist/stringifyPayload.js +1 -2
  148. package/dist/stripAnsi.js +2 -2
  149. package/dist/subscribe.d.ts +7 -1
  150. package/dist/subscribe.js +24 -9
  151. package/dist/thereCanBeOnlyOne.js +1 -1
  152. package/dist/timeSource.d.ts +0 -1
  153. package/dist/utils.d.ts +56 -55
  154. package/dist/utils.js +179 -59
  155. package/dist/version.d.ts +1 -1
  156. package/dist/version.js +1 -1
  157. 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 access_js_1 = require("../steps/access.js");
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, fields, subscriptionPlanResolver, "autoApplyAfterParentSubscribePlan", this.trackedRootValueStep, fieldSpec, trackedArguments);
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
- const stepOptions = {
528
- stream: { initialCount: 0 },
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 = 0) {
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: listStep._stepOptions.stream ?? undefined,
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
- // All grouped fields are equivalent, as mandated by GraphQL validation rules. Thus we can take the first one.
577
- const field = fieldNodes[0];
578
- const fieldName = field.name.value;
579
- const locationDetails = {
580
- parentTypeName: objectType.name,
581
- fieldName,
582
- node: fieldNodes,
583
- };
584
- // explicit matches are the fastest: https://jsben.ch/ajZNf
585
- if (fieldName === "__typename") {
586
- outputPlan.addChild(objectType, responseKey, {
587
- type: "__typename",
588
- locationDetails,
589
- });
590
- continue;
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
- // May have changed due to deduplicate
740
- step = this.stepTracker.getStepById(step.id);
741
- if (haltTree) {
742
- const isNonNull = isNonNullType(fieldType);
743
- outputPlan.addChild(objectType, responseKey, {
744
- type: "outputPlan",
745
- outputPlan: new OutputPlan_js_1.OutputPlan(fieldLayerPlan, step, OUTPUT_PLAN_TYPE_NULL, locationDetails),
746
- isNonNull,
747
- locationDetails,
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
- else {
751
- this.planIntoOutputPlan(outputPlan, fieldLayerPlan, fieldPath, polymorphicPath, polymorphicPaths,
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
- outputPlan.deferredOutputPlans.push(deferredOutputPlan);
773
- this.processGroupedFieldSet(deferredOutputPlan, path, polymorphicPath, polymorphicPaths, parentStep, objectType, objectTypeFields, isMutation, deferred);
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 = 0) {
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 listOutputPlan = new OutputPlan_js_1.OutputPlan(parentLayerPlan, $step, OUTPUT_PLAN_TYPE_ARRAY, locationDetails);
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 $__item = this.itemStepForListStep(parentLayerPlan, $step, listDepth);
816
- const $item = (0, step_js_1.isListCapableStep)($step)
817
- ? (0, withGlobalLayerPlan_js_1.withGlobalLayerPlan)($__item.layerPlan, polymorphicPaths, $step.listItem, $step, $__item)
818
- : $__item;
819
- this.planIntoOutputPlan(listOutputPlan, $item.layerPlan, path, polymorphicPath, polymorphicPaths, selections, null, null, nullableFieldType.ofType, $item, locationDetails, listDepth + 1);
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
- if (scalarPlanResolver?.constructor?.name === "AsyncFunction") {
824
- throw new Error(`Plans must be synchronous, but this schema has an async function at '${nullableFieldType.name}.plan': ${scalarPlanResolver.toString()}`);
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
- parentOutputPlan.addChild(parentObjectType, responseKey, {
842
- type: "outputPlan",
843
- isNonNull,
844
- outputPlan: new OutputPlan_js_1.OutputPlan(parentLayerPlan, $step, {
845
- mode: "leaf",
846
- graphqlType: nullableFieldType,
847
- }, locationDetails),
848
- locationDetails,
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.ExecutableStep ||
859
- stepAssertion.prototype instanceof index_js_1.ExecutableStep) {
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 objectOutputPlan = new OutputPlan_js_1.OutputPlan(objectLayerPlan, $step, {
898
- mode: "object",
899
- deferLabel: undefined,
900
- typeName: nullableFieldType.name,
901
- }, locationDetails);
902
- parentOutputPlan.addChild(parentObjectType, responseKey, {
903
- type: "outputPlan",
904
- outputPlan: objectOutputPlan,
905
- isNonNull,
906
- locationDetails,
907
- });
908
- this.planSelectionSet(objectOutputPlan, path, polymorphicPath, polymorphicPaths, $step, nullableFieldType, selections);
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
- /* TODO: this $oldStep / $step dance turned out to be necessary
975
- * because `$step` was being replaced in the following query against
976
- * the pggql_test `polymorpic` schema; but I'm not sure this is the
977
- * right behavior - shouldn't deduplication have been temporarily
978
- * disabled during this?
979
- *
980
- * ```graphql
981
- * {
982
- * allSingleTableItems {
983
- * nodes {
984
- * id
985
- * type
986
- * singleTableItemsByParentIdList {
987
- * id
988
- * position
989
- * }
990
- * }
991
- * }
992
- * }
993
- * ```
994
- */
995
- const $step = this.stepTracker.getStepById($oldStep.id);
996
- // Bit of a hack, but saves passing it around through all the arguments
997
- const newPolymorphicPath = `${polyBase}>${type.name}`;
998
- polymorphicLayerPlan.reason.polymorphicPaths.add(newPolymorphicPath);
999
- const newPolymorphicPaths = new Set();
1000
- newPolymorphicPaths.add(newPolymorphicPath);
1001
- const $root = (0, withGlobalLayerPlan_js_1.withGlobalLayerPlan)(polymorphicLayerPlan, newPolymorphicPaths, $step.planForType, $step, type);
1002
- const objectOutputPlan = new OutputPlan_js_1.OutputPlan(polymorphicLayerPlan, $root, {
1003
- mode: "object",
1004
- deferLabel: undefined,
1005
- typeName: type.name,
1006
- }, locationDetails);
1007
- // find all selections compatible with `type`
1008
- const fieldNodes = (0, graphqlMergeSelectionSets_js_1.fieldSelectionsForType)(this, type, selections);
1009
- this.planSelectionSet(objectOutputPlan, path, newPolymorphicPath, newPolymorphicPaths, $root, type, fieldNodes, false);
1010
- polymorphicOutputPlan.addChild(type, null, {
1011
- type: "outputPlan",
1012
- isNonNull,
1013
- outputPlan: objectOutputPlan,
1014
- locationDetails,
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, fieldNodes, planResolver, applyAfterMode, rawParentStep, field, trackedArguments, deduplicate = true) {
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, parentStep, trackedArguments, field, applyAfterMode, (fieldArgs) => planResolver(parentStep, fieldArgs, {
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
- // TODO: Check SameStreamDirective still exists in @stream spec at release.
1087
- /*
1088
- * `SameStreamDirective`
1089
- * (https://github.com/graphql/graphql-spec/blob/26fd78c4a89a79552dcc0c7e0140a975ce654400/spec/Section%205%20--%20Validation.md#L450-L458)
1090
- * ensures that every field that has `@stream` must have the same
1091
- * `@stream` arguments; so we can just check the first node in the
1092
- * merged set to see our stream options. NOTE: if this changes before
1093
- * release then we may need to find the stream with the largest
1094
- * `initialCount` to figure what to do; something like:
1095
- *
1096
- * const streamDirective = firstField.directives?.filter(
1097
- * (d) => d.name.value === "stream",
1098
- * ).sort(
1099
- * (a, z) => getArg(z, 'initialCount', 0) - getArg(a, 'initialCount', 0)
1100
- * )[0]
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 (val instanceof index_js_1.ExecutableStep) {
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 && this.stepTracker.stepCount > previousStepCount) {
1332
- throwNoNewStepsError(this, actionDescription, step, previousStepCount, "Creating steps in isReadonly mode is forbidden");
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
- const { dependencies: deps, layerPlan: layerPlan, constructor: stepConstructor, } = step;
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
- for (const possiblyPeer of this.stepTracker.stepsWithNoDependencies) {
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.constructor === stepConstructor) {
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: possiblyPeer, } of dep.dependents) {
1385
- const { layerPlan: peerLayerPlan } = possiblyPeer;
1386
- if (possiblyPeer !== step &&
1387
- peerDependencyIndex === 0 &&
1388
- !possiblyPeer.hasSideEffects &&
1389
- possiblyPeer.constructor === stepConstructor &&
1390
- peerLayerPlan.depth >= minDepth &&
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
- for (let dependencyIndex = 0; dependencyIndex < dependencyCount; dependencyIndex++) {
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: possiblyPeer, } of dep.dependents) {
1429
- if (possiblyPeer !== step &&
1430
- peerDependencyIndex === dependencyIndex &&
1431
- !possiblyPeer.hasSideEffects &&
1432
- possiblyPeer.constructor === stepConstructor &&
1433
- possiblyPeer.dependencies.length === dependencyCount &&
1434
- possiblyPeer.layerPlan === ancestry[possiblyPeer.layerPlan.depth]) {
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 is just one peer, we must still deduplicate because
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 = this.getStepOptionsForStep(step);
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
- ...stepOptions,
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
- if (isDev && this.stepTracker.getStepById(dep.id) !== dep) {
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 step.dependencies) {
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
- if (step.isSyncAndSafe && (0, step_js_1.isUnbatchedExecutableStep)(step)) {
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.isUnbatchedExecutableStep)(step)) {
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
- dependencyIds: step.dependencies.map((d) => d.id),
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