-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
490 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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] |
Oops, something went wrong.