Skip to content

Commit

Permalink
First draft of IPLD based schema
Browse files Browse the repository at this point in the history
  • Loading branch information
Gozala committed Apr 3, 2023
1 parent 59d2db7 commit f398140
Show file tree
Hide file tree
Showing 3 changed files with 490 additions and 0 deletions.
107 changes: 107 additions & 0 deletions packages/schema/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
{
"name": "@ucanto/schema",
"description": "ucanto schema",
"version": "5.2.0",
"keywords": [
"UCAN",
"RPC",
"IPLD",
"JWT",
"multicodec",
"codec",
"invocation"
],
"files": [
"src",
"dist/src"
],
"repository": {
"type": "git",
"url": "https://github.com/web3-storage/ucanto.git"
},
"homepage": "https://github.com/web3-storage/ucanto",
"scripts": {
"test:web": "playwright-test test/*.spec.js --cov && nyc report",
"test:node": "c8 --check-coverage --branches 100 --functions 100 --lines 100 mocha test/*.spec.js",
"test": "npm run test:node",
"coverage": "c8 --reporter=html mocha test/*.spec.js && npm_config_yes=true npx st -d coverage -p 8080",
"check": "tsc --build",
"build": "tsc --build"
},
"dependencies": {
"@ipld/car": "^5.1.0",
"@ipld/dag-cbor": "^9.0.0",
"@ipld/dag-ucan": "^3.3.2",
"@ucanto/interface": "workspace:^",
"multiformats": "^11.0.0"
},
"devDependencies": {
"@types/chai": "^4.3.3",
"@types/mocha": "^10.0.1",
"@ucanto/principal": "workspace:^",
"c8": "^7.13.0",
"chai": "^4.3.6",
"mocha": "^10.1.0",
"nyc": "^15.1.0",
"playwright-test": "^8.2.0",
"typescript": "^4.9.5"
},
"type": "module",
"main": "src/lib.js",
"types": "./dist/src/lib.d.ts",
"typesVersions": {
"*": {
"*": [
"dist/src/*"
],
"dist/src/lib.d.ts": [
"dist/src/lib.d.ts"
]
}
},
"exports": {
".": {
"types": "./dist/src/lib.d.ts",
"import": "./src/lib.js"
},
"./src/lib.js": {
"types": "./dist/src/lib.d.ts",
"import": "./src/lib.js"
},
"./link": {
"types": "./dist/src/link.d.ts",
"import": "./src/link.js"
},
"./delegation": {
"types": "./dist/src/delegation.d.ts",
"import": "./src/delegation.js"
},
"./receipt": {
"types": "./dist/src/receipt.d.ts",
"import": "./src/receipt.js"
},
"./cbor": {
"types": "./dist/src/cbor.d.ts",
"import": "./src/cbor.js"
},
"./car": {
"types": "./dist/src/car.d.ts",
"import": "./src/car.js"
},
"./dag": {
"types": "./dist/src/dag.d.ts",
"import": "./src/dag.js"
},
"./result": {
"types": "./dist/src/result.d.ts",
"import": "./src/result.js"
}
},
"c8": {
"exclude": [
"test/**",
"dist/**"
]
},
"license": "(Apache-2.0 AND MIT)"
}
280 changes: 280 additions & 0 deletions packages/schema/src/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
declare function struct<T extends StructFields>(schema: T): StructSchema<T>

export interface Unit {}

declare function string(): Schema<{ string: Unit }>
declare function boolean(): Schema<{ boolean: Unit }>
declare function int(): Schema<{ int: Unit }>
declare function float(): Schema<{ float: Unit }>
declare function bytes(): Schema<{ bytes: Unit }>
declare function unit(): Schema<{ unit: Unit }>

type SchemaType = Variant<{
string: Unit
boolean: Unit
int: Unit
float: Unit
unit: Unit
bytes: Unit
struct: StructType<StructFields, StructRepresentation<StructFields>>
}>

const Point = struct({
x: int().optional().rename('hello'),
y: int(),
})

Point.__type.y

const PointStr = Point.representation.stringPairs({
innerDelim: '=',
entryDelim: '&',
})

PointStr.__type
PointStr.__representation

const PointList = Point.representation.list()

interface StructFields {
[key: string]: Field
}

interface Field<
T extends SchemaType = SchemaType,
Options extends FieldOptions<Infer<T>> = {}
> {
type: T
field: Options

rename<Name extends string>(name: Name): Field<T, { rename: Name }>
}

interface FieldOptions<T extends {}> {
optional?: boolean
nullable?: boolean
rename?: string
implicit?: T
}

interface Schema<Type extends SchemaType> {
type: Type
field: {}

conforms(value: unknown): value is Infer<Type>

decode(data: Uint8Array): Infer<Type>

// implicit<Implicit extends Infer<Type>>(
// value: Implicit
// ): ImplicitField<Type, Implicit>
optional(): Field<Type, { optional: true }>
rename<Name extends string>(name: Name): Field<Type, { rename: Name }>

__type: Infer<Type>
}

