diff --git a/eslint.config.js b/eslint.config.js index 18e2b0dc..66f676e6 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -3,7 +3,7 @@ import tsEslint from 'typescript-eslint' export default tsEslint.config({ ignores: [ - 'examples/55_document-buildergenerated_document__document.ts', + 'examples/35_custom-scalar/custom-scalar.ts', 'eslint.config.js', 'vite.config.ts', 'vitest*.config.ts', diff --git a/examples/35_custom-scalar/custom-scalar.ts b/examples/35_custom-scalar/custom-scalar.ts new file mode 100644 index 00000000..d548eb2e --- /dev/null +++ b/examples/35_custom-scalar/custom-scalar.ts @@ -0,0 +1,26 @@ +/** + * This example shows how to add a client-side custom scalar codec to + * have arguments and data automatically encoded and decoded respectively. + */ + +import { Graffle } from '../../src/entrypoints/__Graffle.js' +import { Pokemon } from '../../tests/_/schemas/pokemon/graffle/__.js' +import { show } from '../$/helpers.js' + +const graffle = Pokemon + .create() + .scalar(Graffle.Scalars.create(`Date`, { + encode: (value: globalThis.Date) => value.toISOString(), + decode: (value: string) => { + return new globalThis.Date(value) + }, + })) + +const pokemons = await graffle.query.pokemons({ + $: { filter: { birthday: { lte: new Date(`1987-01-13`) } } }, + name: true, + birthday: true, +}) + +show(`pokemons[0].birthday instanceof Date = ${String(pokemons?.[0]?.birthday instanceof Date)}`) +show(pokemons) diff --git a/examples/55_document-builder/document-builder_alias.ts b/examples/55_document-builder/document-builder_alias.ts index 6d00ced2..4a5f5093 100644 --- a/examples/55_document-builder/document-builder_alias.ts +++ b/examples/55_document-builder/document-builder_alias.ts @@ -9,8 +9,8 @@ const pokemon = Pokemon.create() const day = 1000 * 60 * 60 * 24 const year = day * 365.25 -const yearsAgo100 = new Date(Date.now() - year * 100).getTime() -const yearsAgo1 = new Date(Date.now() - year).getTime() +const yearsAgo100 = new Date(Date.now() - year * 100).toISOString() +const yearsAgo1 = new Date(Date.now() - year).toISOString() // dprint-ignore const pokemons = await pokemon.query.$batch({ 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 d337d6ec..a49b793e 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': '1729649902337' + 'x-sent-at-time': '1729654429348' }, 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 daa36c0b..ba12722b 100644 --- a/examples/__outputs__/20_output/output_envelope.output.txt +++ b/examples/__outputs__/20_output/output_envelope.output.txt @@ -18,7 +18,7 @@ headers: Headers { 'content-type': 'application/graphql-response+json; charset=utf-8', 'content-length': '142', - date: 'Wed, 23 Oct 2024 02:18:22 GMT', + date: 'Wed, 23 Oct 2024 03:33:49 GMT', connection: 'keep-alive', 'keep-alive': 'timeout=5' }, diff --git a/examples/__outputs__/35_custom-scalar/custom-scalar.output.txt b/examples/__outputs__/35_custom-scalar/custom-scalar.output.txt new file mode 100644 index 00000000..76b3e9c9 --- /dev/null +++ b/examples/__outputs__/35_custom-scalar/custom-scalar.output.txt @@ -0,0 +1,7 @@ +---------------------------------------- SHOW ---------------------------------------- +pokemons[0].birthday instanceof Date = true +---------------------------------------- SHOW ---------------------------------------- +[ + { name: 'Pikachu', birthday: 1850-01-01T00:00:00.000Z }, + { name: 'Squirtle', birthday: 1910-01-01T00:00:00.000Z } +] \ No newline at end of file diff --git a/examples/__outputs__/60_extension/extension_introspection__introspection.output.txt b/examples/__outputs__/60_extension/extension_introspection__introspection.output.txt index a42d7056..020a32e5 100644 --- a/examples/__outputs__/60_extension/extension_introspection__introspection.output.txt +++ b/examples/__outputs__/60_extension/extension_introspection__introspection.output.txt @@ -332,6 +332,16 @@ enumValues: null, possibleTypes: null }, + { + kind: 'SCALAR', + name: 'Date', + description: 'A date-time string at UTC, such as 2007-12-03T10:15:30Z, compliant with the `date-time` format outlined in section 5.6 of the RFC 3339 profile of the ISO 8601 standard for representation of dates and times using the Gregorian calendar.This scalar is serialized to a string in ISO 8601 format and parsed from a string in ISO 8601 format.', + fields: null, + inputFields: null, + interfaces: null, + enumValues: null, + possibleTypes: null + }, { kind: 'INPUT_OBJECT', name: 'DateFilter', @@ -341,13 +351,13 @@ { name: 'gte', description: null, - type: { kind: 'SCALAR', name: 'Float', ofType: null }, + type: { kind: 'SCALAR', name: 'Date', ofType: null }, defaultValue: null }, { name: 'lte', description: null, - type: { kind: 'SCALAR', name: 'Float', ofType: null }, + type: { kind: 'SCALAR', name: 'Date', ofType: null }, defaultValue: null } ], @@ -495,7 +505,7 @@ name: 'birthday', description: null, args: [], - type: { kind: 'SCALAR', name: 'Int', ofType: null }, + type: { kind: 'SCALAR', name: 'Date', ofType: null }, isDeprecated: false, deprecationReason: null }, diff --git a/examples/__outputs__/60_extension/extension_opentelemetry__opentelemetry.output.txt b/examples/__outputs__/60_extension/extension_opentelemetry__opentelemetry.output.txt index 67a6d7e5..2b7a34ba 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: '2259f0e0e3b890cac862a813639e9c90', - parentId: 'f68c78e8a4e9ae99', + traceId: '4db92619c2c83794eea58ac3b0f81739', + parentId: '0441c81c457852b2', traceState: undefined, name: 'encode', - id: 'c8b432c64c6d23ce', + id: '0fed2ea32096273f', kind: 0, - timestamp: 1729649903315000, - duration: 1492.792, + timestamp: 1729654429675000, + duration: 2681.792, attributes: {}, status: { code: 0 }, events: [], @@ -33,14 +33,14 @@ } }, instrumentationScope: { name: 'graffle', version: undefined, schemaUrl: undefined }, - traceId: '2259f0e0e3b890cac862a813639e9c90', - parentId: 'f68c78e8a4e9ae99', + traceId: '4db92619c2c83794eea58ac3b0f81739', + parentId: '0441c81c457852b2', traceState: undefined, name: 'pack', - id: '385c61790e55b6f4', + id: 'ca490dca8e6606c9', kind: 0, - timestamp: 1729649903318000, - duration: 12042.5, + timestamp: 1729654429680000, + duration: 48326.792, attributes: {}, status: { code: 0 }, events: [], @@ -57,14 +57,14 @@ } }, instrumentationScope: { name: 'graffle', version: undefined, schemaUrl: undefined }, - traceId: '2259f0e0e3b890cac862a813639e9c90', - parentId: 'f68c78e8a4e9ae99', + traceId: '4db92619c2c83794eea58ac3b0f81739', + parentId: '0441c81c457852b2', traceState: undefined, name: 'exchange', - id: '67a764a3a78c819c', + id: '11066eaa0e07c122', kind: 0, - timestamp: 1729649903331000, - duration: 21272.625, + timestamp: 1729654429729000, + duration: 47351.292, attributes: {}, status: { code: 0 }, events: [], @@ -81,14 +81,14 @@ } }, instrumentationScope: { name: 'graffle', version: undefined, schemaUrl: undefined }, - traceId: '2259f0e0e3b890cac862a813639e9c90', - parentId: 'f68c78e8a4e9ae99', + traceId: '4db92619c2c83794eea58ac3b0f81739', + parentId: '0441c81c457852b2', traceState: undefined, name: 'unpack', - id: 'a92c1ad25a186aa9', + id: 'c527bbb4b62958fa', kind: 0, - timestamp: 1729649903352000, - duration: 1640.25, + timestamp: 1729654429777000, + duration: 1775.75, attributes: {}, status: { code: 0 }, events: [], @@ -105,14 +105,14 @@ } }, instrumentationScope: { name: 'graffle', version: undefined, schemaUrl: undefined }, - traceId: '2259f0e0e3b890cac862a813639e9c90', - parentId: 'f68c78e8a4e9ae99', + traceId: '4db92619c2c83794eea58ac3b0f81739', + parentId: '0441c81c457852b2', traceState: undefined, name: 'decode', - id: 'fbdf501193493665', + id: '20eb54a71997e91e', kind: 0, - timestamp: 1729649903354000, - duration: 314.583, + timestamp: 1729654429779000, + duration: 669.25, attributes: {}, status: { code: 0 }, events: [], @@ -129,14 +129,14 @@ } }, instrumentationScope: { name: 'graffle', version: undefined, schemaUrl: undefined }, - traceId: '2259f0e0e3b890cac862a813639e9c90', + traceId: '4db92619c2c83794eea58ac3b0f81739', parentId: undefined, traceState: undefined, name: 'request', - id: 'f68c78e8a4e9ae99', + id: '0441c81c457852b2', kind: 0, - timestamp: 1729649903314000, - duration: 40837.167, + timestamp: 1729654429631000, + duration: 148602.75, attributes: {}, status: { code: 0 }, events: [], diff --git a/src/extensions/CustomScalars/decode.ts b/src/extensions/CustomScalars/decode.ts index 3128f759..f2e1aea4 100644 --- a/src/extensions/CustomScalars/decode.ts +++ b/src/extensions/CustomScalars/decode.ts @@ -27,49 +27,88 @@ export const decodeResultData = ({ request, data, sddm, scalars }: { }) => { const sddmOutputObject = sddm.roots[request.rootType] if (!sddmOutputObject) return + if (!data) return + + for (const [key, value] of Object.entries(data)) { + const documentField = findDocumentField(request.operation.selectionSet, key) + const kSchema = documentField?.name.value ?? key + const sddmOutputField = sddmOutputObject.f[kSchema] + if (!sddmOutputField?.nt) continue - decodeResultData_({ - data, - sddmOutputObject, - documentPart: request.operation.selectionSet, - scalars, - }) + decodeResultValue({ + parentContext: { type: `object`, object: data, key }, + value, + sddmNode: sddmOutputField.nt, + documentPart: documentField?.selectionSet ?? null, + scalars, + }) + } } -const decodeResultData_ = (input: { - data: Grafaid.SomeObjectData | null | undefined - sddmOutputObject: SchemaDrivenDataMap.OutputObject +const decodeResultValue = (input: { + parentContext: { type: `object`; object: Record; key: string } | { + type: `list` + object: any[] + key: number + } + value: Value + sddmNode: SchemaDrivenDataMap.OutputNodes documentPart: null | Grafaid.Document.SelectionSetNode scalars: RegisteredScalars }): void => { - const { data, sddmOutputObject, documentPart, scalars } = input - if (!data) return + const { parentContext, value, sddmNode, documentPart, scalars } = input - for (const [k, v] of Object.entries(data)) { + if (value === null) { // todo: test case of a custom scalar whose encoded value would be falsy in JS, like 0 or empty string - if (v === null) continue - - const documentField = findDocumentField(documentPart, k) - - const kSchema = documentField?.name.value ?? k - - const sddmOutputField = sddmOutputObject.f[kSchema] - if (!sddmOutputField) continue - - const sddmNode = sddmOutputField.nt - - if (SchemaDrivenDataMap.isScalar(sddmNode)) { - data[k] = Schema.Scalar.applyCodec(sddmNode.codec.decode, v) - } else if (SchemaDrivenDataMap.isCustomScalarName(sddmNode)) { - const scalar = Schema.Scalar.lookupCustomScalarOrFallbackToString(scalars, sddmNode) - data[k] = Schema.Scalar.applyCodec(scalar.codec.decode, v) - } else if (SchemaDrivenDataMap.isOutputObject(sddmNode)) { - decodeResultData_({ - data: v, - sddmOutputObject: sddmNode, + return // do nothing + } else if (Array.isArray(value)) { + // todo test case of array data of objects + // todo test case of array data of scalars + value.forEach((item, index) => { + decodeResultValue({ + parentContext: { type: `list`, object: value, key: index }, + value: item, + sddmNode, + documentPart, + scalars, + }) + }) + } else if (typeof value === `object`) { + if (!SchemaDrivenDataMap.isOutputObject(sddmNode)) { + return + // something went wrong + // todo in strict mode throw error that sddmNode is inconsistent with data shape. + } + const object = value + for (const [key, value] of Object.entries(object)) { + const documentField = findDocumentField(documentPart, key) + const kSchema = documentField?.name.value ?? key + const sddmOutputField = sddmNode.f[kSchema] + if (!sddmOutputField?.nt) continue + decodeResultValue({ + parentContext: { type: `object`, object, key }, + value, + sddmNode: sddmOutputField.nt, documentPart: documentField?.selectionSet ?? null, scalars, }) + } + } else { + if (SchemaDrivenDataMap.isScalar(sddmNode)) { + const decodedValue = Schema.Scalar.applyCodec(sddmNode.codec.decode, value) + if (parentContext.type === `object`) { + parentContext.object[parentContext.key] = decodedValue + } else { + parentContext.object[parentContext.key] = decodedValue + } + } else if (SchemaDrivenDataMap.isCustomScalarName(sddmNode)) { + const scalar = Schema.Scalar.lookupCustomScalarOrFallbackToString(scalars, sddmNode) + const decodedValue = Schema.Scalar.applyCodec(scalar.codec.decode, value) + if (parentContext.type === `object`) { + parentContext.object[parentContext.key] = decodedValue + } else { + parentContext.object[parentContext.key] = decodedValue + } } else { // enums not decoded. } @@ -94,3 +133,7 @@ const findDocumentField = ( return null } + +type Value = Grafaid.Schema.StandardScalarRuntimeTypes[] | Grafaid.Schema.StandardScalarRuntimeTypes | null | { + [k: string]: Value +} diff --git a/src/extensions/CustomScalars/encode.ts b/src/extensions/CustomScalars/encode.ts index 4c8a9403..aefa84b4 100644 --- a/src/extensions/CustomScalars/encode.ts +++ b/src/extensions/CustomScalars/encode.ts @@ -35,7 +35,7 @@ const encodeInputFieldLike = ( args: Record, argName: any, argValue: any, - sddmNode: SchemaDrivenDataMap.InputLike, + sddmNode: SchemaDrivenDataMap.InputNodes, scalars: RegisteredScalars, ) => { /** diff --git a/src/generator/config/__snapshots__/config.test.ts.snap b/src/generator/config/__snapshots__/config.test.ts.snap index e53aec8e..2133fbfb 100644 --- a/src/generator/config/__snapshots__/config.test.ts.snap +++ b/src/generator/config/__snapshots__/config.test.ts.snap @@ -48,9 +48,14 @@ type CombatantSinglePokemon { trainer: Trainer } +""" +A date-time string at UTC, such as 2007-12-03T10:15:30Z, compliant with the \`date-time\` format outlined in section 5.6 of the RFC 3339 profile of the ISO 8601 standard for representation of dates and times using the Gregorian calendar.This scalar is serialized to a string in ISO 8601 format and parsed from a string in ISO 8601 format. +""" +scalar Date + input DateFilter { - gte: Float - lte: Float + gte: Date + lte: Date } type Mutation { @@ -65,7 +70,7 @@ type Patron implements Being { type Pokemon implements Being { attack: Int - birthday: Int + birthday: Date defense: Int hp: Int id: ID diff --git a/src/types/SchemaDrivenDataMap/SchemaDrivenDataMap.ts b/src/types/SchemaDrivenDataMap/SchemaDrivenDataMap.ts index 9c3ecf9f..197ca64a 100644 --- a/src/types/SchemaDrivenDataMap/SchemaDrivenDataMap.ts +++ b/src/types/SchemaDrivenDataMap/SchemaDrivenDataMap.ts @@ -13,7 +13,7 @@ declare global { [Grafaid.Schema.RootTypeName.Query]?: SchemaDrivenDataMap.OutputObject [Grafaid.Schema.RootTypeName.Subscription]?: SchemaDrivenDataMap.OutputObject } - types: Record + types: Record directives: Record } @@ -48,7 +48,7 @@ declare global { * - `CodecString` when customScalars enabled and this field's named type is a custom scalar. * - `OutputObject` when customScalars enabled and this field's type contains custom scalars. */ - nt?: OutputLike + nt?: OutputNodes } export interface ArgumentsOrInputObjectFields { @@ -63,7 +63,7 @@ declare global { /** * Named type of this argument or input field. Only present when customScalars is enabled. */ - nt?: InputLike + nt?: InputNodes } export interface InputObject { @@ -127,7 +127,7 @@ export const isCustomScalarName = (value: unknown): value is CustomScalarName => export const isScalar = Schema.Scalar.isScalar -export const isScalarLike = (value: unknown): value is ScalarLike => +export const isScalarLike = (value: unknown): value is ScalarLikeNodes => Schema.Scalar.isScalar(value) || isCustomScalarName(value) export const isOutputObject = ( @@ -146,7 +146,7 @@ export const nullabilityFlags = { // fixing this 1) means branding the data which means more bytes in the sddm // or 2) means using a different key hint like `if` for "input field". export const isInputObject = ( - node?: InputLike, + node?: InputNodes, ): node is InputObject => { return node ? !isString(node) && propertyNames.f in node : false } @@ -157,13 +157,13 @@ export const isOutputField = ( return node ? !isString(node) && propertyNames.a in node : false } -export type NamedLike = ScalarLike | OutputObject | Enum | InputObject +export type NamedNodes = ScalarLikeNodes | OutputObject | Enum | InputObject -export type OutputLike = Scalar | OutputObject | Enum | CustomScalarName +export type OutputNodes = Scalar | OutputObject | Enum | CustomScalarName -export type ScalarLike = Scalar | CustomScalarName +export type ScalarLikeNodes = Scalar | CustomScalarName -export type InputLike = ScalarLike | InputObject | Enum +export type InputNodes = ScalarLikeNodes | InputObject | Enum export type Node = | OutputObject diff --git a/tests/_/schemas/pokemon/data.ts b/tests/_/schemas/pokemon/data.ts index 50d684cf..0d403317 100644 --- a/tests/_/schemas/pokemon/data.ts +++ b/tests/_/schemas/pokemon/data.ts @@ -101,10 +101,7 @@ export namespace DatabaseServer { hp: number attack: number defense: number - /** - * Milliseconds since Unix epoch. - */ - birthday: number + birthday: Date trainerId: string | null // Nullable, as a Pokémon may not be captured by a trainer } @@ -164,12 +161,12 @@ export namespace DatabaseServer { // dprint-ignore database.pokemon.push( - { id: `1`, name: `Pikachu`, type: `electric`, hp: 35, attack: 55, defense: 40, trainerId: `1`, birthday: new Date(Date.UTC(1850, 0, 1)).getTime(), kind: `Pokemon` }, - { id: `2`, name: `Charizard`, type: `fire`, hp: 78, attack: 84, defense: 78, trainerId: `1`, birthday: new Date(Date.now() - day * 5).getTime(), kind: `Pokemon` }, - { id: `3`, name: `Squirtle`, type: `water`, hp: 44, attack: 48, defense: 65, trainerId: `2`, birthday: new Date(Date.UTC(1910, 0, 1)).getTime(), kind: `Pokemon` }, - { id: `4`, name: `Bulbasaur`, type: `grass`, hp: 45, attack: 49, defense: 49, trainerId: null, birthday: new Date(Date.UTC(2000, 0, 1)).getTime(), kind: `Pokemon` }, - { id: `5`, name: `Caterpie`, type: `bug`, hp: 45, attack: 30, defense: 35, trainerId: null, birthday: new Date(Date.UTC(2000, 0, 1)).getTime(), kind: `Pokemon` }, - { id: `6`, name: `Weedle`, type: `bug`, hp: 40, attack: 35, defense: 50, trainerId: null, birthday: new Date(Date.UTC(2000, 0, 1)).getTime(), kind: `Pokemon` }, + { id: `1`, name: `Pikachu`, type: `electric`, hp: 35, attack: 55, defense: 40, trainerId: `1`, birthday: new Date(Date.UTC(1850, 0, 1)), kind: `Pokemon` }, + { id: `2`, name: `Charizard`, type: `fire`, hp: 78, attack: 84, defense: 78, trainerId: `1`, birthday: new Date(Date.now() - day * 5), kind: `Pokemon` }, + { id: `3`, name: `Squirtle`, type: `water`, hp: 44, attack: 48, defense: 65, trainerId: `2`, birthday: new Date(Date.UTC(1910, 0, 1)), kind: `Pokemon` }, + { id: `4`, name: `Bulbasaur`, type: `grass`, hp: 45, attack: 49, defense: 49, trainerId: null, birthday: new Date(Date.UTC(2000, 0, 1)), kind: `Pokemon` }, + { id: `5`, name: `Caterpie`, type: `bug`, hp: 45, attack: 30, defense: 35, trainerId: null, birthday: new Date(Date.UTC(2000, 0, 1)), kind: `Pokemon` }, + { id: `6`, name: `Weedle`, type: `bug`, hp: 40, attack: 35, defense: 50, trainerId: null, birthday: new Date(Date.UTC(2000, 0, 1)), kind: `Pokemon` }, ) diff --git a/tests/_/schemas/pokemon/graffle/modules/Global.ts b/tests/_/schemas/pokemon/graffle/modules/Global.ts index 7cca17d3..9c53c607 100644 --- a/tests/_/schemas/pokemon/graffle/modules/Global.ts +++ b/tests/_/schemas/pokemon/graffle/modules/Global.ts @@ -2,6 +2,7 @@ import type * as Data from './Data.js' import type * as MethodsDocument from './MethodsDocument.js' import type * as MethodsRoot from './MethodsRoot.js' import type * as MethodsSelect from './MethodsSelect.js' +import type * as $Scalar from './Scalar.js' import type { Schema } from './Schema.js' declare global { diff --git a/tests/_/schemas/pokemon/graffle/modules/Scalar.ts b/tests/_/schemas/pokemon/graffle/modules/Scalar.ts index dc2e4caf..a2a35c31 100644 --- a/tests/_/schemas/pokemon/graffle/modules/Scalar.ts +++ b/tests/_/schemas/pokemon/graffle/modules/Scalar.ts @@ -1,3 +1,17 @@ import type * as $$Utilities from '../../../../../../src/entrypoints/utilities-for-generated.js' export * from '../../../../../../src/types/Schema/StandardTypes/scalar.js' + +// +// +// +// +// CUSTOM SCALAR TYPE +// DATE +// -------------------------------------------------------------------------------------------------- +// Date +// -------------------------------------------------------------------------------------------------- +// +// + +export type Date = $$Utilities.Schema.Scalar.ScalarCodecless<'Date'> diff --git a/tests/_/schemas/pokemon/graffle/modules/Schema.ts b/tests/_/schemas/pokemon/graffle/modules/Schema.ts index f4c52126..4740985b 100644 --- a/tests/_/schemas/pokemon/graffle/modules/Schema.ts +++ b/tests/_/schemas/pokemon/graffle/modules/Schema.ts @@ -554,7 +554,7 @@ export namespace Schema { name: 'birthday' arguments: {} inlineType: [0] - namedType: $$NamedTypes.$$Int + namedType: $$NamedTypes.$$Date } export interface defense extends $.OutputField { @@ -696,13 +696,13 @@ export namespace Schema { export interface gte extends $.InputField { name: 'gte' inlineType: [0] - namedType: $$NamedTypes.$$Float + namedType: $$NamedTypes.$$Date } export interface lte extends $.InputField { name: 'lte' inlineType: [0] - namedType: $$NamedTypes.$$Float + namedType: $$NamedTypes.$$Date } } @@ -925,6 +925,12 @@ export namespace Schema { // // + // Date + // -------------------------------------------------------------------------------------------------- + // + + export type Date = $Scalar.Date + // // // @@ -1014,6 +1020,7 @@ export namespace Schema { export type $$BattleWildResult = BattleWildResult export type $$PokemonType = PokemonType export type $$TrainerClass = TrainerClass + export type $$Date = Date export type $$Boolean = Boolean export type $$Float = Float export type $$ID = ID diff --git a/tests/_/schemas/pokemon/graffle/modules/SchemaDrivenDataMap.ts b/tests/_/schemas/pokemon/graffle/modules/SchemaDrivenDataMap.ts index 15dfec0c..122846ba 100644 --- a/tests/_/schemas/pokemon/graffle/modules/SchemaDrivenDataMap.ts +++ b/tests/_/schemas/pokemon/graffle/modules/SchemaDrivenDataMap.ts @@ -42,7 +42,7 @@ const String = $Scalar.String // // -// None of your ScalarCustoms have custom scalars. +const Date = 'Date' // // @@ -93,16 +93,24 @@ const TrainerClass: $$Utilities.SchemaDrivenDataMap.Enum = { const DateFilter: $$Utilities.SchemaDrivenDataMap.InputObject = { n: 'DateFilter', + fcs: ['gte', 'lte'], f: { - gte: {}, - lte: {}, + gte: { + nt: Date, + }, + lte: { + nt: Date, + }, }, } const PokemonFilter: $$Utilities.SchemaDrivenDataMap.InputObject = { n: 'PokemonFilter', + fcs: ['birthday'], f: { - birthday: {}, + birthday: { + // nt: DateFilter, <-- Assigned later to avoid potential circular dependency. + }, name: {}, }, } @@ -210,7 +218,9 @@ const Patron: $$Utilities.SchemaDrivenDataMap.OutputObject = { const Pokemon: $$Utilities.SchemaDrivenDataMap.OutputObject = { f: { attack: {}, - birthday: {}, + birthday: { + nt: Date, + }, defense: {}, hp: {}, id: {}, @@ -253,7 +263,10 @@ const Trainer: $$Utilities.SchemaDrivenDataMap.OutputObject = { // const Being: $$Utilities.SchemaDrivenDataMap.OutputObject = { - f: {}, + f: { + ...Pokemon.f, + ...Trainer.f, + }, } // @@ -273,7 +286,11 @@ const Being: $$Utilities.SchemaDrivenDataMap.OutputObject = { // const Battle: $$Utilities.SchemaDrivenDataMap.OutputObject = { - f: {}, + f: { + ...BattleRoyale.f, + ...BattleTrainer.f, + ...BattleWild.f, + }, } // @@ -383,6 +400,7 @@ const Query: $$Utilities.SchemaDrivenDataMap.OutputObject = { // // +PokemonFilter.f![`birthday`]!.nt = DateFilter BattleRoyale.f[`combatants`]!.nt = CombatantMultiPokemon BattleRoyale.f[`winner`]!.nt = Trainer BattleTrainer.f[`combatant1`]!.nt = CombatantSinglePokemon @@ -435,6 +453,7 @@ const $schemaDrivenDataMap: $$Utilities.SchemaDrivenDataMap = { ID, Int, String, + Date, BattleWildResult, PokemonType, TrainerClass, diff --git a/tests/_/schemas/pokemon/graffle/modules/SelectionSets.ts b/tests/_/schemas/pokemon/graffle/modules/SelectionSets.ts index 6d22da4a..72c3eebe 100644 --- a/tests/_/schemas/pokemon/graffle/modules/SelectionSets.ts +++ b/tests/_/schemas/pokemon/graffle/modules/SelectionSets.ts @@ -1,5 +1,6 @@ import type { Select as $Select } from '../../../../../../src/entrypoints/schema.js' import type * as $$Utilities from '../../../../../../src/entrypoints/utilities-for-generated.js' +import type * as $Scalar from './Scalar.js' // // @@ -419,9 +420,19 @@ export type TrainerClass = // export interface DateFilter<$Scalars extends $$Utilities.Schema.Scalar.ScalarMap = {}> { - gte?: number | undefined | null - - lte?: number | undefined | null + gte?: + | $$Utilities.Schema.Scalar.GetDecoded< + $$Utilities.Schema.Scalar.LookupCustomScalarOrFallbackToString<'Date', $Scalars> + > + | undefined + | null + + lte?: + | $$Utilities.Schema.Scalar.GetDecoded< + $$Utilities.Schema.Scalar.LookupCustomScalarOrFallbackToString<'Date', $Scalars> + > + | undefined + | null } export interface PokemonFilter<$Scalars extends $$Utilities.Schema.Scalar.ScalarMap = {}> { @@ -1300,7 +1311,7 @@ export interface Pokemon<$Scalars extends $$Utilities.Schema.Scalar.ScalarMap = */ attack?: Pokemon.attack$Expanded<$Scalars> | $Select.SelectAlias.SelectAlias> /** - * Select the `birthday` field on the `Pokemon` object. Its type is `Int` (a `ScalarStandard` kind of type). + * Select the `birthday` field on the `Pokemon` object. Its type is `Date` (a `ScalarCustom` kind of type). */ birthday?: Pokemon.birthday$Expanded<$Scalars> | $Select.SelectAlias.SelectAlias> /** diff --git a/tests/_/schemas/pokemon/graffle/schema.graphql b/tests/_/schemas/pokemon/graffle/schema.graphql index a6b3bf50..7856b5c1 100644 --- a/tests/_/schemas/pokemon/graffle/schema.graphql +++ b/tests/_/schemas/pokemon/graffle/schema.graphql @@ -45,9 +45,14 @@ type CombatantSinglePokemon { trainer: Trainer } +""" +A date-time string at UTC, such as 2007-12-03T10:15:30Z, compliant with the `date-time` format outlined in section 5.6 of the RFC 3339 profile of the ISO 8601 standard for representation of dates and times using the Gregorian calendar.This scalar is serialized to a string in ISO 8601 format and parsed from a string in ISO 8601 format. +""" +scalar Date + input DateFilter { - gte: Float - lte: Float + gte: Date + lte: Date } type Mutation { @@ -62,7 +67,7 @@ type Patron implements Being { type Pokemon implements Being { attack: Int - birthday: Int + birthday: Date defense: Int hp: Int id: ID diff --git a/tests/_/schemas/pokemon/schema.ts b/tests/_/schemas/pokemon/schema.ts index 22a514a8..71442f83 100644 --- a/tests/_/schemas/pokemon/schema.ts +++ b/tests/_/schemas/pokemon/schema.ts @@ -1,6 +1,7 @@ import SchemaBuilder from '@pothos/core' import SimpleObjectsPlugin from '@pothos/plugin-simple-objects' import ZodPlugin from '@pothos/plugin-zod' +import { DateTimeISOResolver } from 'graphql-scalars' import { DatabaseServer } from './data.js' export const builder = new SchemaBuilder<{ @@ -17,6 +18,8 @@ export const builder = new SchemaBuilder<{ plugins: [SimpleObjectsPlugin, ZodPlugin], }) +builder.addScalarType(`Date`, DateTimeISOResolver, {}) + const TrainerClass = builder.enumType(`TrainerClass`, { values: Object.values(DatabaseServer.TrainerClass) }) const Being = builder.interfaceRef(`Being`).implement({ @@ -61,7 +64,7 @@ const Pokemon = builder.objectRef(`Pokemon`).implement({ hp: t.int({ resolve: (pokemon) => pokemon.hp }), attack: t.int({ resolve: (pokemon) => pokemon.attack }), defense: t.int({ resolve: (pokemon) => pokemon.defense }), - birthday: t.int({ resolve: (pokemon) => pokemon.birthday }), + birthday: t.field({ type: `Date`, resolve: (pokemon) => pokemon.birthday }), trainer: t.field({ type: Trainer, nullable: true, @@ -218,8 +221,8 @@ const StringFilter = builder.inputType(`StringFilter`, { const DateFilter = builder.inputType(`DateFilter`, { fields: (t) => ({ - lte: t.float(), - gte: t.float(), + lte: t.field({ type: `Date` }), + gte: t.field({ type: `Date` }), }), }) @@ -242,8 +245,8 @@ builder.queryField(`pokemons`, (t) => filter: t.arg({ type: PokemonFilter, required: false }), }, type: [Pokemon], - resolve: (_, args, ctx) => - DatabaseServer.tenant(ctx.tenant).pokemon.filter((p) => { + resolve: (_, args, ctx) => { + return DatabaseServer.tenant(ctx.tenant).pokemon.filter((p) => { if (args.filter?.name) { if (args.filter.name.contains) { return p.name.includes(args.filter.name.contains) @@ -261,7 +264,8 @@ builder.queryField(`pokemons`, (t) => } } return true - }), + }) + }, })) builder.queryField(`battles`, (t) => @@ -341,7 +345,7 @@ builder.mutationField(`addPokemon`, (t) => attack: attack || 0, defense: defense || 0, trainerId: null, - birthday: new Date().getTime(), + birthday: new Date(), } DatabaseServer.tenant(ctx.tenant).pokemon.push(newPokemon) return newPokemon diff --git a/tests/examples/35_custom-scalar/custom-scalar.test.ts b/tests/examples/35_custom-scalar/custom-scalar.test.ts new file mode 100644 index 00000000..bbfabf70 --- /dev/null +++ b/tests/examples/35_custom-scalar/custom-scalar.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(`custom-scalar`, async () => { + const exampleResult = await runExample(`./examples/35_custom-scalar/custom-scalar.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__/35_custom-scalar/custom-scalar.output.txt`, + ) +}) diff --git a/website/content/_snippets/example-links/custom-scalar.md b/website/content/_snippets/example-links/custom-scalar.md new file mode 100644 index 00000000..ef152e34 --- /dev/null +++ b/website/content/_snippets/example-links/custom-scalar.md @@ -0,0 +1 @@ + diff --git a/website/content/_snippets/examples/custom-scalar/custom-scalar.detail.md b/website/content/_snippets/examples/custom-scalar/custom-scalar.detail.md new file mode 100644 index 00000000..3aec7db1 --- /dev/null +++ b/website/content/_snippets/examples/custom-scalar/custom-scalar.detail.md @@ -0,0 +1,46 @@ +::: details Example + +
+Custom Scalar + + +```ts twoslash +import { Graffle } from 'graffle/__Graffle' +import { Pokemon } from './pokemon/__.js' + +const graffle = Pokemon + .create() + .scalar(Graffle.Scalars.create(`Date`, { + encode: (value: globalThis.Date) => value.toISOString(), + decode: (value: string) => { + return new globalThis.Date(value) + }, + })) + +const pokemons = await graffle.query.pokemons({ + $: { filter: { birthday: { lte: new Date(`1987-01-13`) } } }, + name: true, + birthday: true, +}) + +console.log(`pokemons[0].birthday instanceof Date = ${String(pokemons?.[0]?.birthday instanceof Date)}`) +console.log(pokemons) +``` + + + +```txt +pokemons[0].birthday instanceof Date = true +``` + + +```txt +[ + { name: 'Pikachu', birthday: 1850-01-01T00:00:00.000Z }, + { name: 'Squirtle', birthday: 1910-01-01T00:00:00.000Z } +] +``` + + +
+::: diff --git a/website/content/_snippets/examples/custom-scalar/custom-scalar.md b/website/content/_snippets/examples/custom-scalar/custom-scalar.md new file mode 100644 index 00000000..3bc31d5f --- /dev/null +++ b/website/content/_snippets/examples/custom-scalar/custom-scalar.md @@ -0,0 +1,43 @@ +
+Custom Scalar + + +```ts twoslash +import { Graffle } from 'graffle/__Graffle' +import { Pokemon } from './pokemon/__.js' + +const graffle = Pokemon + .create() + .scalar(Graffle.Scalars.create(`Date`, { + encode: (value: globalThis.Date) => value.toISOString(), + decode: (value: string) => { + return new globalThis.Date(value) + }, + })) + +const pokemons = await graffle.query.pokemons({ + $: { filter: { birthday: { lte: new Date(`1987-01-13`) } } }, + name: true, + birthday: true, +}) + +console.log(`pokemons[0].birthday instanceof Date = ${String(pokemons?.[0]?.birthday instanceof Date)}`) +console.log(pokemons) +``` + + + +```txt +pokemons[0].birthday instanceof Date = true +``` + + +```txt +[ + { name: 'Pikachu', birthday: 1850-01-01T00:00:00.000Z }, + { name: 'Squirtle', birthday: 1910-01-01T00:00:00.000Z } +] +``` + + +
diff --git a/website/content/_snippets/examples/document-builder/alias.detail.md b/website/content/_snippets/examples/document-builder/alias.detail.md index 82bc3aa3..c08cae36 100644 --- a/website/content/_snippets/examples/document-builder/alias.detail.md +++ b/website/content/_snippets/examples/document-builder/alias.detail.md @@ -11,8 +11,8 @@ const pokemon = Pokemon.create() const day = 1000 * 60 * 60 * 24 const year = day * 365.25 -const yearsAgo100 = new Date(Date.now() - year * 100).getTime() -const yearsAgo1 = new Date(Date.now() - year).getTime() +const yearsAgo100 = new Date(Date.now() - year * 100).toISOString() +const yearsAgo1 = new Date(Date.now() - year).toISOString() const pokemons = await pokemon.query.$batch({ pokemons: [ diff --git a/website/content/_snippets/examples/document-builder/alias.md b/website/content/_snippets/examples/document-builder/alias.md index 5a1e6ca0..bc1870b6 100644 --- a/website/content/_snippets/examples/document-builder/alias.md +++ b/website/content/_snippets/examples/document-builder/alias.md @@ -9,8 +9,8 @@ const pokemon = Pokemon.create() const day = 1000 * 60 * 60 * 24 const year = day * 365.25 -const yearsAgo100 = new Date(Date.now() - year * 100).getTime() -const yearsAgo1 = new Date(Date.now() - year).getTime() +const yearsAgo100 = new Date(Date.now() - year * 100).toISOString() +const yearsAgo1 = new Date(Date.now() - year).toISOString() const pokemons = await pokemon.query.$batch({ pokemons: [ diff --git a/website/content/_snippets/examples/extension/introspection.detail.md b/website/content/_snippets/examples/extension/introspection.detail.md index 354edd06..9cfd4514 100644 --- a/website/content/_snippets/examples/extension/introspection.detail.md +++ b/website/content/_snippets/examples/extension/introspection.detail.md @@ -350,6 +350,16 @@ console.log(data) enumValues: null, possibleTypes: null }, + { + kind: 'SCALAR', + name: 'Date', + description: 'A date-time string at UTC, such as 2007-12-03T10:15:30Z, compliant with the `date-time` format outlined in section 5.6 of the RFC 3339 profile of the ISO 8601 standard for representation of dates and times using the Gregorian calendar.This scalar is serialized to a string in ISO 8601 format and parsed from a string in ISO 8601 format.', + fields: null, + inputFields: null, + interfaces: null, + enumValues: null, + possibleTypes: null + }, { kind: 'INPUT_OBJECT', name: 'DateFilter', @@ -359,13 +369,13 @@ console.log(data) { name: 'gte', description: null, - type: { kind: 'SCALAR', name: 'Float', ofType: null }, + type: { kind: 'SCALAR', name: 'Date', ofType: null }, defaultValue: null }, { name: 'lte', description: null, - type: { kind: 'SCALAR', name: 'Float', ofType: null }, + type: { kind: 'SCALAR', name: 'Date', ofType: null }, defaultValue: null } ], @@ -513,7 +523,7 @@ console.log(data) name: 'birthday', description: null, args: [], - type: { kind: 'SCALAR', name: 'Int', ofType: null }, + type: { kind: 'SCALAR', name: 'Date', ofType: null }, isDeprecated: false, deprecationReason: null }, diff --git a/website/content/_snippets/examples/extension/introspection.md b/website/content/_snippets/examples/extension/introspection.md index 13d7395d..90175c53 100644 --- a/website/content/_snippets/examples/extension/introspection.md +++ b/website/content/_snippets/examples/extension/introspection.md @@ -348,6 +348,16 @@ console.log(data) enumValues: null, possibleTypes: null }, + { + kind: 'SCALAR', + name: 'Date', + description: 'A date-time string at UTC, such as 2007-12-03T10:15:30Z, compliant with the `date-time` format outlined in section 5.6 of the RFC 3339 profile of the ISO 8601 standard for representation of dates and times using the Gregorian calendar.This scalar is serialized to a string in ISO 8601 format and parsed from a string in ISO 8601 format.', + fields: null, + inputFields: null, + interfaces: null, + enumValues: null, + possibleTypes: null + }, { kind: 'INPUT_OBJECT', name: 'DateFilter', @@ -357,13 +367,13 @@ console.log(data) { name: 'gte', description: null, - type: { kind: 'SCALAR', name: 'Float', ofType: null }, + type: { kind: 'SCALAR', name: 'Date', ofType: null }, defaultValue: null }, { name: 'lte', description: null, - type: { kind: 'SCALAR', name: 'Float', ofType: null }, + type: { kind: 'SCALAR', name: 'Date', ofType: null }, defaultValue: null } ], @@ -511,7 +521,7 @@ console.log(data) name: 'birthday', description: null, args: [], - type: { kind: 'SCALAR', name: 'Int', ofType: null }, + type: { kind: 'SCALAR', name: 'Date', ofType: null }, isDeprecated: false, deprecationReason: null }, diff --git a/website/content/_snippets/examples/extension/opentelemetry.detail.md b/website/content/_snippets/examples/extension/opentelemetry.detail.md index 887c2030..65d1f8be 100644 --- a/website/content/_snippets/examples/extension/opentelemetry.detail.md +++ b/website/content/_snippets/examples/extension/opentelemetry.detail.md @@ -37,14 +37,14 @@ console.log(data) } }, instrumentationScope: { name: 'graffle', version: undefined, schemaUrl: undefined }, - traceId: '2259f0e0e3b890cac862a813639e9c90', - parentId: 'f68c78e8a4e9ae99', + traceId: '4db92619c2c83794eea58ac3b0f81739', + parentId: '0441c81c457852b2', traceState: undefined, name: 'encode', - id: 'c8b432c64c6d23ce', + id: '0fed2ea32096273f', kind: 0, - timestamp: 1729649903315000, - duration: 1492.792, + timestamp: 1729654429675000, + duration: 2681.792, attributes: {}, status: { code: 0 }, events: [], @@ -64,14 +64,14 @@ console.log(data) } }, instrumentationScope: { name: 'graffle', version: undefined, schemaUrl: undefined }, - traceId: '2259f0e0e3b890cac862a813639e9c90', - parentId: 'f68c78e8a4e9ae99', + traceId: '4db92619c2c83794eea58ac3b0f81739', + parentId: '0441c81c457852b2', traceState: undefined, name: 'pack', - id: '385c61790e55b6f4', + id: 'ca490dca8e6606c9', kind: 0, - timestamp: 1729649903318000, - duration: 12042.5, + timestamp: 1729654429680000, + duration: 48326.792, attributes: {}, status: { code: 0 }, events: [], @@ -91,14 +91,14 @@ console.log(data) } }, instrumentationScope: { name: 'graffle', version: undefined, schemaUrl: undefined }, - traceId: '2259f0e0e3b890cac862a813639e9c90', - parentId: 'f68c78e8a4e9ae99', + traceId: '4db92619c2c83794eea58ac3b0f81739', + parentId: '0441c81c457852b2', traceState: undefined, name: 'exchange', - id: '67a764a3a78c819c', + id: '11066eaa0e07c122', kind: 0, - timestamp: 1729649903331000, - duration: 21272.625, + timestamp: 1729654429729000, + duration: 47351.292, attributes: {}, status: { code: 0 }, events: [], @@ -118,14 +118,14 @@ console.log(data) } }, instrumentationScope: { name: 'graffle', version: undefined, schemaUrl: undefined }, - traceId: '2259f0e0e3b890cac862a813639e9c90', - parentId: 'f68c78e8a4e9ae99', + traceId: '4db92619c2c83794eea58ac3b0f81739', + parentId: '0441c81c457852b2', traceState: undefined, name: 'unpack', - id: 'a92c1ad25a186aa9', + id: 'c527bbb4b62958fa', kind: 0, - timestamp: 1729649903352000, - duration: 1640.25, + timestamp: 1729654429777000, + duration: 1775.75, attributes: {}, status: { code: 0 }, events: [], @@ -145,14 +145,14 @@ console.log(data) } }, instrumentationScope: { name: 'graffle', version: undefined, schemaUrl: undefined }, - traceId: '2259f0e0e3b890cac862a813639e9c90', - parentId: 'f68c78e8a4e9ae99', + traceId: '4db92619c2c83794eea58ac3b0f81739', + parentId: '0441c81c457852b2', traceState: undefined, name: 'decode', - id: 'fbdf501193493665', + id: '20eb54a71997e91e', kind: 0, - timestamp: 1729649903354000, - duration: 314.583, + timestamp: 1729654429779000, + duration: 669.25, attributes: {}, status: { code: 0 }, events: [], @@ -172,14 +172,14 @@ console.log(data) } }, instrumentationScope: { name: 'graffle', version: undefined, schemaUrl: undefined }, - traceId: '2259f0e0e3b890cac862a813639e9c90', + traceId: '4db92619c2c83794eea58ac3b0f81739', parentId: undefined, traceState: undefined, name: 'request', - id: 'f68c78e8a4e9ae99', + id: '0441c81c457852b2', kind: 0, - timestamp: 1729649903314000, - duration: 40837.167, + timestamp: 1729654429631000, + duration: 148602.75, attributes: {}, status: { code: 0 }, events: [], diff --git a/website/content/_snippets/examples/extension/opentelemetry.md b/website/content/_snippets/examples/extension/opentelemetry.md index 70cb9566..3fb3c2ed 100644 --- a/website/content/_snippets/examples/extension/opentelemetry.md +++ b/website/content/_snippets/examples/extension/opentelemetry.md @@ -35,14 +35,14 @@ console.log(data) } }, instrumentationScope: { name: 'graffle', version: undefined, schemaUrl: undefined }, - traceId: '2259f0e0e3b890cac862a813639e9c90', - parentId: 'f68c78e8a4e9ae99', + traceId: '4db92619c2c83794eea58ac3b0f81739', + parentId: '0441c81c457852b2', traceState: undefined, name: 'encode', - id: 'c8b432c64c6d23ce', + id: '0fed2ea32096273f', kind: 0, - timestamp: 1729649903315000, - duration: 1492.792, + timestamp: 1729654429675000, + duration: 2681.792, attributes: {}, status: { code: 0 }, events: [], @@ -62,14 +62,14 @@ console.log(data) } }, instrumentationScope: { name: 'graffle', version: undefined, schemaUrl: undefined }, - traceId: '2259f0e0e3b890cac862a813639e9c90', - parentId: 'f68c78e8a4e9ae99', + traceId: '4db92619c2c83794eea58ac3b0f81739', + parentId: '0441c81c457852b2', traceState: undefined, name: 'pack', - id: '385c61790e55b6f4', + id: 'ca490dca8e6606c9', kind: 0, - timestamp: 1729649903318000, - duration: 12042.5, + timestamp: 1729654429680000, + duration: 48326.792, attributes: {}, status: { code: 0 }, events: [], @@ -89,14 +89,14 @@ console.log(data) } }, instrumentationScope: { name: 'graffle', version: undefined, schemaUrl: undefined }, - traceId: '2259f0e0e3b890cac862a813639e9c90', - parentId: 'f68c78e8a4e9ae99', + traceId: '4db92619c2c83794eea58ac3b0f81739', + parentId: '0441c81c457852b2', traceState: undefined, name: 'exchange', - id: '67a764a3a78c819c', + id: '11066eaa0e07c122', kind: 0, - timestamp: 1729649903331000, - duration: 21272.625, + timestamp: 1729654429729000, + duration: 47351.292, attributes: {}, status: { code: 0 }, events: [], @@ -116,14 +116,14 @@ console.log(data) } }, instrumentationScope: { name: 'graffle', version: undefined, schemaUrl: undefined }, - traceId: '2259f0e0e3b890cac862a813639e9c90', - parentId: 'f68c78e8a4e9ae99', + traceId: '4db92619c2c83794eea58ac3b0f81739', + parentId: '0441c81c457852b2', traceState: undefined, name: 'unpack', - id: 'a92c1ad25a186aa9', + id: 'c527bbb4b62958fa', kind: 0, - timestamp: 1729649903352000, - duration: 1640.25, + timestamp: 1729654429777000, + duration: 1775.75, attributes: {}, status: { code: 0 }, events: [], @@ -143,14 +143,14 @@ console.log(data) } }, instrumentationScope: { name: 'graffle', version: undefined, schemaUrl: undefined }, - traceId: '2259f0e0e3b890cac862a813639e9c90', - parentId: 'f68c78e8a4e9ae99', + traceId: '4db92619c2c83794eea58ac3b0f81739', + parentId: '0441c81c457852b2', traceState: undefined, name: 'decode', - id: 'fbdf501193493665', + id: '20eb54a71997e91e', kind: 0, - timestamp: 1729649903354000, - duration: 314.583, + timestamp: 1729654429779000, + duration: 669.25, attributes: {}, status: { code: 0 }, events: [], @@ -170,14 +170,14 @@ console.log(data) } }, instrumentationScope: { name: 'graffle', version: undefined, schemaUrl: undefined }, - traceId: '2259f0e0e3b890cac862a813639e9c90', + traceId: '4db92619c2c83794eea58ac3b0f81739', parentId: undefined, traceState: undefined, name: 'request', - id: 'f68c78e8a4e9ae99', + id: '0441c81c457852b2', kind: 0, - timestamp: 1729649903314000, - duration: 40837.167, + timestamp: 1729654429631000, + duration: 148602.75, attributes: {}, status: { code: 0 }, events: [], diff --git a/website/content/_snippets/examples/output/envelope.detail.md b/website/content/_snippets/examples/output/envelope.detail.md index 43b080a5..38e2739b 100644 --- a/website/content/_snippets/examples/output/envelope.detail.md +++ b/website/content/_snippets/examples/output/envelope.detail.md @@ -40,7 +40,7 @@ console.log(result) headers: Headers { 'content-type': 'application/graphql-response+json; charset=utf-8', 'content-length': '142', - date: 'Wed, 23 Oct 2024 02:18:22 GMT', + date: 'Wed, 23 Oct 2024 03:33:49 GMT', connection: 'keep-alive', 'keep-alive': 'timeout=5' }, diff --git a/website/content/_snippets/examples/output/envelope.md b/website/content/_snippets/examples/output/envelope.md index cdbbec42..1849dc96 100644 --- a/website/content/_snippets/examples/output/envelope.md +++ b/website/content/_snippets/examples/output/envelope.md @@ -38,7 +38,7 @@ console.log(result) headers: Headers { 'content-type': 'application/graphql-response+json; charset=utf-8', 'content-length': '142', - date: 'Wed, 23 Oct 2024 02:18:22 GMT', + date: 'Wed, 23 Oct 2024 03:33:49 GMT', connection: 'keep-alive', 'keep-alive': 'timeout=5' }, diff --git a/website/content/_snippets/examples/transport-http/dynamic-headers.detail.md b/website/content/_snippets/examples/transport-http/dynamic-headers.detail.md index 46a860b1..f8000b3d 100644 --- a/website/content/_snippets/examples/transport-http/dynamic-headers.detail.md +++ b/website/content/_snippets/examples/transport-http/dynamic-headers.detail.md @@ -38,7 +38,7 @@ await graffle.gql`{ pokemons { name } }`.send() headers: Headers { accept: 'application/graphql-response+json; charset=utf-8, application/json; charset=utf-8', 'content-type': 'application/json', - 'x-sent-at-time': '1729649902337' + 'x-sent-at-time': '1729654429348' }, signal: undefined, method: 'post', diff --git a/website/content/_snippets/examples/transport-http/dynamic-headers.md b/website/content/_snippets/examples/transport-http/dynamic-headers.md index a849f51b..bbe02d7d 100644 --- a/website/content/_snippets/examples/transport-http/dynamic-headers.md +++ b/website/content/_snippets/examples/transport-http/dynamic-headers.md @@ -36,7 +36,7 @@ await graffle.gql`{ pokemons { name } }`.send() headers: Headers { accept: 'application/graphql-response+json; charset=utf-8, application/json; charset=utf-8', 'content-type': 'application/json', - 'x-sent-at-time': '1729649902337' + 'x-sent-at-time': '1729654429348' }, signal: undefined, method: 'post', diff --git a/website/content/examples/10_transport-http/dynamic-headers.md b/website/content/examples/10_transport-http/dynamic-headers.md index 4f2744a9..f3503d0c 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.gql`{ pokemons { name } }`.send() headers: Headers { accept: 'application/graphql-response+json; charset=utf-8, application/json; charset=utf-8', 'content-type': 'application/json', - 'x-sent-at-time': '1729649902337' + 'x-sent-at-time': '1729654429348' }, signal: undefined, method: 'post', diff --git a/website/content/examples/20_output/envelope.md b/website/content/examples/20_output/envelope.md index 3866905e..605142aa 100644 --- a/website/content/examples/20_output/envelope.md +++ b/website/content/examples/20_output/envelope.md @@ -45,7 +45,7 @@ console.log(result) headers: Headers { 'content-type': 'application/graphql-response+json; charset=utf-8', 'content-length': '142', - date: 'Wed, 23 Oct 2024 02:18:22 GMT', + date: 'Wed, 23 Oct 2024 03:33:49 GMT', connection: 'keep-alive', 'keep-alive': 'timeout=5' }, diff --git a/website/content/examples/35_custom-scalar/custom-scalar.md b/website/content/examples/35_custom-scalar/custom-scalar.md new file mode 100644 index 00000000..5153c9cb --- /dev/null +++ b/website/content/examples/35_custom-scalar/custom-scalar.md @@ -0,0 +1,49 @@ +--- +aside: false +--- + +# Custom Scalar + +This example shows how to add a client-side custom scalar codec to +have arguments and data automatically encoded and decoded respectively. + + +```ts twoslash +import { Graffle } from 'graffle/__Graffle' +import { Pokemon } from './pokemon/__.js' + +const graffle = Pokemon + .create() + .scalar(Graffle.Scalars.create(`Date`, { + encode: (value: globalThis.Date) => value.toISOString(), + decode: (value: string) => { + return new globalThis.Date(value) + }, + })) + +const pokemons = await graffle.query.pokemons({ + $: { filter: { birthday: { lte: new Date(`1987-01-13`) } } }, + name: true, + birthday: true, +}) + +console.log(`pokemons[0].birthday instanceof Date = ${String(pokemons?.[0]?.birthday instanceof Date)}`) +console.log(pokemons) +``` + + +#### Outputs + + +```txt +pokemons[0].birthday instanceof Date = true +``` + + +```txt +[ + { name: 'Pikachu', birthday: 1850-01-01T00:00:00.000Z }, + { name: 'Squirtle', birthday: 1910-01-01T00:00:00.000Z } +] +``` + diff --git a/website/content/examples/55_document-builder/alias.md b/website/content/examples/55_document-builder/alias.md index 67f34870..7bd65c60 100644 --- a/website/content/examples/55_document-builder/alias.md +++ b/website/content/examples/55_document-builder/alias.md @@ -14,8 +14,8 @@ const pokemon = Pokemon.create() const day = 1000 * 60 * 60 * 24 const year = day * 365.25 -const yearsAgo100 = new Date(Date.now() - year * 100).getTime() -const yearsAgo1 = new Date(Date.now() - year).getTime() +const yearsAgo100 = new Date(Date.now() - year * 100).toISOString() +const yearsAgo1 = new Date(Date.now() - year).toISOString() const pokemons = await pokemon.query.$batch({ pokemons: [ diff --git a/website/content/examples/60_extension/introspection.md b/website/content/examples/60_extension/introspection.md index 3a33db20..42d338c3 100644 --- a/website/content/examples/60_extension/introspection.md +++ b/website/content/examples/60_extension/introspection.md @@ -355,6 +355,16 @@ console.log(data) enumValues: null, possibleTypes: null }, + { + kind: 'SCALAR', + name: 'Date', + description: 'A date-time string at UTC, such as 2007-12-03T10:15:30Z, compliant with the `date-time` format outlined in section 5.6 of the RFC 3339 profile of the ISO 8601 standard for representation of dates and times using the Gregorian calendar.This scalar is serialized to a string in ISO 8601 format and parsed from a string in ISO 8601 format.', + fields: null, + inputFields: null, + interfaces: null, + enumValues: null, + possibleTypes: null + }, { kind: 'INPUT_OBJECT', name: 'DateFilter', @@ -364,13 +374,13 @@ console.log(data) { name: 'gte', description: null, - type: { kind: 'SCALAR', name: 'Float', ofType: null }, + type: { kind: 'SCALAR', name: 'Date', ofType: null }, defaultValue: null }, { name: 'lte', description: null, - type: { kind: 'SCALAR', name: 'Float', ofType: null }, + type: { kind: 'SCALAR', name: 'Date', ofType: null }, defaultValue: null } ], @@ -518,7 +528,7 @@ console.log(data) name: 'birthday', description: null, args: [], - type: { kind: 'SCALAR', name: 'Int', ofType: null }, + type: { kind: 'SCALAR', name: 'Date', ofType: null }, isDeprecated: false, deprecationReason: null }, diff --git a/website/content/examples/60_extension/opentelemetry.md b/website/content/examples/60_extension/opentelemetry.md index a3025e24..8acf40ee 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: '2259f0e0e3b890cac862a813639e9c90', - parentId: 'f68c78e8a4e9ae99', + traceId: '4db92619c2c83794eea58ac3b0f81739', + parentId: '0441c81c457852b2', traceState: undefined, name: 'encode', - id: 'c8b432c64c6d23ce', + id: '0fed2ea32096273f', kind: 0, - timestamp: 1729649903315000, - duration: 1492.792, + timestamp: 1729654429675000, + duration: 2681.792, attributes: {}, status: { code: 0 }, events: [], @@ -67,14 +67,14 @@ console.log(data) } }, instrumentationScope: { name: 'graffle', version: undefined, schemaUrl: undefined }, - traceId: '2259f0e0e3b890cac862a813639e9c90', - parentId: 'f68c78e8a4e9ae99', + traceId: '4db92619c2c83794eea58ac3b0f81739', + parentId: '0441c81c457852b2', traceState: undefined, name: 'pack', - id: '385c61790e55b6f4', + id: 'ca490dca8e6606c9', kind: 0, - timestamp: 1729649903318000, - duration: 12042.5, + timestamp: 1729654429680000, + duration: 48326.792, attributes: {}, status: { code: 0 }, events: [], @@ -94,14 +94,14 @@ console.log(data) } }, instrumentationScope: { name: 'graffle', version: undefined, schemaUrl: undefined }, - traceId: '2259f0e0e3b890cac862a813639e9c90', - parentId: 'f68c78e8a4e9ae99', + traceId: '4db92619c2c83794eea58ac3b0f81739', + parentId: '0441c81c457852b2', traceState: undefined, name: 'exchange', - id: '67a764a3a78c819c', + id: '11066eaa0e07c122', kind: 0, - timestamp: 1729649903331000, - duration: 21272.625, + timestamp: 1729654429729000, + duration: 47351.292, attributes: {}, status: { code: 0 }, events: [], @@ -121,14 +121,14 @@ console.log(data) } }, instrumentationScope: { name: 'graffle', version: undefined, schemaUrl: undefined }, - traceId: '2259f0e0e3b890cac862a813639e9c90', - parentId: 'f68c78e8a4e9ae99', + traceId: '4db92619c2c83794eea58ac3b0f81739', + parentId: '0441c81c457852b2', traceState: undefined, name: 'unpack', - id: 'a92c1ad25a186aa9', + id: 'c527bbb4b62958fa', kind: 0, - timestamp: 1729649903352000, - duration: 1640.25, + timestamp: 1729654429777000, + duration: 1775.75, attributes: {}, status: { code: 0 }, events: [], @@ -148,14 +148,14 @@ console.log(data) } }, instrumentationScope: { name: 'graffle', version: undefined, schemaUrl: undefined }, - traceId: '2259f0e0e3b890cac862a813639e9c90', - parentId: 'f68c78e8a4e9ae99', + traceId: '4db92619c2c83794eea58ac3b0f81739', + parentId: '0441c81c457852b2', traceState: undefined, name: 'decode', - id: 'fbdf501193493665', + id: '20eb54a71997e91e', kind: 0, - timestamp: 1729649903354000, - duration: 314.583, + timestamp: 1729654429779000, + duration: 669.25, attributes: {}, status: { code: 0 }, events: [], @@ -175,14 +175,14 @@ console.log(data) } }, instrumentationScope: { name: 'graffle', version: undefined, schemaUrl: undefined }, - traceId: '2259f0e0e3b890cac862a813639e9c90', + traceId: '4db92619c2c83794eea58ac3b0f81739', parentId: undefined, traceState: undefined, name: 'request', - id: 'f68c78e8a4e9ae99', + id: '0441c81c457852b2', kind: 0, - timestamp: 1729649903314000, - duration: 40837.167, + timestamp: 1729654429631000, + duration: 148602.75, attributes: {}, status: { code: 0 }, events: [],