From b8f410a4142da4af1a54ff0353d9de009339034b Mon Sep 17 00:00:00 2001 From: David Blass Date: Mon, 23 Dec 2024 12:12:37 -0500 Subject: [PATCH 1/6] fix type.define --- ark/docs/components/snippets/contentsById.ts | 12 ++++++++++++ ark/repo/scratch.ts | 4 ++-- ark/type/__tests__/type.test.ts | 11 +++++++++++ ark/type/scope.ts | 4 +++- 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/ark/docs/components/snippets/contentsById.ts b/ark/docs/components/snippets/contentsById.ts index e69de29bb2..3aab750344 100644 --- a/ark/docs/components/snippets/contentsById.ts +++ b/ark/docs/components/snippets/contentsById.ts @@ -0,0 +1,12 @@ +export default { + betterErrors: + 'import { type, type ArkErrors } from "arktype"\n\nconst user = type({\n\tname: "string",\n\tplatform: "\'android\' | \'ios\'",\n\t"versions?": "(number | string)[]"\n})\n\ninterface RuntimeErrors extends ArkErrors {\n\t/**platform must be "android" or "ios" (was "enigma")\nversions[2] must be a number or a string (was bigint)*/\n\tsummary: string\n}\n\nconst narrowMessage = (e: ArkErrors): e is RuntimeErrors => true\n\n// ---cut---\nconst out = user({\n\tname: "Alan Turing",\n\tplatform: "enigma",\n\tversions: [0, "1", 0n]\n})\n\nif (out instanceof type.errors) {\n\t// ---cut-start---\n\tif (!narrowMessage(out)) throw new Error()\n\t// ---cut-end---\n\t// hover summary to see validation errors\n\tconsole.error(out.summary)\n}\n', + clarityAndConcision: + '// @errors: 2322\nimport { type } from "arktype"\n// this file is written in JS so that it can include a syntax error\n// without creating a type error while still displaying the error in twoslash\n// ---cut---\n// hover me\nconst user = type({\n\tname: "string",\n\tplatform: "\'android\' | \'ios\'",\n\t"versions?": "number | string)[]"\n})\n', + deepIntrospectability: + 'import { type } from "arktype"\n\nconst user = type({\n\tname: "string",\n\tdevice: {\n\t\tplatform: "\'android\' | \'ios\'",\n\t\t"version?": "number | string"\n\t}\n})\n\n// ---cut---\nuser.extends("object") // true\nuser.extends("string") // false\n// true (string is narrower than unknown)\nuser.extends({\n\tname: "unknown"\n})\n// false (string is wider than "Alan")\nuser.extends({\n\tname: "\'Alan\'"\n})\n', + intrinsicOptimization: + 'import { type } from "arktype"\n// prettier-ignore\n// ---cut---\n// all unions are optimally discriminated\n// even if multiple/nested paths are needed\nconst account = type({\n\tkind: "\'admin\'",\n\t"powers?": "string[]"\n}).or({\n\tkind: "\'superadmin\'",\n\t"superpowers?": "string[]"\n}).or({\n\tkind: "\'pleb\'"\n})\n', + unparalleledDx: + '// @noErrors\nimport { type } from "arktype"\n// prettier-ignore\n// ---cut---\nconst user = type({\n\tname: "string",\n\tplatform: "\'android\' | \'ios\'",\n\t"version?": "number | s"\n\t// ^|\n})\n' +} diff --git a/ark/repo/scratch.ts b/ark/repo/scratch.ts index 534dc11f86..d1d82ec0f9 100644 --- a/ark/repo/scratch.ts +++ b/ark/repo/scratch.ts @@ -8,6 +8,6 @@ import { type } from "arktype" // } // false -const t = type({ foo: "string" }).extends("Record") +// const t = type({ foo: "string" }).extends("Record") -const tt = type(["...", "string[]", "...", ["string?"]]) +type.define("'foo'") diff --git a/ark/type/__tests__/type.test.ts b/ark/type/__tests__/type.test.ts index 945dd7fe17..dee7fdbd08 100644 --- a/ark/type/__tests__/type.test.ts +++ b/ark/type/__tests__/type.test.ts @@ -11,6 +11,17 @@ contextualize(() => { else attest(out) }) + it("define", () => { + const t = type.define({ + foo: "string" + }) + + attest<{ readonly foo: "string" }>(t).equals({ foo: "string" }) + + // @ts-expect-error + attest(() => type.define({ foo: "str" })).completions({ str: ["string"] }) + }) + it("allows", () => { const t = type("number%2") const data: unknown = 4 diff --git a/ark/type/scope.ts b/ark/type/scope.ts index b246957c41..88e46d1bf5 100644 --- a/ark/type/scope.ts +++ b/ark/type/scope.ts @@ -292,7 +292,9 @@ export class InternalScope<$ extends {} = {}> extends BaseScope<$> { type: this.type }) - define: (def: unknown) => unknown = ((def: unknown) => def).bind(this) + define(def: def): def { + return def + } static scope: ScopeParser = ((def: Dict, config: ArkScopeConfig = {}) => new InternalScope(def, config)) as never From 8d36365d07525f41a79b59368645f204262a839c Mon Sep 17 00:00:00 2001 From: David Blass Date: Mon, 23 Dec 2024 15:40:04 -0500 Subject: [PATCH 2/6] big improve keyword big improve tables --- ark/docs/components/AutoDocs.tsx | 67 ---------------- ark/docs/components/KeywordTable.tsx | 78 +++++++++++++++++++ ark/docs/components/snippets/contentsById.ts | 12 --- ark/docs/content/docs/about.mdx | 5 -- ark/docs/content/docs/auto.mdx | 7 -- ark/docs/content/docs/configuration/index.mdx | 2 +- ark/docs/content/docs/expressions/index.mdx | 6 +- ark/docs/content/docs/intro/setup.mdx | 2 +- ark/docs/content/docs/keywords.mdx | 7 ++ ark/docs/content/docs/meta.json | 3 +- ark/docs/content/docs/objects/index.mdx | 4 +- ark/docs/content/docs/primitives/index.mdx | 20 ++--- ark/docs/tsconfig.json | 3 +- ark/repo/scratch.ts | 7 +- ark/schema/generic.ts | 12 ++- ark/schema/scope.ts | 10 ++- ark/type/keywords/ts.ts | 3 + ark/type/type.ts | 3 +- ark/util/hkt.ts | 8 +- eslint.config.js | 3 +- 20 files changed, 137 insertions(+), 125 deletions(-) delete mode 100644 ark/docs/components/AutoDocs.tsx create mode 100644 ark/docs/components/KeywordTable.tsx delete mode 100644 ark/docs/content/docs/about.mdx delete mode 100644 ark/docs/content/docs/auto.mdx diff --git a/ark/docs/components/AutoDocs.tsx b/ark/docs/components/AutoDocs.tsx deleted file mode 100644 index f3246cf339..0000000000 --- a/ark/docs/components/AutoDocs.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { entriesOf, hasKey } from "@ark/util" -import { ark } from "arktype" -import { Fragment } from "react" - -type DescriptionEntry = [alias: string, description: string] - -const root: DescriptionEntry[] = [] -const other: Record< - string, - { description?: string; vals: DescriptionEntry[] } -> = {} - -entriesOf(ark.internal.resolutions).forEach(([k, v]) => { - if (!hasKey(v, "description")) return - - if (!k.includes(".")) return root.push([k, v.description]) - - const [group, ...rest] = k.split(".") - other[group] ??= { vals: [] } - const groupType = rest.join(".") - if (groupType === "root") other[group].description = v.description - else other[group].vals.push([groupType, v.description]) -}) - -const rootElements = root.map(([k, v]) => ( - - {k} - {v} - -)) - -const otherElements = Object.entries(other).map(([k, v]) => ( - - - - - {k} - {v.description ? ` - ${v.description}` : ""} - - - - {v.vals.map(([k, v]) => ( - - {k} - {v} - - ))} - -)) - -export const AutoDocs = () => ( - - - - - - - - - {...rootElements} - {...otherElements} - -
TypeDescription
-) diff --git a/ark/docs/components/KeywordTable.tsx b/ark/docs/components/KeywordTable.tsx new file mode 100644 index 0000000000..8adc9e3644 --- /dev/null +++ b/ark/docs/components/KeywordTable.tsx @@ -0,0 +1,78 @@ +import { append, entriesOf, flatMorph } from "@ark/util" +import { ark, Generic } from "arktype" +import { arkPrototypes } from "arktype/internal/keywords/constructors.ts" +import type { JSX } from "react" + +const tableNames = [ + "string", + "number", + "other", + "object", + "array", + "FormData", + "TypedArray", + "instanceof", + "generic" +] as const + +type TableName = (typeof tableNames)[number] + +const tableRowsByName = flatMorph(tableNames, (i, name) => [ + name, + [] as JSX.Element[] +]) + +entriesOf(ark.internal.resolutions) + .map( + ([alias, v]) => + [alias.endsWith(".root") ? alias.slice(0, -5) : alias, v] as const + ) + .sort((l, r) => (l[0] < r[0] ? -1 : 1)) + .forEach(([alias, v]) => { + // should not occur, only for temporary resolutions of cyclic definition + if (typeof v === "string") return + + const name = + alias.startsWith("string") ? "string" + : alias.startsWith("number") ? "number" + : alias.startsWith("FormData") ? "FormData" + : alias.startsWith("Array") ? "array" + : alias.startsWith("object") ? "object" + : alias.startsWith("TypedArray") ? "TypedArray" + : v instanceof Generic ? "generic" + : alias in arkPrototypes ? "instanceof" + : "other" + + tableRowsByName[name] = append( + tableRowsByName[name], + + {alias} + {v.description} + + ) + }) + +export type KeywordTableProps = { + name: TableName + rows: JSX.Element[] +} + +export const KeywordTable = ({ name, rows }: KeywordTableProps) => ( + <> +

{name}

+ + + + + + + + {...rows} +
AliasDescription
+ +) + +export const AllKeywordTables = () => + tableNames.map(name => ( + + )) diff --git a/ark/docs/components/snippets/contentsById.ts b/ark/docs/components/snippets/contentsById.ts index 3aab750344..e69de29bb2 100644 --- a/ark/docs/components/snippets/contentsById.ts +++ b/ark/docs/components/snippets/contentsById.ts @@ -1,12 +0,0 @@ -export default { - betterErrors: - 'import { type, type ArkErrors } from "arktype"\n\nconst user = type({\n\tname: "string",\n\tplatform: "\'android\' | \'ios\'",\n\t"versions?": "(number | string)[]"\n})\n\ninterface RuntimeErrors extends ArkErrors {\n\t/**platform must be "android" or "ios" (was "enigma")\nversions[2] must be a number or a string (was bigint)*/\n\tsummary: string\n}\n\nconst narrowMessage = (e: ArkErrors): e is RuntimeErrors => true\n\n// ---cut---\nconst out = user({\n\tname: "Alan Turing",\n\tplatform: "enigma",\n\tversions: [0, "1", 0n]\n})\n\nif (out instanceof type.errors) {\n\t// ---cut-start---\n\tif (!narrowMessage(out)) throw new Error()\n\t// ---cut-end---\n\t// hover summary to see validation errors\n\tconsole.error(out.summary)\n}\n', - clarityAndConcision: - '// @errors: 2322\nimport { type } from "arktype"\n// this file is written in JS so that it can include a syntax error\n// without creating a type error while still displaying the error in twoslash\n// ---cut---\n// hover me\nconst user = type({\n\tname: "string",\n\tplatform: "\'android\' | \'ios\'",\n\t"versions?": "number | string)[]"\n})\n', - deepIntrospectability: - 'import { type } from "arktype"\n\nconst user = type({\n\tname: "string",\n\tdevice: {\n\t\tplatform: "\'android\' | \'ios\'",\n\t\t"version?": "number | string"\n\t}\n})\n\n// ---cut---\nuser.extends("object") // true\nuser.extends("string") // false\n// true (string is narrower than unknown)\nuser.extends({\n\tname: "unknown"\n})\n// false (string is wider than "Alan")\nuser.extends({\n\tname: "\'Alan\'"\n})\n', - intrinsicOptimization: - 'import { type } from "arktype"\n// prettier-ignore\n// ---cut---\n// all unions are optimally discriminated\n// even if multiple/nested paths are needed\nconst account = type({\n\tkind: "\'admin\'",\n\t"powers?": "string[]"\n}).or({\n\tkind: "\'superadmin\'",\n\t"superpowers?": "string[]"\n}).or({\n\tkind: "\'pleb\'"\n})\n', - unparalleledDx: - '// @noErrors\nimport { type } from "arktype"\n// prettier-ignore\n// ---cut---\nconst user = type({\n\tname: "string",\n\tplatform: "\'android\' | \'ios\'",\n\t"version?": "number | s"\n\t// ^|\n})\n' -} diff --git a/ark/docs/content/docs/about.mdx b/ark/docs/content/docs/about.mdx deleted file mode 100644 index b01ed16e8c..0000000000 --- a/ark/docs/content/docs/about.mdx +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: About the project ---- - -🚧 Coming soon ™️🚧 diff --git a/ark/docs/content/docs/auto.mdx b/ark/docs/content/docs/auto.mdx deleted file mode 100644 index 373ce1fcfe..0000000000 --- a/ark/docs/content/docs/auto.mdx +++ /dev/null @@ -1,7 +0,0 @@ ---- -title: AutoDocs ---- - -import { AutoDocs } from "../../components/AutoDocs.tsx" - - diff --git a/ark/docs/content/docs/configuration/index.mdx b/ark/docs/content/docs/configuration/index.mdx index f32e7e487d..c92223889a 100644 --- a/ark/docs/content/docs/configuration/index.mdx +++ b/ark/docs/content/docs/configuration/index.mdx @@ -8,7 +8,7 @@ title: Configuration ### Clone -By default, before a [morph](/intro/morphs-and-more) is applied, ArkType will deeply clone the original input value with a built-in `deepClone` function that tries to make reasonable assumptions about preserving prototypes etc. The implementation of `deepClone` can be found [here](https://github.com/arktypeio/arktype/blob/main/ark/util/clone.ts). +By default, before a [morph](/docs/intro/morphs-and-more) is applied, ArkType will deeply clone the original input value with a built-in `deepClone` function that tries to make reasonable assumptions about preserving prototypes etc. The implementation of `deepClone` can be found [here](https://github.com/arktypeio/arktype/blob/main/ark/util/clone.ts). You can provide an alternate clone implementation to the `clone` config option. diff --git a/ark/docs/content/docs/expressions/index.mdx b/ark/docs/content/docs/expressions/index.mdx index 463283d734..dee9b333ad 100644 --- a/ark/docs/content/docs/expressions/index.mdx +++ b/ark/docs/content/docs/expressions/index.mdx @@ -150,7 +150,7 @@ const myObject = type({ ### Narrow -Narrow expressions allow you to add custom validation logic and error messages. You can read more about them in [their intro section](/intro/adding-constraints#narrow). +Narrow expressions allow you to add custom validation logic and error messages. You can read more about them in [their intro section](/docs/intro/adding-constraints#narrow). @@ -291,7 +291,7 @@ const arkString = type( ### Morph -Morphs allow you to transform your data after it is validated. You can read more about them in [their intro section](/intro/morphs-and-more/). +Morphs allow you to transform your data after it is validated. You can read more about them in [their intro section](/docs/intro/morphs-and-more/). @@ -325,7 +325,7 @@ const trimStringStart = type("string", "=>", str => str.trimStart()) ### Unit -While embedded [literal syntax](/literals) is usually ideal for defining exact primitive values, `===` and `type.unit` can be helpful for referencing a non-serialiazable value like a `symbol` from your type. +While embedded [literal syntax](/docs/primitives#number-literals) is usually ideal for defining exact primitive values, `===` and `type.unit` can be helpful for referencing a non-serialiazable value like a `symbol` from your type. diff --git a/ark/docs/content/docs/intro/setup.mdx b/ark/docs/content/docs/intro/setup.mdx index 605fae4cd9..f4bc30c1b9 100644 --- a/ark/docs/content/docs/intro/setup.mdx +++ b/ark/docs/content/docs/intro/setup.mdx @@ -17,7 +17,7 @@ You'll also need... - A `package.json` with `"type": "module"` (or an environment that supports ESM imports) - A `tsconfig.json` with... - [`strict`](https://www.typescriptlang.org/tsconfig#strict) or [`strictNullChecks`](https://www.typescriptlang.org/tsconfig/#strictNullChecks) (**required**) - - [`skipLibCheck`](https://www.typescriptlang.org/tsconfig/#exactOptionalPropertyTypes) (strongly recommended, see [FAQ](/reference/faq#why-do-i-see-type-errors-in-an-arktype-package-in-node_modules)) + - [`skipLibCheck`](https://www.typescriptlang.org/tsconfig/#exactOptionalPropertyTypes) (strongly recommended, see [FAQ](/docs/faq#why-do-i-see-type-errors-in-an-arktype-package-in-node_modules)) - [`exactOptionalPropertyTypes`](https://www.typescriptlang.org/tsconfig/#exactOptionalPropertyTypes) (recommended) ## VSCode diff --git a/ark/docs/content/docs/keywords.mdx b/ark/docs/content/docs/keywords.mdx index 86bb95405a..4f0c49035c 100644 --- a/ark/docs/content/docs/keywords.mdx +++ b/ark/docs/content/docs/keywords.mdx @@ -3,6 +3,7 @@ title: Keywords --- import { Asterisk } from "../../components/Asterisk.tsx" +import { AllKeywordTables } from "../../components/KeywordTable.tsx" import { SyntaxTab, SyntaxTabs } from "../../components/SyntaxTabs.tsx" ### TypeScript @@ -79,3 +80,9 @@ All builtin keywords and modules are available in `type.keywords`. + +### All Keywords + +This table includes all keywords available in default `type` API. To define your own string-embeddable keywords, see [scopes](/docs/scopes). + + diff --git a/ark/docs/content/docs/meta.json b/ark/docs/content/docs/meta.json index bafe11247e..cb8fa6f63e 100644 --- a/ark/docs/content/docs/meta.json +++ b/ark/docs/content/docs/meta.json @@ -10,7 +10,6 @@ "integrations", "scopes", "generics", - "faq", - "about" + "faq" ] } diff --git a/ark/docs/content/docs/objects/index.mdx b/ark/docs/content/docs/objects/index.mdx index 822d17fb85..7161b7723d 100644 --- a/ark/docs/content/docs/objects/index.mdx +++ b/ark/docs/content/docs/objects/index.mdx @@ -497,7 +497,7 @@ const literals = type({ Constrain a Date with an inclusive or exclusive min or max. -Bounds can be expressed as either a [number](/primitives#number/literals) representing its corresponding Unix epoch value or a [Date literal](/objects#dates/literals). +Bounds can be expressed as either a [number](/docs/primitives#number/literals) representing its corresponding Unix epoch value or a [Date literal](/docs/objects#dates/literals). @@ -602,4 +602,4 @@ const instances = type({ ### keywords [#instanceof-keywords] -🚧 Coming soon ™️🚧 +A list of instanceof keywords can be found [here](/docs/keywords#instanceof) alongside the base and subtype keywords for [Array](/docs/keywords#array) and [FormData](/docs/keywords#formdata). diff --git a/ark/docs/content/docs/primitives/index.mdx b/ark/docs/content/docs/primitives/index.mdx index 01cc1b2213..22226584a5 100644 --- a/ark/docs/content/docs/primitives/index.mdx +++ b/ark/docs/content/docs/primitives/index.mdx @@ -6,9 +6,7 @@ import { SyntaxTab, SyntaxTabs } from "../../../components/SyntaxTabs.tsx" ## string -### keywords [#string-keywords] - -🚧 Coming soon ™️🚧 +A full list of string keywords can be found [here](/docs/keywords#string). ### literals [#string-literals] @@ -97,9 +95,7 @@ const range = type({ ## number -### keywords [#number-keywords] - -🚧 Coming soon ™️🚧 +A full list of number keywords can be found [here](/docs/keywords#number). ### literals [#number-literals] @@ -228,7 +224,7 @@ const symbols = type({ ### literals [#bigint-literals] -To require an exact `bigint` value in your type, you can use add the suffix `n` to a string-embedded [number literal](/primitives#number/literals) to make it a `bigint`. +To require an exact `bigint` value in your type, you can use add the suffix `n` to a string-embedded [number literal](/docs/primitives#number-literals) to make it a `bigint`. ```ts const literals = type({ @@ -236,7 +232,7 @@ const literals = type({ }) ``` -You may also use a [unit expression](/expressions#unit) to define `bigint` literals. +You may also use a [unit expression](/docs/expressions#unit) to define `bigint` literals. ## symbol @@ -265,9 +261,9 @@ const symbols = type({ -To reference a specific symbol in your definition, use a [unit expression](/expressions#unit). +To reference a specific symbol in your definition, use a [unit expression](/docs/expressions#unit). -No special syntax is required to define symbolic properties like `{ [mySymbol]: "string" }`. For more information and examples of how to combine symbolic keys with other syntax like optionality, see [properties](/objects#properties). +No special syntax is required to define symbolic properties like `{ [mySymbol]: "string" }`. For more information and examples of how to combine symbolic keys with other syntax like optionality, see [properties](/docs/objects#properties). ## boolean @@ -331,7 +327,7 @@ const booleans = type({ ## null -The `"null"` keyword can be used to allow the exact value `null`, generally as part of a [union](/expressions#union). +The `"null"` keyword can be used to allow the exact value `null`, generally as part of a [union](/docs/expressions#union). @@ -358,7 +354,7 @@ const nullable = type({ ## undefined -The `"undefined"` keyword can be used to allow the exact value `undefined`, generally as part of a [union](/expressions#union). +The `"undefined"` keyword can be used to allow the exact value `undefined`, generally as part of a [union](/docs/expressions#union). diff --git a/ark/docs/tsconfig.json b/ark/docs/tsconfig.json index b487954ea6..b00f222e13 100644 --- a/ark/docs/tsconfig.json +++ b/ark/docs/tsconfig.json @@ -29,5 +29,6 @@ "**/*.tsx", ".next/types/**/*.ts" ], - "exclude": ["node_modules"] + "exclude": ["node_modules"], + "customConditions": ["ark-ts"] } diff --git a/ark/repo/scratch.ts b/ark/repo/scratch.ts index d1d82ec0f9..c6c9bef0e6 100644 --- a/ark/repo/scratch.ts +++ b/ark/repo/scratch.ts @@ -1,4 +1,5 @@ -import { type } from "arktype" +import { flatMorph } from "@ark/util" +import { ark, type } from "arktype" // type stats on attribute removal merge 12/18/2024 // { @@ -10,4 +11,6 @@ import { type } from "arktype" // false // const t = type({ foo: "string" }).extends("Record") -type.define("'foo'") +flatMorph(ark.internal.resolutions, (k, v) => [k, v]) + +console.log(Object.keys(ark.internal.resolutions)) diff --git a/ark/schema/generic.ts b/ark/schema/generic.ts index 1c14ada26a..0a1ed7aa50 100644 --- a/ark/schema/generic.ts +++ b/ark/schema/generic.ts @@ -27,7 +27,7 @@ export const parseGeneric = ( paramDefs: array, bodyDef: unknown, $: BaseScope -): GenericRoot => new GenericRoot(paramDefs, bodyDef, $, $) +): GenericRoot => new GenericRoot(paramDefs, bodyDef, $, $, null) export type genericParamNames> = { [i in keyof params]: params[i][0] @@ -76,12 +76,15 @@ export class GenericRoot< $: BaseScope arg$: BaseScope baseInstantiation: BaseRoot + hkt: Hkt.constructor | null + description: string constructor( paramDefs: array, bodyDef: bodyDef, $: BaseScope, - arg$: BaseScope + arg$: BaseScope, + hkt: Hkt.constructor | null ) { super((...args: any[]) => { const argNodes = flatMorph(this.names, (i, name) => { @@ -111,6 +114,11 @@ export class GenericRoot< this.bodyDef = bodyDef this.$ = $ this.arg$ = arg$ + this.hkt = hkt + this.description = + hkt ? + (new hkt().description ?? `a generic type for ${hkt.constructor.name}`) + : "a generic type" this.baseInstantiation = this(...(this.constraints as never)) as never } diff --git a/ark/schema/scope.ts b/ark/schema/scope.ts index 921234063e..69e52491fb 100644 --- a/ark/schema/scope.ts +++ b/ark/schema/scope.ts @@ -7,9 +7,9 @@ import { printable, throwInternalError, throwParseError, - type Constructor, type Dict, type Fn, + type Hkt, type JsonStructure, type anyOrNever, type array, @@ -224,12 +224,13 @@ export abstract class BaseScope<$ extends {} = {}> { generic: GenericRootParser = (...params) => { const $: BaseScope = this as never - return (def: unknown, possibleHkt?: Constructor) => + return (def: unknown, possibleHkt?: Hkt.constructor) => new GenericRoot( params, possibleHkt ? new LazyGenericBody(def as Fn) : def, $, - $ + $, + possibleHkt ?? null ) as never } @@ -323,7 +324,8 @@ export abstract class BaseScope<$ extends {} = {}> { reference.params as never, reference.bodyDef, reference.$, - this as never + this as never, + reference.hkt ) if (!this.resolved) { diff --git a/ark/type/keywords/ts.ts b/ark/type/keywords/ts.ts index 04788fb79a..f276f0e289 100644 --- a/ark/type/keywords/ts.ts +++ b/ark/type/keywords/ts.ts @@ -95,6 +95,9 @@ export declare namespace object { class RecordHkt extends Hkt<[Key, unknown]> { declare body: Record + + description = + "a generic type that instantiates an object from a single index signature and corresponding value type" } const Record = genericNode(["K", intrinsic.key], "V")( diff --git a/ark/type/type.ts b/ark/type/type.ts index 4a13d946ae..ec186ac7b0 100644 --- a/ark/type/type.ts +++ b/ark/type/type.ts @@ -162,7 +162,8 @@ export class InternalTypeParser extends Callable< params, args[1], $ as never, - $ as never + $ as never, + null ) as never } // otherwise, treat as a tuple expression. technically, this also allows diff --git a/ark/util/hkt.ts b/ark/util/hkt.ts index 63dc9a02ba..a017be5f2f 100644 --- a/ark/util/hkt.ts +++ b/ark/util/hkt.ts @@ -1,4 +1,6 @@ -const args = "~args" +import { noSuggest } from "./errors.ts" + +const args = noSuggest("args") type args = typeof args export abstract class Hkt { @@ -10,6 +12,10 @@ export abstract class Hkt { declare 2: this[args] extends [any, any, infer arg, ...any] ? arg : never declare 3: this[args] extends [any, any, any, infer arg, ...any] ? arg : never abstract body: unknown + + declare description?: string + + constructor() {} } /** A small set of HKT utility types based on https://github.com/gvergnaud/hotscript diff --git a/eslint.config.js b/eslint.config.js index 75cf021a90..d3581004e5 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -102,8 +102,7 @@ export default tseslint.config( "**/attest/**", "**/schema/**", "**/type/**", - "**/util/**", - "arktype/internal/**" + "**/util/**" ], message: From f6df0ca92d1a78e0048dcdf9e4ea3daa96db5772 Mon Sep 17 00:00:00 2001 From: David Blass Date: Mon, 23 Dec 2024 16:36:15 -0500 Subject: [PATCH 3/6] fix links --- ark/docs/app/discord/page.tsx | 11 +++++++++++ ark/docs/components/snippets/contentsById.ts | 12 ++++++++++++ ark/docs/content/docs/definitions.mdx | 2 -- ark/docs/content/docs/faq.mdx | 2 -- .../content/docs/intro/adding-constraints.mdx | 2 -- ark/docs/content/docs/intro/morphs-and-more.mdx | 2 -- ark/docs/content/docs/intro/setup.mdx | 8 +++----- ark/docs/content/docs/intro/your-first-type.mdx | 2 -- ark/docs/content/docs/objects/arrays/meta.json | 10 +++++----- ark/docs/content/docs/objects/index.mdx | 2 +- .../content/docs/objects/properties/meta.json | 16 ++++++++-------- .../content/docs/primitives/number/meta.json | 8 ++++---- .../content/docs/primitives/string/meta.json | 8 ++++---- 13 files changed, 48 insertions(+), 37 deletions(-) create mode 100644 ark/docs/app/discord/page.tsx diff --git a/ark/docs/app/discord/page.tsx b/ark/docs/app/discord/page.tsx new file mode 100644 index 0000000000..810a74689e --- /dev/null +++ b/ark/docs/app/discord/page.tsx @@ -0,0 +1,11 @@ +"use client" + +import { useEffect } from "react" + +export default () => { + useEffect(() => { + window.location.href = "https://discord.com/invite/xEzdc3fJQC" + }, []) + + return null +} diff --git a/ark/docs/components/snippets/contentsById.ts b/ark/docs/components/snippets/contentsById.ts index e69de29bb2..3aab750344 100644 --- a/ark/docs/components/snippets/contentsById.ts +++ b/ark/docs/components/snippets/contentsById.ts @@ -0,0 +1,12 @@ +export default { + betterErrors: + 'import { type, type ArkErrors } from "arktype"\n\nconst user = type({\n\tname: "string",\n\tplatform: "\'android\' | \'ios\'",\n\t"versions?": "(number | string)[]"\n})\n\ninterface RuntimeErrors extends ArkErrors {\n\t/**platform must be "android" or "ios" (was "enigma")\nversions[2] must be a number or a string (was bigint)*/\n\tsummary: string\n}\n\nconst narrowMessage = (e: ArkErrors): e is RuntimeErrors => true\n\n// ---cut---\nconst out = user({\n\tname: "Alan Turing",\n\tplatform: "enigma",\n\tversions: [0, "1", 0n]\n})\n\nif (out instanceof type.errors) {\n\t// ---cut-start---\n\tif (!narrowMessage(out)) throw new Error()\n\t// ---cut-end---\n\t// hover summary to see validation errors\n\tconsole.error(out.summary)\n}\n', + clarityAndConcision: + '// @errors: 2322\nimport { type } from "arktype"\n// this file is written in JS so that it can include a syntax error\n// without creating a type error while still displaying the error in twoslash\n// ---cut---\n// hover me\nconst user = type({\n\tname: "string",\n\tplatform: "\'android\' | \'ios\'",\n\t"versions?": "number | string)[]"\n})\n', + deepIntrospectability: + 'import { type } from "arktype"\n\nconst user = type({\n\tname: "string",\n\tdevice: {\n\t\tplatform: "\'android\' | \'ios\'",\n\t\t"version?": "number | string"\n\t}\n})\n\n// ---cut---\nuser.extends("object") // true\nuser.extends("string") // false\n// true (string is narrower than unknown)\nuser.extends({\n\tname: "unknown"\n})\n// false (string is wider than "Alan")\nuser.extends({\n\tname: "\'Alan\'"\n})\n', + intrinsicOptimization: + 'import { type } from "arktype"\n// prettier-ignore\n// ---cut---\n// all unions are optimally discriminated\n// even if multiple/nested paths are needed\nconst account = type({\n\tkind: "\'admin\'",\n\t"powers?": "string[]"\n}).or({\n\tkind: "\'superadmin\'",\n\t"superpowers?": "string[]"\n}).or({\n\tkind: "\'pleb\'"\n})\n', + unparalleledDx: + '// @noErrors\nimport { type } from "arktype"\n// prettier-ignore\n// ---cut---\nconst user = type({\n\tname: "string",\n\tplatform: "\'android\' | \'ios\'",\n\t"version?": "number | s"\n\t// ^|\n})\n' +} diff --git a/ark/docs/content/docs/definitions.mdx b/ark/docs/content/docs/definitions.mdx index 1248c114d0..c503a10573 100644 --- a/ark/docs/content/docs/definitions.mdx +++ b/ark/docs/content/docs/definitions.mdx @@ -1,7 +1,5 @@ --- title: Definitions -sidebar: - order: 1 --- import { SyntaxTab, SyntaxTabs } from "../../components/SyntaxTabs.tsx" diff --git a/ark/docs/content/docs/faq.mdx b/ark/docs/content/docs/faq.mdx index 6212bb2ea7..38ce20d9ea 100644 --- a/ark/docs/content/docs/faq.mdx +++ b/ark/docs/content/docs/faq.mdx @@ -1,7 +1,5 @@ --- title: FAQ -sidebar: - order: 4 --- ### Why do I see type errors in an ArkType package in `node_modules`? diff --git a/ark/docs/content/docs/intro/adding-constraints.mdx b/ark/docs/content/docs/intro/adding-constraints.mdx index 7046b1ed0d..d703f20816 100644 --- a/ark/docs/content/docs/intro/adding-constraints.mdx +++ b/ark/docs/content/docs/intro/adding-constraints.mdx @@ -1,7 +1,5 @@ --- title: Adding Constraints -sidebar: - order: 3 --- TypeScript is extremely versatile for representing types like `string` or `number`, but what about `email` or `integer less than 100`? diff --git a/ark/docs/content/docs/intro/morphs-and-more.mdx b/ark/docs/content/docs/intro/morphs-and-more.mdx index f7392815c9..4fbaa8a04a 100644 --- a/ark/docs/content/docs/intro/morphs-and-more.mdx +++ b/ark/docs/content/docs/intro/morphs-and-more.mdx @@ -1,7 +1,5 @@ --- title: Morphs & More -sidebar: - order: 4 --- Sometimes, data at the boundaries of your code requires more than validation before it's ready to use. diff --git a/ark/docs/content/docs/intro/setup.mdx b/ark/docs/content/docs/intro/setup.mdx index f4bc30c1b9..ef6cb68c0c 100644 --- a/ark/docs/content/docs/intro/setup.mdx +++ b/ark/docs/content/docs/intro/setup.mdx @@ -1,7 +1,5 @@ --- title: Setup -sidebar: - order: 1 --- import { Tab, Tabs } from "fumadocs-ui/components/tabs" @@ -16,9 +14,9 @@ You'll also need... - TypeScript version `>=5.1`. - A `package.json` with `"type": "module"` (or an environment that supports ESM imports) - A `tsconfig.json` with... - - [`strict`](https://www.typescriptlang.org/tsconfig#strict) or [`strictNullChecks`](https://www.typescriptlang.org/tsconfig/#strictNullChecks) (**required**) - - [`skipLibCheck`](https://www.typescriptlang.org/tsconfig/#exactOptionalPropertyTypes) (strongly recommended, see [FAQ](/docs/faq#why-do-i-see-type-errors-in-an-arktype-package-in-node_modules)) - - [`exactOptionalPropertyTypes`](https://www.typescriptlang.org/tsconfig/#exactOptionalPropertyTypes) (recommended) + - [`strict`](https://www.typescriptlang.org/tsconfig#strict) or [`strictNullChecks`](https://www.typescriptlang.org/tsconfig#strictNullChecks) (**required**) + - [`skipLibCheck`](https://www.typescriptlang.org/tsconfig#skipLibCheck) (strongly recommended, see [FAQ](/docs/faq#why-do-i-see-type-errors-in-an-arktype-package-in-node_modules)) + - [`exactOptionalPropertyTypes`](https://www.typescriptlang.org/tsconfig#exactOptionalPropertyTypes) (recommended) ## VSCode diff --git a/ark/docs/content/docs/intro/your-first-type.mdx b/ark/docs/content/docs/intro/your-first-type.mdx index 69723b2317..89d23c9702 100644 --- a/ark/docs/content/docs/intro/your-first-type.mdx +++ b/ark/docs/content/docs/intro/your-first-type.mdx @@ -1,7 +1,5 @@ --- title: Your First Type -sidebar: - order: 2 --- If you already know TypeScript, congratulations- you just learned most of ArkType's syntax 🎉 diff --git a/ark/docs/content/docs/objects/arrays/meta.json b/ark/docs/content/docs/objects/arrays/meta.json index 36ff60b1bb..0212bf418e 100644 --- a/ark/docs/content/docs/objects/arrays/meta.json +++ b/ark/docs/content/docs/objects/arrays/meta.json @@ -2,10 +2,10 @@ "title": "arrays and tuples", "pages": [ "lengths", - "[prefix](/docs/objects#arrays/prefix)", - "[optional](/docs/objects#arrays/optional)", - "[defaultable](/docs/objects#arrays/defaultable)", - "[variadic](/docs/objects#arrays/variadic)", - "[postfix](/docs/objects#arrays/postfix)" + "[prefix](/docs/objects#arrays-prefix)", + "[optional](/docs/objects#arrays-optional)", + "[defaultable](/docs/objects#arrays-defaultable)", + "[variadic](/docs/objects#arrays-variadic)", + "[postfix](/docs/objects#arrays-postfix)" ] } diff --git a/ark/docs/content/docs/objects/index.mdx b/ark/docs/content/docs/objects/index.mdx index 7161b7723d..2f4ff9be3a 100644 --- a/ark/docs/content/docs/objects/index.mdx +++ b/ark/docs/content/docs/objects/index.mdx @@ -497,7 +497,7 @@ const literals = type({ Constrain a Date with an inclusive or exclusive min or max. -Bounds can be expressed as either a [number](/docs/primitives#number/literals) representing its corresponding Unix epoch value or a [Date literal](/docs/objects#dates/literals). +Bounds can be expressed as either a [number](/docs/primitives#number-literals) representing its corresponding Unix epoch value or a [Date literal](/docs/objects#dates-literals). diff --git a/ark/docs/content/docs/objects/properties/meta.json b/ark/docs/content/docs/objects/properties/meta.json index aa27b3ae6b..82d3784b38 100644 --- a/ark/docs/content/docs/objects/properties/meta.json +++ b/ark/docs/content/docs/objects/properties/meta.json @@ -1,13 +1,13 @@ { "title": "properties", "pages": [ - "[required](/docs/objects#properties/required)", - "[optional](/docs/objects#properties/optional)", - "[defaultable](/docs/objects#properties/defaultable)", - "[index](/docs/objects#properties/index)", - "[undeclared](/docs/objects#properties/undeclared)", - "[merge](/docs/objects#properties/merge)", - "[keyof](/docs/objects#properties/keyof)", - "[get](/docs/objects#properties/get)" + "[required](/docs/objects#properties-required)", + "[optional](/docs/objects#properties-optional)", + "[defaultable](/docs/objects#properties-defaultable)", + "[index](/docs/objects#properties-index)", + "[undeclared](/docs/objects#properties-undeclared)", + "[merge](/docs/objects#properties-merge)", + "[keyof](/docs/objects#properties-keyof)", + "[get](/docs/objects#properties-get)" ] } diff --git a/ark/docs/content/docs/primitives/number/meta.json b/ark/docs/content/docs/primitives/number/meta.json index 60c2f6bbd6..eb6c619154 100644 --- a/ark/docs/content/docs/primitives/number/meta.json +++ b/ark/docs/content/docs/primitives/number/meta.json @@ -1,9 +1,9 @@ { "title": "number", "pages": [ - "[keywords](/docs/primitives#number/keywords)", - "[literals](/docs/primitives#number/literals)", - "[ranges](/docs/primitives#number/ranges)", - "[divisors](/docs/primitives#number/divisors)" + "[keywords](/docs/primitives#number-keywords)", + "[literals](/docs/primitives#number-literals)", + "[ranges](/docs/primitives#number-ranges)", + "[divisors](/docs/primitives#number-divisors)" ] } diff --git a/ark/docs/content/docs/primitives/string/meta.json b/ark/docs/content/docs/primitives/string/meta.json index f6754bd550..0d8367badf 100644 --- a/ark/docs/content/docs/primitives/string/meta.json +++ b/ark/docs/content/docs/primitives/string/meta.json @@ -1,9 +1,9 @@ { "title": "string", "pages": [ - "[keywords](/docs/primitives#string/keywords)", - "[literals](/docs/primitives#string/literals)", - "[patterns](/docs/primitives#string/patterns)", - "[lengths](/docs/primitives#string/lengths)" + "[keywords](/docs/primitives#string-keywords)", + "[literals](/docs/primitives#string-literals)", + "[patterns](/docs/primitives#string-patterns)", + "[lengths](/docs/primitives#string-lengths)" ] } From 88802cddbd911bc45edbf9b57719e9b61fe502c8 Mon Sep 17 00:00:00 2001 From: David Blass Date: Mon, 23 Dec 2024 16:37:55 -0500 Subject: [PATCH 4/6] more docs, fix links --- ark/docs/package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ark/docs/package.json b/ark/docs/package.json index 21ab94399d..0988091ba1 100644 --- a/ark/docs/package.json +++ b/ark/docs/package.json @@ -9,7 +9,8 @@ "start": "next start", "clean": "rm -rf .next .source out", "postinstall": "fumadocs-mdx", - "upDeps": "pnpm up --latest" + "upDeps": "pnpm up --latest", + "checkLinks": "linkinator https://arktype.io -r" }, "dependencies": { "@ark/fs": "workspace:*", @@ -30,6 +31,7 @@ "fumadocs-twoslash": "2.0.2", "fumadocs-ui": "14.6.2", "hast-util-to-jsx-runtime": "2.3.2", + "linkinator": "6.1.2", "lucide-react": "0.469.0", "next": "15.1.2", "postcss": "8.4.49", From 718e7923bdb704a8aed22e39b1fa6ff1e080bf01 Mon Sep 17 00:00:00 2001 From: David Blass Date: Mon, 23 Dec 2024 18:30:45 -0500 Subject: [PATCH 5/6] fix define test --- ark/type/__tests__/define.test.ts | 46 +++++++++++++++++++++++++++++++ ark/type/__tests__/scope.test.ts | 33 +--------------------- ark/type/__tests__/type.test.ts | 11 -------- 3 files changed, 47 insertions(+), 43 deletions(-) create mode 100644 ark/type/__tests__/define.test.ts diff --git a/ark/type/__tests__/define.test.ts b/ark/type/__tests__/define.test.ts new file mode 100644 index 0000000000..dc8839b5ad --- /dev/null +++ b/ark/type/__tests__/define.test.ts @@ -0,0 +1,46 @@ +import { attest, contextualize } from "@ark/attest" +import { writeUnresolvableMessage } from "@ark/schema" +import { define, scope, type } from "arktype" + +contextualize(() => { + it("ark", () => { + const def = define({ + a: "string|number", + b: ["boolean"] + }) + attest<{ a: "string|number"; b: readonly ["boolean"] }>(def) + }) + + it("type attached", () => { + const t = type.define({ + foo: "string" + }) + + attest<{ readonly foo: "string" }>(t).equals({ foo: "string" }) + + // @ts-expect-error + attest(() => type.define({ foo: "str" })).completions({ str: ["string"] }) + }) + + it("ark error", () => { + // currently is a no-op, so only has type error + // @ts-expect-error + attest(() => define({ a: "boolean|foo" })).type.errors( + writeUnresolvableMessage("foo") + ) + }) + + it("custom scope", () => { + const $ = scope({ + a: "string[]" + }) + + const ok = $.define(["a[]|boolean"]) + attest(ok) + + // @ts-expect-error + attest(() => $.define({ not: "ok" })).type.errors( + writeUnresolvableMessage("ok") + ) + }) +}) diff --git a/ark/type/__tests__/scope.test.ts b/ark/type/__tests__/scope.test.ts index 82cdb8e8c5..5e55905d0e 100644 --- a/ark/type/__tests__/scope.test.ts +++ b/ark/type/__tests__/scope.test.ts @@ -5,7 +5,7 @@ import { writeUnresolvableMessage, type ArkErrors } from "@ark/schema" -import { define, scope, type, type Module } from "arktype" +import { scope, type, type Module } from "arktype" import type { distill } from "arktype/internal/attributes.ts" import { writeUnexpectedCharacterMessage } from "arktype/internal/parser/shift/operator/operator.ts" @@ -185,37 +185,6 @@ contextualize(() => { attest(out).snap({ pear: { tasty: true } }) }) - describe("define", () => { - it("ark", () => { - const def = define({ - a: "string|number", - b: ["boolean"] - }) - attest<{ a: "string|number"; b: readonly ["boolean"] }>(def) - }) - - it("ark error", () => { - // currently is a no-op, so only has type error - // @ts-expect-error - attest(() => define({ a: "boolean|foo" })).type.errors( - writeUnresolvableMessage("foo") - ) - }) - - it("custom scope", () => { - const $ = scope({ - a: "string[]" - }) - - const ok = $.define(["a[]|boolean"]) - attest(ok) - - // @ts-expect-error - attest(() => $.define({ not: "ok" })).type.errors( - writeUnresolvableMessage("ok") - ) - }) - }) describe("cyclic", () => { it("base", () => { const types = scope({ a: { b: "b" }, b: { a: "a" } }).export() diff --git a/ark/type/__tests__/type.test.ts b/ark/type/__tests__/type.test.ts index dee7fdbd08..945dd7fe17 100644 --- a/ark/type/__tests__/type.test.ts +++ b/ark/type/__tests__/type.test.ts @@ -11,17 +11,6 @@ contextualize(() => { else attest(out) }) - it("define", () => { - const t = type.define({ - foo: "string" - }) - - attest<{ readonly foo: "string" }>(t).equals({ foo: "string" }) - - // @ts-expect-error - attest(() => type.define({ foo: "str" })).completions({ str: ["string"] }) - }) - it("allows", () => { const t = type("number%2") const data: unknown = 4 From 90022cfb68db953fd6f7b4664a152821e27af7ee Mon Sep 17 00:00:00 2001 From: David Blass Date: Mon, 23 Dec 2024 18:31:51 -0500 Subject: [PATCH 6/6] bump versions --- ark/attest/package.json | 2 +- ark/fs/package.json | 2 +- ark/schema/package.json | 2 +- ark/type/package.json | 2 +- ark/util/package.json | 2 +- ark/util/registry.ts | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ark/attest/package.json b/ark/attest/package.json index 8c15621b3e..4bb716520a 100644 --- a/ark/attest/package.json +++ b/ark/attest/package.json @@ -1,6 +1,6 @@ { "name": "@ark/attest", - "version": "0.33.0", + "version": "0.34.0", "license": "MIT", "author": { "name": "David Blass", diff --git a/ark/fs/package.json b/ark/fs/package.json index b715807be0..f9b77d5d4e 100644 --- a/ark/fs/package.json +++ b/ark/fs/package.json @@ -1,6 +1,6 @@ { "name": "@ark/fs", - "version": "0.29.0", + "version": "0.30.0", "license": "MIT", "author": { "name": "David Blass", diff --git a/ark/schema/package.json b/ark/schema/package.json index a8c425ddaa..02be0b2602 100644 --- a/ark/schema/package.json +++ b/ark/schema/package.json @@ -1,6 +1,6 @@ { "name": "@ark/schema", - "version": "0.29.0", + "version": "0.30.0", "license": "MIT", "author": { "name": "David Blass", diff --git a/ark/type/package.json b/ark/type/package.json index 88a07a107e..714e1f6e64 100644 --- a/ark/type/package.json +++ b/ark/type/package.json @@ -1,7 +1,7 @@ { "name": "arktype", "description": "TypeScript's 1:1 validator, optimized from editor to runtime", - "version": "2.0.0-rc.29", + "version": "2.0.0-rc.30", "license": "MIT", "author": { "name": "David Blass", diff --git a/ark/util/package.json b/ark/util/package.json index 98b1c923ef..214b4a3516 100644 --- a/ark/util/package.json +++ b/ark/util/package.json @@ -1,6 +1,6 @@ { "name": "@ark/util", - "version": "0.29.0", + "version": "0.30.0", "license": "MIT", "author": { "name": "David Blass", diff --git a/ark/util/registry.ts b/ark/util/registry.ts index 336fdccae0..578d45f326 100644 --- a/ark/util/registry.ts +++ b/ark/util/registry.ts @@ -7,7 +7,7 @@ import { FileConstructor, objectKindOf } from "./objectKinds.ts" // recent node versions (https://nodejs.org/api/esm.html#json-modules). // For now, we assert this matches the package.json version via a unit test. -export const arkUtilVersion = "0.29.0" +export const arkUtilVersion = "0.30.0" export const initialRegistryContents = { version: arkUtilVersion,