Skip to content

Commit

Permalink
fragmentify all
Browse files Browse the repository at this point in the history
  • Loading branch information
jasonkuhrt committed Feb 2, 2025
1 parent 20d0b74 commit 993f3e7
Show file tree
Hide file tree
Showing 26 changed files with 271 additions and 210 deletions.
73 changes: 35 additions & 38 deletions src/client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,21 @@ import type { ObjectMergeShallow } from '../lib/prelude.js'
import type { TypeFunction } from '../lib/type-function/__.js'
import type { RequestPipeline } from '../requestPipeline/RequestPipeline.js'
import type { ConfigurationIndex } from '../types/ConfigurationIndex.js'
import { Context, contextMergeFragment } from '../types/context.js'
import { Context, type ContextFragment, contextMergeFragment } from '../types/context.js'
import { handleOutput } from './handleOutput.js'
import { type ContextAddConfiguration, contextAddConfigurationInput } from './properties/addConfiguration.js'
import { type ContextAddOneExtension, contextFragmentExtensionsAddOne } from './properties/addExtension.js'
import { type AddPropertiesMethod, contextFragmentAddProperties } from './properties/addProperties.js'
import { contextFragmentAddRequestInterceptor } from './properties/addRequestInterceptor.js'
import { contextAddScalar, ScalarMethod } from './properties/addScalar.js'
import {
type ContextFragmentConfigurationConfigure,
contextFragmentConfigurationConfigure,
} from './properties/configuration.js'
import { type ContextAddOneExtension, contextFragmentExtensionsAdd } from './properties/extensions.js'
import { type AddPropertiesMethod, contextFragmentPropertiesAdd } from './properties/properties.js'
import { GqlMethod } from './properties/request/request.js'
import { SendMethod } from './properties/request/send.js'
import { contextFragmentRequestInterceptorsAdd } from './properties/requestInterceptors.js'
import { contextAddScalar, ScalarMethod } from './properties/scalars.js'
import type { ContextFragmentTransports, ContextTransports } from './properties/transport.js'
import {
contextFragmentTransportsAddType,
contextFragmentTransportsAdd,
contextFragmentTransportsConfigureCurrent,
contextFragmentTransportsSetCurrent,
TransportMethod,
Expand Down Expand Up @@ -87,7 +90,7 @@ export interface ClientBase<
const configurationInput extends CalcConfigurationInputForContext<$Context>,
>(configurationInput: configurationInput) => Client<
// @ts-expect-error Non-index type being used
ContextAddConfiguration<$Context, configurationInput>,
ContextFragmentConfigurationConfigure<$Context, configurationInput>,
$Extension
>
}
Expand All @@ -105,16 +108,20 @@ export type Create<$Context extends Context = Context.States.Empty> = <
const configurationInput extends CalcConfigurationInputForContext<$Context>,
>(configurationInput?: configurationInput) => Client<
// @ts-expect-error: Is missing standard configurators
ContextAddConfiguration<$Context, configurationInput>,
ContextFragmentConfigurationConfigure<$Context, configurationInput>,
{}
>

