Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/eager-cities-sneeze.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@traversable/schema": patch
---

feat(schema): adds `Seed.fromSchema`
8 changes: 6 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
coverage/
*.tsbuildinfo
node_modules/
.DS_Store
*/.DS_Store

.attest
.nvmrc
# tmp/
Expand All @@ -18,3 +17,8 @@ packages/*/test/__generated__/*
packages/*/build/*
packages/*/dist/*
!packages/*/test/*

# Keep this line at the bottom, otherwise .DS_Store files in directories re-included with `!` will start adding .DS_Store files again.
# If you ever accidentally commit a .DS_Store file, run this command to remove it from the cache recursively:
# $ find . -name .DS_Store -print0 | xargs -0 git rm --ignore-unmatch
.DS_Store
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,10 @@ flowchart TD
registry(@traversable/registry)
json(@traversable/json) -.-> registry(@traversable/registry)
schema-zod-adapter(@traversable/schema-zod-adapter) -.-> registry(@traversable/registry)
schema-core(@traversable/schema-core) -.-> json(@traversable/json)
schema-core(@traversable/schema-core) -.-> registry(@traversable/registry)
schema(@traversable/schema) -.-> json(@traversable/json)
schema(@traversable/schema) -.-> schema-core(@traversable/schema-core)
schema(@traversable/schema) -.-> schema-zod-adapter(@traversable/schema-zod-adapter)
schema(@traversable/schema) -.depends on.-> registry(@traversable/registry)
```
6 changes: 3 additions & 3 deletions bin/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -487,12 +487,12 @@ namespace write {
export const workspaceTestVersion = defineEffect(
($) => pipe(
([
`import { ${Transform.toCamelCase($.pkgName)} } from '${SCOPE}/${$.pkgName}'`,
`import * as vi from 'vitest'`,
`import pkg from '../package.json'`,
`import { ${Transform.toCamelCase($.pkgName)} } from '${SCOPE}/${$.pkgName}'`,
``,
`vi.describe('〖⛳️〗‹‹‹ ❲@${$.pkgName}❳', () => {`,
` vi.it('〖⛳️〗› ❲${Transform.toCamelCase($.pkgName)}.VERSION❳', () => {`,
`vi.describe('〖⛳️〗‹‹‹ ❲${SCOPE}/${$.pkgName}❳', () => {`,
` vi.it('〖⛳️〗› ❲${Transform.toCamelCase($.pkgName)}#VERSION❳', () => {`,
` const expected = \`\${pkg.name}@\${pkg.version}\``,
` vi.assert.equal(${Transform.toCamelCase($.pkgName)}.VERSION, expected)`,
` })`,
Expand Down
1 change: 1 addition & 0 deletions config/__generated__/package-list.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 2 additions & 3 deletions packages/json/src/json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,10 +137,9 @@ export const Functor: T.Functor<Free, Json> = {

export const fold = fn.cata(Functor)
export const unfold = fn.ana(Functor)
export const reduce = <S, T>(toJson: T.Coalgebra<Free, S>, fromJson: T.Algebra<Free, T>) => fn.hylo(Functor)(fromJson, toJson)

export namespace Recursive {
export const show: T.Functor.Algebra<Free, string> = (x) => {
export const toStringImpl: T.Functor.Algebra<Free, string> = (x) => {
switch (true) {
default: return fn.exhaustive(x)
case typeof x === 'string': return JSON_stringify(x, null, 1)
Expand All @@ -155,7 +154,7 @@ export namespace Recursive {
}
}

export const toString = fold(Recursive.show)
export const toString = fold(Recursive.toStringImpl)
}

/**
Expand Down
37 changes: 15 additions & 22 deletions packages/registry/src/function.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,20 @@
import type { Functor, Kind, HKT, IndexedFunctor } from './types.js'

export {
ana,
cata,
constant as const,
exhaustive,
identity,
}
import type { Functor, Kind, HKT } from './types.js'

/** @internal */
const Object_keys
: <T>(x: T) => map.keyof<T>[]
= <never>globalThis.Object.keys

