Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
pmelab committed Dec 26, 2023
1 parent 746b776 commit 5f2a461
Show file tree
Hide file tree
Showing 24 changed files with 264 additions and 177 deletions.
3 changes: 1 addition & 2 deletions apps/decap/src/helpers/frame.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { FrameQuery, Locale, Url } from '@custom/schema';
import { FrameQuery, Locale, registerExecutor, Url } from '@custom/schema';
import { NavigationItemSource } from '@custom/schema/source';
import { registerExecutor } from '@custom/ui';
import { IntlProvider } from '@custom/ui/intl';
import { Frame } from '@custom/ui/routes/Frame';
import { PropsWithChildren } from 'react';
Expand Down
7 changes: 5 additions & 2 deletions apps/decap/src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { PreviewDecapPageQuery, ViewPageQuery } from '@custom/schema';
import { registerExecutor } from '@custom/ui';
import {
PreviewDecapPageQuery,
registerExecutor,
ViewPageQuery,
} from '@custom/schema';
import { Page } from '@custom/ui/routes/Page';
import CMS from 'decap-cms-app';

Expand Down
2 changes: 1 addition & 1 deletion apps/website/gatsby-browser.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import './styles.css';

import { registerExecutor } from '@custom/ui';
import { registerExecutor } from '@custom/schema';
import { GatsbyBrowser } from 'gatsby';

import { drupalExecutor } from './src/utils/drupal-executor';
Expand Down
3 changes: 1 addition & 2 deletions apps/website/gatsby-ssr.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import './styles.css';

import { Locale } from '@custom/schema';
import { registerExecutor } from '@custom/ui';
import { Locale, registerExecutor } from '@custom/schema';
import { GatsbySSR } from 'gatsby';

import { drupalExecutor } from './src/utils/drupal-executor';
Expand Down
3 changes: 1 addition & 2 deletions apps/website/src/layouts/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { SilverbackPageContext } from '@amazeelabs/gatsby-source-silverback';
import { FrameQuery } from '@custom/schema';
import { registerExecutor } from '@custom/ui';
import { FrameQuery, registerExecutor } from '@custom/schema';
import { IntlProvider } from '@custom/ui/intl';
import { Frame } from '@custom/ui/routes/Frame';
import { graphql, useStaticQuery, WrapPageElementNodeArgs } from 'gatsby';
Expand Down
7 changes: 5 additions & 2 deletions apps/website/src/pages/404.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { NotFoundPageQuery, ViewPageQuery } from '@custom/schema';
import { registerExecutor } from '@custom/ui';
import {
NotFoundPageQuery,
registerExecutor,
ViewPageQuery,
} from '@custom/schema';
import { Page } from '@custom/ui/routes/Page';
import { graphql, PageProps } from 'gatsby';
import React from 'react';
Expand Down
7 changes: 5 additions & 2 deletions apps/website/src/preview/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { PreviewDrupalPageQuery, ViewPageQuery } from '@custom/schema';
import { registerExecutor } from '@custom/ui';
import {
PreviewDrupalPageQuery,
registerExecutor,
ViewPageQuery,
} from '@custom/schema';
import { Page } from '@custom/ui/routes/Page';
import React from 'react';

Expand Down
3 changes: 1 addition & 2 deletions apps/website/src/templates/decap-page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { SilverbackPageContext } from '@amazeelabs/gatsby-source-silverback';
import { Locale, ViewPageQuery } from '@custom/schema';
import { registerExecutor } from '@custom/ui';
import { Locale, registerExecutor, ViewPageQuery } from '@custom/schema';
import { Page } from '@custom/ui/routes/Page';
import { graphql, HeadProps, PageProps } from 'gatsby';
import React from 'react';
Expand Down
8 changes: 6 additions & 2 deletions apps/website/src/templates/drupal-page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { SilverbackPageContext } from '@amazeelabs/gatsby-source-silverback';
import { Locale, PageFragment, ViewPageQuery } from '@custom/schema';
import { registerExecutor } from '@custom/ui';
import {
Locale,
PageFragment,
registerExecutor,
ViewPageQuery,
} from '@custom/schema';
import { Page } from '@custom/ui/routes/Page';
import { graphql, HeadProps, PageProps } from 'gatsby';
import React from 'react';
Expand Down
17 changes: 1 addition & 16 deletions apps/website/src/utils/drupal-executor.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,4 @@
// TODO: Duplication of types generated in the schema package.
export type OperationId<
TQueryResult extends any,
TQueryVariables extends any,
> = string & {
___query_result: TQueryResult;
___query_variables: TQueryVariables;
};

export type AnyOperationId = OperationId<any, any>;

export type OperationResult<TQueryID extends OperationId<any, any>> =
TQueryID['___query_result'];

export type OperationVariables<TQueryID extends OperationId<any, any>> =
TQueryID['___query_variables'];
import { AnyOperationId, OperationVariables } from '@custom/schema';

