Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/old-clocks-enter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"grafast": minor
"postgraphile": minor
---

Support branded dependency IDs
12 changes: 5 additions & 7 deletions grafast/grafast/examples/complexInputs.mts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ setTimeout(async () => {
// Everything below this line will be output in the docs at
// http://build.graphile.org/graphile-build/next/all-hooks
/******************************************************************************/
import type { ExecutionDetails, ExecutionValue, Maybe } from "grafast";
import type { DepId, ExecutionDetails, Maybe } from "grafast";
import { makeGrafastSchema, Modifier, Step } from "grafast";

interface Filterable {
Expand Down Expand Up @@ -117,8 +117,8 @@ class SearchRequestStep extends Step<{
sql: string;
patchJSON: string;
}> {
private readonly patchDepId: number;
private readonly applyDepIds: number[] = [];
private readonly patchDepId: DepId<Step<BakedUserPatch>>;
private readonly applyDepIds: DepId<Step<FilterCallbacks>>[] = [];

constructor($patch: Step<BakedUserPatch>) {
super();
Expand All @@ -130,17 +130,15 @@ class SearchRequestStep extends Step<{
}

execute(details: ExecutionDetails) {
const patchDep = details.values[
this.patchDepId
] as ExecutionValue<BakedUserPatch>;
const patchDep = details.values.at(this.patchDepId);

const clauses: string[] = [
// Put any initial clauses here
];

// Apply the callbacks
const applyCallbacks = this.applyDepIds
.flatMap((id) => details.values[id].unaryValue() as FilterCallbacks)
.flatMap((id) => details.values.at(id).unaryValue())
.filter((cb) => cb != null);
const filterable: Filterable = {
addClause: (clause) => void clauses.push(clause),
Expand Down
3 changes: 2 additions & 1 deletion grafast/grafast/src/engine/executeBucket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import type {
ExecutionExtra,
ExecutionResults,
ExecutionValue,
ExecutionValues,
ForcedValues,
GrafastInternalResultsOrStream,
IndexForEach,
Expand Down Expand Up @@ -830,7 +831,7 @@ export function executeBucket(
indexMap: makeIndexMap(count),
indexForEach: makeIndexForEach(count),
count,
values,
values: values as ExecutionValues,
extra,
stream: evaluateStream(bucket, step, distributorOptions),
};
Expand Down
8 changes: 7 additions & 1 deletion grafast/grafast/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,12 @@ import { getGrafastMiddleware } from "./middleware.ts";
import type { Multistep, UnwrapMultistep } from "./multistep.ts";
import { multistep } from "./multistep.ts";
import { getNullableInputTypeAtPath } from "./operationPlan-input.ts";
import type { ListCapableStep, ListLikeStep, ObjectLikeStep } from "./step.ts";
import type {
DepId,
ListCapableStep,
ListLikeStep,
ObjectLikeStep,
} from "./step.ts";
import {
assertExecutableStep,
assertListCapableStep,
Expand Down Expand Up @@ -313,6 +318,7 @@ export type {
ConnectionOptimizedStep,
DataFromObjectSteps,
DataFromStep,
DepId,
DeprecatedInputObjectPlan,
DeprecatedObjectPlan,
EnumPlan,
Expand Down
27 changes: 20 additions & 7 deletions grafast/grafast/src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import type { LayerPlanReasonListItemStream } from "./engine/LayerPlan.ts";
import type { OperationPlan } from "./engine/OperationPlan.ts";
import type { FlaggedValue, SafeError } from "./error.ts";
import type { GrafastOperationOptions } from "./prepare.ts";
import type { Step } from "./step.ts";
import type { DepId, Step, StepData } from "./step.ts";
import type { __InputDefaultStep } from "./steps/__inputDefault.ts";
import type { __InputDynamicScalarStep } from "./steps/__inputDynamicScalar.ts";
import type { ApplyableStep } from "./steps/applyInput.ts";
Expand Down Expand Up @@ -720,19 +720,19 @@ export interface ExecutionDetailsStream {
initialCount: number;
}

type TupleIndicies<T extends readonly [...any[]], K = keyof T> = K extends K
? K extends `${infer N extends number}`
? N
: never
: never;
export interface ExecutionDetails<
TDeps extends readonly [...any[]] = readonly [...any[]],
> {
/** The size of the batch being processed */
count: number;

/** An "execution value" for each dependency of the step */
values: {
[DepIdx in keyof TDeps]: ExecutionValue<TDeps[DepIdx]>;
} & {
length: TDeps["length"];
map: ReadonlyArray<ExecutionValue<TDeps[number]>>["map"];
};
values: ExecutionValues<TDeps>;

/** Helper; makes array from `callback(batchIndex)` for each `0 <= batchIndex < count` */
indexMap: IndexMap;
Expand All @@ -751,6 +751,19 @@ export interface ExecutionDetails<
/** Currently experimental, use it at your own risk (and see the source for docs) */
extra: ExecutionExtra;
}
export type ExecutionValues<
TDeps extends readonly [...any[]] = readonly [...any[]],
> = {
[DepIdx in TupleIndicies<TDeps>]: ExecutionValue<TDeps[DepIdx]>;
} & Omit<ExecutionValue<TDeps[number]>[], "at"> & {
at<TDepId extends DepId<Step>>(
id: TDepId,
): ExecutionValue<
NonNullable<TDepId["__step"]> extends Step
? StepData<NonNullable<TDepId["__step"]>>
: TDeps[TDepId]
>;
};

export interface LocationDetails {
node: ASTNode | readonly ASTNode[];
Expand Down
46 changes: 26 additions & 20 deletions grafast/grafast/src/step.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ function reallyAssertFinalized(plan: Step): void {
// Optimise this away in production.
export const assertFinalized = !isDev ? noop : reallyAssertFinalized;

export type DepId<TStep extends Step> = number & { __step?: TStep };
export type StepData<TStep extends Step> =
TStep extends Step<infer Data> ? Data : never;

/**
* Executable plans are the plans associated with leaves on the GraphQL tree,
* they must be able to execute to return values.
Expand Down Expand Up @@ -448,7 +452,7 @@ export /* abstract */ class Step<TData = any> {
}

protected getDepOptions<TStep extends Step = Step>(
depId: number,
depId: DepId<TStep>,
): DependencyOptions<TStep> {
this._assertAccessAllowed(depId);
return this._getDepOptions(depId);
Expand Down Expand Up @@ -480,14 +484,14 @@ export /* abstract */ class Step<TData = any> {
}

protected getDep<TStep extends Step = Step>(
_depId: number,
_depId: DepId<TStep>,
): TStep | __FlagStep<TStep>;
protected getDep<TStep extends Step = Step>(
_depId: number,
_depId: DepId<TStep>,
throwOnFlagged: true,
): TStep;
protected getDep<TStep extends Step = Step>(
_depId: number,
_depId: DepId<TStep>,
_throwOnFlagged = false,
): TStep | __FlagStep<TStep> {
// This gets replaced when `__FlagStep` is loaded. Were we on ESM we could
Expand All @@ -497,14 +501,14 @@ export /* abstract */ class Step<TData = any> {
}

protected maybeGetDep<TStep extends Step = Step>(
depId: number | null | undefined,
depId: DepId<TStep> | null | undefined,
): TStep | __FlagStep<TStep> | null;
protected maybeGetDep<TStep extends Step = Step>(
depId: number | null | undefined,
depId: DepId<TStep> | null | undefined,
throwOnFlagged: true,
): TStep | null;
protected maybeGetDep<TStep extends Step = Step>(
depId: number | null | undefined,
depId: DepId<TStep> | null | undefined,
throwOnFlagged = false,
): TStep | __FlagStep<TStep> | null {
return depId == null
Expand All @@ -515,7 +519,7 @@ export /* abstract */ class Step<TData = any> {
}

protected getDepOrConstant<TData = any>(
_depId: number | null,
_depId: DepId<Step<TData>> | null,
_fallback: TData,
): Step<TData> {
// This gets replaced when `constant` is loaded. Were we on ESM we could
Expand Down Expand Up @@ -686,8 +690,10 @@ ${printDeps(step, 1)}
}
return this.operationPlan.stepTracker.addStepDependency(this, options);
}
protected addDependency(stepOrOptions: Step | AddDependencyOptions): number {
const options: AddDependencyOptions = {
protected addDependency<TStep extends Step>(
stepOrOptions: TStep | AddDependencyOptions<TStep>,
): DepId<TStep> {
const options: AddDependencyOptions<TStep> = {
dataOnly: false,
skipDeduplication: false,
...(stepOrOptions instanceof Step
Expand All @@ -697,9 +703,9 @@ ${printDeps(step, 1)}
assertStep(options.step, () => `${this}.addDependency`);
return this._addDependency(options);
}
protected addDataDependency(
stepOrOptions: Step | AddDependencyOptions,
): number {
protected addDataDependency<TStep extends Step>(
stepOrOptions: TStep | AddDependencyOptions<TStep>,
): DepId<TStep> {
const options =
stepOrOptions instanceof Step ? { step: stepOrOptions } : stepOrOptions;
assertStep(options.step, () => `${this}.addDataDependency`);
Expand All @@ -710,9 +716,9 @@ ${printDeps(step, 1)}
});
}
// Currently identical to addDependency
protected addStrongDependency(
stepOrOptions: Step | AddDependencyOptions,
): number {
protected addStrongDependency<TStep extends Step>(
stepOrOptions: TStep | AddDependencyOptions<TStep>,
): DepId<TStep> {
const options = {
dataOnly: false,
skipDeduplication: false,
Expand All @@ -730,10 +736,10 @@ ${printDeps(step, 1)}
* `isBatch = false` so you can use the `values[index].value` property
* directly.
*/
protected addUnaryDependency(
stepOrOptions: Step | AddUnaryDependencyOptions,
): number {
const options: AddUnaryDependencyOptions =
protected addUnaryDependency<TStep extends Step>(
stepOrOptions: TStep | AddUnaryDependencyOptions<TStep>,
): DepId<TStep> {
const options: AddUnaryDependencyOptions<TStep> =
stepOrOptions instanceof Step ? { step: stepOrOptions } : stepOrOptions;
assertStep(options.step, () => `${this}.addUnaryDependency`);
if (options.step.layerPlan.id > this.layerPlan.id) {
Expand Down
8 changes: 3 additions & 5 deletions grafast/grafast/src/steps/__flag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export class __FlagStep<TStep extends Step> extends Step<DataFromStep<TStep>> {
};

isSyncAndSafe = false;
private ifDep: number | null = null;
private ifDep;
private forbiddenFlags: ExecutionEntryFlags;
private onRejectReturnValue: FlaggedValue<Error> | FlaggedValue<null>;
private valueForInhibited: ResolvedTrapValue;
Expand Down Expand Up @@ -146,8 +146,7 @@ export class __FlagStep<TStep extends Step> extends Step<DataFromStep<TStep>> {
const rej = this.onRejectReturnValue
? trim(String(this.onRejectReturnValue))
: inspect(this.onRejectReturnValue);
const $if =
this.ifDep !== null ? this.getDepOptions(this.ifDep).step : null;
const $if = this.ifDep != null ? this.getDepOptions(this.ifDep).step : null;
return `${this.dependencies[0].id}, ${
$if ? `if(${$if.id}), ` : ``
}${digestAcceptFlags(acceptFlags)}, onReject: ${rej}`;
Expand Down Expand Up @@ -229,8 +228,7 @@ export class __FlagStep<TStep extends Step> extends Step<DataFromStep<TStep>> {
details: ExecutionDetails<[data: DataFromStep<TStep>, cond?: boolean]>,
): any {
const dataEv = details.values[0]!;
const condEv =
this.ifDep === null ? null : details.values[this.ifDep as 1]!;
const condEv = this.ifDep == null ? null : details.values.at(this.ifDep);
const {
forbiddenFlags: thisForbiddenFlags,
onRejectReturnValue,
Expand Down
2 changes: 1 addition & 1 deletion grafast/grafast/src/steps/listTransform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ export class __ListTransformStep<
);
}

const listStepValue = values[this.listStepDepId];
const listStepValue = values[this.listStepDepId as 0];

if (itemStep._isUnary) {
const list = listStepValue.unaryValue();
Expand Down
4 changes: 2 additions & 2 deletions grafast/grafast/src/steps/listen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,15 +68,15 @@ export class ListenStep<
values,
stream,
}: ExecutionDetails<
readonly [GrafastSubscriber<TTopics>, TTopic]
readonly [GrafastSubscriber<TTopics>, TTopic, TTopics[TTopic] | undefined]
>): GrafastResultStreamList<TTopics[TTopic]> {
if (!stream) {
throw new Error("ListenStep must be streamed, never merely executed");
}
const pubsubValue = values[this.pubsubDep as 0];
const topicValue = values[this.topicDep as 1];
const initialEventValue =
this.initialEventDep !== null ? values[this.initialEventDep] : null;
this.initialEventDep !== null ? values[this.initialEventDep as 2] : null;
return indexMap((i) => {
const pubsub = pubsubValue.at(i);
if (!pubsub) {
Expand Down
8 changes: 6 additions & 2 deletions grafast/grafast/src/steps/proxy.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { $$proxy } from "../constants.ts";
import type {
AddDependencyOptions,
ExecutionDetails,
GrafastResultsList,
UnbatchedExecutionExtra,
} from "../interfaces.ts";
import type { Step } from "../step.ts";
import type { DepId, Step } from "../step.ts";
import { UnbatchedStep } from "../step.ts";
import { arrayOfLength } from "../utils.ts";

Expand All @@ -30,7 +31,9 @@ export class ProxyStep<T> extends UnbatchedStep<T> {
return $dep.toString();
}
// Publicly expose this
public addDependency(step: Step): number {
public addDependency<TStep extends Step>(
step: TStep | AddDependencyOptions<TStep>,
): DepId<TStep> {
return super.addDependency(step);
}
execute({
Expand Down Expand Up @@ -121,6 +124,7 @@ export function proxy<TData, TStep extends Step<TData>>(
$actualDep: Step = $step,
): TStep & { addDependency(step: Step): number } {
const $proxy = new ProxyStep($step, $actualDep);
// @ts-ignore
const proxy = new Proxy($proxy, makeProxyHandler($step)) as any; // Lie.
$proxy[$$proxy] = proxy;
return proxy;
Expand Down
10 changes: 5 additions & 5 deletions grafast/grafast/src/steps/reverse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,15 @@ export class ReverseStep<TData> extends UnbatchedStep<readonly TData[]> {
isSyncAndSafe = true;
allowMultipleOptimizations = true;

private depId;

constructor(plan: Step<readonly TData[]>) {
super();
this.addDependency(plan);
this.depId = this.addDependency(plan);
}

execute({
indexMap,
values: [values0],
}: ExecutionDetails<[TData[]]>): GrafastResultsList<TData[]> {
execute({ indexMap, values }: ExecutionDetails): GrafastResultsList<TData[]> {
const values0 = values.at(this.depId);
return indexMap((i) => {
const arr = values0.at(i);
return arr == null ? arr : reverseArray(arr);
Expand Down
Loading
Loading