Skip to content

Commit

Permalink
non-hkt computed properties
Browse files Browse the repository at this point in the history
  • Loading branch information
jasonkuhrt committed Feb 2, 2025
1 parent 993f3e7 commit 5264bd4
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 31 deletions.
10 changes: 10 additions & 0 deletions src/client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,16 @@ export const createWithContext = <$Context extends Context>(

Object.assign(client, context.properties.static)

context.properties.computed.forEach(propertiesComputer => {
Object.assign(
client,
propertiesComputer({

Check failure on line 224 in src/client/client.ts

View workflow job for this annotation

GitHub Actions / test e2e usingNode 22

Argument of type '{ context: $Context; client: Client<$Context, {}>; }' is not assignable to parameter of type 'ConstructorParameters<$Context>'.

Check failure on line 224 in src/client/client.ts

View workflow job for this annotation

GitHub Actions / test unit Node 22 @env jsdom

Argument of type '{ context: $Context; client: Client<$Context, {}>; }' is not assignable to parameter of type 'ConstructorParameters<$Context>'.

Check failure on line 224 in src/client/client.ts

View workflow job for this annotation

GitHub Actions / test examples Node 20 @env node

Argument of type '{ context: $Context; client: Client<$Context, {}>; }' is not assignable to parameter of type 'ConstructorParameters<$Context>'.

Check failure on line 224 in src/client/client.ts

View workflow job for this annotation

GitHub Actions / publint

Argument of type '{ context: $Context; client: Client<$Context, {}>; }' is not assignable to parameter of type 'ConstructorParameters<$Context>'.

Check failure on line 224 in src/client/client.ts

View workflow job for this annotation

GitHub Actions / test unit Node 22 @env node

Argument of type '{ context: $Context; client: Client<$Context, {}>; }' is not assignable to parameter of type 'ConstructorParameters<$Context>'.

Check failure on line 224 in src/client/client.ts

View workflow job for this annotation

GitHub Actions / test examples Node 22 @env jsdom

Argument of type '{ context: $Context; client: Client<$Context, {}>; }' is not assignable to parameter of type 'ConstructorParameters<$Context>'.

Check failure on line 224 in src/client/client.ts

View workflow job for this annotation

GitHub Actions / lint

Argument of type '{ context: $Context; client: Client<$Context, {}>; }' is not assignable to parameter of type 'ConstructorParameters<$Context>'.

Check failure on line 224 in src/client/client.ts

View workflow job for this annotation

GitHub Actions / test unit Node 20 @env jsdom

Argument of type '{ context: $Context; client: Client<$Context, {}>; }' is not assignable to parameter of type 'ConstructorParameters<$Context>'.

Check failure on line 224 in src/client/client.ts

View workflow job for this annotation

GitHub Actions / test examples Node 22 @env node

Argument of type '{ context: $Context; client: Client<$Context, {}>; }' is not assignable to parameter of type 'ConstructorParameters<$Context>'.

Check failure on line 224 in src/client/client.ts

View workflow job for this annotation

GitHub Actions / website

Argument of type '{ context: $Context; client: Client<$Context, {}>; }' is not assignable to parameter of type 'ConstructorParameters<$Context>'.

Check failure on line 224 in src/client/client.ts

View workflow job for this annotation

GitHub Actions / test unit Node 20 @env node

Argument of type '{ context: $Context; client: Client<$Context, {}>; }' is not assignable to parameter of type 'ConstructorParameters<$Context>'.

Check failure on line 224 in src/client/client.ts

View workflow job for this annotation

GitHub Actions / types

Argument of type '{ context: $Context; client: Client<$Context, {}>; }' is not assignable to parameter of type 'ConstructorParameters<$Context>'.
context,
client,
}),
)
})

// todo: access computed properties from context
context.extensions.forEach(_ => {
const configurationIndex = context.configuration as ConfigurationIndex
Expand Down
6 changes: 3 additions & 3 deletions src/client/properties/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,15 @@ export interface ContextFragmentConfiguration {
readonly configuration: {
readonly output: {
readonly configurator: Configurators.Output.OutputConfigurator
readonly current: Configurators.Output.OutputConfigurator['default']
readonly current: Configurators.Output.OutputConfigurator['normalizedIncremental']
}
readonly check: {
readonly configurator: Configurators.Check.CheckConfigurator
readonly current: Configurators.Check.CheckConfigurator['default']
readonly current: Configurators.Check.CheckConfigurator['normalizedIncremental']
}
readonly schema: {
readonly configurator: Configurators.Schema.SchemaConfigurator
readonly current: Configurators.Schema.SchemaConfigurator['default']
readonly current: Configurators.Schema.SchemaConfigurator['normalizedIncremental']
}
}
}
Expand Down
23 changes: 12 additions & 11 deletions src/client/properties/extensions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,18 @@ describe(`transport`, () => {
})
})

describe(`properties`, () => {
const properties1 = { foo: `bar` }

test(`can be added (static)`, ({ g0 }) => {
const BExtension = Extension(`BExtension`).properties(properties1).return()
const g1a = g0.use(BExtension())
const g1b = g0.properties(properties1)
expect(g1a._.properties).toEqual(g1b._.properties)
expectTypeOf(g1a._.properties).toEqualTypeOf(g1b._.properties)
})
})

describe(`request interceptor`, () => {
test(`can be added`, ({ g0 }) => {
const i1 = createInterceptor(async ({ pack }) => {

Check failure on line 52 in src/client/properties/extensions.test.ts

View workflow job for this annotation

GitHub Actions / types

Property 'pack' does not exist on type '{}'.
Expand All @@ -50,17 +62,6 @@ describe(`request interceptor`, () => {
expectTypeOf(g1a._.requestPipelineInterceptors).toEqualTypeOf(g1b._.requestPipelineInterceptors)
})
})
describe(`properties`, () => {
const properties1 = { foo: `bar` }

test(`can be added (statically given)`, ({ g0 }) => {
const BExtension = Extension(`BExtension`).properties(properties1).return()
const g1a = g0.use(BExtension())
const g1b = g0.properties(properties1)
expect(g1a._.properties).toEqual(g1b._.properties)
expectTypeOf(g1a._.properties).toEqualTypeOf(g1b._.properties)
})
})

// test('using an extension without type hooks leaves them empty', () => {
// const Ex = Extension('test').done()
Expand Down
43 changes: 36 additions & 7 deletions src/client/properties/properties.test.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,49 @@
import { expect, expectTypeOf } from 'vitest'
import { describe, expect, expectTypeOf } from 'vitest'
import { test } from '../../../tests/_/helpers.js'
import { Context } from '../../types/context.js'
import { createPropertiesComputer } from './properties.js'

const properties1 = { foo: `bar` }
const propertiesStatic1 = { foo: `bar` }
// const propertiesComputer1 = createPropertiesComputer((parameters) => ({ parameters }))

test(`initial context is empty`, ({ g0 }) => {
expect(g0._.properties).toEqual(Context.States.empty.properties)
expectTypeOf(g0._.properties).toEqualTypeOf<Context.States.Empty['properties']>()
})

test(`can add static properties`, ({ g0 }) => {
const g1 = g0.properties(properties1)
const g1 = g0.properties(propertiesStatic1)
// Context extended
expect(g1._.properties.static).toEqual(properties1)
expectTypeOf(g1._.properties.static).toMatchTypeOf<typeof properties1>()
expect(g1._.properties.static).toEqual(propertiesStatic1)
expectTypeOf(g1._.properties.static).toMatchTypeOf<typeof propertiesStatic1>()
// Client extended
expect(g1).toMatchObject(properties1)
expectTypeOf(g1.foo).toMatchTypeOf<typeof properties1['foo']>()
expect(g1).toMatchObject(propertiesStatic1)
expectTypeOf(g1.foo).toMatchTypeOf<typeof propertiesStatic1['foo']>()
})

describe(`computed properties`, () => {
test(`can be added`, ({ g0 }) => {
const computer = createPropertiesComputer<typeof g0>()((parameters) => ({ parameters }))
const g1 = g0.properties(computer)
// Context extended
expect(g1._.properties.static).toEqual({})
expect(g1._.properties.computed).toEqual([computer])
expectTypeOf(g1._.properties.static).toMatchTypeOf<{ parameters: { context: typeof g0._; client: typeof g0 } }>()
expectTypeOf(g1._.properties.computed).toEqualTypeOf<readonly []>()
// Client extended
expect(g1.parameters.client).toBe(g1)
expect(g1.parameters.context).toEqual(g1._)
})
test(`Are computed every time the client is copied`, ({ g0 }) => {
const computer = createPropertiesComputer()((parameters) => ({
preflightCheckNow: parameters.context.configuration.check.current.preflight,
}))
const g1 = g0.properties(computer)
const g2 = g1.with({ check: { preflight: false } })
const g3 = g2.with({ check: { preflight: true } })
expect(g1.preflightCheckNow).toEqual(true)
expect(g2.preflightCheckNow).toEqual(false)
expect(g3.preflightCheckNow).toEqual(true)
})
// todo: test computed properties that use HKT to also compute at the type level.
})
55 changes: 47 additions & 8 deletions src/client/properties/properties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,51 @@ import type { Client } from '../client.js'
// Method
// ------------------------------------------------------------

// todo have the client type be passed through too? Using `this` from parent?
export interface AddPropertiesMethod<$Context extends Context> {
<$PropertiesStatic extends PropertiesStatic>(propertiesStatic: $PropertiesStatic): Client<
<$Properties extends Properties>(properties: $Properties | PropertiesComputer<$Context, $Properties>): Client<
{
[_ in keyof $Context]: _ extends 'properties' ? ContextFragmentAddProperties<$Context, $PropertiesStatic>
[_ in keyof $Context]: _ extends 'properties' ? ContextFragmentAddProperties<$Context, $Properties>
: $Context[_]
},
{}
>
}

// ------------------------------------------------------------
// Helpers
// ------------------------------------------------------------

export type PropertiesComputer<$Context extends Context, $Properties extends Properties> = (
parameters: {
context: $Context
client: Client<$Context, {}>
},
) => $Properties

export const createPropertiesComputer = <
$Client extends { _: Context },
>() =>
<
$PropertiesComputer extends (
parameters: {
context: $Client['_']
client: $Client
},
) => Properties,
>(
propertiesComputer: $PropertiesComputer,
) => propertiesComputer

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

export type PropertiesStatic = object
export type Properties = object

export interface ContextFragmentProperties {
readonly properties: {
readonly static: PropertiesStatic
readonly static: Properties
readonly computed: ReadonlyArray<
<$Context extends Context>(parameters: Extension.ConstructorParameters<$Context>) => object
>
Expand All @@ -55,7 +81,7 @@ export const contextFragmentPropertiesEmpty: ContextFragmentPropertiesEmpty = Ob

export type ContextFragmentAddProperties<
$Context extends Context,
$PropertiesStatic extends PropertiesStatic,
$PropertiesStatic extends Properties,
__NewStaticProperties = ObjectMergeShallow<$Context['properties']['static'], $PropertiesStatic>,
__NewContextProperties = {
static: __NewStaticProperties
Expand All @@ -67,17 +93,30 @@ export type ContextFragmentAddProperties<
// ContextReducer
// ------------------------------------------------------------

type MethodArguments = Properties | PropertiesComputer<Context, Properties>

export const contextFragmentPropertiesAdd = <$Context extends Context>(
context: $Context,
propertiesStatic: PropertiesStatic,
propertiesInput: MethodArguments,
): null | ContextFragmentProperties => {
if (isObjectEmpty(propertiesStatic)) return null
if (typeof propertiesInput === `function`) {
const properties = {
...context.properties,
computed: [
...context.properties.computed,
propertiesInput,
],
}
return { properties }

Check failure on line 110 in src/client/properties/properties.ts

View workflow job for this annotation

GitHub Actions / test e2e usingNode 22

Type '{ computed: Function[]; static: Properties; }' is not assignable to type '{ readonly static: object; readonly computed: readonly (<$Context extends Context>(parameters: ConstructorParameters<$Context>) => object)[]; }'.

Check failure on line 110 in src/client/properties/properties.ts

View workflow job for this annotation

GitHub Actions / test unit Node 22 @env jsdom

Type '{ computed: Function[]; static: Properties; }' is not assignable to type '{ readonly static: object; readonly computed: readonly (<$Context extends Context>(parameters: ConstructorParameters<$Context>) => object)[]; }'.

Check failure on line 110 in src/client/properties/properties.ts

View workflow job for this annotation

GitHub Actions / test examples Node 20 @env node

Type '{ computed: Function[]; static: Properties; }' is not assignable to type '{ readonly static: object; readonly computed: readonly (<$Context extends Context>(parameters: ConstructorParameters<$Context>) => object)[]; }'.

Check failure on line 110 in src/client/properties/properties.ts

View workflow job for this annotation

GitHub Actions / publint

Type '{ computed: Function[]; static: Properties; }' is not assignable to type '{ readonly static: object; readonly computed: readonly (<$Context extends Context>(parameters: ConstructorParameters<$Context>) => object)[]; }'.

Check failure on line 110 in src/client/properties/properties.ts

View workflow job for this annotation

GitHub Actions / test unit Node 22 @env node

Type '{ computed: Function[]; static: Properties; }' is not assignable to type '{ readonly static: object; readonly computed: readonly (<$Context extends Context>(parameters: ConstructorParameters<$Context>) => object)[]; }'.

Check failure on line 110 in src/client/properties/properties.ts

View workflow job for this annotation

GitHub Actions / test examples Node 22 @env jsdom

Type '{ computed: Function[]; static: Properties; }' is not assignable to type '{ readonly static: object; readonly computed: readonly (<$Context extends Context>(parameters: ConstructorParameters<$Context>) => object)[]; }'.

Check failure on line 110 in src/client/properties/properties.ts

View workflow job for this annotation

GitHub Actions / lint

Type '{ computed: Function[]; static: Properties; }' is not assignable to type '{ readonly static: object; readonly computed: readonly (<$Context extends Context>(parameters: ConstructorParameters<$Context>) => object)[]; }'.

Check failure on line 110 in src/client/properties/properties.ts

View workflow job for this annotation

GitHub Actions / test unit Node 20 @env jsdom

Type '{ computed: Function[]; static: Properties; }' is not assignable to type '{ readonly static: object; readonly computed: readonly (<$Context extends Context>(parameters: ConstructorParameters<$Context>) => object)[]; }'.

Check failure on line 110 in src/client/properties/properties.ts

View workflow job for this annotation

GitHub Actions / test examples Node 22 @env node

Type '{ computed: Function[]; static: Properties; }' is not assignable to type '{ readonly static: object; readonly computed: readonly (<$Context extends Context>(parameters: ConstructorParameters<$Context>) => object)[]; }'.

Check failure on line 110 in src/client/properties/properties.ts

View workflow job for this annotation

GitHub Actions / website

Type '{ computed: Function[]; static: Properties; }' is not assignable to type '{ readonly static: object; readonly computed: readonly (<$Context extends Context>(parameters: ConstructorParameters<$Context>) => object)[]; }'.

Check failure on line 110 in src/client/properties/properties.ts

View workflow job for this annotation

GitHub Actions / test unit Node 20 @env node

Type '{ computed: Function[]; static: Properties; }' is not assignable to type '{ readonly static: object; readonly computed: readonly (<$Context extends Context>(parameters: ConstructorParameters<$Context>) => object)[]; }'.

Check failure on line 110 in src/client/properties/properties.ts

View workflow job for this annotation

GitHub Actions / types

Type '{ computed: Function[]; static: Properties; }' is not assignable to type '{ readonly static: object; readonly computed: readonly (<$Context extends Context>(parameters: ConstructorParameters<$Context>) => object)[]; }'.
}

if (isObjectEmpty(propertiesInput)) return null

const properties = {
...context.properties,
static: Object.freeze({
...context.properties.static,
...propertiesStatic,
...propertiesInput,
}),
}

Expand Down
4 changes: 2 additions & 2 deletions src/types/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,12 @@ import { type EmptyArray, type ObjectMergeShallow } from '../lib/prelude.js'

export interface Context
extends
ContextFragmentConfiguration,
ContextFragmentTransports,
ContextFragmentProperties,
ContextFragmentRequestInterceptors,
ContextFragmentExtensions,
ContextFragmentScalars,
ContextFragmentConfiguration
ContextFragmentScalars
{
// Type Level Properties
/**
Expand Down

0 comments on commit 5264bd4

Please sign in to comment.