const identity
export const identity
: <T>(x: T) => T
= (x) => x

const constant
export { const_ as const }
export const const_
: <T>(x: T) => <S>(y?: S) => T
= (x) => () => x

const exhaustive
export const exhaustive
: <_ extends never = never>(..._: _[]) => _
= (..._) => { throw new Error('Exhaustive match failed') }

Expand Down Expand Up @@ -52,16 +45,16 @@ const exhaustive
* - {@link cata `fn.cata`}
*/

function ana<F extends HKT, _F>(Functor: Functor<F, _F>):
export function ana<F extends HKT, Fixpoint>(Functor: Functor<F, Fixpoint>):
<T>(coalgebra: Functor.Coalgebra<F, T>)
=> <S extends _F>(expr: S)
=> <S extends Fixpoint>(expr: S)
=> Kind<F, T>

/// impl.
function ana<F extends HKT>(Functor: Functor<F>) {
export function ana<F extends HKT>(F: Functor<F>) {
return <T>(coalgebra: Functor.Coalgebra<F, T>) => {
return function loop(expr: T): Kind<F, T> {
return Functor.map(loop)(coalgebra(expr))
return F.map(loop)(coalgebra(expr))
}
}
}
Expand All @@ -75,12 +68,12 @@ function ana<F extends HKT>(Functor: Functor<F>) {
* - the [Wikipedia page](https://en.wikipedia.org/wiki/Catamorphism) on catamorphisms
* - {@link ana `ana`}
*/
function cata<F extends HKT, _F>(F: Functor<F, _F>):
export function cata<F extends HKT, Fixpoint>(Functor: Functor<F, Fixpoint>):
<T>(algebra: Functor.Algebra<F, T>)
=> <S extends _F>(term: S)
=> <S extends Fixpoint>(term: S)
=> T

function cata<F extends HKT>(F: Functor<F>) {
export function cata<F extends HKT>(F: Functor<F>) {
return <T>(algebra: Functor.Algebra<F, T>) => {
return function loop(term: Kind<F, T>): T {
return algebra(F.map(loop)(term))
Expand All @@ -89,12 +82,12 @@ function cata<F extends HKT>(F: Functor<F>) {
}

export function cataIx
<Ix, F extends HKT, _F>(F: IndexedFunctor<Ix, F, _F>):
<Ix, F extends HKT, Fixpoint>(IxFunctor: Functor.Ix<Ix, F, Fixpoint>):
<T>(algebra: Functor.IndexedAlgebra<Ix, F, T>)
=> <S extends _F>(term: S, ix: Ix)
=> <S extends Fixpoint>(term: S, ix: Ix)
=> T

export function cataIx<Ix, F extends HKT, _F>(F: IndexedFunctor<Ix, F, _F>) {
export function cataIx<Ix, F extends HKT, Fixpoint>(F: Functor.Ix<Ix, F, Fixpoint>) {
return <T>(g: Functor.IndexedAlgebra<Ix, F, T>) => {
return function loop(term: Kind<F, T>, ix: Ix): T {
return g(F.mapWithIndex(loop)(term, ix), ix)
Expand Down
23 changes: 18 additions & 5 deletions packages/registry/src/satisfies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,30 @@ export type Atoms = [
/**
* ## {@link Mut `Mut`}
*
* Applies an inductive constraint to a type parameter such that
* its instantiations are all "mutable".
*
* This isn't a pattern I've seen used elsewhere in the wild, but
* I suspect that it's more efficient that applying a transformation
* to a previously declared type parameter to force it to be mutable.
*
* Because {@link Mut `Mut`} is an "inductive constraint", the compiler
* applies the transformation _while_ instantiating its initial value.
*
* @example
* declare function defineImmutable<const T>(x: T): T
* declare function defineMutable<const T extends Mut<T>>(x: T): T
* import { Mut } from '@traversable/registry'
*
* // BEFORE:
* declare function defineImmutable<const T>(x: T): T
* const ex_01 = defineImmutable([1, [2, { x: [3, { y: { z: [4] } } ] } ] ])
* const ex_02 = defineMutable([1, [2, { x: [3, { y: { z: [4] } } ] } ] ])
*
* ex_01
* // ^? const ex_01: readonly [1, readonly [2, readonly [3, { readonly x: readonly [4, { readonly y: { readonly z: readonly [5] } } ] } ] ] ]
*
* // AFTER:
* declare function defineMutable<const T extends Mut<T>>(x: T): T
* // ^^^^^^^^^^^^^^^^ note how Mut is applied to T in T's own 'extends' clause
*
* const ex_02 = defineMutable([1, [2, { x: [3, { y: { z: [4] } } ] } ] ])
* ex_02
* // ^? const ex_02: [1, [2, [3, { x: [4, { y: { z: [5, [6, [7] ] ] } } ] } ] ] ]
*
Expand All @@ -28,4 +42,3 @@ export type Mut<T, Atom = Atoms[number]>
: { -readonly [ix in keyof T]: Mut<T[ix], Atom> }

export type Mutable<T> = never | { -readonly [K in keyof T]: T[K] }

66 changes: 10 additions & 56 deletions packages/registry/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,61 +1,15 @@
import type { symbol as Symbol } from './uri.js'
export type * from './types/functor.js'
export type * from './types/hkt.js'
export type * from './types/newtype.js'

// data types
export type Primitive = null | undefined | symbol | boolean | number | bigint | string
export type Force<T> = never | { -readonly [K in keyof T]: T[K] }
export type Entry<T> = readonly [k: string, v: T]
export type Entries<T> = readonly Entry<T>[]
export type Param<T> = T extends (_: infer I) => unknown ? I : never
export type Intersect<X, _ = unknown> = X extends readonly [infer H, ...infer T] ? Intersect<T, _ & H> : _
export type inline<T> = never | T

/** @ts-expect-error hush */
export interface newtype<T extends {} = {}> extends inline<T> { }
export interface TypeError<Msg extends string = string> extends newtype<{ [K in Msg]: Symbol.type_error }> { }

export interface HKT<I = unknown, O = unknown> extends newtype<{ [0]: I;[-1]: O }> { _applied?: unknown }
export type Kind<F extends HKT, T extends F[0] = F[0]> = (F & { [0]: T })[-1]

export declare namespace Kind {
export type infer<G extends HKT> = G extends
& { [0]: G[0 & keyof G]; _applied: G["_applied" & keyof G] }
& (infer F extends HKT)
? F
: never
}

export interface Const<T = unknown> extends HKT { [-1]: T }
export interface Identity extends HKT { [-1]: this[0] }

interface Typeclass<F extends HKT, _F = any> {
readonly _F?: 0 extends _F & 1 ? F : Extract<_F, HKT>
}

export interface IndexedFunctor<Ix, F extends HKT = HKT, _F = any> extends Functor<F, _F> {
mapWithIndex<S, T>(f: (s: S, ix: Ix) => T): (F: Kind<F, S>, ix: Ix) => Kind<F, T>
}

/**
* ## {@link Functor `Functor`}
*/
export interface Functor<F extends HKT = HKT, _F = any> extends Typeclass<F, 0 extends _F & 1 ? F : _F> {
map<S, T>(f: (s: S) => T): (F: Kind<F, S>) => Kind<F, T>
}

export declare namespace Functor {
export { Algebra, IndexedAlgebra, Coalgebra }
export type infer<T> = T extends Functor<any, infer F> ? Exclude<F, undefined> : never
}
export declare namespace Functor {
type map<F extends HKT> =
| never
| {
<S, T>(F: Kind<F, S>, f: (s: S) => T): Kind<F, T>
<S, T>(f: (s: S) => T): { (F: Kind<F, S>): Kind<F, T> }
}
}

export type Algebra<F extends HKT, T> = never | { (term: Kind<F, T>): T }
export type Coalgebra<F extends HKT, T> = never | { (expr: T): Kind<F, T> }
export type IndexedAlgebra<Ix, F extends HKT, T> = never | { (term: Kind<F, T>, ix: Ix): T }
export type Entries<T = unknown> = readonly Entry<T>[]

// transforms
export type Force<T> = never | { -readonly [K in keyof T]: T[K] }
export type Intersect<X, _ = unknown> = X extends readonly [infer H, ...infer T] ? Intersect<T, _ & H> : _

// infererence
export type Param<T> = T extends (_: infer I) => unknown ? I : never
44 changes: 44 additions & 0 deletions packages/registry/src/types/functor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import type { Kind, HKT } from './hkt.js'

export type Algebra<F extends HKT, T> = never | { (term: Kind<F, T>): T }
export type Coalgebra<F extends HKT, T> = never | { (expr: T): Kind<F, T> }
export type IndexedAlgebra<Ix, F extends HKT, T> = never | { (term: Kind<F, T>, ix: Ix): T }

/**
* ## {@link Functor `Functor`}
*/
export interface Functor<F extends HKT = HKT, Fixpoint = Kind<F, unknown>> {
map<S, T>(f: (s: S) => T): (F: Kind<F, S>) => Kind<F, T>
}

export declare namespace Functor {
export {
Algebra,
Coalgebra,
IndexedAlgebra,
}
/**
* ## {@link Ix `Functor.Ix`}
*
* A {@link Functor `Functor`} that has been equipped with an index.
*
* In addition to `map`, instances of {@link Ix `Functor.Ix`} also implement a method called
* `mapWithIndex` that will provide to its callback argument an additional parameter, `ix`.
*
* Note that `ix` does not have to be a number. If the source data type is a record, `ix`
* would be a `string`. For a tree, the index might be a path.
*
* The nice thing about separating the "index" logic into a functor is that it clearly delineates
* who is responsible for what:
*
* - When defining a {@link Ix `Functor.Ix`} instance, it's your responsibility to
*
* 1. keep track of the index
* 2. make sure updates to the index are deterministic
* 3. provide the index to the caller
* 4. define its semantics, and communicate them clearly
*/
export interface Ix<Ix, F extends HKT = HKT, Fixpoint = Kind<F, unknown>> extends Functor<F, Fixpoint> {
mapWithIndex<S, T>(f: (s: S, ix: Ix) => T): (F: Kind<F, S>, ix: Ix) => Kind<F, T>
}
}
17 changes: 17 additions & 0 deletions packages/registry/src/types/hkt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { newtype } from './newtype.js'

export interface HKT<I = unknown, O = unknown> extends newtype<{ [0]: I, [-1]: O }> { _applied?: unknown }

export type Kind<F extends HKT, T extends F[0] = F[0]> = (F & { [0]: T })[-1]

export declare namespace Kind {
export type infer<G extends HKT> = G extends
& { [0]: G[0 & keyof G]; _applied: G["_applied" & keyof G] }
& (infer F extends HKT)
? F
: never
}

export interface Const<T = unknown> extends HKT { [-1]: T }

export interface Identity extends HKT { [-1]: this[0] }
14 changes: 14 additions & 0 deletions packages/registry/src/types/newtype.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { symbol } from '../uri.js'

export type inline<T> = never | T

/** @ts-expect-error hush */
export interface newtype<T extends {} = {}> extends inline<T> { }

export interface TypeError<Msg extends string = string>
extends newtype<{ [K in Msg]: symbol.type_error }> { }

export declare namespace TypeError {
interface Unary<Msg extends string = string, T = {}>
extends newtype<{ [K in Msg]: symbol.type_error } & T> { }
}
Loading