interface StructSchema<
T extends StructFields,
R extends StructRepresentation<T> = {
map: DeriveStructMapRepresentation<T>
}
> extends Schema<{ struct: StructType<T, R> }> {
representation: {
stringPairs<I extends string, E extends string>(options: {
innerDelim: I
entryDelim: E
}): StructSchema<T, { stringPairs: { innerDelim: I; entryDelim: E } }>

list(): StructSchema<T, { list: {} }>
}

__representation: R
}

type InferStructField<T> = T extends Field<infer O> ? Infer<O> : never

interface StructType<
Fields extends StructFields,
Representation extends StructRepresentation<Fields> = {
map: DeriveStructMapRepresentation<Fields>
}
> {
fields: Fields
representation: Representation
}

type StructRepresentation<Fields extends StructFields> = Variant<{
map: StructRepresentationMap<Fields>
stringPairs: StructRepresentationStringPairs
list: StructRepresentationList
}>

interface StructRepresentationMap<Fields extends StructFields> {
fields?: {
[K in keyof Fields]?: FieldDetails
}
}

interface StructRepresentationStringPairs<
I extends string = string,
E extends string = string
> {
innerDelim: I
entryDelim: E
}

interface StructRepresentationList {}

interface FieldDetails {
rename?: string
implicit?: {}
}

type DeriveStructMapRepresentation<Fields extends StructFields> =
// OmitUnitFields<{
// fields?: OmitUnitFields<{
// [K in keyof Fields]: (Fields[K]['fieldName'] extends string
// ? { rename: Fields[K]['fieldName'] }
// : {}) &
// (Fields[K]['implicitValue'] extends {}
// ? { implicit: Fields[K]['implicitValue'] }
// : {})
// }>
// }>
{
fields?: {
[K in keyof Fields]: Fields[K] extends Field<infer _, infer Options>
? Options
: never
}
}

type OmitUnitFields<T extends Record<string, unknown>> = {
[K in keyof T]: keyof T[K] extends never ? never : T[K]
}

// INFER

type Infer<T extends SchemaType> = T extends { type: { string: Unit } }
? string
: T extends { boolean: Unit }
? boolean
: T extends { int: Unit }
? number
: T extends { float: Unit }
? number
: T extends { unit: Unit }
? Unit
: T extends { bytes: Unit }
? Uint8Array
: T extends { struct: StructType<infer U, infer E> }
? InferStruct<U, E>
: never

type InferStruct<
T extends StructFields,
R extends StructRepresentation<T>
> = R extends { map: infer U }
? InferStructMap<T, R>
: R extends { stringPairs: infer U }
? InferStructStringPairs<T, U & StructRepresentationStringPairs>
: R extends { list: infer U }
? InferStructList<T>
: never

type InferStructMap<
T extends StructFields,
R extends StructRepresentation<T>
> = {
[K in keyof T]: InferStructField<T[K]>
}

type InferStructStringPairs<
T extends StructFields,
R extends StructRepresentationStringPairs
> = JoinTulpe<
UnionToTuple<
{
[K in keyof T]: `${K & string}${R['innerDelim']}${InferStructField<T[K]> &
(string | number | boolean | null)}`
}[keyof T]
>,
R['entryDelim']
>

type InferStructList<T extends StructFields> = {
[K in keyof T]: [K, InferStructField<T[K]>]
}[keyof T]

type UnionToTuple<T> = (
(T extends any ? (t: T) => T : never) extends infer U
? (U extends any ? (u: U) => any : never) extends (v: infer V) => any
? V
: never
: never
) extends (_: any) => infer W
? [...UnionToTuple<Exclude<T, W>>, W]
: []

type JoinTulpe<T extends unknown[], delimiter extends string> = T extends []
? ''
: T extends [infer First]
? First
: T extends [infer First, infer Second, ...infer Rest]
? `${First & string}${delimiter}${JoinTulpe<[Second, ...Rest], delimiter> &
string}`
: never

// UTILS

/**
* Defines result type as per invocation spec
*
* @see https://github.com/ucan-wg/invocation/#6-result
*/

export type Result<T = unknown, X extends {} = {}> = Variant<{
ok: T
error: X
}>

/**
* Utility type for defining a [keyed union] type as in IPLD Schema. In practice
* this just works around typescript limitation that requires discriminant field
* on all variants.
*
* ```ts
* type Result<T, X> =
* | { ok: T }
* | { error: X }
*
* const demo = (result: Result<string, Error>) => {
* if (result.ok) {
* // ^^^^^^^^^ Property 'ok' does not exist on type '{ error: Error; }`
* }
* }
* ```
*
* Using `Variant` type we can define same union type that works as expected:
*
* ```ts
* type Result<T, X> = Variant<{
* ok: T
* error: X
* }>
*
* const demo = (result: Result<string, Error>) => {
* if (result.ok) {
* result.ok.toUpperCase()
* }
* }
* ```
*
* [keyed union]:https://ipld.io/docs/schemas/features/representation-strategies/#union-keyed-representation
*/
export type Variant<U extends Record<string, unknown>> = {
[Key in keyof U]: { [K in Exclude<keyof U, Key>]?: never } & {
[K in Key]: U[Key]
}
}[keyof U]
Loading

0 comments on commit f398140

Please sign in to comment.