grafast 0.1.1-beta.11 → 0.1.1-beta.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +0 -3
  2. package/dist/args.js +8 -2
  3. package/dist/constraints.d.ts +15 -1
  4. package/dist/constraints.js +40 -1
  5. package/dist/engine/LayerPlan.d.ts +1 -0
  6. package/dist/engine/LayerPlan.js +62 -9
  7. package/dist/engine/OperationPlan.js +370 -274
  8. package/dist/engine/OutputPlan.d.ts +6 -5
  9. package/dist/engine/OutputPlan.js +423 -444
  10. package/dist/engine/executeBucket.js +36 -77
  11. package/dist/error.d.ts +13 -0
  12. package/dist/grafastGraphql.d.ts +5 -5
  13. package/dist/graphqlCollectFields.d.ts +6 -0
  14. package/dist/graphqlCollectFields.js +14 -8
  15. package/dist/interfaces.d.ts +18 -0
  16. package/dist/interfaces.js +0 -2
  17. package/dist/mermaid.js +20 -11
  18. package/dist/operationPlan-input.js +8 -1
  19. package/dist/planJSONInterfaces.d.ts +2 -0
  20. package/dist/prepare.js +3 -3
  21. package/dist/step.d.ts +5 -5
  22. package/dist/step.js +27 -2
  23. package/dist/steps/__flag.d.ts +3 -2
  24. package/dist/steps/__inputObject.d.ts +1 -0
  25. package/dist/steps/__inputObject.js +36 -0
  26. package/dist/steps/__item.d.ts +3 -3
  27. package/dist/steps/__trackedValue.d.ts +8 -0
  28. package/dist/steps/__trackedValue.js +40 -0
  29. package/dist/steps/__value.d.ts +2 -1
  30. package/dist/steps/constant.d.ts +2 -0
  31. package/dist/steps/constant.js +12 -0
  32. package/dist/steps/graphqlResolver.js +8 -5
  33. package/dist/steps/listen.d.ts +2 -2
  34. package/dist/steps/object.d.ts +1 -1
  35. package/dist/steps/sideEffect.d.ts +0 -1
  36. package/dist/thereCanBeOnlyOne.js +1 -1
  37. package/dist/utils.d.ts +4 -0
  38. package/dist/utils.js +7 -0
  39. package/dist/version.d.ts +1 -1
  40. package/dist/version.js +1 -1
  41. package/package.json +1 -1
package/README.md CHANGED
@@ -51,11 +51,8 @@ And please give some love to our featured sponsors 🤩:
51
51
  <table><tr>
52
52
  <td align="center"><a href="https://www.the-guild.dev/"><img src="https://graphile.org/images/sponsors/theguild.png" width="90" height="90" alt="The Guild" /><br />The Guild</a> *</td>
53
53
  <td align="center"><a href="https://dovetailapp.com/"><img src="https://graphile.org/images/sponsors/dovetail.png" width="90" height="90" alt="Dovetail" /><br />Dovetail</a> *</td>
54
- <td align="center"><a href="https://www.netflix.com/"><img src="https://graphile.org/images/sponsors/Netflix.png" width="90" height="90" alt="Netflix" /><br />Netflix</a> *</td>
55
54
  <td align="center"><a href="https://stellate.co/"><img src="https://graphile.org/images/sponsors/Stellate.png" width="90" height="90" alt="Stellate" /><br />Stellate</a> *</td>
56
- </tr><tr>
57
55
  <td align="center"><a href="https://gosteelhead.com/"><img src="https://graphile.org/images/sponsors/steelhead.svg" width="90" height="90" alt="Steelhead" /><br />Steelhead</a> *</td>
58
- <td align="center"><a href="https://www.sylvera.com/"><img src="https://graphile.org/images/sponsors/sylvera.svg" width="90" height="90" alt="Sylvera" /><br />Sylvera</a> *</td>
59
56
  </tr></table>
60
57
 
61
58
  <em>\* Sponsors the entire Graphile suite</em>
package/dist/args.js CHANGED
@@ -5,6 +5,7 @@ const interfaces_js_1 = require("./interfaces.js");
5
5
  const middleware_js_1 = require("./middleware.js");
6
6
  const utils_js_1 = require("./utils.js");
7
7
  const EMPTY_OBJECT = Object.freeze(Object.create(null));