/**
* Create an executor that operates against a Drupal endpoint.
Expand Down
34 changes: 11 additions & 23 deletions packages/executors/src/lib.test.ts
Original file line number Diff line number Diff line change
@@ -1,61 +1,49 @@
import { beforeEach, expect, test, vi } from 'vitest';

import {
AnyOperationId,
clearRegistry,
createExecutor,
OperationId,
registerExecutor,
} from './lib';
import { clearRegistry, createExecutor, registerExecutor } from './lib';

beforeEach(clearRegistry);

test('no operator', () => {
expect(() => createExecutor('unknown' as AnyOperationId)).toThrow();
expect(() => createExecutor('unknown')).toThrow();
});

test('global default operator', () => {
const op = vi.fn();
registerExecutor(op);
expect(() => createExecutor('unknown' as AnyOperationId)()).not.toThrow();
expect(() => createExecutor('unknown')()).not.toThrow();
expect(op).toHaveBeenCalledOnce();
expect(op).toHaveBeenCalledWith('unknown', undefined);
});

test('global default operator with arguments', () => {
const op = vi.fn();
registerExecutor(op);
expect(() =>
createExecutor('unknown' as AnyOperationId, { foo: 'bar' })(),
).not.toThrow();
expect(() => createExecutor('unknown', { foo: 'bar' })()).not.toThrow();
expect(op).toHaveBeenCalledOnce();
expect(op).toHaveBeenCalledWith('unknown', { foo: 'bar' });
});

test('operation default operator', () => {
const a = vi.fn();
registerExecutor('a', a);
expect(() => createExecutor('b' as AnyOperationId)()).toThrow();
expect(() => createExecutor('a' as AnyOperationId)()).not.toThrow();
expect(() => createExecutor('b')()).toThrow();
expect(() => createExecutor('a')()).not.toThrow();
expect(a).toHaveBeenCalledOnce();
expect(a).toHaveBeenCalledWith('a', undefined);
});

test('operation default operator with arguments', () => {
const a = vi.fn();
registerExecutor('a', a);
expect(() =>
createExecutor('b' as AnyOperationId, { foo: 'bar' })(),
).toThrow();
expect(() =>
createExecutor('a' as AnyOperationId, { foo: 'bar' })(),
).not.toThrow();
expect(() => createExecutor('b', { foo: 'bar' })()).toThrow();
expect(() => createExecutor('a', { foo: 'bar' })()).not.toThrow();
expect(a).toHaveBeenCalledOnce();
expect(a).toHaveBeenCalledWith('a', { foo: 'bar' });
});

test('structural argument matching', () => {
const id = 'x' as OperationId<boolean, { y: number; z?: number }>;
const id = 'x';
const a = vi.fn();
const b = vi.fn();
const c = vi.fn();
Expand All @@ -76,13 +64,13 @@ test('structural argument matching', () => {
});

test('static data resolution', () => {
const id = 'x' as OperationId<object, { y: number; z?: number }>;
const id = 'x';
registerExecutor(id, { foo: 'bar' });
expect(createExecutor(id)).toEqual({ foo: 'bar' });
});

test('static data resolution with arguments', () => {
const id = 'x' as OperationId<object, { y: number; z?: number }>;
const id = 'x';
registerExecutor(id, { y: 1 }, { foo: 'bar' });
expect(() => createExecutor(id)).toThrow();
expect(createExecutor(id, { y: 1 })).toEqual({ foo: 'bar' });
Expand Down
76 changes: 21 additions & 55 deletions packages/executors/src/lib.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,17 @@
import { isMatch, isString } from 'lodash-es';

// TODO: Duplication of types generated in the schema package.
export type OperationId<
TQueryResult extends any,
TQueryVariables extends any,
> = string & {
___query_result: TQueryResult;
___query_variables: TQueryVariables;
};

export type AnyOperationId = OperationId<any, any>;

export type OperationResult<TQueryID extends OperationId<any, any>> =
TQueryID['___query_result'];

export type OperationVariables<TQueryID extends OperationId<any, any>> =
TQueryID['___query_variables'];
type Executor =
| any
| ((id: string, variables: Record<string, any>) => any | Promise<any>);

type Executor<OperationId extends AnyOperationId> =
| OperationResult<OperationId>
| ((
id: OperationId,
variables: OperationVariables<OperationId>,
) => OperationResult<OperationId> | Promise<OperationResult<OperationId>>);

type VariablesMatcher<OperationId extends AnyOperationId> =
| Partial<OperationVariables<OperationId>>
| ((vars: OperationVariables<OperationId>) => boolean);
type VariablesMatcher =
| Record<string, any>
| ((vars: Record<string, any>) => boolean);

type RegistryEntry = {
executor: Executor<AnyOperationId>;
id?: AnyOperationId;
variables?: VariablesMatcher<OperationVariables<AnyOperationId>>;
executor: Executor;
id?: string;
variables?: VariablesMatcher;
};

let registry: Array<any> = [];
Expand All @@ -40,19 +20,14 @@ export function clearRegistry() {
registry = [];
}

export function registerExecutor<OperationId extends AnyOperationId>(
executor: Executor<OperationId>,
): void;
export function registerExecutor(executor: Executor): void;

export function registerExecutor<OperationId extends AnyOperationId>(
id: OperationId,
executor: Executor<OperationId>,
): void;
export function registerExecutor(id: string, executor: Executor): void;

export function registerExecutor<OperationId extends AnyOperationId>(
id: OperationId,
variables: VariablesMatcher<OperationVariables<OperationId>>,
executor: Executor<OperationId>,
export function registerExecutor(
id: string,
variables: VariablesMatcher,
executor: Executor,
): void;

export function registerExecutor(...args: unknown[]) {
Expand All @@ -63,10 +38,7 @@ export function registerExecutor(...args: unknown[]) {
});
}

function matchVariables(
matcher: VariablesMatcher<AnyOperationId> | undefined,
variables: any,
) {
function matchVariables(matcher: VariablesMatcher | undefined, variables: any) {
if (typeof matcher === 'undefined') {
return true;
}
Expand All @@ -76,21 +48,18 @@ function matchVariables(
return isMatch(variables, matcher);
}

function getCandidates(id: AnyOperationId) {
function getCandidates(id: string) {
return (registry as Array<RegistryEntry>).filter(
(entry) => id === entry.id || entry.id === undefined,
);
}

function formatEntry<T extends AnyOperationId>(
id: T | undefined,
variables: OperationVariables<T> | undefined,
) {
function formatEntry(id: string | undefined, variables?: Record<string, any>) {
return ` ${id ? id : '*'}:${variables ? JSON.stringify(variables) : '*'}`;
}

class ExecutorRegistryError<OperationId extends AnyOperationId> extends Error {
constructor(id: OperationId, variables: OperationVariables<OperationId>) {
class ExecutorRegistryError extends Error {
constructor(id: string, variables?: Record<string, any>) {
const candidates = getCandidates(id);
const candidatesMessage =
candidates.length > 0
Expand Down Expand Up @@ -120,10 +89,7 @@ class ExecutorRegistryError<OperationId extends AnyOperationId> extends Error {
* @param variables
* A dictionary of variables to be passed to the operation.
*/
export function createExecutor<OperationId extends AnyOperationId>(
id: OperationId,
variables?: OperationVariables<OperationId>,
) {
export function createExecutor(id: string, variables?: Record<string, any>) {
const op = getCandidates(id)
.filter((entry) => matchVariables(entry.variables, variables))
.pop();
Expand Down
3 changes: 2 additions & 1 deletion packages/schema/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
},
"devDependencies": {
"@amazeelabs/codegen-gatsby-fragments": "^1.1.8",
"@amazeelabs/codegen-autoloader": "^1.1.1",
"@amazeelabs/codegen-operation-ids": "^0.1.21",
"@graphql-codegen/cli": "^4.0.1",
"@graphql-codegen/schema-ast": "^4.0.0",
Expand All @@ -49,7 +50,7 @@
"typescript": "^5.1.6"
},
"dependencies": {
"@amazeelabs/codegen-autoloader": "^1.1.1",
"@amazeelabs/executors": "workspace:*",
"@amazeelabs/scalars": "^1.6.2",
"@swc/cli": "^0.1.62",
"@swc/core": "^1.3.80",
Expand Down
50 changes: 50 additions & 0 deletions packages/schema/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,52 @@
import {
createExecutor as untypedCreateExecutor,
registerExecutor as untypedRegisterExecutor,
} from '@amazeelabs/executors';

import type {
AnyOperationId,
OperationResult,
OperationVariables,
} from './generated/index.js';

export { clearRegistry } from '@amazeelabs/executors';

export * from './generated/index.js';
export * from '@amazeelabs/scalars';

type Executor<OperationId extends AnyOperationId> =
| OperationResult<OperationId>
| ((
id: OperationId,
variables: OperationVariables<OperationId>,
) => OperationResult<OperationId> | Promise<OperationResult<OperationId>>);

type VariablesMatcher<OperationId extends AnyOperationId> =
| Partial<OperationVariables<OperationId>>
| ((vars: OperationVariables<OperationId>) => boolean);

export function registerExecutor<OperationId extends AnyOperationId>(
executor: Executor<OperationId>,
): void;

export function registerExecutor<OperationId extends AnyOperationId>(
id: OperationId,
executor: Executor<OperationId>,
): void;

export function registerExecutor<OperationId extends AnyOperationId>(
id: OperationId,
variables: VariablesMatcher<OperationVariables<OperationId>>,
executor: Executor<OperationId>,
): void;

export function registerExecutor(...args: [unknown]) {
return untypedRegisterExecutor(...args);
}

export function createExecutor<OperationId extends AnyOperationId>(
id: OperationId,
variables?: OperationVariables<OperationId>,
) {
return untypedCreateExecutor(id, variables);
}
Loading

0 comments on commit 5f2a461

Please sign in to comment.