diff --git a/examples/40_other/anyware.ts b/examples/40_other/anyware.ts deleted file mode 100644 index 40f94181c..000000000 --- a/examples/40_other/anyware.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Graffle } from '../../src/entrypoints/main.js' -import { publicGraphQLSchemaEndpoints } from '../$/helpers.js' - -Graffle - .create({ schema: publicGraphQLSchemaEndpoints.Atlas }) - .anyware(async ({ encode }) => { - if (encode.input.interface === 'typed') { - // Do something here. - } - - if (encode.input.transport === 'memory') { - // Do something here. - } - - // etc. - - return encode() - }) diff --git a/examples/50_anyware/anyware_jump-start__jump-start.ts b/examples/50_anyware/anyware_jump-start__jump-start.ts new file mode 100644 index 000000000..74672f6a2 --- /dev/null +++ b/examples/50_anyware/anyware_jump-start__jump-start.ts @@ -0,0 +1,26 @@ +/** + * This example shows how you can jump start your anyware into any hook. + * This is more succinct than having to manually write each hook execution + * until your reach your desired one. + */ +import { Graffle } from '../../src/entrypoints/main.js' +import { publicGraphQLSchemaEndpoints } from '../$/helpers.js' + +Graffle + .create({ schema: publicGraphQLSchemaEndpoints.Atlas }) + // Notice how we **start** with the `exchange` hook, skipping the `encode` and `pack` hooks. + .anyware(async ({ exchange }) => { + // ^^^^^^^^ + const mergedHeaders = new Headers(exchange.input.request.headers) + mergedHeaders.set(`X-Custom-Header`, `123`) + + const { unpack } = await exchange({ + input: { + ...exchange.input, + headers: mergedHeaders, + }, + }) + const { decode } = await unpack() + const result = await decode() + return result + }) diff --git a/examples/50_anyware/anyware_short-circuit__short-circuit.ts b/examples/50_anyware/anyware_short-circuit__short-circuit.ts new file mode 100644 index 000000000..c77f72027 --- /dev/null +++ b/examples/50_anyware/anyware_short-circuit__short-circuit.ts @@ -0,0 +1,23 @@ +/** + * This example shows how you can short circuit your anyware at any hook. + * This is more succinct than having to manually write each hook execution + * even past your desired one until the final result. + */ +import { Graffle } from '../../src/entrypoints/main.js' +import { publicGraphQLSchemaEndpoints } from '../$/helpers.js' + +Graffle + .create({ schema: publicGraphQLSchemaEndpoints.Atlas }) + .anyware(async ({ encode }) => { + const { pack } = await encode() + const { exchange } = await pack() + const mergedHeaders = new Headers(exchange.input.request.headers) + mergedHeaders.set(`X-Custom-Header`, `123`) + // Notice how we **end** with the `exchange` hook, skipping the `unpack` and `decode` hooks. + return await exchange({ + input: { + ...exchange.input, + headers: mergedHeaders, + }, + }) + }) diff --git a/examples/50_anyware/anyware_slot_slot-body__slot-body.ts b/examples/50_anyware/anyware_slot_slot-body__slot-body.ts new file mode 100644 index 000000000..5b80841fb --- /dev/null +++ b/examples/50_anyware/anyware_slot_slot-body__slot-body.ts @@ -0,0 +1,34 @@ +/** + * This example shows how to use the `body` slot on the `pack` hook. + */ +import { Graffle } from '../../src/entrypoints/main.js' +import { publicGraphQLSchemaEndpoints, show } from '../$/helpers.js' + +const graffle = Graffle + .create({ schema: publicGraphQLSchemaEndpoints.Atlas }) + .anyware(async ({ pack }) => { + return await pack({ + using: { + body: (graphqlRequest) => { + return JSON.stringify({ + ...graphqlRequest, + operationName: `queryContinents`, + }) + }, + }, + }) + }) + +const result = await graffle.rawString({ + document: ` + query queryContinents { + continents { name } + } + query queryCountries { + countries { name } + } + `, + operationName: `queryCountries`, +}) + +show(result) diff --git a/examples/50_anyware/anyware_slot_slot-body__slot-search-params.ts b/examples/50_anyware/anyware_slot_slot-body__slot-search-params.ts new file mode 100644 index 000000000..a619fb2a9 --- /dev/null +++ b/examples/50_anyware/anyware_slot_slot-body__slot-search-params.ts @@ -0,0 +1,34 @@ +/** + * This example shows how to use the `searchParams` slot on the `pack` hook. + */ +import { Graffle } from '../../src/entrypoints/main.js' +import { publicGraphQLSchemaEndpoints, show } from '../$/helpers.js' + +const graffle = Graffle + .create({ schema: publicGraphQLSchemaEndpoints.Atlas, transport: { methodMode: `getReads` } }) + .anyware(async ({ pack }) => { + return await pack({ + using: { + searchParams: (graphqlRequest) => { + return { + query: graphqlRequest.query, + operationName: `queryContinents`, + } + }, + }, + }) + }) + +const result = await graffle.rawString({ + document: ` + query queryContinents { + continents { name } + } + query queryCountries { + countries { name } + } + `, + operationName: `queryCountries`, +}) + +show(result) diff --git a/examples/50_anyware/anyware_slot_slot-fetch__slot-fetch.ts b/examples/50_anyware/anyware_slot_slot-fetch__slot-fetch.ts new file mode 100644 index 000000000..80a54f2f4 --- /dev/null +++ b/examples/50_anyware/anyware_slot_slot-fetch__slot-fetch.ts @@ -0,0 +1,23 @@ +/** + * This example shows how to use the `fetch` slot on `exchange` hook. + */ +import { Graffle } from '../../src/entrypoints/main.js' +import { publicGraphQLSchemaEndpoints, show } from '../$/helpers.js' + +const graffle = Graffle + .create({ schema: publicGraphQLSchemaEndpoints.Atlas }) + .anyware(async ({ exchange }) => { + return await exchange({ + using: { + fetch: () => { + return new Response(JSON.stringify({ data: { continents: [{ name: `Earthsea` }] } })) + }, + }, + }) + }) + +const result = await graffle.rawString({ + document: `query { continents { name } }`, +}) + +show(result) diff --git a/examples/50_generated/generated_arguments__arguments.ts b/examples/55_generated/generated_arguments__arguments.ts similarity index 100% rename from examples/50_generated/generated_arguments__arguments.ts rename to examples/55_generated/generated_arguments__arguments.ts diff --git a/examples/__outputs__/10_transport-http/transport-http_extension_headers__dynamicHeaders.output.txt b/examples/__outputs__/10_transport-http/transport-http_extension_headers__dynamicHeaders.output.txt index e1a350915..4e127763b 100644 --- a/examples/__outputs__/10_transport-http/transport-http_extension_headers__dynamicHeaders.output.txt +++ b/examples/__outputs__/10_transport-http/transport-http_extension_headers__dynamicHeaders.output.txt @@ -4,7 +4,7 @@ headers: Headers { accept: 'application/graphql-response+json; charset=utf-8, application/json; charset=utf-8', 'content-type': 'application/json', - 'x-sent-at-time': '1726541102597' + 'x-sent-at-time': '1726587622992' }, signal: undefined, method: 'post', diff --git a/examples/__outputs__/20_output/output_envelope.output.txt b/examples/__outputs__/20_output/output_envelope.output.txt index 164805ded..8a336d6a4 100644 --- a/examples/__outputs__/20_output/output_envelope.output.txt +++ b/examples/__outputs__/20_output/output_envelope.output.txt @@ -19,7 +19,7 @@ headers: Headers { connection: 'keep-alive', 'content-length': '119', - 'x-served-by': 'cache-yul1970024-YUL', + 'x-served-by': 'cache-yul1970028-YUL', 'accept-ranges': 'bytes', date: 'Sun, 08 Sep 2024 18:13:26 GMT', 'content-type': 'application/graphql-response+json; charset=utf-8', @@ -32,13 +32,13 @@ 'alt-svc': 'h3=":443"; ma=86400', 'access-control-allow-origin': '*', 'x-powered-by': 'Stellate', - age: '721898', + age: '768418', 'cache-control': 'public, s-maxage=2628000, stale-while-revalidate=2628000', 'x-cache': 'HIT', - 'x-cache-hits': '84', + 'x-cache-hits': '99', 'gcdn-cache': 'HIT', - 'stellate-rate-limit-budget-remaining': '44', - 'stellate-rate-limit-rules': '"IP limit";type="RequestCount";budget=50;limited=?0;remaining=44;refill=59', + 'stellate-rate-limit-budget-remaining': '41', + 'stellate-rate-limit-rules': '"IP limit";type="RequestCount";budget=50;limited=?0;remaining=41;refill=60', 'stellate-rate-limit-decision': 'pass', 'stellate-rate-limit-budget-required': '5', 'content-encoding': 'br' diff --git a/examples/__outputs__/40_other/anyware.output.txt b/examples/__outputs__/50_anyware/anyware_jump-start__jump-start.output.txt similarity index 100% rename from examples/__outputs__/40_other/anyware.output.txt rename to examples/__outputs__/50_anyware/anyware_jump-start__jump-start.output.txt diff --git a/examples/__outputs__/50_anyware/anyware_short-circuit__short-circuit.output.txt b/examples/__outputs__/50_anyware/anyware_short-circuit__short-circuit.output.txt new file mode 100644 index 000000000..e69de29bb diff --git a/examples/__outputs__/50_anyware/anyware_slot_slot-body__slot-body.output.txt b/examples/__outputs__/50_anyware/anyware_slot_slot-body__slot-body.output.txt new file mode 100644 index 000000000..e11f59583 --- /dev/null +++ b/examples/__outputs__/50_anyware/anyware_slot_slot-body__slot-body.output.txt @@ -0,0 +1,12 @@ +---------------------------------------- SHOW ---------------------------------------- +{ + continents: [ + { name: 'Africa' }, + { name: 'Antarctica' }, + { name: 'Asia' }, + { name: 'Europe' }, + { name: 'North America' }, + { name: 'Oceania' }, + { name: 'South America' } + ] +} \ No newline at end of file diff --git a/examples/__outputs__/50_anyware/anyware_slot_slot-body__slot-search-params.output.txt b/examples/__outputs__/50_anyware/anyware_slot_slot-body__slot-search-params.output.txt new file mode 100644 index 000000000..e11f59583 --- /dev/null +++ b/examples/__outputs__/50_anyware/anyware_slot_slot-body__slot-search-params.output.txt @@ -0,0 +1,12 @@ +---------------------------------------- SHOW ---------------------------------------- +{ + continents: [ + { name: 'Africa' }, + { name: 'Antarctica' }, + { name: 'Asia' }, + { name: 'Europe' }, + { name: 'North America' }, + { name: 'Oceania' }, + { name: 'South America' } + ] +} \ No newline at end of file diff --git a/examples/__outputs__/50_anyware/anyware_slot_slot-fetch__slot-fetch.output.txt b/examples/__outputs__/50_anyware/anyware_slot_slot-fetch__slot-fetch.output.txt new file mode 100644 index 000000000..9b6a8e26c --- /dev/null +++ b/examples/__outputs__/50_anyware/anyware_slot_slot-fetch__slot-fetch.output.txt @@ -0,0 +1,2 @@ +---------------------------------------- SHOW ---------------------------------------- +{ continents: [ { name: 'Earthsea' } ] } \ No newline at end of file diff --git a/examples/__outputs__/50_generated/generated_arguments__arguments.output.txt b/examples/__outputs__/55_generated/generated_arguments__arguments.output.txt similarity index 100% rename from examples/__outputs__/50_generated/generated_arguments__arguments.output.txt rename to examples/__outputs__/55_generated/generated_arguments__arguments.output.txt diff --git a/examples/__outputs__/60_extension/extension_opentelemetry__opentelemetry.output.txt b/examples/__outputs__/60_extension/extension_opentelemetry__opentelemetry.output.txt index 5be26dcac..ff2a385e8 100644 --- a/examples/__outputs__/60_extension/extension_opentelemetry__opentelemetry.output.txt +++ b/examples/__outputs__/60_extension/extension_opentelemetry__opentelemetry.output.txt @@ -9,14 +9,14 @@ } }, instrumentationScope: { name: 'graffle', version: undefined, schemaUrl: undefined }, - traceId: '9f4dcc1213a3f3d4605da3d9c3340361', - parentId: 'f0c18d7e669cfd3d', + traceId: '74881568a3d7d3c86f1657709c86b4a0', + parentId: 'f3a059903360f365', traceState: undefined, name: 'encode', - id: 'e06d4e860f48bc58', + id: '5219af43fdf80466', kind: 0, - timestamp: 1726541103051000, - duration: 552.125, + timestamp: 1726587623552000, + duration: 542.375, attributes: {}, status: { code: 0 }, events: [], @@ -33,14 +33,14 @@ } }, instrumentationScope: { name: 'graffle', version: undefined, schemaUrl: undefined }, - traceId: '9f4dcc1213a3f3d4605da3d9c3340361', - parentId: 'f0c18d7e669cfd3d', + traceId: '74881568a3d7d3c86f1657709c86b4a0', + parentId: 'f3a059903360f365', traceState: undefined, name: 'pack', - id: 'a29e1e9baaa80c0e', + id: '063a4b532a0241a3', kind: 0, - timestamp: 1726541103053000, - duration: 1086.75, + timestamp: 1726587623556000, + duration: 1065.958, attributes: {}, status: { code: 0 }, events: [], @@ -57,14 +57,14 @@ } }, instrumentationScope: { name: 'graffle', version: undefined, schemaUrl: undefined }, - traceId: '9f4dcc1213a3f3d4605da3d9c3340361', - parentId: 'f0c18d7e669cfd3d', + traceId: '74881568a3d7d3c86f1657709c86b4a0', + parentId: 'f3a059903360f365', traceState: undefined, name: 'exchange', - id: 'f2a21df585a2fdbb', + id: 'f5b369b304e991b6', kind: 0, - timestamp: 1726541103055000, - duration: 177279.959, + timestamp: 1726587623558000, + duration: 148649.083, attributes: {}, status: { code: 0 }, events: [], @@ -81,14 +81,14 @@ } }, instrumentationScope: { name: 'graffle', version: undefined, schemaUrl: undefined }, - traceId: '9f4dcc1213a3f3d4605da3d9c3340361', - parentId: 'f0c18d7e669cfd3d', + traceId: '74881568a3d7d3c86f1657709c86b4a0', + parentId: 'f3a059903360f365', traceState: undefined, name: 'unpack', - id: '2088b4af9a15807e', + id: '0e527bfe6c4088cd', kind: 0, - timestamp: 1726541103233000, - duration: 2944, + timestamp: 1726587623707000, + duration: 2221.792, attributes: {}, status: { code: 0 }, events: [], @@ -105,14 +105,14 @@ } }, instrumentationScope: { name: 'graffle', version: undefined, schemaUrl: undefined }, - traceId: '9f4dcc1213a3f3d4605da3d9c3340361', - parentId: 'f0c18d7e669cfd3d', + traceId: '74881568a3d7d3c86f1657709c86b4a0', + parentId: 'f3a059903360f365', traceState: undefined, name: 'decode', - id: '11a1c3568453721f', + id: '8abb47d1d55ae667', kind: 0, - timestamp: 1726541103236000, - duration: 127.375, + timestamp: 1726587623709000, + duration: 122.375, attributes: {}, status: { code: 0 }, events: [], @@ -129,14 +129,14 @@ } }, instrumentationScope: { name: 'graffle', version: undefined, schemaUrl: undefined }, - traceId: '9f4dcc1213a3f3d4605da3d9c3340361', + traceId: '74881568a3d7d3c86f1657709c86b4a0', parentId: undefined, traceState: undefined, name: 'request', - id: 'f0c18d7e669cfd3d', + id: 'f3a059903360f365', kind: 0, - timestamp: 1726541103050000, - duration: 186111.542, + timestamp: 1726587623552000, + duration: 157809.333, attributes: {}, status: { code: 0 }, events: [], diff --git a/scripts/generate-examples-derivatives/generate-docs.ts b/scripts/generate-examples-derivatives/generate-docs.ts index 1d33939ca..0fd38c372 100644 --- a/scripts/generate-examples-derivatives/generate-docs.ts +++ b/scripts/generate-examples-derivatives/generate-docs.ts @@ -179,7 +179,25 @@ const transformOther = (example: Example) => { * 2. Add twoslash code block. */ const transformMarkdown = (example: Example) => { - const newContent = ` + const outputBlocks = example.output.blocks.map(block => { + return ` + +\`\`\`${example.isUsingJsonOutput ? `json` : `txt`} +${block} +\`\`\` + +`.trim() + }).join(`\n`) + + const outputs = outputBlocks.length > 0 + ? ` +#### Outputs + +${outputBlocks} +`.trim() + : `` + + const source = ` --- aside: false --- @@ -191,22 +209,10 @@ aside: false ${example.file.content.trim()} \`\`\` - -#### Outputs - -${ - example.output.blocks.map(block => { - return ` - -\`\`\`${example.isUsingJsonOutput ? `json` : `txt`} -${block} -\`\`\` - -`.trim() - }).join(`\n`) - } `.trim() + const newContent = [source, outputs].filter(_ => _ !== ``).join(`\n\n`) + return { ...example, file: { diff --git a/src/layers/5_core/core.ts b/src/layers/5_core/core.ts index beea929a6..3ebab36e7 100644 --- a/src/layers/5_core/core.ts +++ b/src/layers/5_core/core.ts @@ -109,7 +109,7 @@ export type HookDefPack<$Config extends Config> = { export type HookDefExchange<$Config extends Config> = { slots: { - fetch: typeof fetch + fetch: (request: Request) => Response | Promise } input: & InterfaceInput @@ -252,7 +252,11 @@ export const anyware = Anyware.create({ ...baseProperties, method: `post`, url: input.url, - body: slots.body(input), + body: slots.body({ + query: input.query, + variables: input.variables, + operationName: input.operationName, + }), } return { ...input, diff --git a/tests/examples/30_raw/raw.test.ts b/tests/examples/30_raw/raw.test.ts deleted file mode 100644 index 97855f98c..000000000 --- a/tests/examples/30_raw/raw.test.ts +++ /dev/null @@ -1,17 +0,0 @@ -// @vitest-environment node - -// WARNING: -// This test is generated by scripts/generate-example-derivatives/generate.ts -// Do not modify this file directly. - -import { expect, test } from 'vitest' -import { runExample } from '../../../scripts/generate-examples-derivatives/helpers.js' - -test(`raw`, async () => { - const exampleResult = await runExample(`./examples/30_raw/raw.ts`) - // Examples should output their data results. - const exampleResultMaybeEncoded = exampleResult - // If ever outputs vary by Node version, you can use this to snapshot by Node version. - // const nodeMajor = process.version.match(/v(\d+)/)?.[1] ?? `unknown` - await expect(exampleResultMaybeEncoded).toMatchFileSnapshot(`../../../examples/__outputs__/30_raw/raw.output.txt`) -}) diff --git a/tests/examples/30_raw/raw_rawTyped__raw-typed.test.ts b/tests/examples/50_anyware/anyware_jump-start__jump-start.test.ts similarity index 71% rename from tests/examples/30_raw/raw_rawTyped__raw-typed.test.ts rename to tests/examples/50_anyware/anyware_jump-start__jump-start.test.ts index 16727a02f..3bc80dda1 100644 --- a/tests/examples/30_raw/raw_rawTyped__raw-typed.test.ts +++ b/tests/examples/50_anyware/anyware_jump-start__jump-start.test.ts @@ -7,13 +7,13 @@ import { expect, test } from 'vitest' import { runExample } from '../../../scripts/generate-examples-derivatives/helpers.js' -test(`raw_rawTyped__raw-typed`, async () => { - const exampleResult = await runExample(`./examples/30_raw/raw_rawTyped__raw-typed.ts`) +test(`anyware_jump-start__jump-start`, async () => { + const exampleResult = await runExample(`./examples/50_anyware/anyware_jump-start__jump-start.ts`) // Examples should output their data results. const exampleResultMaybeEncoded = exampleResult // If ever outputs vary by Node version, you can use this to snapshot by Node version. // const nodeMajor = process.version.match(/v(\d+)/)?.[1] ?? `unknown` await expect(exampleResultMaybeEncoded).toMatchFileSnapshot( - `../../../examples/__outputs__/30_raw/raw_rawTyped__raw-typed.output.txt`, + `../../../examples/__outputs__/50_anyware/anyware_jump-start__jump-start.output.txt`, ) }) diff --git a/tests/examples/30_raw/raw_rawDocumentNode_rawTyped__raw-typed.test.ts b/tests/examples/50_anyware/anyware_short-circuit__short-circuit.test.ts similarity index 70% rename from tests/examples/30_raw/raw_rawDocumentNode_rawTyped__raw-typed.test.ts rename to tests/examples/50_anyware/anyware_short-circuit__short-circuit.test.ts index 58d7048d7..59ce7eb28 100644 --- a/tests/examples/30_raw/raw_rawDocumentNode_rawTyped__raw-typed.test.ts +++ b/tests/examples/50_anyware/anyware_short-circuit__short-circuit.test.ts @@ -7,13 +7,13 @@ import { expect, test } from 'vitest' import { runExample } from '../../../scripts/generate-examples-derivatives/helpers.js' -test(`raw_rawDocumentNode_rawTyped__raw-typed`, async () => { - const exampleResult = await runExample(`./examples/30_raw/raw_rawDocumentNode_rawTyped__raw-typed.ts`) +test(`anyware_short-circuit__short-circuit`, async () => { + const exampleResult = await runExample(`./examples/50_anyware/anyware_short-circuit__short-circuit.ts`) // Examples should output their data results. const exampleResultMaybeEncoded = exampleResult // If ever outputs vary by Node version, you can use this to snapshot by Node version. // const nodeMajor = process.version.match(/v(\d+)/)?.[1] ?? `unknown` await expect(exampleResultMaybeEncoded).toMatchFileSnapshot( - `../../../examples/__outputs__/30_raw/raw_rawDocumentNode_rawTyped__raw-typed.output.txt`, + `../../../examples/__outputs__/50_anyware/anyware_short-circuit__short-circuit.output.txt`, ) }) diff --git a/tests/examples/40_other/anyware.test.ts b/tests/examples/50_anyware/anyware_slot_slot-body__slot-body.test.ts similarity index 70% rename from tests/examples/40_other/anyware.test.ts rename to tests/examples/50_anyware/anyware_slot_slot-body__slot-body.test.ts index 273b4cc02..c70c91e55 100644 --- a/tests/examples/40_other/anyware.test.ts +++ b/tests/examples/50_anyware/anyware_slot_slot-body__slot-body.test.ts @@ -7,13 +7,13 @@ import { expect, test } from 'vitest' import { runExample } from '../../../scripts/generate-examples-derivatives/helpers.js' -test(`anyware`, async () => { - const exampleResult = await runExample(`./examples/40_other/anyware.ts`) +test(`anyware_slot_slot-body__slot-body`, async () => { + const exampleResult = await runExample(`./examples/50_anyware/anyware_slot_slot-body__slot-body.ts`) // Examples should output their data results. const exampleResultMaybeEncoded = exampleResult // If ever outputs vary by Node version, you can use this to snapshot by Node version. // const nodeMajor = process.version.match(/v(\d+)/)?.[1] ?? `unknown` await expect(exampleResultMaybeEncoded).toMatchFileSnapshot( - `../../../examples/__outputs__/40_other/anyware.output.txt`, + `../../../examples/__outputs__/50_anyware/anyware_slot_slot-body__slot-body.output.txt`, ) }) diff --git a/tests/examples/50_anyware/anyware_slot_slot-body__slot-search-params.test.ts b/tests/examples/50_anyware/anyware_slot_slot-body__slot-search-params.test.ts new file mode 100644 index 000000000..3405c5034 --- /dev/null +++ b/tests/examples/50_anyware/anyware_slot_slot-body__slot-search-params.test.ts @@ -0,0 +1,19 @@ +// @vitest-environment node + +// WARNING: +// This test is generated by scripts/generate-example-derivatives/generate.ts +// Do not modify this file directly. + +import { expect, test } from 'vitest' +import { runExample } from '../../../scripts/generate-examples-derivatives/helpers.js' + +test(`anyware_slot_slot-body__slot-search-params`, async () => { + const exampleResult = await runExample(`./examples/50_anyware/anyware_slot_slot-body__slot-search-params.ts`) + // Examples should output their data results. + const exampleResultMaybeEncoded = exampleResult + // If ever outputs vary by Node version, you can use this to snapshot by Node version. + // const nodeMajor = process.version.match(/v(\d+)/)?.[1] ?? `unknown` + await expect(exampleResultMaybeEncoded).toMatchFileSnapshot( + `../../../examples/__outputs__/50_anyware/anyware_slot_slot-body__slot-search-params.output.txt`, + ) +}) diff --git a/tests/examples/30_raw/raw_rawDocumentNode__raw.test.ts b/tests/examples/50_anyware/anyware_slot_slot-fetch__slot-fetch.test.ts similarity index 70% rename from tests/examples/30_raw/raw_rawDocumentNode__raw.test.ts rename to tests/examples/50_anyware/anyware_slot_slot-fetch__slot-fetch.test.ts index 9dfd4914a..01469b706 100644 --- a/tests/examples/30_raw/raw_rawDocumentNode__raw.test.ts +++ b/tests/examples/50_anyware/anyware_slot_slot-fetch__slot-fetch.test.ts @@ -7,13 +7,13 @@ import { expect, test } from 'vitest' import { runExample } from '../../../scripts/generate-examples-derivatives/helpers.js' -test(`raw_rawDocumentNode__raw`, async () => { - const exampleResult = await runExample(`./examples/30_raw/raw_rawDocumentNode__raw.ts`) +test(`anyware_slot_slot-fetch__slot-fetch`, async () => { + const exampleResult = await runExample(`./examples/50_anyware/anyware_slot_slot-fetch__slot-fetch.ts`) // Examples should output their data results. const exampleResultMaybeEncoded = exampleResult // If ever outputs vary by Node version, you can use this to snapshot by Node version. // const nodeMajor = process.version.match(/v(\d+)/)?.[1] ?? `unknown` await expect(exampleResultMaybeEncoded).toMatchFileSnapshot( - `../../../examples/__outputs__/30_raw/raw_rawDocumentNode__raw.output.txt`, + `../../../examples/__outputs__/50_anyware/anyware_slot_slot-fetch__slot-fetch.output.txt`, ) }) diff --git a/tests/examples/50_generated/generated_arguments__arguments.test.ts b/tests/examples/55_generated/generated_arguments__arguments.test.ts similarity index 85% rename from tests/examples/50_generated/generated_arguments__arguments.test.ts rename to tests/examples/55_generated/generated_arguments__arguments.test.ts index aed8ebd23..9a8e950cb 100644 --- a/tests/examples/50_generated/generated_arguments__arguments.test.ts +++ b/tests/examples/55_generated/generated_arguments__arguments.test.ts @@ -8,12 +8,12 @@ import { expect, test } from 'vitest' import { runExample } from '../../../scripts/generate-examples-derivatives/helpers.js' test(`generated_arguments__arguments`, async () => { - const exampleResult = await runExample(`./examples/50_generated/generated_arguments__arguments.ts`) + const exampleResult = await runExample(`./examples/55_generated/generated_arguments__arguments.ts`) // Examples should output their data results. const exampleResultMaybeEncoded = exampleResult // If ever outputs vary by Node version, you can use this to snapshot by Node version. // const nodeMajor = process.version.match(/v(\d+)/)?.[1] ?? `unknown` await expect(exampleResultMaybeEncoded).toMatchFileSnapshot( - `../../../examples/__outputs__/50_generated/generated_arguments__arguments.output.txt`, + `../../../examples/__outputs__/55_generated/generated_arguments__arguments.output.txt`, ) }) diff --git a/website/.vitepress/config.ts b/website/.vitepress/config.ts index 8de2f8709..966ecadd7 100644 --- a/website/.vitepress/config.ts +++ b/website/.vitepress/config.ts @@ -26,7 +26,8 @@ const fixTitles = (sidebarMulti: SidebarMulti) => { return sidebarMultiVisitItems(sidebarMulti, (sidebarItem) => { const [title, maybeHtml] = sidebarItem.text?.split('<') as [string, string | undefined] if (sidebarItem.text) { - sidebarItem.text = capitalize(title.replaceAll(/-/g, ' ')) + (maybeHtml ? `<${maybeHtml}` : '') + sidebarItem.text = title.replaceAll(/-/g, ' ').split(' ').map(capitalize).join(' ') + + (maybeHtml ? `<${maybeHtml}` : '') } }) } diff --git a/website/content/_snippets/example-links/anyware.md b/website/content/_snippets/example-links/anyware.md index 1a1a43ccf..883f886f7 100644 --- a/website/content/_snippets/example-links/anyware.md +++ b/website/content/_snippets/example-links/anyware.md @@ -1 +1 @@ - + diff --git a/website/content/_snippets/example-links/anyware_jump-start.md b/website/content/_snippets/example-links/anyware_jump-start.md new file mode 100644 index 000000000..0f0bdd3b4 --- /dev/null +++ b/website/content/_snippets/example-links/anyware_jump-start.md @@ -0,0 +1 @@ + diff --git a/website/content/_snippets/example-links/anyware_short-circuit.md b/website/content/_snippets/example-links/anyware_short-circuit.md new file mode 100644 index 000000000..046034261 --- /dev/null +++ b/website/content/_snippets/example-links/anyware_short-circuit.md @@ -0,0 +1 @@ + diff --git a/website/content/_snippets/example-links/anyware_slot-body.md b/website/content/_snippets/example-links/anyware_slot-body.md new file mode 100644 index 000000000..cb03ee957 --- /dev/null +++ b/website/content/_snippets/example-links/anyware_slot-body.md @@ -0,0 +1 @@ + diff --git a/website/content/_snippets/example-links/anyware_slot-fetch.md b/website/content/_snippets/example-links/anyware_slot-fetch.md new file mode 100644 index 000000000..5fd40f41a --- /dev/null +++ b/website/content/_snippets/example-links/anyware_slot-fetch.md @@ -0,0 +1 @@ + diff --git a/website/content/_snippets/example-links/anyware_slot.md b/website/content/_snippets/example-links/anyware_slot.md new file mode 100644 index 000000000..f72eaf14a --- /dev/null +++ b/website/content/_snippets/example-links/anyware_slot.md @@ -0,0 +1 @@ + diff --git a/website/content/_snippets/example-links/anyware_slot_slot-body.md b/website/content/_snippets/example-links/anyware_slot_slot-body.md new file mode 100644 index 000000000..cb03ee957 --- /dev/null +++ b/website/content/_snippets/example-links/anyware_slot_slot-body.md @@ -0,0 +1 @@ + diff --git a/website/content/_snippets/example-links/anyware_slot_slot-fetch.md b/website/content/_snippets/example-links/anyware_slot_slot-fetch.md new file mode 100644 index 000000000..5fd40f41a --- /dev/null +++ b/website/content/_snippets/example-links/anyware_slot_slot-fetch.md @@ -0,0 +1 @@ + diff --git a/website/content/_snippets/example-links/jump-start.md b/website/content/_snippets/example-links/jump-start.md new file mode 100644 index 000000000..0f0bdd3b4 --- /dev/null +++ b/website/content/_snippets/example-links/jump-start.md @@ -0,0 +1 @@ + diff --git a/website/content/_snippets/example-links/short-circuit.md b/website/content/_snippets/example-links/short-circuit.md new file mode 100644 index 000000000..046034261 --- /dev/null +++ b/website/content/_snippets/example-links/short-circuit.md @@ -0,0 +1 @@ + diff --git a/website/content/_snippets/example-links/slot-body.md b/website/content/_snippets/example-links/slot-body.md new file mode 100644 index 000000000..cb03ee957 --- /dev/null +++ b/website/content/_snippets/example-links/slot-body.md @@ -0,0 +1 @@ + diff --git a/website/content/_snippets/example-links/slot-fetch.md b/website/content/_snippets/example-links/slot-fetch.md new file mode 100644 index 000000000..5fd40f41a --- /dev/null +++ b/website/content/_snippets/example-links/slot-fetch.md @@ -0,0 +1 @@ + diff --git a/website/content/_snippets/example-links/slot.md b/website/content/_snippets/example-links/slot.md new file mode 100644 index 000000000..f72eaf14a --- /dev/null +++ b/website/content/_snippets/example-links/slot.md @@ -0,0 +1 @@ + diff --git a/website/content/_snippets/example-links/slot_slot-body.md b/website/content/_snippets/example-links/slot_slot-body.md new file mode 100644 index 000000000..cb03ee957 --- /dev/null +++ b/website/content/_snippets/example-links/slot_slot-body.md @@ -0,0 +1 @@ + diff --git a/website/content/_snippets/example-links/slot_slot-fetch.md b/website/content/_snippets/example-links/slot_slot-fetch.md new file mode 100644 index 000000000..5fd40f41a --- /dev/null +++ b/website/content/_snippets/example-links/slot_slot-fetch.md @@ -0,0 +1 @@ + diff --git a/website/content/examples/10_transport-http/dynamic-headers.md b/website/content/examples/10_transport-http/dynamic-headers.md index fc1ffc9d2..eec09197d 100644 --- a/website/content/examples/10_transport-http/dynamic-headers.md +++ b/website/content/examples/10_transport-http/dynamic-headers.md @@ -43,7 +43,7 @@ await graffle.rawString({ document: `{ languages { code } }` }) headers: Headers { accept: 'application/graphql-response+json; charset=utf-8, application/json; charset=utf-8', 'content-type': 'application/json', - 'x-sent-at-time': '1726541102597' + 'x-sent-at-time': '1726587622992' }, signal: undefined, method: 'post', diff --git a/website/content/examples/20_output/envelope.md b/website/content/examples/20_output/envelope.md index c6632126c..0a452bcc3 100644 --- a/website/content/examples/20_output/envelope.md +++ b/website/content/examples/20_output/envelope.md @@ -46,7 +46,7 @@ console.log(result) headers: Headers { connection: 'keep-alive', 'content-length': '119', - 'x-served-by': 'cache-yul1970024-YUL', + 'x-served-by': 'cache-yul1970028-YUL', 'accept-ranges': 'bytes', date: 'Sun, 08 Sep 2024 18:13:26 GMT', 'content-type': 'application/graphql-response+json; charset=utf-8', @@ -59,13 +59,13 @@ console.log(result) 'alt-svc': 'h3=":443"; ma=86400', 'access-control-allow-origin': '*', 'x-powered-by': 'Stellate', - age: '721898', + age: '768418', 'cache-control': 'public, s-maxage=2628000, stale-while-revalidate=2628000', 'x-cache': 'HIT', - 'x-cache-hits': '84', + 'x-cache-hits': '99', 'gcdn-cache': 'HIT', - 'stellate-rate-limit-budget-remaining': '44', - 'stellate-rate-limit-rules': '"IP limit";type="RequestCount";budget=50;limited=?0;remaining=44;refill=59', + 'stellate-rate-limit-budget-remaining': '41', + 'stellate-rate-limit-rules': '"IP limit";type="RequestCount";budget=50;limited=?0;remaining=41;refill=60', 'stellate-rate-limit-decision': 'pass', 'stellate-rate-limit-budget-required': '5', 'content-encoding': 'br' diff --git a/website/content/examples/40_other/anyware.md b/website/content/examples/40_other/anyware.md deleted file mode 100644 index f0e6b2f92..000000000 --- a/website/content/examples/40_other/anyware.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -aside: false ---- - -# Anyware - - -```ts twoslash -import { Graffle } from 'graffle' - -Graffle - .create({ schema: `https://countries.trevorblades.com/graphql` }) - .anyware(async ({ encode }) => { - if (encode.input.interface === 'typed') { - // Do something here. - } - - if (encode.input.transport === 'memory') { - // Do something here. - } - - // etc. - - return encode() - }) -``` - - -#### Outputs diff --git a/website/content/examples/50_anyware/jump-start.md b/website/content/examples/50_anyware/jump-start.md new file mode 100644 index 000000000..d87959f21 --- /dev/null +++ b/website/content/examples/50_anyware/jump-start.md @@ -0,0 +1,34 @@ +--- +aside: false +--- + +# Jump Start + +This example shows how you can jump start your anyware into any hook. +This is more succinct than having to manually write each hook execution +until your reach your desired one. + + +```ts twoslash +import { Graffle } from 'graffle' + +Graffle + .create({ schema: `https://countries.trevorblades.com/graphql` }) + // Notice how we **start** with the `exchange` hook, skipping the `encode` and `pack` hooks. + .anyware(async ({ exchange }) => { + // ^^^^^^^^ + const mergedHeaders = new Headers(exchange.input.request.headers) + mergedHeaders.set(`X-Custom-Header`, `123`) + + const { unpack } = await exchange({ + input: { + ...exchange.input, + headers: mergedHeaders, + }, + }) + const { decode } = await unpack() + const result = await decode() + return result + }) +``` + diff --git a/website/content/examples/50_anyware/short-circuit.md b/website/content/examples/50_anyware/short-circuit.md new file mode 100644 index 000000000..14509a5f3 --- /dev/null +++ b/website/content/examples/50_anyware/short-circuit.md @@ -0,0 +1,31 @@ +--- +aside: false +--- + +# Short Circuit + +This example shows how you can short circuit your anyware at any hook. +This is more succinct than having to manually write each hook execution +even past your desired one until the final result. + + +```ts twoslash +import { Graffle } from 'graffle' + +Graffle + .create({ schema: `https://countries.trevorblades.com/graphql` }) + .anyware(async ({ encode }) => { + const { pack } = await encode() + const { exchange } = await pack() + const mergedHeaders = new Headers(exchange.input.request.headers) + mergedHeaders.set(`X-Custom-Header`, `123`) + // Notice how we **end** with the `exchange` hook, skipping the `unpack` and `decode` hooks. + return await exchange({ + input: { + ...exchange.input, + headers: mergedHeaders, + }, + }) + }) +``` + diff --git a/website/content/examples/50_anyware/slot-body.md b/website/content/examples/50_anyware/slot-body.md new file mode 100644 index 000000000..3340ed680 --- /dev/null +++ b/website/content/examples/50_anyware/slot-body.md @@ -0,0 +1,60 @@ +--- +aside: false +--- + +# Slot Body + +This example shows how to use the `body` slot on the `pack` hook. + + +```ts twoslash +import { Graffle } from 'graffle' + +const graffle = Graffle + .create({ schema: `https://countries.trevorblades.com/graphql` }) + .anyware(async ({ pack }) => { + return await pack({ + using: { + body: (graphqlRequest) => { + return JSON.stringify({ + ...graphqlRequest, + operationName: `queryContinents`, + }) + }, + }, + }) + }) + +const result = await graffle.rawString({ + document: ` + query queryContinents { + continents { name } + } + query queryCountries { + countries { name } + } + `, + operationName: `queryCountries`, +}) + +console.log(result) +``` + + +#### Outputs + + +```txt +{ + continents: [ + { name: 'Africa' }, + { name: 'Antarctica' }, + { name: 'Asia' }, + { name: 'Europe' }, + { name: 'North America' }, + { name: 'Oceania' }, + { name: 'South America' } + ] +} +``` + diff --git a/website/content/examples/50_anyware/slot-fetch.md b/website/content/examples/50_anyware/slot-fetch.md new file mode 100644 index 000000000..cc5dce0ce --- /dev/null +++ b/website/content/examples/50_anyware/slot-fetch.md @@ -0,0 +1,39 @@ +--- +aside: false +--- + +# Slot Fetch + +This example shows how to use the `fetch` slot on `exchange` hook. + + +```ts twoslash +import { Graffle } from 'graffle' + +const graffle = Graffle + .create({ schema: `https://countries.trevorblades.com/graphql` }) + .anyware(async ({ exchange }) => { + return await exchange({ + using: { + fetch: () => { + return new Response(JSON.stringify({ data: { continents: [{ name: `Earthsea` }] } })) + }, + }, + }) + }) + +const result = await graffle.rawString({ + document: `query { continents { name } }`, +}) + +console.log(result) +``` + + +#### Outputs + + +```txt +{ continents: [ { name: 'Earthsea' } ] } +``` + diff --git a/website/content/examples/50_anyware/slot-search-params.md b/website/content/examples/50_anyware/slot-search-params.md new file mode 100644 index 000000000..c35300d2c --- /dev/null +++ b/website/content/examples/50_anyware/slot-search-params.md @@ -0,0 +1,60 @@ +--- +aside: false +--- + +# Slot Search Params + +This example shows how to use the `searchParams` slot on the `pack` hook. + + +```ts twoslash +import { Graffle } from 'graffle' + +const graffle = Graffle + .create({ schema: `https://countries.trevorblades.com/graphql`, transport: { methodMode: `getReads` } }) + .anyware(async ({ pack }) => { + return await pack({ + using: { + searchParams: (graphqlRequest) => { + return { + query: graphqlRequest.query, + operationName: `queryContinents`, + } + }, + }, + }) + }) + +const result = await graffle.rawString({ + document: ` + query queryContinents { + continents { name } + } + query queryCountries { + countries { name } + } + `, + operationName: `queryCountries`, +}) + +console.log(result) +``` + + +#### Outputs + + +```txt +{ + continents: [ + { name: 'Africa' }, + { name: 'Antarctica' }, + { name: 'Asia' }, + { name: 'Europe' }, + { name: 'North America' }, + { name: 'Oceania' }, + { name: 'South America' } + ] +} +``` + diff --git a/website/content/examples/50_generated/arguments.md b/website/content/examples/55_generated/arguments.md similarity index 100% rename from website/content/examples/50_generated/arguments.md rename to website/content/examples/55_generated/arguments.md diff --git a/website/content/examples/60_extension/opentelemetry.md b/website/content/examples/60_extension/opentelemetry.md index 54cb52a60..dd802b031 100644 --- a/website/content/examples/60_extension/opentelemetry.md +++ b/website/content/examples/60_extension/opentelemetry.md @@ -40,14 +40,14 @@ console.log(data) } }, instrumentationScope: { name: 'graffle', version: undefined, schemaUrl: undefined }, - traceId: '9f4dcc1213a3f3d4605da3d9c3340361', - parentId: 'f0c18d7e669cfd3d', + traceId: '74881568a3d7d3c86f1657709c86b4a0', + parentId: 'f3a059903360f365', traceState: undefined, name: 'encode', - id: 'e06d4e860f48bc58', + id: '5219af43fdf80466', kind: 0, - timestamp: 1726541103051000, - duration: 552.125, + timestamp: 1726587623552000, + duration: 542.375, attributes: {}, status: { code: 0 }, events: [], @@ -67,14 +67,14 @@ console.log(data) } }, instrumentationScope: { name: 'graffle', version: undefined, schemaUrl: undefined }, - traceId: '9f4dcc1213a3f3d4605da3d9c3340361', - parentId: 'f0c18d7e669cfd3d', + traceId: '74881568a3d7d3c86f1657709c86b4a0', + parentId: 'f3a059903360f365', traceState: undefined, name: 'pack', - id: 'a29e1e9baaa80c0e', + id: '063a4b532a0241a3', kind: 0, - timestamp: 1726541103053000, - duration: 1086.75, + timestamp: 1726587623556000, + duration: 1065.958, attributes: {}, status: { code: 0 }, events: [], @@ -94,14 +94,14 @@ console.log(data) } }, instrumentationScope: { name: 'graffle', version: undefined, schemaUrl: undefined }, - traceId: '9f4dcc1213a3f3d4605da3d9c3340361', - parentId: 'f0c18d7e669cfd3d', + traceId: '74881568a3d7d3c86f1657709c86b4a0', + parentId: 'f3a059903360f365', traceState: undefined, name: 'exchange', - id: 'f2a21df585a2fdbb', + id: 'f5b369b304e991b6', kind: 0, - timestamp: 1726541103055000, - duration: 177279.959, + timestamp: 1726587623558000, + duration: 148649.083, attributes: {}, status: { code: 0 }, events: [], @@ -121,14 +121,14 @@ console.log(data) } }, instrumentationScope: { name: 'graffle', version: undefined, schemaUrl: undefined }, - traceId: '9f4dcc1213a3f3d4605da3d9c3340361', - parentId: 'f0c18d7e669cfd3d', + traceId: '74881568a3d7d3c86f1657709c86b4a0', + parentId: 'f3a059903360f365', traceState: undefined, name: 'unpack', - id: '2088b4af9a15807e', + id: '0e527bfe6c4088cd', kind: 0, - timestamp: 1726541103233000, - duration: 2944, + timestamp: 1726587623707000, + duration: 2221.792, attributes: {}, status: { code: 0 }, events: [], @@ -148,14 +148,14 @@ console.log(data) } }, instrumentationScope: { name: 'graffle', version: undefined, schemaUrl: undefined }, - traceId: '9f4dcc1213a3f3d4605da3d9c3340361', - parentId: 'f0c18d7e669cfd3d', + traceId: '74881568a3d7d3c86f1657709c86b4a0', + parentId: 'f3a059903360f365', traceState: undefined, name: 'decode', - id: '11a1c3568453721f', + id: '8abb47d1d55ae667', kind: 0, - timestamp: 1726541103236000, - duration: 127.375, + timestamp: 1726587623709000, + duration: 122.375, attributes: {}, status: { code: 0 }, events: [], @@ -175,14 +175,14 @@ console.log(data) } }, instrumentationScope: { name: 'graffle', version: undefined, schemaUrl: undefined }, - traceId: '9f4dcc1213a3f3d4605da3d9c3340361', + traceId: '74881568a3d7d3c86f1657709c86b4a0', parentId: undefined, traceState: undefined, name: 'request', - id: 'f0c18d7e669cfd3d', + id: 'f3a059903360f365', kind: 0, - timestamp: 1726541103050000, - duration: 186111.542, + timestamp: 1726587623552000, + duration: 157809.333, attributes: {}, status: { code: 0 }, events: [], diff --git a/website/content/guides/20_methods/50_anyware.md b/website/content/guides/20_methods/50_anyware.md index 02eff8977..c0b942b79 100644 --- a/website/content/guides/20_methods/50_anyware.md +++ b/website/content/guides/20_methods/50_anyware.md @@ -4,6 +4,8 @@ outline: [2,5] # Anyware + + ## Introduction Graffle allows you to apply one or more anyware's to the request pipeline. Each anyware runs on every request. @@ -136,6 +138,8 @@ If Graffle is created with a URL for schema then it will automatically type `tra ## Jump-Starting + + If you want to jump straight to a specific hook other than `encode` you can do so by simplify destructing to the desired hook. For example here we write anyware for `exchange`: ```ts twoslash @@ -159,6 +163,8 @@ Note that you will hit a runtime error if you try to destructure more than one h ## Short-Circuiting + + If you want to end your work before `decode` you can do so by returning any hook result. This will cause the reset of the hooks to become passthroughs. For example: ```ts twoslash @@ -206,6 +212,8 @@ Graffle ## Slots + + Hooks can have one or more slots that allow you to override a part of their implementation. When two anywares are used that both customize the same slot then its the later anyware whose customization is used. diff --git a/website/content/guides/index.md b/website/content/guides/index.md index bf0223fa6..1352944ee 100644 --- a/website/content/guides/index.md +++ b/website/content/guides/index.md @@ -1,6 +1,6 @@ # Introduction -Graffle's documentation is thorough, primarily divided into guides and examples. Detailed reference information is light, left mainly to JSDoc and TypeScript types. Thanks to [Twoslash](https://twoslash.netlify.app) that information is made available in most Guide code blocks and every Example code block throughout these docs. +Graffle's website documentation is primarily divided between these guides and [examples](../examples/index.md). Detailed reference information is largely left to JSDoc and TypeScript types. However, thanks to [Twoslash](https://twoslash.netlify.app) that information is also made available within the website docs. Guides are built around domains rather than technical locality so for example many aspects of configuration are embedded into each one's respective area of concern like [HTTP Transport](transports/http.md) or [Output](10_overview/output.md). Throughout, guides reference [Examples](../examples/raw) in context helping you jump between theory and practice. As well, know that all examples are automatically tested in Graffle's continuous integration so you can be confident in their functionality. diff --git a/website/content/index.md b/website/content/index.md index 3bd6fd00d..d5a5b2350 100644 --- a/website/content/index.md +++ b/website/content/index.md @@ -18,6 +18,7 @@ hero: features: - title: Spec Compliant details: Graffle complies with the GraphQL over HTTP and GraphQL Multipart Request specifications. + icon: 'x' - title: Extensible details: Powerful type-safe extension system. Intercept and manipulate inputs, outputs, and core with hooks; Add new methods; And more. - title: Ecosystem