export const createConstructorWithContext = <$Context extends Context>(
context: $Context,
): Create<$Context> =>
(configurationInput) => {
const newContext = configurationInput
? contextAddConfigurationInput(context, configurationInput as ConfigurationIndex.Input)
const configurationInput_ = configurationInput as undefined | ConfigurationIndex.Input
const newContext = configurationInput_
? contextMergeFragment(
context,
contextFragmentConfigurationConfigure(context, configurationInput_),
)
: context
return createWithContext(newContext) as any
}
Expand All @@ -124,58 +131,48 @@ export const create: Create = createConstructorWithContext(Context.States.empty)
export const createWithContext = <$Context extends Context>(
context: $Context,
): Client<$Context, {}> => {
const copy = (fragment: null | ContextFragment) => {
if (!fragment) return client
const newContext = contextMergeFragment(context, fragment)
// if (newContext === context) return client // todo is needed?
return createWithContext(newContext) as any
}

const client: Client<$Context, {}> = {
...({} as Client<$Context, {}>),
_: context,
anyware(interceptor) {
const interceptor_ = interceptor as any as RequestPipeline.BaseInterceptor
const newContext = contextMergeFragment(context, contextFragmentAddRequestInterceptor(context, interceptor_))
return createWithContext(newContext) as any
return copy(contextFragmentRequestInterceptorsAdd(context, interceptor_))
},
properties(properties) {
const newContext = contextMergeFragment(context, contextFragmentAddProperties(context, properties))
return createWithContext(newContext) as any
return copy(contextFragmentPropertiesAdd(context, properties))
},
use(extension) {
const newContext = contextMergeFragment(context, contextFragmentExtensionsAddOne(context, extension))
return createWithContext(newContext) as any
return copy(contextFragmentExtensionsAdd(context, extension))
},
scalar: ((...args: ScalarMethod.Arguments) => {
const scalar = ScalarMethod.normalizeArguments(args)
const newContext = contextAddScalar(context, scalar)
return createWithContext(newContext) as any
return copy(contextAddScalar(context, scalar))
}) as any,
with(configurationInput) {
const newContext = contextAddConfigurationInput(context, configurationInput as ConfigurationIndex.Input)
if (newContext === context) return client
return createWithContext(newContext) as any
const configurationInput_ = configurationInput as ConfigurationIndex.Input
return copy(contextFragmentConfigurationConfigure(context, configurationInput_))
},
transport: ((...args: TransportMethod.Arguments) => {
const input = TransportMethod.normalizeArguments(args)
let fragment2: ContextFragmentTransports
// let fragment2: ContextFragmentTransports
switch (input[0]) {
case TransportMethod.overloadCase.configureCurrent: {
const fragmentMaybe = contextFragmentTransportsConfigureCurrent(context, input[1])
if (!fragmentMaybe) return client
fragment2 = fragmentMaybe
break
return copy(contextFragmentTransportsConfigureCurrent(context, input[1]))
}
case TransportMethod.overloadCase.setCurrent: {
const fragmentMaybe = contextFragmentTransportsSetCurrent(context, input[1], input[2])
if (!fragmentMaybe) return client
fragment2 = fragmentMaybe
break
return copy(contextFragmentTransportsSetCurrent(context, input[1], input[2]))
}
case TransportMethod.overloadCase.addType: {
fragment2 = contextFragmentTransportsAddType(context, input[1])
break
return copy(contextFragmentTransportsAdd(context, input[1]))
}
}

return createWithContext(Object.freeze({
...context,
...fragment2,
})) as any
}) as any,
gql: ((...args: GqlMethod.Arguments) => {
const { document: query } = GqlMethod.normalizeArguments(args)
Expand Down
59 changes: 0 additions & 59 deletions src/client/properties/addConfiguration.ts

This file was deleted.

File renamed without changes.
100 changes: 100 additions & 0 deletions src/client/properties/configuration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { hasNonUndefinedKeys, type Writeable } from '../../lib/prelude.js'
import type { ConfigurationIndex } from '../../types/ConfigurationIndex.js'
import type { Configurator } from '../../types/configurator.js'
import { Configurators } from '../../types/configurators/_namespace.js'
import type { Context } from '../../types/context.js'

export const contextFragmentConfigurationConfigure = <
context extends Context,
configurationInput extends ConfigurationIndex.Input,
>(context: context, configurationInput: configurationInput): null | Writeable<ContextFragmentConfiguration> => {
if (!hasNonUndefinedKeys(configurationInput)) return null
// todo: performant checking if input changes configuration. If no change, then no copy context.
// For default input resolvers we can do this automatically (shallow merge)
// Any custom input resolvers would need to implement their own "is changed" logic.

const configuration: Writeable<ContextFragmentConfiguration['configuration'] & ConfigurationIndex> = {
...context.configuration,
}

for (const configuratorName in configurationInput) {
const entry = configuration[configuratorName]
const current = entry.configurator.inputResolver({
current: entry.current,
input: configurationInput[configuratorName]!,
})
const newEntry = Object.freeze({
...entry,
current,
})
configuration[configuratorName] = newEntry
}

const fragment = {
configuration,
}

return fragment
}

export type ContextFragmentConfigurationConfigure<
$Context extends Context,
$ConfigurationInput extends ConfigurationIndex.Input,
__ = {
[_ in keyof $Context]: _ extends 'configuration' ? {
readonly [_ in keyof $Context['configuration']]: _ extends keyof $ConfigurationInput
? $ConfigurationInput[_] extends object ? {
// @ts-expect-error Non-index type being used
configurator: $Context['configuration'][_]['configurator']
current: Configurator.ApplyInputResolver$Func<
// @ts-expect-error Non-index type being used
$Context['configuration'][_]['configurator'],
// @ts-expect-error Non-index type being used
$Context['configuration'][_]['current'],
$ConfigurationInput[_]
>
}
: $Context['configuration'][_]
: $Context['configuration'][_]
}
: $Context[_]
},
> = __

// ------------------------------------------------------------
// Context Fragment
// ------------------------------------------------------------

export interface ContextFragmentConfiguration {
readonly configuration: {
readonly output: {
readonly configurator: Configurators.Output.OutputConfigurator
readonly current: Configurators.Output.OutputConfigurator['default']
}
readonly check: {
readonly configurator: Configurators.Check.CheckConfigurator
readonly current: Configurators.Check.CheckConfigurator['default']
}
readonly schema: {
readonly configurator: Configurators.Schema.SchemaConfigurator
readonly current: Configurators.Schema.SchemaConfigurator['default']
}
}
}

export const contextFragmentConfigurationEmpty: ContextFragmentConfiguration = {
configuration: Object.freeze({
output: Object.freeze({
configurator: Configurators.Output.configurator,
current: Configurators.Output.configurator.default,
}),
check: Object.freeze({
configurator: Configurators.Check.configurator,
current: Configurators.Check.configurator.default,
}),
schema: Object.freeze({
configurator: Configurators.Schema.configurator,
current: Configurators.Schema.configurator.default,
}),
}),
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { describe, expect, expectTypeOf } from 'vitest'
import { ATransport, ATransportBuilder } from '../../../tests/_/fixtures/transports.js'
import { test } from '../../../tests/_/helpers.js'
import { Extension } from '../../extension/$.js'
import { createInterceptor } from './addRequestInterceptor.js'
import { createInterceptor } from './requestInterceptors.js'

const AExtension = Extension(`AExtension`).return()
type AExtension = ReturnType<typeof AExtension>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import type { Writable } from 'type-fest'
import type { EmptyObject, Writable } from 'type-fest'
import type { Extension } from '../../extension/$.js'
import type { Anyware } from '../../lib/anyware/_namespace.js'
import type { UnknownOrAnyToNever } from '../../lib/prelude.js'
import { type EmptyArray, emptyArray, emptyObject, type UnknownOrAnyToNever } from '../../lib/prelude.js'
import { type Context } from '../../types/context.js'
import type { Transport } from '../../types/Transport.js'
import { type ContextFragmentAddProperties, contextFragmentAddProperties } from './addProperties.js'
import { contextFragmentAddRequestInterceptor } from './addRequestInterceptor.js'
import { type ContextAddTransportOptional, contextFragmentTransportsAddType } from './transport.js'
import { type ContextFragmentAddProperties, contextFragmentPropertiesAdd } from './properties.js'
import { contextFragmentRequestInterceptorsAdd } from './requestInterceptors.js'
import { type ContextAddTransportOptional, contextFragmentTransportsAdd } from './transport.js'

// todo: type to use multiple to reduce type instantiation
// useful for presets
Expand Down Expand Up @@ -47,7 +47,7 @@ export type ContextAddOneExtension<
}

// todo: make return a fragment
export const contextFragmentExtensionsAddOne = <
export const contextFragmentExtensionsAdd = <
const $Context extends Context,
$Extension extends Extension,
>(context: $Context, extension: $Extension): ContextAddOneExtension<$Context, $Extension> => {
Expand All @@ -60,16 +60,37 @@ export const contextFragmentExtensionsAddOne = <
}),
}

const newContextPropertiesFragment = contextFragmentAddProperties(context, extension.propertiesStatic)
if (newContextPropertiesFragment) {
Object.assign(fragment, newContextPropertiesFragment)
const propertiesFragment = contextFragmentPropertiesAdd(context, extension.propertiesStatic)
if (propertiesFragment) {
Object.assign(fragment, propertiesFragment)
}
if (extension.transport) {
Object.assign(fragment, contextFragmentTransportsAddType(context, extension.transport))
Object.assign(fragment, contextFragmentTransportsAdd(context, extension.transport))
}
if (extension.requestInterceptor) {
Object.assign(fragment, contextFragmentAddRequestInterceptor(context, extension.requestInterceptor))
Object.assign(fragment, contextFragmentRequestInterceptorsAdd(context, extension.requestInterceptor))
}

return fragment as any
}

// ------------------------------------------------------------
// Context Fragment
// ------------------------------------------------------------

export interface ContextFragmentExtensions {
readonly extensions: readonly Extension[]
readonly extensionsIndex: {
[extensionName: string]: Extension
}
}

export interface ContextFragmentExtensionsEmpty extends ContextFragmentExtensions {
extensions: EmptyArray
extensionsIndex: EmptyObject
}

export const contextFragmentExtensionsEmpty: ContextFragmentExtensionsEmpty = {
extensions: emptyArray,
extensionsIndex: emptyObject,
}
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export type ContextFragmentAddProperties<
// ContextReducer
// ------------------------------------------------------------

export const contextFragmentAddProperties = <$Context extends Context>(
export const contextFragmentPropertiesAdd = <$Context extends Context>(
context: $Context,
propertiesStatic: PropertiesStatic,
): null | ContextFragmentProperties => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { expect, expectTypeOf } from 'vitest'
import { ATransport, BTransport } from '../../../tests/_/fixtures/transports.js'
import { g0, test } from '../../../tests/_/helpers.js'
import { createInterceptor } from './addRequestInterceptor.js'
import { createInterceptor } from './requestInterceptors.js'

const g1 = g0.transport(ATransport)

Expand Down
Loading

0 comments on commit 993f3e7

Please sign in to comment.