8
+ const $$writeTest = Symbol("grafastWriteTest");
8
9
  /**
9
10
  * Applies Graphile Config hooks to your GraphQL request, e.g. to
10
11
  * populate context or similar.
@@ -19,8 +20,13 @@ function hookArgs(rawArgs, legacyResolvedPreset, legacyCtx) {
19
20
  rawArgs.requestContext = rawArgs.requestContext ?? legacyCtx;
20
21
  }
21
22
  const { middleware: rawMiddleware, resolvedPreset, contextValue: rawContextValue, } = rawArgs;
22
- // Make context mutable
23
- rawArgs.contextValue = Object.assign(Object.create(null), rawContextValue);
23
+ try {
24
+ rawContextValue[$$writeTest] = true;
25
+ }
26
+ catch (e) {
27
+ // Make context mutable
28
+ rawArgs.contextValue = Object.assign(Object.create(null), rawContextValue);
29
+ }
24
30
  const middleware = rawMiddleware === undefined && resolvedPreset != null
25
31
  ? (0, middleware_js_1.getGrafastMiddleware)(resolvedPreset)
26
32
  : rawMiddleware ?? null;
@@ -27,6 +27,20 @@ interface ExistsConstraint {
27
27
  path: (string | number)[];
28
28
  exists: boolean;
29
29
  }
30
+ /**
31
+ * If `keys` is null: asserts that there is no value at the given
32
+ * path.
33
+ *
34
+ * Otherwise: asserts that the value at the given path has the exact same keys.
35
+ */
36
+ interface KeysConstraint {
37
+ type: "keys";
38
+ path: (string | number)[];
39
+ /**
40
+ * If this is null it implies that the object did not exist.
41
+ */
42
+ keys: ReadonlyArray<string> | null;
43
+ }
30
44
  /**
31
45
  * If `expectedLength` is null: asserts that there is no value at the given
32
46
  * path.
@@ -52,7 +66,7 @@ interface IsEmptyConstraint {
52
66
  path: (string | number)[];
53
67
  isEmpty: boolean;
54
68
  }
55
- export type Constraint = ValueConstraint | EqualityConstraint | ExistsConstraint | LengthConstraint | IsEmptyConstraint;
69
+ export type Constraint = ValueConstraint | EqualityConstraint | ExistsConstraint | LengthConstraint | IsEmptyConstraint | KeysConstraint;
56
70
  /**
57
71
  * Implements the `MatchesConstraints` algorithm.
58
72
  */
@@ -32,7 +32,8 @@ function matchesConstraint(constraint, object) {
32
32
  const value = valueAtPath(object, constraint.path);
33
33
  switch (constraint.type) {
34
34
  case "length": {
35
- return Array.isArray(value) && value.length === constraint.expectedLength;
35
+ const actualLength = Array.isArray(value) ? value.length : null;
36
+ return actualLength === constraint.expectedLength;
36
37
  }
37
38
  case "exists": {
38
39
  return (value !== undefined) === constraint.exists;
@@ -49,6 +50,44 @@ function matchesConstraint(constraint, object) {
49
50
  Object.keys(value).length === 0;
50
51
  return isEmpty === constraint.isEmpty;
51
52
  }
53
+ case "keys": {
54
+ const { keys: expectedKeys } = constraint;
55
+ if (expectedKeys === null) {
56
+ return value == null || typeof value !== "object";
57
+ }
58
+ else if (value == null || typeof value !== "object") {
59
+ return false;
60
+ }
61
+ else {
62
+ // keys are always in order of the gql type; see coerceInputValue and __InputObjectStep ctor
63
+ const valueKeys = Object.keys(value);
64
+ const valueKeyCount = valueKeys.length;
65
+ const expectedKeyCount = expectedKeys.length;
66
+ // Optimization: early bail
67
+ if (valueKeyCount < expectedKeyCount) {
68
+ return false;
69
+ }
70
+ /**
71
+ * This is `i` but adjusted so that `undefined` doesn't increment it.
72
+ * Should match index in `expectedKeys`.
73
+ */
74
+ let definedRawKeyCount = 0;
75
+ for (let i = 0; i < valueKeyCount; i++) {
76
+ const valueKey = valueKeys[i];
77
+ if (value[valueKey] !== undefined) {
78
+ if (valueKey !== expectedKeys[definedRawKeyCount]) {
79
+ return false;
80
+ }
81
+ definedRawKeyCount++;
82
+ }
83
+ }
84
+ // Make sure there aren't any additional expected keys
85
+ if (definedRawKeyCount !== expectedKeyCount) {
86
+ return false;
87
+ }
88
+ return true;
89
+ }
90
+ }
52
91
  default: {
53
92
  const never = constraint;
54
93
  throw new Error(`Unsupported constraint type '${never.type}'`);
@@ -113,6 +113,7 @@ export declare class LayerPlan<TReason extends LayerPlanReason = LayerPlanReason
113
113
  constructor(operationPlan: OperationPlan, parentLayerPlan: LayerPlan | null, reason: TReason);
114
114
  toString(): string;
115
115
  print(depth?: number): string;
116
+ _hasSetRootStep: boolean;
116
117
  setRootStep($root: ExecutableStep): void;
117
118
  finalize(): void;
118
119
  newBucket(parentBucket: Bucket): Bucket | null;
@@ -89,6 +89,23 @@ class LayerPlan {
89
89
  this.steps = [];
90
90
  /** @internal */
91
91
  this.pendingSteps = [];
92
+ /**
93
+ * This goes along with `parentStep` in the reason, except it applies to all
94
+ * layer plan types and we figure it out automatically from the parent layer
95
+ * plan. If this step has an error at a given index, then it should be
96
+ * treated as if the parentStep had an error at that same index.
97
+ *
98
+ * @internal
99
+ */
100
+ this.parentSideEffectStep = null;
101
+ /**
102
+ * This tracks the latest seen side effect at the current point in planning
103
+ * (such that created steps take this to be their implicitSideEffectStep).
104
+ * This isn't used once planning is complete.
105
+ *
106
+ * @internal
107
+ */
108
+ this.latestSideEffectStep = null;
92
109
  /**
93
110
  * Describes the order in which the steps within this LayerPlan are executed.
94
111
  *
@@ -97,6 +114,13 @@ class LayerPlan {
97
114
  * @internal
98
115
  */
99
116
  this.phases = [];
117
+ this._hasSetRootStep = false;
118
+ // This layer plan is dependent on the latest side effect. Note that when
119
+ // we set a `rootStep` later, if the root step is dependent on this step
120
+ // (directly or indirectly) we will clear this property.
121
+ this.parentSideEffectStep = parentLayerPlan?.latestSideEffectStep ?? null;
122
+ // There has yet to be any side effects created in this layer.
123
+ this.latestSideEffectStep = null;
100
124
  this.stepsByConstructor = new Map();
101
125
  if (parentLayerPlan !== null) {
102
126
  this.depth = parentLayerPlan.depth + 1;
@@ -133,7 +157,7 @@ class LayerPlan {
133
157
  ? `{${this.reason.typeNames.join(",")}}`
134
158
  : "";
135
159
  const deps = this.copyStepIds.length > 0 ? `/${this.copyStepIds}` : "";
136
- return `LayerPlan<${this.id}${chain}?${this.reason.type}${reasonExtra}!${this.rootStep?.id ?? "x"}${deps}>`;
160
+ return `LayerPlan<${this.id}${chain}${this.parentSideEffectStep ? `^${this.parentSideEffectStep.id}` : ""}?${this.reason.type}${reasonExtra}!${this.rootStep?.id ?? "x"}${deps}>`;
137
161
  }
138
162
  print(depth = 0) {
139
163
  const output = [`${" ".repeat(depth * 2)}${this}`];
@@ -143,7 +167,15 @@ class LayerPlan {
143
167
  return output.join("\n");
144
168
  }
145
169
  setRootStep($root) {
170
+ if (this._hasSetRootStep) {
171
+ throw new Error(`Set root step on ${this} more than once`);
172
+ }
173
+ this._hasSetRootStep = true;
146
174
  this.operationPlan.stepTracker.setLayerPlanRootStep(this, $root);
175
+ // NOTE: we may clear `this.parentSideEffectStep` based on the `$root` step
176
+ // having an explicit dependency on `this.parentSideEffectStep`; but that
177
+ // will be done as part of `OperationPlan::finalizeLayerPlans()` because
178
+ // steps aren't assigned `implicitSideEffectStep`s until that point.
147
179
  }
148
180
  /** @internal Use plan.getStep(id) instead. */
149
181
  getStep(id, requestingStep) {
@@ -176,6 +208,14 @@ class LayerPlan {
176
208
  : [];
177
209
  const iterators = this.reason.type === "mutationField" ? parentBucket.iterators : [];
178
210
  const map = new Map();
211
+ const $parentSideEffect = this.parentSideEffectStep;
212
+ let parentSideEffectValue;
213
+ if ($parentSideEffect) {
214
+ parentSideEffectValue = parentBucket.store.get($parentSideEffect.id);
215
+ }
216
+ else {
217
+ parentSideEffectValue = null;
218
+ }
179
219
  let size = 0;
180
220
  switch (this.reason.type) {
181
221
  case "nullableBoundary": {
@@ -194,15 +234,20 @@ class LayerPlan {
194
234
  size = 0;
195
235
  }
196
236
  else {
197
- size = parentBucket.size;
198
237
  store.set(itemStepId, fieldValue);
199
238
  for (const stepId of copyStepIds) {
200
239
  store.set(stepId, parentBucket.store.get(stepId));
201
240
  }
202
- for (let i = 0; i < size; i++) {
203
- map.set(i, i);
204
- polymorphicPathList[i] = parentBucket.polymorphicPathList[i];
205
- iterators[i] = parentBucket.iterators[i];
241
+ const parentBucketSize = parentBucket.size;
242
+ for (let originalIndex = 0; originalIndex < parentBucketSize; originalIndex++) {
243
+ if (parentSideEffectValue === null ||
244
+ !(parentSideEffectValue._flagsAt(originalIndex) & interfaces_js_1.FLAG_ERROR)) {
245
+ const newIndex = size++;
246
+ map.set(originalIndex, newIndex);
247
+ polymorphicPathList[newIndex] =
248
+ parentBucket.polymorphicPathList[originalIndex];
249
+ iterators[newIndex] = parentBucket.iterators[originalIndex];
250
+ }
206
251
  }
207
252
  }
208
253
  }
@@ -244,10 +289,12 @@ class LayerPlan {
244
289
  // than we have parent bucket entries (because we exclude nulls), so
245
290
  // we must "multiply up" (down) the store entries.
246
291
  for (let originalIndex = 0; originalIndex < parentBucket.size; originalIndex++) {
247
- const fieldValue = nullableStepStore.at(originalIndex);
248
- if (fieldValue != null) {
292
+ if ((parentSideEffectValue === null ||
293
+ !(parentSideEffectValue._flagsAt(originalIndex) & interfaces_js_1.FLAG_ERROR)) &&
294
+ !(nullableStepStore._flagsAt(originalIndex) & interfaces_js_1.FLAG_NULL)) {
249
295
  const newIndex = size++;
250
296
  map.set(originalIndex, newIndex);
297
+ const fieldValue = nullableStepStore.at(originalIndex);
251
298
  itemStepIdList[newIndex] = fieldValue;
252
299
  polymorphicPathList[newIndex] =
253
300
  parentBucket.polymorphicPathList[originalIndex];
@@ -295,7 +342,9 @@ class LayerPlan {
295
342
  // have parent buckets, so we must "multiply up" the store entries.
296
343
  for (let originalIndex = 0; originalIndex < parentBucket.size; originalIndex++) {
297
344
  const list = listStepStore.at(originalIndex);
298
- if (Array.isArray(list)) {
345
+ if ((parentSideEffectValue === null ||
346
+ !(parentSideEffectValue._flagsAt(originalIndex) & interfaces_js_1.FLAG_ERROR)) &&
347
+ Array.isArray(list)) {
299
348
  const newIndexes = [];
300
349
  map.set(originalIndex, newIndexes);
301
350
  for (let j = 0, l = list.length; j < l; j++) {
@@ -356,6 +405,10 @@ class LayerPlan {
356
405
  if (flags & (interfaces_js_1.FLAG_ERROR | interfaces_js_1.FLAG_INHIBITED | interfaces_js_1.FLAG_NULL)) {
357
406
  continue;
358
407
  }
408
+ if (parentSideEffectValue !== null &&
409
+ parentSideEffectValue._flagsAt(originalIndex) & interfaces_js_1.FLAG_ERROR) {
410
+ continue;
411
+ }
359
412
  const value = polymorphicPlanStore.at(originalIndex);
360
413
  const typeName = (0, polymorphic_js_1.resolveType)(value);
361
414
  if (!targetTypeNames.includes(typeName)) {